Intel® Developer Zone offers tools and how-to information for cross-platform app development, platform and technology information, code samples, and peer expertise to help developers innovate and succeed. Join our communities for the Internet of Things, Android, Intel® RealSense™ Technology and Windows to download tools, access dev kits, share ideas with like-minded developers, and participate in hackathon’s, contests, roadshows, and local events.
Related articles:
Developer's Guide for Intel® Processor Graphics for 4th Generation Intel® Core™ Processors
Touch and Sensors
How to Create a Usable Touch UI
How to Adjust Controls for Touch
Multi-touch All-in-One (AIO) platforms extend the bounds of what’s possible for those writing touch screen apps. Because of the limited screen size of most touch-screen phones and tablets, it’s generally practical for only one person to be tapping or swiping on the device at a time. However, with the larger multi-touch AIOs, whose screens often stretch more than two feet across, it’s possible for several people to gather around and tap on the screen simultaneously, particularly when the AIO is laid flat on a table.
Of course, touch screen apps built for bigger screens and multiple users have their own programming challenges. After participating in an earlier Intel contest, developer Dave Gannon took on these challenges, coming back with a vengeance with Wormhole Pinball, the winning app in the games category in the Intel App Innovation Contest 2013 in partnership with the Intel® Developer Zone.
Gannon has loved computers ever since his parents brought home a Commodore* 64 when he was a kid. He got his programming start typing in the BASIC commands he found in the C-64 manual and then seeing what happened on the start-up screen.
Today, when he's not in the office, Gannon does some fairly serious dabbling in the world of computer games. What started as a hobby making short demoscene clips has morphed into writing full-fledged games. Describing his intent for the game on CodeProject last summer, Gannon wrote “[w]hat I want to do is use the AIO to expand on what it means to have a pinball game.” The CodeProject community judged Gannon’s pinball app submission to be the grand prize winner in the games category, a distinction that netted him USD 10,000.
The Appeal of AIO Platforms
Gannon developed Wormhole Pinball for the Lenovo IdeaCentre* Horizon 27, a tabletop PC built for casual gaming. Gannon, who has fond memories of huddling around coffee-table arcade machines with his friends, always wanted one in his house, "When I saw the tabletop All-in-One, I had to do a game for that,” he said.
Figure 1: Wormhole Pinball screenshot. Gannon wanted his entry to recreate the experience of coffee-table-type arcade games.
Gannon wanted to make the most of the Horizon's “tableness” in a game. Pinball was the first idea that sprang to mind. One consideration was how to render the graphics. 3D balls and flippers conceivably could look great on a larger screen, though Gannon was cautious after his experience in last year's contest. He had spent the lion's share of his time trying to incorporate 3D in a feature that would follow the gamer's face with a webcam, leaving hardly any time for the rest of the game development.
This time around Gannon stuck with 2D graphics and spent the time he saved on interesting features to make the game fun for players. The most obvious example is the wormhole, which players can draw on the screen and use to teleport the balls, a nifty feature obviously not possible in the old-fashioned arcade version of the game. "I needed something to make it a little bit crazy and a little bit unpredictable," said Gannon. Other features include adaptive multi-dimensional gravity that changes as the number of players grows. Gannon's app supports up to four players, which means it can make sense of simultaneous gestures, among the most distinctive features of the AIO platform.
Building the Game Elements
Gannon used the open source Farseer* Physics Engine to create the core elements of his game, including the flippers, a pair of which appears in each corner of the screen. These were a logical starting point for Gannon, a self-declared hobbyist. “I just sort of do it,” he said, when asked to summarize his strategy for planning and building his app. “You'd start with the main thing that you would need, which is a ball and some flippers (Figure 2), and go from there.”
Figure 2: Gannon constructed the complex shape of his flippers using the simple primitives available in the Farseer* Physics API.
To build the shapes that combined to make the flipper, the hard-coded trapezium was first plotted on graph paper as Gannon sought to understand the nuances of Farseer. “Any polygon needs to be triangulated before it can be used in Farseer,” he said. “You must also make sure your polygon is convex as triangulation of concave polygons using the built-in functions is a bit ropey.”
Farseer, primarily a collision-detection system, creates its own mass for a given object based on the density and area of the shapes that were used to construct it, a default setting Gannon found troublesome. “Most of the time you probably don’t want this, so remember to set the mass explicitly at the end or it will be overwritten,” he said. Figure 3 is the code Gannon used to accomplish this.
private void CreateFlipper()
{
var fixtures = this.Body.FixtureList;
fixtures.ForEach(i => this.Body.DestroyFixture(i));
fixtures = null;
const float largeCircleRadius = 42.8f;
const float smallCircleRadius = 21.4f;
var largeCircle = new CircleShape(FarseerUnitConverter.ToPhysicsUnits(largeCircleRadius) * this.Scale, 1.0f);
var smallCircle = new CircleShape(FarseerUnitConverter.ToPhysicsUnits(smallCircleRadius) * this.Scale, 1.0f);
smallCircle.Position = FarseerUnitConverter.ToPhysicsUnits(new Vector2(235.4f * this.Scale, 0.0f));
List<Vector2> points = new Vector2[] {
FarseerUnitConverter.ToPhysicsUnits(new Vector2(0.0f, -largeCircleRadius * this.Scale)),
FarseerUnitConverter.ToPhysicsUnits(new Vector2(235.4f * this.Scale, -21.8f * this.Scale)),
FarseerUnitConverter.ToPhysicsUnits(new Vector2(235.4f * this.Scale, 21.8f * this.Scale)),
FarseerUnitConverter.ToPhysicsUnits(new Vector2(0.0f, largeCircleRadius * this.Scale)) }.ToList();
var trapezium = new Vertices(points);
var triangulatedTrapezium = FarseerPhysics.Common.Decomposition.Triangulate.ConvexPartition(trapezium, TriangulationAlgorithm.Bayazit);
var trapeziumShape = new PolygonShape(triangulatedTrapezium[0], 1.0f);
this.Body.CreateFixture(largeCircle);
this.Body.CreateFixture(smallCircle);
this.Body.CreateFixture(trapeziumShape);
this.Body.BodyType = BodyType.Dynamic;
this.Body.Mass = this.mass;
this.Body.AngularDamping = 0.1f;
}
Figure 3: The code that creates the game’s flippers out of simple shapes—circles and trapeziums.
The geometry created in Farseer is not actually rendered. Instead, sprites are rendered with the same shape. “This makes the code needed to draw the graphics much simpler as no geometry has to be rendered, yet you can achieve really nice-looking graphics,” said Gannon. Figure 4 is a sprite for the flipper that’s drawn at the same position and angle given by Farseer geometry in Gannon’s code.
Figure 4: A sprite for the flipper drawn at the same position and angle as the Farseer* geometry shown in the code sample given in Figure 3.
Touch Recognition Challenges and Solutions
Unsurprisingly, one of Gannon’s biggest challenges was how to make touch recognition work in a typical game, where up to four people might be huddled around the screen, tapping and swiping on it at the same time. Touch points within a certain radius are deemed part of the same touch event. Easy enough. But what about determining which way—clockwise or counterclockwise—a player drew a circle to create a wormhole? This is a key feature of the game, since the wormholes (Figure 5) behave differently depending on which way they're drawn.
Figure 5: The particle trail that displays when a player draws a circle with his or her finger to create a wormhole.
Solving this challenge meant first iterating over a list of touch points that are determined to be part of the same touch event. Next, Gannon's code computes the signed area of each right-angled triangle created by two adjacent points in the list. The sum of the handedness score is computed and if the score is positive, the touch event is deemed to be clockwise; if it's negative, the touch event is counterclockwise. Figure 6 is the code, along with an assigned area function that Gannon found after searching on the Stack Exchange Q&A website.
private void UpdateOrientation()
{
int pointCount = this.pointHistory.Count;
if (pointCount < 3)
return;
this.handednessScores.Clear();
for (int i = 0; i < pointCount - 1; i++)
{
Point p1 = this.pointHistory[i];
Point p2 = this.pointHistory[i + 1];
int score = (p1.X * p2.Y) - (p1.Y * p2.X);
this.handednessScores.Add(score);
}
int sumScores = this.handednessScores.Sum();
this.PortalOrientation = sumScores > 0 ? PortalOrientation.Clockwise : PortalOrientation.Anticlockwise;
}
Figure 6: The code for determining whether the circles creating the wormholes are drawn clockwise or counterclockwise. (In the game, wormholes behave differently depending on which way they are drawn.)
Another challenge had to do with how the flippers work. As any pinball wizard knows, flippers need to operate over a fairly limited range of motion. Gannon struggled a bit with joint limits defined in Farseer. Occasionally, he said, earlier iterations of his flippers would "bust through the limits and end up flopping about on the wrong side." Gannon added the code in Figure 7 as a hack that he's fairly proud of. "It ended up working rather well," he said. "It even adds some bounce to the flippers by reversing the angular velocity when the limit is reached and multiplying by the variable bounce, which is very close to but not quite 1.0, to prevent the flippers from bouncing forever."
private void BounceOffLimits()
{
float angularVelocity = this.Body.AngularVelocity;
float bounce = -2.9f;
if (this.Body.Rotation <= this.Joint.LowerLimit)
{
this.Body.Rotation = this.Joint.LowerLimit + 0.0001f;
this.Body.AngularVelocity = angularVelocity * bounce;
}
if (this.Body.Rotation >= this.Joint.UpperLimit)
{
this.Body.Rotation = this.Joint.UpperLimit - 0.0001f;
this.Body.AngularVelocity = angularVelocity * bounce;
}
}
Figure 7: The code controlling the range of motion and bounce of the flippers.
Gannon used the Windows* 8 API to harvest touch data, storing information in a buffer about 10 touches deep. Other challenges included deciding what language and framework to use. Gannon wrote the app in C# using Microsoft's XNA* Framework. He followed the standard component model, with top-level components and services and associated subcomponents (for individual parts of the game, such as the ball or flippers) further down the hierarchy.
Gannon said that one drawback to building the app in XNA is that Microsoft counts these as desktop apps and therefore doesn't publish them in the Windows Store, where most Windows 8 apps are sold. One of the first tasks undertaken by Null Ref, Gannon’s newly formed company with fellow AIC 2013 contestant Adam Hill, might well be to port Wormhole Pinball to MonoGame for wider coverage, but only after Gannon and Hill work to polish up Hill's Hot Shots as their first commercial offering.
Advice for Other Developers
Gannon said most of his advice to developers boils down to appropriately scoping the project and not being afraid to ditch features that don't fit. It’s also important to remember how AIO devices might be used when they are laid flat on a table. There's no up or down for someone playing Wormhole Pinball, and the experience needs to be more or less the same no matter where a player is positioned around the screen. Obviously notions of up and down are central to the user experience of most apps built for smaller touch-screen devices.
Figure 8: Graphic indicating winner of a Wormhole Pinball game.
Perhaps most importantly, Gannon said that developers need to be aware of some of the cyclical trends apparent in the gaming industry, especially the resurgence of simple games created by small teams of developers for touch screens that end up reaching huge audiences. (Candy Crush Saga*, anyone?) Gannon notes that the early 1980s, when he was pecking out BASIC commands on his Commodore 64, also happened to the heyday of the 8-bit era of games.
“In some ways it's gone full circle back to the 8-bit days, when you used to get guys making a game on their own in their bedroom and selling it and making a lot of money,” he said. “Then it all started getting a bit more complicated and you needed bigger and bigger teams. Now the software and the tools have gotten to the stage where they're much easier to use, and it's now easy for someone like me to sit down, write a game on my own, and get it out there.”
Helpful Resources
Gannon used the free, open source Farseer Physics Engine to build the dynamic game elements. Farseer is a collision-detection system with realistic physics responses. Features of the engine include continuous collision detection (with time-of-impact solver); contact callbacks: begin, end, pre-solve, post-solve; convex and concave polygons and circles; and more. When stumped by a particular programming challenge, Gannon spent timing mining Stack Overflow, a Q&A site for professional and enthusiast programmers. He wrote the app in C# using the Microsoft XNA Framework. Currently he is exploring porting Wormhole Pinball to MonoGame, an open source implementation of the Microsoft XNA 4 Framework that allows for easy porting of games to iOS*, Android*, and other operating systems.
Intel® Developer Zone offers tools and how-to information for cross-platform app development, platform and technology information, code samples, and peer expertise to help developers innovate and succeed. Join our communities for the Internet of Things, Android, Intel® RealSense™ technology, and Windows to download tools, access dev kits, share ideas with like-minded developers, and participate in hackathons, contests, roadshows, and local events.
Related articles:
Intel, the Intel logo, Intel Core, and Intel RealSense are trademarks of Intel Corporation in the U.S. and/or other countries.
*Other names and brands may be claimed as the property of others.
Copyright © 2014. Intel Corporation. All rights reserved.