Games Demystified: Rolando
Continuing his popular series, Jeremy Alessi presents a unique guide to the iPhone hit's Rolando's unique gameplay mechanics, with example code showcasing touch and physics-related game concepts.
[Continuing his popular series, Jeremy Alessi presents a unique guide to the iPhone hit's Rolando's unique gameplay mechanics, with example code showcasing touch and physics-related game concepts.]
It's time for another trip through the workings of modern gameplay with Games Demystified. Previously, we covered the gravity of Super Mario Galaxy and the Einstein-Rosen bridges in Portal. This go-round we'll be looking into the tiny-big world of the iPhone's popular platformer Rolando, developed by HandCircus and published by Ngmoco.
Scale is a fascinating subject. It's one of the early fundamental paradoxes we come across as children. In fact, it was one of the early paradoxes contemplated by our society as a whole. From the early conception of atomism by Leucippus to the telescope first invented by Hans Lippershey, we've always wondered how big or small things could get.
Perhaps it is the classic sci-fi film Attack of the 50 Foot Woman that best sums up Rolando's sense of scale. How cool would it be to pick up a room full of people and shake them around, turn their world upside down, or perhaps pick up and fix things that the tiny people inside couldn't do themselves? The answer, of course, is very cool!
Rolando addresses the paradox of scale in two ways: the game's miniscule finger worshipping characters and the massive world that fits in the palm of the player's hand. Without the iPhone hardware, Rolando would not be the same.
Therefore this segment of Games Demystified will pull double duty. In addition to breaking down gameplay mechanics, this column will also help anyone who's been looking for a leg up on Apple's hyper-popular gizmo.
Rolando was one of the first games designed solely with the iPhone hardware in mind. Full tilt and multi-touch are utilized harmoniously. The question then becomes, how specifically can we leverage these newly emerging techniques to make it feel like we're holding a tiny world in the palm of our hand?
Touch the World
Let's begin with the simplest technique: touch. This is a sensation we almost take for granted because it's so innate. Touch has always been a component in video game play. It's actually the joint that hinges our audio-visual experience to the rest of our body and makes a game feel tangible. Though it is widely perceived that touching a flat screen somehow produces a less tangible game, Rolando proves otherwise.
In Rolando, players can call upon characters -- the rolandos -- by touching or drag-box selecting them. When this happens the character is ready to go and then reacts to the tilt mechanism. In addition players can touch and move additional objects like elevators and gears.
In these instances players are directly manipulating objects that the rolandos are either too "weak" or lack the proper appendages to manipulate themselves. It is in these instances that the rolandos' worship sessions of the finger are most appropriate.
Let's have a look at some touch code. Though the original game was not written in this engine, the samples provided for this segment of Games Demystified -- downloadable here in a package -- are written in JavaScript for the Unity Engine, which is a fantastic component based engine.
The first component we'll examine is camera and the touch functionality of the device. In the game you can select a single rolando by tapping on them or you can select an entire gang of rolandos with an RTS-style selection box. Below is the code responsible for the highlight and selection of a rolando.
Camera Component:
var rolando : Transform;
private var debug : String;
private var initialSelection : Vector2 = Vector2( 0, 0 );
private var hit : RaycastHit;
private var selected : boolean = false;
private var touch : iPhoneTouch;
function FixedUpdate()
{
var ray = Camera.main.ScreenPointToRay( Vector2( touch.position.x, touch.position.y ) );
var layerMask = 1 << 2;
layerMask = ~layerMask;
if ( Physics.Raycast( ray, hit, 100 ) )
{
if ( hit.collider == rolando.collider )
{
selected = true;
rolando.SendMessage( "select", true );
rolando.SendMessage( "highlight", true );
}
else
{
selected = false;
rolando.SendMessage( "select", false );
rolando.SendMessage( "highlight", false );
}
}
}
function LateUpdate()
{
if ( !selected )
{
if ( iPhoneInput.touchCount == 2 )
{
touch = iPhoneInput.GetTouch( 0 );
transform.position == Vector3( transform.position.x, transform.position.y, -20 );
transform.position -= Vector3( touch.positionDelta.x, touch.positionDelta.y, 0 );
}
}
else
transform.position = Vector3( rolando.transform.position.x, rolando.transform.position.y, -10);
}
function OnGUI()
{
useGUILayout = false;
GUI.Box( Rect( 0, 0, 480, 20 ), "Debug - " + debug );
var screenPos : Vector2;
if ( iPhoneInput.touchCount == 1 )
{
touch = iPhoneInput.GetTouch( 0 );
if ( initialSelection == Vector2( 0, 0 ) )
initialSelection = touch.position;
if ( touch.position.x > initialSelection.x && initialSelection.y > touch.position.y )
GUI.Box( Rect( initialSelection.x, 320 - initialSelection.y, touch.position.x - initialSelection.x, initialSelection.y - touch.position.y ), "" );
if ( touch.position.x < initialSelection.x && initialSelection.y > touch.position.y )
GUI.Box( Rect( touch.position.x, 320 - initialSelection.y, initialSelection.x - touch.position.x, initialSelection.y - touch.position.y ), "" );
if ( touch.position.x > initialSelection.x && initialSelection.y < touch.position.y )
GUI.Box( Rect( initialSelection.x, 320 - touch.position.y, touch.position.x - initialSelection.x, touch.position.y - initialSelection.y ), "" );
if ( touch.position.x < initialSelection.x && initialSelection.y < touch.position.y )
GUI.Box( Rect( touch.position.x, 320 - touch.position.y, initialSelection.x - touch.position.x, touch.position.y - initialSelection.y ), "" );
if ( initialSelection != touch.position )
{
screenPos = Camera.main.WorldToScreenPoint( rolando.position );
if ( ( ( screenPos.x > initialSelection.x && screenPos.x < touch.position.x ) || ( screenPos.x < initialSelection.x && screenPos.x > touch.position.x ) ) && ( ( screenPos.y > initialSelection.y && screenPos.y < touch.position.y ) || ( screenPos.y < initialSelection.y && screenPos.y > touch.position.y ) ) )
rolando.SendMessage( "highlight", true );
else
rolando.SendMessage( "highlight", false );
}
}
if ( iPhoneInput.touchCount == 0 )
{
if ( initialSelection != Vector2( 0, 0 ) || ( initialSelection - touch.position ).magnitude < 5 )
{
screenPos = Camera.main.WorldToScreenPoint( rolando.position );
if ( ( ( screenPos.x > initialSelection.x && screenPos.x < touch.position.x ) || ( screenPos.x < initialSelection.x && screenPos.x > touch.position.x ) ) && ( ( screenPos.y > initialSelection.y && screenPos.y < touch.position.y ) || ( screenPos.y < initialSelection.y && screenPos.y > touch.position.y ) ) )
{
selected = true;
rolando.SendMessage( "select", true );
rolando.SendMessage( "highlight", true );
}
else
{
selected = false;
rolando.SendMessage( "select", false );
rolando.SendMessage( "highlight", false );
}
}
initialSelection = Vector2( 0, 0 );
}
}
In the Unity Editor the main element of the rolando character is actually a physics sphere. The animating face is a plane attached to the physics sphere and an orthogonal camera is used so that no perspective is present.
In this code you'll see a rolando.SendMessage( "highlight", true ). This call sends a message to the base of the rolando character (the physics sphere) and enables or disables its renderer.
In the actual game a white outline appears around selected rolandos. This code showcases one method of accomplishing this task by using the base physics element as the highlight. Thus, the physics sphere does double duty as a gameplay mechanic and as a subtle visual cue.
There are several elements incorporated into this code. They are as follows; tap selecting, drag-box selecting, deselecting, and dual-touch camera dragging. The tap selection uses a simple camera ray cast to determine if the player tapped on the rolando.
If the player did then the rolando is instantly highlighted and selected. If the player taps elsewhere then the rolando is deselected and the highlight is removed as well. It's important to note the var rolando : Transform; at the top of this camera code component.
In the Unity editor a reference is passed to the camera containing this script. The reference is a direct link to the actual rolando in the scene. There are countless other ways to find an object in a scene but this is simple solution that saves CPU cycles and is an important aspect of component based Unity development.
Even if there are multiple character references in a scene it's no big deal to pre-plug them. Throughout this camera script rolando refers directly to the Transform ( position and orientation ) of the rolando character. This information can then be used to lock the camera to a certain position and more.
Drag selecting creates a GUI box from the initial touch position to the current touch position. Note that there are some non-intuitive aspects to the drag selection feature. First of all, the GUI code's y-axis is inverted from the touch or ray cast code.
The coordinate ( 0, 0 ) in GUI code represents the upper left hand corner of the iPhone's screen while ( 0, 0 ) in terms of touch or screen point ray cast represents the lower left corner of the screen. Because of this it is sometimes necessary to subtract for example the touch.position.y from 320 to get an appropriate GUI y-axis position.
The drag selection is simple in spite of the counter intuitive nature of GUI coordinates versus touch or screen coordinates. If a rolando's 2D screen position, retrieved via Camera.main.WorldToScreenPoint( rolando.position ) is within the GUI box formed between the Vector2's initialSelection and touch.position then the rolando becomes highlighted.
Finally, if the finger is lifted and the initialPosition isn't equal to ( 0, 0 ) or the magnitude of the difference between initialPosition and touch.position is less than five screen units then the box check is run again. If the rolando is still contained then it becomes highlighted and selected, otherwise it loses both highlight and selection status.
The last part of the touch input and camera component is the dual finger camera drag. If a rolando is not selected then the camera is free to drag with two fingers. This is an essential part of planning a strategy in the actual game of Rolando.
Multi-touch is such an important feature of the hardware and it was nice that Rolando utilized it in a vital and intuitive manner. The function LateUpdate() contains the dual touch code which is very simple. The LateUpdate() callback is utilized in this script to create smoother movement for the camera as it is updated after all other instructions are completed during the current frame.
The next most important component is the rolando script itself, which contains tilt control for a selected rolando.
private var selected : boolean;
function FixedUpdate()
{
if ( selected )
{
accel = iPhoneInput.acceleration;
rigidbody.AddForce( -accel.y * 100, 0, 0 );
}
}
function highlight( flag : boolean )
{
renderer.enabled = flag;
}
function select( flag : boolean )
{
selected = flag;
}
This small script is extraordinarily simple. The most important feature is the FixedUpdate() callback. This is used for physics sensitive aspects of the gameplay. The main fixture of the rolando character is a physics sphere and code within FixedUpdate() applies a force to the physics sphere if the rolando is selected upon tilting the device.
The iPhone hardware can measure acceleration along three axes. In this instance the y-axis is used which represents the top and bottom of the device when viewing the device in its vertical orientation. Rolando is played in landscape mode, however, so it's necessary to apply the hardware's y-axis acceleration along the game world's x-axis.
The final script included in this simple project is an animation script. In Rolando the characters blink from time to time. This script showcases simple 2D animation functionality. Although Unity is a 3D engine it is wholly possible to create a 2D game (as has been demonstrated by numerous hits on the app store). This script only scratches the surface of 2D animation but it's a start.
var materials : Material[];
private var blinkTimer : float;
function Update () {
if (materials.length == 0)
return;
if ( blinkTimer + 0.2 > Time.time )
renderer.sharedMaterial = materials[ 1 ];
else
renderer.sharedMaterial = materials[ 0 ];
if ( blinkTimer + 3 < Time.time )
blinkTimer = Time.time;
}
As with the other scripts any var without the private prefix can be assigned manually through the Unity editor. In this instance the materials or textures for the animation can be chosen via the editor. The blinkTimer is simply a time stamp variable that determines when the character blinks.
Well, that about wraps up this segment of Games Demystified. Hopefully, this little journey through the tiny-big world of Rolando delivered some insight into the iPhone hardware as well as the fun and innovative gameplay that allowed Rolando to rise to the top of the iPhone crop this past winter.
Read more about:
FeaturesAbout the Author
You May Also Like