Displaying Our First Objects - Sparrow iOS Game Framework Beginner's Guide (2014)

Sparrow iOS Game Framework Beginner's Guide (2014)

Chapter 2. Displaying Our First Objects

In the previous chapter, we installed and configured Xcode developer tools and also as downloaded the Sparrow framework and linked it to a sample project. We proceeded to test it in both the iOS Simulator and a real device. We also set the scope for the game we are going to develop throughout the book. However, before we get into the game development process, let's touch on some core concepts of Sparrow and get accustomed with the way things work in Sparrow. We will draw some objects on the screen and manipulate these objects by applying rotation and scaling transforms.

Understanding display objects

As the name suggests, a display object is something that will be displayed on the screen. We can think of display objects as separate graphical entities that contain different kinds of graphical data. While this may sound a bit abstract at first, every image (SPImage), quads (SPQuad), or other geometrical shapes are derived from the SPDisplayObject class, which is the representation of a display object in Sparrow.

Explaining display object containers

A display object container (SPDisplayObjectContainer) inherits from SPDisplayObject, adding the facility to own a set of child display objects. When you add a child display object to a parent display object container, you can think of it as attaching one display object to another. If you move, scale, or rotate the parent display object, all the changes are inherited by any children it might have. This concept is more or less identical to how objects on the screen are managed in the Adobe Flash API. The full set of parent and child nodes is referred to as the display list, or sometimes as a display tree. This is because, like a tree, it contains many branches that all ultimately converge into one single trunk, often referred to as the root. Yet another name for a display tree is a scene graph.

The display list draws the display objects in the order they are added to their parent display object container. If we were to add a second child display object to the same parent as that of the previously added display object, the second display object will be drawn in front of the first.

Let's go ahead and imagine ourselves as a cardboard puppet doll. We need a head, a torso and a leg, arm, and hand on the left side and the same goes for the right side. Refer to the following diagram that displays this concept:

Explaining display object containers

The root object for this arrangement would be the body object. The head, torso, legs, and arms would be directly bound to the body and the hands would be bound to each arm.

Setting the background color

Before we draw a couple of objects on the screen, let's change the background color of our application that will eventually become our game.

Time for action – changing the background color

Let's take a look at the required steps to change the background color:

1. Open our Xcode game template if it's not already open.

2. Open the Game.m source file.

3. After the initialization method and before the existing SPQuad object, add the following lines:

4. SPQuad *background = [SPQuad quadWithWidth:Sparrow.stage.width height:Sparrow.stage.height color:0xffffff];

[self addChild:background];

5. Run the example.

When the example is running, we see our red rectangle on a white background as shown in the following screenshot:

Time for action – changing the background color

What just happened?

In step 1, we opened our Xcode template that we created in the previous chapter, and in step 2, we navigated to the Game.m file, which is where our game code currently lies. The game is the red rectangle that keeps showing up.

In step 3, right before we drew our red rectangle, we defined the background variable that is a pointer to an instance of SPQuad. The SPQuad class is derived from SPDisplayObject. The function of SPQuad is to draw a rectangular shape with a background color.

The SPQuad class provides a few factory methods for operations such as creating a quad with a width and height and also adds a color value to it. In this example, we are creating a quad with a predefined width and height and a color value of 0xffffff. A color is defined as 0xRRGGBB in a hexadecimal notation, that is, REDRED GREENGREEN BLUEBLUE.

While at the surface, the call to [SPQuad quadWithWidth:Sparrow.stage.width height:Sparrow.stage.height] seems to be the same as the one to [[SPQuad alloc] initWithWidth:Sparrow.stage.width height:Sparrow.stage.height], but there is one major difference under the hood. When the factory method is called, it returns an auto-released object, which means we don't have an ownership over the instance and it's being destroyed at some point. On the other hand, if we use the alloc-and-init combination, we do have the ownership and the need to release the instance ourselves.

As our application uses Automatic Reference Counting (ARC), we don't need to worry about releasing instances ourselves. On the other hand, Sparrow itself uses Manual Reference Counting (MRC).

To cover the whole screen, we need to get the width and height of the screen itself. Those values are available as properties in the Sparrow.stage object.

We need to add the background to the Game class, which is exactly what [self addChild:background] does. The self keyword is a reference to the Game class, which is derived from the SPSprite class.

Now, we have a white background with a red rectangle that appears on top of it.

Our Game.m source file should contain the following code:

#import "Game.h"

@implementation Game

- (id)init

{

if ((self = [super init]))

{

SPQuad *background = [SPQuad quadWithWidth:Sparrow.stage.width height:Sparrow.stage.height color:0xffffff];

[self addChild:background];

SPQuad *quad = [SPQuad quadWithWidth:100 height:100];

quad.color = 0xff0000;

quad.x = 50;

quad.y = 50;

[self addChild:quad];

}

return self;

}

@end

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

There is also an easier way to set the background color. While the performance penalty in this specific case is not that high to worry about, we could directly set the color through Sparrow.stage by using its color property: Sparrow.stage.color = 0xffffff. It consists of fewer lines, is more readable, and shows its intention better.

What is a stage?

We briefly touched on the topic of Sparrow.stage, which so far proved to have some useful properties for getting the width and height of the screen and setting the background color directly.

A stage is the top-level element of any Sparrow game and logically the root element of the display tree, which Sparrow automatically creates for us.

Creating our cardboard puppet doll

Let's implement the cardboard puppet doll that we talked about in the beginning of the chapter. Remove the red rectangle that has been drawn on the screen.

Time for action – creating a cardboard puppet doll

To create the cardboard puppet doll, we need to perform the following steps:

1. Open the Game.m file if it's not already open.

2. Add a body container with the following lines:

3. SPSprite *body = [[SPSprite alloc] init];

4. body.x = 85;

5. body.y = 120;

6.

[self addChild:body];

7. Add torso as shown in the following code:

8. SPQuad *torso = [SPQuad quadWithWidth:150 height:150];

9. torso.color = 0xff0000;

10.

[body addChild:torso];

11. Now add a local variable head as shown in the following code:

12.SPQuad *head = [SPQuad quadWithWidth:80 height:80 color:SP_YELLOW];

13.head.x = 35;

14.head.y = -70;

15.

[body addChild: head];

16. Add a container for the arms local variable as shown in the following code:

17.SPSprite *arms = [[SPSprite alloc] init];

18.

[body addChild:arms];

19. Add a container for the legs local variable as shown in the following code:

20.SPSprite *legs = [[SPSprite alloc] init];

21.legs.y = 140;

22.

[body addChild:legs];

23. Add the left arm as shown in the following code:

24.SPQuad *leftArm = [SPQuad quadWithWidth:100 height:50 color:0x00ff00];

25.leftArm.x = -80;

26.

[arms addChild:leftArm];

27. Add the right arm as shown in the following code:

28.SPQuad *rightArm = [SPQuad quadWithWidth:100 height:50 color:0x00ff00];

29.rightArm.x = 130;

30.

[arms addChild:rightArm];

31. Every arm needs a hand. Let's add the left one first as shown in the following code:

32.SPQuad *leftHand = [SPQuad quadWithWidth:40 height:50 color:SP_YELLOW];

33.leftHand.x = -80;

34.

[arms addChild:leftHand];

35. Now use the following code for the right hand:

36.SPQuad *rightHand = [SPQuad quadWithWidth:40 height:50 color:SP_YELLOW];

37.rightHand.x = 190;

38.

[arms addChild:rightHand];

39. Let's move on to the legs. We'll create the left one first with the following code:

40.SPQuad *leftLeg = [SPQuad quadWithWidth:50 height:150 color:0x0000ff];

41.

[legs addChild:leftLeg];

42. We'll create the right leg with the following code:

43.SPQuad *rightLeg = [SPQuad quadWithWidth:50 height:150 color:0x0000ff];

44.rightLeg.x = 100;

45.

[legs addChild:rightLeg];

46. Run the example.

When we run the example, a simple cardboard puppet doll made of rectangles is looking right at us, as shown in the following screenshot:

Time for action – creating a cardboard puppet doll

What just happened?

In step 1, we used the Game.m source file we are already familiar with.

At first, we needed a container object, which we called body for this example. A quad would not suffice in this case because SPQuad does not inherit from SPDisplayObjectContainer and so cannot have children added to it. We set the x and y properties, so the contents of the body element appeared somewhere in the middle of our screen. The coordinate system in Sparrow started at the top-left corner of the screen, just like how the coordinate system works in Flash or in traditional application development when adding control elements to a window. Developers from a traditional graphics development may take some time to get used to this. In OpenGL, for example, the y axis is flipped. We then add the body element to our Game instance.

In step 3, we took the torso, which is a quad and added it to the body element. If we don't specify an x or y property, their default value is 0.

After that, we added the head. The x and y properties are measured relative to the parent display object. So, if we use a negative value, it doesn't necessarily mean that the element is drawn outside the screen. It depends on the position of the parent display object container.

While we know that we can use colors with the hexadecimal notation, we are using SP_YELLOW in this step. This has the same effect as typing 0xffff00.

For the arms and legs, we added a container for each in step 5 and step 6, respectively. SPSprite is the most basic and lightweight container class that should be used when grouping objects. The leg container is already being positioned a bit to the bottom, so its children elements only need to be positioned horizontally.

In the remaining steps, we added each limb and when we finally ran the application, we had a cardboard puppet doll made of rectangles looking at us.

Have a go hero – improving the cardboard puppet doll

Our code can be improved quite a bit; the legs, arms, and hands code are practically the same, but we define each element separately. We could try to group and simplify the code a bit.

Also, in the current arrangement, the hands are not directly connected to the arms of the doll. Instead, they are bound to the arms container object. So if we were to move a single arm, the hand would not move with the arm.

The following are some ideas on how to solve these problems:

· In order to connect the hands to the arms, we would need at least two new container objects

· Make a cardboard puppet doll class in which its elements are classes inheriting from the display object containers

Explaining macros

While we know that we can use colors with the hexadecimal notation, Sparrow provides some shorthand constants for the most commonly used colors. In the previous example, instead of using 0xffff00 for the color yellow, we used SP_YELLOW.

To generalize, macros are handy little functions that allow us to simplify the workflow when working with repetitious tasks.

Macros in Objective-C are preprocessor directives and work the same way that macros work in C and C++. Before the code is compiled, the preprocessor goes through the entire code and replaces all occurrences of the macro with the result of the macro.

While we could write each color in the hexadecimal color value notation, sometimes it does make more sense to use an RGB value. The SP_COLOR macro does exactly that, converting a RGB color into a hexadecimal color value.

In this section, we will look at what the different kinds of macros are and how to use them.

The Angles macro

Sparrow uses radians to describe the rotation of its display objects. If we want to use degrees for our calculations, we would need the following macros:

Name

Description

Example

SP_R2D

Converts radians to degrees

SP_R2D(PI);

// 180

SP_D2R

Converts degrees to radians

SP_D2R(180);

// PI

The Colors macro

If we need to create a custom color or take an existing color apart, the following macros would fit our purpose:

Name

Description

Example

SP_COLOR_PART_ALPHA

SP_COLOR_PART_RED

SP_COLOR_PART_GREEN

SP_COLOR_PART_BLUE

Getting the partial value of a color

SP_COLOR_PART_RED(0xff0000);

// 0xff

SP_COLOR

Sets an RGB color

SP_COLOR(255, 255, 0);

// 0xffff00

SP_COLOR_ARGB

Sets an ARGB color

SP_COLOR_ARGB(128, 255, 255, 0);

// 0x80ffff00

The utility functions

Let's take a look at the last group of macros that aren't angle- or color-related:

Name

Description

Example

SP_IS_FLOAT_EQUAL

Does a float comparison between two values. Returns 0 if it's false, 1 if it's true.

SP_IS_FLOAT_EQUAL(0.11, 0.12);

// 0

SP_CLAMP

Clamps between two values. The first parameter is the initial value. The other two parameters are the minimum and maximum values respectively.

SP_CLAMP(0.6, 1.0, 2.0);

// 1.0

SP_SWAP

Swaps two values with each other.

NSUInteger x = 0;

NSUInteger y = 1;

SP_SWAP(x, y, NSUInteger);

// x = 1; y = 0

Constants in Sparrow

We already know about SP_YELLOW, so let's take a look at what constants are defined in Sparrow.

Math

The PI constant, for example, is used in the macro to convert radians to degrees and vice versa. The following are the examples of PI constant:

Name

Description

PI

The value of Pi

PI_HALF

The value of Pi divided by two

TWO_PI

The value of Pi multiplied by two

Color

Sparrow predefines 16 colors for easier usage, so we don't have to use a macro each time. These are the most basic colors and are also defined in a number of different libraries and frameworks, for example, HTML 4.01. The following table shows 16 colors that are predefined in Sparrow:

Name

RGB value

Hex value

SP_WHITE

255, 255, 255

0xffffff

SP_SILVER

208, 208, 208

0xc0c0c0

SP_GRAY

128, 128, 128

0x808080

SP_BLACK

0, 0, 0

0x000000

SP_RED

255, 0, 0

0xff0000

SP_MAROON

128, 0, 0

0x800000

SP_YELLOW

255, 255, 0

0xffff00

SP_OLIVE

128, 128, 0

0x808000

SP_LIME

0, 255, 0

0x00ff00

SP_GREEN

0, 128, 0

0x008000

SP_AQUA

0, 255, 255

0x00ffff

SP_TEAL

0, 128, 128

0x008080

SP_BLUE

0, 0, 255

0x0000ff

SP_NAVY

0, 0, 128

0x000080

SP_FUCHSIA

255, 0, 255

0xff00ff

SP_PURPLE

128, 0, 128

0x800080

Manipulating display objects

Now that we have our cardboard puppet doll on the screen, let's start manipulating the objects on the screen.

In this example, we will take a look at how to rotate, scale, and skew objects, and then set the origin of these objects.

Time for action – manipulating display objects

Perform the following steps to manipulate the display objects we created earlier:

1. Add a new method to Game.m below the init method we used to create the body parts:

2. - (void)onLegTouch:(SPTouchEvent *)event

3. {

4. SPTouch *touch = [[event touchesWithTarget:self andPhase:SPTouchPhaseBegan] anyObject];

5. if (touch) {

6. SPQuad* target = (SPQuad *) event.target;

7.

8. float currentRotation = SP_R2D(target.rotation);

9. currentRoration = currentRotation + 10;

10.

11. if (currentRotation >= 360.0)

12. {

13. currentRotation = currentRotation - 360.0;

14. }

15. target.rotation = SP_D2R(currentRotation);

16. }

}

17. Next, we'll need to set the anchor (pivot) of our legs in the initializer, as shown in the following code:

18.leftLeg.pivotX = 25;

19.leftLeg.pivotY = 10;

20.

21.rightLeg.pivotX = 25;

rightLeg.pivotY = 10;

22. Update the leg positions using the following code:

23.SPQuad *leftLeg = [SPQuad quadWithWidth:50 height:150 color:0x0000ff];

24.[legs addChild:leftLeg];

25.leftLeg.x = 25;

26.

27.SPQuad *rightLeg = [SPQuad quadWithWidth:50 height:150 color:0x0000ff];

28.rightLeg.x = 125;

[legs addChild:rightLeg];

29. We'll set an event listener for the legs using the following code:

30.[rightLeg addEventListener:@selector(onLegTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];

[leftLeg addEventListener:@selector(onLegTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];

31. Let's add another method that should be called when we touch the head of our cardboard puppet doll. This method should be below the initializer and the onLegTouch method:

32.- (void)onHeadTouch:(SPTouchEvent *)event

33.{

34. SPTouch *touch = [[event touchesWithTarget:self andPhase:SPTouchPhaseBegan] anyObject];

35. if (touch) {

36. SPQuad* target = (SPQuad *) event.target;

37. target.scaleX = (target.scaleX == 1.0) ? 1.5 : 1.0;

38. target.scaleY = (target.scaleY == 1.0) ? 1.5 : 1.0;

39. }

}

40. We'll need to set the pivot for the head as well:

41.head.pivotX = head.width / 2;

head.pivotY = head.height / 2;

42. Let's update the position of the head as shown in the following code:

43.SPQuad *head = [SPQuad quadWithWidth:80 height:80 color:SP_YELLOW];

44.head.x = 75;

45.head.y = -30;

[body addChild: head];

46. Let's add an event listener for the head as shown in the following code:

[head addEventListener:@selector(onHeadTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];

47. Add another method that should be called if we touch the arms. This is shown in the following code:

48.- (void)onArmsTouch:(SPTouchEvent *)event

49.{

50. SPTouch *touch = [[event touchesWithTarget:self andPhase:SPTouchPhaseBegan] anyObject];

51. if (touch) {

52. SPQuad* target = (SPQuad *) event.target;

53. target.skewX = (target.skewX == SP_D2R(20)) ? SP_D2R(0) : SP_D2R(20);

54. target.skewY = (target.skewY == SP_D2R(20)) ? SP_D2R(0) : SP_D2R(20);

55. }

}

56. Bind the event listener to this newly added method:

[arms addEventListener:@selector(onArmsTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];

57. Run the example and touch some limbs of our cardboard puppet doll. We should now see our cardboard puppet doll on the screen and if we touch an arm, leg, or the head, we see these objects rotated, skewed, or scaled.

Time for action – manipulating display objects

What just happened?

In step 1, we defined a method that should be called when we touch one of the legs. We need to get a reference to the touch event, which in Sparrow is described as SPTouchEvent. To get the touch instance (SPTouch), we looked for touches on any object in the touch began phase. Each touch moves through three phases: first SPTouchPhaseBegan, then SPTouchPhaseMoved, and finally SPTouchPhaseEnded. We need to check whether the touch is valid as objects have been touched by using it as a condition in the if-statement. The current target of the event is available in event.target although it needs to be casted to the appropriate display object type, in this case, SPQuad.

We then got the current rotation of the touched object and add 10 degrees to the rotation. The new rotation will be set to the quad. If the rotation is bigger than 360 degrees, we'll subtract 360 degrees.

The origin for display objects is, by default, the top-left corner of the display object itself. If we want a different origin, we'll need to modify it using the pivotX and pivotY properties of a display object.

Modifying the origin also has an effect on the positioning of the element; so, if we want to keep the same position, we need to add the pivot value to the position values, which is what happened in step 3.

In step 4, we added an event listener for each of the legs, so when we actually touch the legs, something happens. When using addEventListener, we are binding a selector that will be called once the event is triggered, in our case, SP_EVENT_TYPE_TOUCH. This event will be called if any touch occurs on the specified object, which was self (the Game instance) in this step. Multiple selectors can be bound to one event when using addEventListener each time.

For the next step, we added a method for touching the head of our cardboard puppet doll. We also needed to do the same touch check and target casting we did last time. This time when we touch the head, it should scale up to 150 percent of its original size, and if we touch the head again, it'll shrink back to its original size.

In step 6, we set the origin to the center of the element. In step 7, we needed to update the position accordingly, and in step 8, we bound the method to the head element.

The last method that we defined is what would happen when we touch any arms element. If we are binding a touch event to a SPSprite instance, it will fire for all its children as well. The same touch check applies to this method. We'll skew an element by 20 degrees with the first touch and reset it to its original state when the element is touched again.

We use a ternary statement here to check whether the target is already skewed. We check for the condition within the parenthesis. If the condition evaluates against true, the statement after the question mark will be executed; otherwise, the statement after the colon will be executed. The advantage is that the ternary statement is an expression and can be assigned to a value in a single step. It would translate to the following code if we were to use if statements instead:

if (target.skewX == SP_D2R(20)) {

target.skewX = SP_D2R(0);

} else {

target.skewX = SP_D2R(20);

}

if (target.skewY == SP_D2R(20)) {

target.skewY = SP_D2R(0);

} else {

target.skewY = SP_D2R(20);

}

The onArmsTouch method was then bound to the arms object in step 10.

When we run the example and touch various elements, we'll see all the skewing, scaling, and rotating in action.

Pop quiz

Q1. What is an alternate term for display list/tree?

1. Display block

2. Display object

3. Scene graph

Q2. What is a Sparrow stage?

1. A game level

2. Root element of the display tree

3. A display object on the Game class

Q3. What are macros?

1. Functions that are evaluated at runtime

2. Preprocessor directives that are evaluated before compiling

3. Dynamic constants

Summary

We learned a lot in this chapter about how to display objects on the screen and how to manipulate them.

Specifically, we covered how to display objects on the screen and use macros and constants Sparrow provides. Another important aspect is that we manipulated the objects we drew on the screen.

We also touched on some topics such as the Sparrow stage and got an overview of how the Sparrow API works.

Now that we know how to draw objects on the screen, we're ready to learn about asset and scene management—which is the topic of the next chapter.