The Basics of Our Game - Sparrow iOS Game Framework Beginner's Guide (2014)

Sparrow iOS Game Framework Beginner's Guide (2014)

Chapter 4. The Basics of Our Game

In the previous chapter, we learned about assets and how to implement our own asset management system which loads the assets from the application bundle and caches them. We used the asset management setup to load our first image. We covered how to group display objects into scenes and wrote a scene director that manages our scenes. In this chapter, we will begin setting up our game. We will learn about what to consider when targeting different devices, and we will take the first step in setting up our game. This includes creating the scenes we need and displaying static images on the screen.

Taking care of cross-device compatibility

When developing an iOS game, we need to know which device to target. Besides the obvious technical differences between all of the iOS devices, there are two factors we need to actively take care of: screen size and texture size limit.

Note

For a quick reference on the differences between iOS devices, take a look at the comparison table at http://www.iosres.com/.

Let's take a closer look at how to deal with the texture size limit and screen sizes.

Understanding the texture size limit

Every graphics card has a limit for the maximum size texture it can display. If a texture is bigger than the texture size limit, it can't be loaded and will appear black on the screen. A texture size limit has power-of-two dimensions and is a square such as 1024 pixels in width and in height or 2048 x 2048 pixels.

When loading a texture, they don't need to have power-of-two dimensions. In fact, the texture does not have to be a square. However, it is a best practice for a texture to have power-of-two dimensions.

This limit holds for big images as well as a bunch of small images packed into a big image. The latter is commonly referred to as a sprite sheet. Take a look at the following sample sprite sheet to see how it's structured:

Understanding the texture size limit

How to deal with different screen sizes

While the screen size is always measured in pixels, the iOS coordinate system is measured in points.

The screen size of an iPhone 3GS is 320 x 480 pixels and also 320 x 480 points. On an iPhone 4, the screen size is 640 x 960 pixels, but is still 320 by 480 points. So, in this case, each point represents four pixels: two in width and two in height. A 100-point wide rectangle will be 200 pixels wide on an iPhone 4 and 100 pixels on an iPhone 3GS.

It works similarly for the devices with large display screens, such as the iPhone 5. Instead of 480 points, it's 568 points.

How to deal with different screen sizes

Scaling the viewport

Let's explain the term viewport first: the viewport is the visible portion of the complete screen area.

We need to be clear about which devices we want our game to run on. We take the biggest resolution that we want to support and scale it down to a smaller resolution. This is the easiest option, but it might not lead to the best results; touch areas and the user interface scale down as well. Apple recommends for touch areas to be at least a 40-point square; so, depending on the user interface, some elements might get scaled down so much that they get harder to touch.

Take a look at the following screenshot, where we choose the iPad Retina resolution (2048 x 1536 pixels) as our biggest resolution and scale down all display objects on the screen for the iPad resolution (1024 x 768 pixels):

Scaling the viewport

Scaling is a popular option for non-iOS environments, especially for PC and Mac games that support resolutions from 1024 x 600 pixels to full HD.

As we will learn later in this chapter, Sparrow and the iOS SDK provide some mechanisms that will facilitate handling Retina and non-Retina iPad devices without the need to scale the whole viewport.

Black borders

Some games in the past have been designed for a 4:3 resolution display but then made to run on a widescreen device that had more screen space.

So, the option was to either scale a 4:3 resolution to widescreen, which will distort the whole screen, or put some black borders on either side of the screen to maintain the original scale factor.

Black borders

Showing black borders is something that is now considered as bad practice, especially when there are so many games out there which scale quite well across different screen sizes and platforms.

Showing non-interactive screen space

If our pirate game is a multiplayer, we may have a player on an iPad and another on an iPhone 5. So, the player with the iPad has a bigger screen and more screen space to maneuver their ship. The worst case will be if the player with the iPad is able to move their ship outside the visual range for the iPhone player to see, which will result in a serious advantage for the iPad player.

Luckily for us, we don't require competitive multiplayer functionality. Still, we need to keep a consistent screen space for players to move their ship in for game balance purposes. We wouldn't want to tie the difficulty level to the device someone is playing on.

Showing non-interactive screen space

Let's compare the previous screenshot to the black border example. Instead of the ugly black borders, we just show more of the background.

In some cases, it's also possible to move some user interface elements to the areas which are not visible on other devices. However, we will need to consider whether we want to keep the same user experience across devices and whether moving these elements will result in a disadvantage for users who don't have this extra screen space on their devices.

Rearranging screen elements

Rearranging screen elements is probably the most time-intensive and sophisticated way of solving this issue. In this example, we have a big user interface at the top of the screen in the portrait mode. Now, if we were to leave it like this in the landscape mode, the top of the screen will be just the user interface, leaving very little room for the game itself.

Rearranging screen elements

In this case, we have to be deliberate about what kind of elements we need to see on the screen and which elements are using up too much screen estate. Screen real estate (or screen estate) is the amount of space available on a display for an application or a game to provide output. We will then have to reposition them, cut them up in to smaller pieces, or both.

The most prominent example of this technique is Candy Crush (a popular trending game) by King. While this concept applies particularly to device rotation, this does not mean that it can't be used for universal applications.

Choosing the best option

None of these options are mutually exclusive. For our purposes, we are going to show non-interactive screen space, and if things get complicated, we might also resort to rearranging screen elements depending on our needs.

Differences between various devices

Let's take a look at the differences in the screen size and the texture size limit between the different iOS devices:

Device

Screen size (in pixels)

Texture size limit (in pixels)

iPhone 3GS

480 x 360

2048 x 2048

iPhone 4 (including iPhone 4S) and iPod Touch 4th generation

960 x 640

2048 x 2048

iPhone 5 (including iPhone 5C and iPhone 5S) and iPod Touch 5th generation

1136 x 640

2048 x 2048

iPad 2

1024 x 768

2048 x 2048

iPad (3rd and 4th generations) and iPad Air

2048 x 1536

4096 x 4096

iPad Mini

1024 x 768

4096 x 4096

Utilizing the iOS SDK

Both the iOS SDK and Sparrow can aid us in creating a universal application. Universal application is the term for apps that target more than one device, especially for an app that targets the iPhone and iPad device family.

The iOS SDK provides a handy mechanism for loading files for specific devices. Let's say we are developing an iPhone application and we have an image that's called my_amazing_image.png. If we load this image on our devices, it will get loaded—no questions asked. However, if it's not a universal application, we can only scale the application using the regular scale button on iPad and iPhone Retina devices. This button appears on the bottom-right of the screen.

If we want to target iPad, we have two options:

· The first option is to load the image as is. The device will scale the image. Depending on the image quality, the scaled image may look bad. In this case, we also need to consider that the device's CPU will do all the scaling work, which might result in some slowdown depending on the app's complexity.

· The second option is to add an extra image for iPad devices. This one will use the ~ipad suffix, for example, my_amazing_image~ipad.png. When loading the required image, we will still use the filename my_amazing_image.png. The iOS SDK will automatically detect the different sizes of the image supplied and use the correct size for the device.

Beginning with Xcode 5 and iOS 7, it is possible to use asset catalogs. Asset catalogs can contain a variety of images grouped into image sets. Image sets contain all the images for the targeted devices. These asset catalogs don't require files with suffixes any more. These can only be used for splash images and application icons. We can't use asset catalogs for textures we load with Sparrow though.

The following table shows which suffix is needed for which device:

Device

Retina

File suffix

iPhone 3GS

No

None

iPhone 4 (including iPhone 4S) and iPod Touch (4th generation)

Yes

@2x

@2x~iphone

iPhone 5 (including iPhone 5C and iPhone 5S) and iPod Touch (5th generation)

Yes

-568h@2x

iPad 2

No

~ipad

iPad (3rd and 4th generations) and iPad Air

Yes

@2x~ipad

iPad Mini

No

~ipad

How does this affect the graphics we wish to display? The non-Retina image will be 128 pixels in width and 128 pixels in height. The Retina image, the one with the @2x suffix, will be exactly double the size of the non-Retina image, that is, 256 pixels in width and 256 pixels in height.

Utilizing the iOS SDK

Retina and iPad support in Sparrow

Sparrow supports all the filename suffixes shown in the previous table, and there is a special case for iPad devices, which we will take a closer look at now.

When we take a look at AppDelegate.m in our game's source, note the following line:

[_viewController startWithRoot:[Game class] supportHighResolutions:YES doubleOnPad:YES];

The first parameter, supportHighResolutions, tells the application to load Retina images (with the @2x suffix) if they are available.

The doubleOnPad parameter is the interesting one. If this is set to true, Sparrow will use the @2x images for iPad devices. So, we don't need to create a separate set of images for iPad, but we can use the Retina iPhone images for the iPad application.

In this case, the width and height are 512 and 384 points respectively. If we are targeting iPad Retina devices, Sparrow introduces the @4x suffix, which requires larger images and leaves the coordinate system at 512 x 384 points.

App icons and splash images

If we are talking about images of different sizes for the actual game content, app icons and splash images are also required to be in different sizes.

Splash images (also referred to as launch images) are the images that show up while the application loads. The iOS naming scheme applies for these images as well, so for Retina iPhone devices such as iPhone 4, we will name an image as Default@2x.png, and for iPhone 5 devices, we will name an image as Default-568h@2x.png.

For the correct size of app icons, take a look at the following table:

Device

Retina

App icon size

iPhone 3GS

No

57 x 57 pixels

iPhone 4 (including iPhone 4S) and iPod Touch 4th generation

Yes

120 x 120 pixels

iPhone 5 (including iPhone 5C and iPhone 5S) and iPod Touch 5th generation

Yes

120 x 120 pixels

iPad 2

No

76 x 76 pixels

iPad (3rd and 4th generation) and iPad Air

Yes

152 x 152 pixels

iPad Mini

No

76 x 76 pixels

The bottom line

The more devices we want to support, the more graphics we need, which directly increases the application file size, of course. Adding iPad support to our application is not a simple task, but Sparrow does some groundwork.

One thing we should keep in mind though: if we are only targeting iOS 7.0 and higher, we don't need to include non-Retina iPhone images any more. Using @2x and @4x will be enough in this case, as support for non-Retina devices will soon end.

Starting with the development of our game

Now that we have enough theory and experience with the Sparrow framework, let's put all that knowledge to use and turn theory into practice by creating our pirate game.

Note

If you miss any of the development of our game, the source code of the game is also available on GitHub at https://github.com/freezedev/pirategame.

Our game consists of two main gameplay parts:

· Battlefield/arena: This is the scene where our pirate ship battles against other ships

· Pirate cove: The pirate cove is the hub for activities after battling other ships such as hiring new crew members and upgrading the ship

In this chapter, we will set up the required scenes and load the textures, display them as images, and arrange the entities on the screen.

Note

The graphics for the game are on GitHub as well: https://github.com/freezedev/pirategame-assets. The graphics are made with the open-source 3D modeling software, Blender (http://www.blender.org); Version 2.69 is required to open and edit these files. Don'tworry, we don't need to update these files for the purposes of this book, but if you want to in order to look for inspiration, you are definitely encouraged to do so.

Let's download the required images for this chapter by navigating to https://github.com/freezedev/pirategame-assets/releases. This will show all the available releases for this particular repository, as shown in the following screenshot:

Starting with the development of our game

Go ahead and download the Graphics.zip package and unzip the contents somewhere on your computer. This package contains the following images:

Filename

Description

water.png

This is the background for the battlefield scene.

island.png

This is the background for the pirate base. Technically, it's more of an island than a cove, which is why this image is called island, but it's referred to as the pirate cove everywhere else.

house.png

This is a shelter for our pirates.

tavern.png

This is the building where we get to hire new pirates.

weaponsmith.png

This will be the place where we upgrade our ship with additional cannons or ammunition.

ship.png

This is our basic enemy.

ship_pirate.png

This is the ship we are going to control.

All of the assets are in a non-Retina resolution, Retina for iPad 2, iPad Mini, and iPhone/iPod Touch using the @2x filename suffix and @4x for iPad Retina devices.

Drag and drop the files into the Resources folder of our Xcode project. When a dialog pops up, we need to check Copy items into destination group's folder (if needed), so we don't have to worry about references to the original files. Click on Finish to start the process.

So far, the images have been optimized for the landscape mode, so we need to deactivate the portrait mode for now. We need to select the PirateGame project and uncheck Portrait and Upside Down in the Deployment Info section, as shown in the following screenshot. Make sure to uncheck them for both iPhone and iPad.

Starting with the development of our game

We can also safely delete the cardboard puppet doll code that is still in our Game.m file.

Creating our scene manager setup

In the previous chapter, we created a scene manager which we will now use for our scenes. In our first step, we will need two dummy scenes that we will later fill with gameplay mechanics. We will also need to add these scenes to our scene director and display one of the two scenes.

Time for action – creating our scene manager setup

To create our scene manager setup, we need to follow these steps:

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

2. Right-click on the Classes folder and select New Group.

3. Rename the group to GameScenes.

4. Create a new Objective-C class called PirateCove which is sub-classed from Scene.

5. Add an initializer with the following content:

6. if ((self = [super init])) {

7. NSLog(@"Pirate cove scene created");

}

8. Create another Objective-C class which is sub-classed from Scene. Call this Battlefield.

9. Add an initializer with the following content:

10.-(id) init

11.{

12. if ((self = [super init])) {

13. NSLog(@"Battlefield scene created");

14. }

15.

16. return self;

}

17. Switch to the Game.m file.

18. Add the PirateCove.h, Battlefield.h, and SceneDirector.h files to the import section, as shown in the following code:

19.#import "SceneDirector.h"

20.#import "PirateCove.h"

#import "Battlefield.h"

21. In the init method, create an instance of the PirateCove and Battlefield classes and call the initWithName method using @"piratecove" and @"battlefield" respectively for its parameter:

22.PirateCove *pirateCove = [[PirateCove alloc] initWithName:@"piratecove"];

Battlefield *battlefield = [[Battlefield alloc] initWithName:@"battlefield"];

23. Create an instance of the scene director and add it to the Game class, as shown in the following code:

24.SceneDirector *director = [[SceneDirector alloc] init];

[self addChild:director];

25. Add both scenes to the scene director and show the pirate cove scene:

26.[director addScene:pirateCove];

27.[director addScene:battlefield];

28.

[director showScene:@"battlefield"];

29. Run the example and you will get the following output:

Time for action – creating our scene manager setup

What just happened?

In step 1, we opened our Xcode template from where we left off in the previous chapter. In step 2, we created a new group where everything that is related to our game scenes will be put. In step 3, we renamed the newly created group.

In step 4, we created a new Objective-C class, which is derived from the Scene class. In the next step, we added the initializer method where we added a log message to see whether the scene has been created.

In steps 6 and 7, we did the same for the battlefield scene.

After we switched to the Game.m file in step 8, we imported all the source files we need, which is the header from the scene director and both scenes we just created.

We created instances of our scenes and our scene director in step 11. The scene director is a sprite itself, so we need to add it to the Game class, which also derives from SPSprite.

In step 12, we added our scene instances to the scene director, which means that the scenes are now in the display tree. We then called the method in the SceneDirector instance to show the battlefield scene.

When we ran the example, we didn't see anything worthwhile on the screen as the scenes didn't have anything in them, but if we take a look at the console, we see that our two scenes have been successfully created.

Here is the full source code from this example:

Pirate cove scene

Battlefield scene

PirateCove.h

#import "Scene.h"

@interface PirateCove : Scene

@end

PirateCove.m

#import "PirateCove.h"

@implementation PirateCove

-(id) init

{

if ((self = [super init])) {

NSLog(@"Pirate cove scene created");

}

return self;

}

@end

Battlefield.h

#import "Scene.h"

@interface Battlefield : Scene

@end

Battlefield.m

#import "Battlefield.h"

@implementation Battlefield

-(id) init

{

if ((self = [super init])) {

NSLog(@"Battlefield scene created");

}

return self;

}

@end

The Game.m file contains the following code:

#import "Game.h"

#import "SceneDirector.h"

#import "PirateCove.h"

#import "Battlefield.h"

@implementation Game

- (id)init

{

if ((self = [super init]))

{

Sparrow.stage.color = 0xffffff;

PirateCove *pirateCove = [[PirateCove alloc] initWithName:@"piratecove"];

Battlefield *battlefield = [[Battlefield alloc] initWithName:@"battlefield"];

SceneDirector *director = [[SceneDirector alloc] init];

[self addChild:director];

[director addScene:pirateCove];

[director addScene:battlefield];

[director showScene:@"battlefield"];

}

return self;

}

@end

Adding images to the battlefield scene

Now that the scenes are ready to use, let's add some ships to the battlefield scene.

Time for action – adding images to the battlefield scene

Let's take a look at the following steps in order to add images to the battlefield scene:

1. Open the Battlefield.m file and import the Assets header file:

#import "Assets.h"

2. Remove the log message and add the background image, as shown in the following code:

3. SPImage *background = [SPImage imageWithTexture:[Assets texture:@"water.png"]];

4. background.x = (Sparrow.stage.width - background.width) / 2;

background.y = (Sparrow.stage.height - background.height) / 2;

5. Add the pirate ship, as shown in the following code:

6. SPImage *pirateShip = [SPImage imageWithTexture:[Assets texture:@"ship_pirate.png"]];

7. pirateShip.x = (Sparrow.stage.width - pirateShip.width) / 2;

pirateShip.y = (Sparrow.stage.height - pirateShip.height) / 2;

8. Add an enemy ship using the following code:

9. SPImage *ship = [SPImage imageWithTexture:[Assets texture:@"ship.png"]];

10.ship.x = 100;

ship.y = 100;

11. Add all children to the display tree, as shown in the following code:

12.[self addChild:background];

13.[self addChild:pirateShip];

[self addChild:ship];

14. Run the example and you will get the following output:

Time for action – adding images to the battlefield scene

What just happened?

In step 1, we opened the Battlefield.m file as this is the file we need if we want to change anything in the battlefield scene and we imported the Assets.h file in order to use our asset management system.

In step 2, we prepared the background, which should be in the center of the screen. We used our asset management system to get a texture from a specified file which returns either the cached or newly loaded texture, and the texture will then be used to drawSPImage on the screen.

In step 3, we added the pirate ship, which should be in the center of the screen as well. In the next step, we added an enemy ship, which should not be too far away from our ship.

In step 5, we added all our display objects to the display tree, and when we ran the example, we saw two ships on the screen.

The Battlefield.m file will contain the following code:

#import "Battlefield.h"

#import "Assets.h"

@implementation Battlefield

-(id) init

{

if ((self = [super init])) {

SPImage *background = [SPImage imageWithTexture:[Assets texture:@"water.png"]];

background.x = (Sparrow.stage.width - background.width) / 2;

background.y = (Sparrow.stage.height - background.height) / 2;

SPImage *pirateShip = [SPImage imageWithTexture:[Assets texture:@"ship_pirate.png"]];

pirateShip.x = (Sparrow.stage.width - pirateShip.width) / 2;

pirateShip.y = (Sparrow.stage.height - pirateShip.height) / 2;

SPImage *ship = [SPImage imageWithTexture:[Assets texture:@"ship.png"]];

ship.x = 100;

ship.y = 100;

[self addChild:background];

[self addChild:pirateShip];

[self addChild:ship];

}

return self;

}

@end

Arranging images in the pirate cove scene

Let's move on to the pirate cove scene to give our pirates a nice little home. What we will be doing in this example is adding a house, a tavern, and a weaponsmith to the scene. These will serve as places where we can update our ship later on.

Time for action – arranging images in the pirate cove scene

To add images to the pirate cove scene, follow these steps:

1. Open PirateCove.m.

2. Import the Assets header file using the following line of code:

#import "Assets.h"

3. Remove the log message and add the background image, as shown in the following code:

4. SPImage *background = [SPImage imageWithTexture:[Assets texture:@"cove.png"]];

5. background.x = (Sparrow.stage.width - background.width) / 2;

background.y = (Sparrow.stage.height - background.height) / 2;

6. Add our pirate ship, as shown in the following code:

7. SPImage *pirateShip = [SPImage imageWithTexture:[Assets texture:@"ship_pirate.png"]];

8. pirateShip.x = Sparrow.stage.width - pirateShip.width - 120;

pirateShip.y = Sparrow.stage.height - pirateShip.height - 10;

9. Add a house, as shown in the following code:

10.SPImage *house = [SPImage imageWithTexture:[Assets texture:@"house.png"]];

11.house.x = 100;

house.y = 100;

12. Add a tavern, as shown in the following code:

13.SPImage *tavern = [SPImage imageWithTexture:[Assets texture:@"tavern.png"]];

14.tavern.x = 220;

tavern.y = 40;

15. Add a weaponsmith, as shown in the following code:

16.SPImage *weaponsmith = [SPImage imageWithTexture:[Assets texture:@"weaponsmith.png"]];

17.weaponsmith.x = 350;

weaponsmith.y = 130;

18. Register all images to the display tree:

19.[self addChild:background];

20.[self addChild:pirateShip];

21.[self addChild:house];

22.[self addChild:tavern];

[self addChild:weaponsmith];

23. Go to the Game.m file and change the default scene to the pirate cove, as shown in the following code:

[director showScene:@"piratecove"];

24. Run the example and you will get the following output:

Time for action – arranging images in the pirate cove scene

What just happened?

Most of the steps are quite similar to the battlefield scene, so we don't need to explain every step in detail.

In step 1, we opened the PirateCove.m file where everything with regard to the pirate cove should be. We needed the asset management system here as well, so we imported it in step 2.

In step 3, we loaded the fitting image, which should be in the center of the screen. In steps 4 to 7, we loaded different entities we wanted to display on the screen, such as the pirate ship and the house. We positioned them more or less randomly on the screen, but left enough space between them so that it won't leave a cluttered impression.

In step 8, we added all of our display objects to the screen. Remember that the order matters. If we were to add the background image last, we will only see the background and nothing else.

We set the scene director to load the pirate cove scene instead of the battlefield scene, and when we ran the example, we saw the pirate cove on the screen.

Pop quiz

Q1. What do we need to actively take care of when developing a universal application?

1. Battery power

2. Screen size and texture size limit

3. GPU memory

Q2. If we want to display an image with the suffix ~ipad, on which device(s) will it load?

1. Non-Retina iPad

2. Retina iPhone

3. Retina iPad

Q3. What will the dimensions be of an image of 256 x 256 pixels on, Retina iPhone in the iOS point coordinate system?

1. 128 x 128 pt

2. 256 x 256 pt

3. 512 x 512 pt

Q4. Which suffix is required to load images on, Retina iPad if the doubleOnPad parameter is set to YES?

1. @2x

2. @3x

3. @4x

Summary

In this chapter, we learned about cross-platform device compatibility between iPad and iPhone devices.

Specifically, we covered which filename suffix we need to identify, which file to load for which device, how the coordinate system in points works, and texture size limits when loading images.

We also set up the bare bone, of our game where we loaded the images for different kinds of devices utilizing our asset and scene managers.

Now that the scenes of our game are available and we have put some images on the screen, we're ready to beautify our game—which is the topic of the next chapter.