Chapter     2

SKActions and SKTextures: Your First Animated Sprite

The “Hello World” application was a good introduction to a few Sprite Kit fundamentals such as the SKScene, SKLabelNode, SKSpriteNode, and SKAction objects, but it was seriously lacking in functionality (unless your idea of a great game is to spawn 100 spinning spaceships faster than the next developer)!

In this chapter, you’re going to begin writing a game, entitled “Sewer Bros.” You will start by having a splash screen presented to the user, and when the user touches the screen to start a game, you will transition to a new screen and add an animated sprite to it. Each subsequent chapter will add more features and functionality until, upon reaching the end of the book, you will have a fully functional game. You will be introduced to many Sprite Kit functions along the way and by the end, you should have enough knowledge and experience using Sprite Kit to begin writing your own best-selling 2D game to sell on the iOS App Store.

Humble Beginnings

To make things as easy as possible, you will begin with a template just as you did in the last chapter. This gives you an SKScene and the initial code that you can to use as a bare-bones foundation from which you can build the game.

So, just as before you’ll need to choose Create a new Xcode project from the Welcome to Xcode screen (if you just launched Xcode). If you have already have Xcode open so that the Welcome screen is not visible, you can choose New Project from the File menu.

From the New Project Template sheet (see Figure 1-1 from Chapter 1), make sure that iOS - Application is selected from the list on the left and click on the Sprite Kit Game template icon. Finally, click the Next button.

Fill in the fields on this sheet using the following data (see Figure 1-2 to refresh your memory):

Product Name:

Sewer Bros

Organization Name:

Your name

Company Identifier:

com.yourname

Class Prefix:

SKB

Devices:

iPhone

The next sheet is the standard Save sheet, which allows you to choose where to store your new project. Make your choice and click the Create button when you’re ready.

Feel free to build and run it if you want to verify that everything is working as expected. It’s always a good idea to do this often so that simple errors and problems can be addressed as soon as possible. If you wait until a lot of code has changed before building and running a program, it becomes very painful to find that illusive bug that may have been generated much earlier.

Removing Unnecessary Tidbits

Let’s go back into the template-supplied code and remove some things that you won’t need so that the finished product will be as slim as possible and easy to read as a future point of reference.

Begin in the SKBAppDelegate.m file.

Remove these five methods in the code (leaving just the single method called application didFinishLaunchingWithOptions):

- (void)applicationWillResignActive:(UIApplication *)application
{
    // ...
}
 
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // ...
}
 
- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // ...
}
 
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // ...
}
 
- (void)applicationWillTerminate:(UIApplication *)application
{
    // ...
}

Device Orientation

You are going to support only one device orientation for the game: Landscape. There are two places where you can change this:

  1. In the SKBViewController.m file, modify the supportedInterfaceOrientations method to match the following:
    - (NSUInteger)supportedInterfaceOrientations
    {
        return UIInterfaceOrientationMaskLandscape;
    }
  2. Click once on the main Sewer Bros Project file in the Project Navigator window on the left. Make sure that the “Sewer Bros” target is selected, not the project. Then select the General tab across the top so that you can see and edit the Deployment Info (see Figure 2-1).

9781430264392_Fig02-01.jpg

Figure 2-1. The Sewer Bros Deployment Info editor

Uncheck the Portrait checkbox listed under Device Orientation. Now if you build and run the program, you’ll see that the simulator launches in landscape mode. When you try to turn the device, it will only look and function correctly in landscape mode.

Slight View Controller Changes

Because the View Controller views load in portrait by default, the size values will not be what you’d expect when your scene is added to the view. To fix this, you can set your scene along with its scaling attribute in the viewWillLayoutSubviews method instead of in the usual viewDidLoad method because by the time the view has loaded it is too late—it needs to be done before loading.

In the SKBViewController.m file, replace the viewDidLoad method with the following:

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    
    // Configure the view.
    SKView * skView = (SKView *)self.view;
    if (!skView.scene) {
        skView.showsFPS = YES;
        skView.showsNodeCount = YES;
        
        // Create and configure the scene.
        SKScene * scene = [SKScene sceneWithSize:skView.bounds.size];
        scene.scaleMode = SKSceneScaleModeAspectFill;
        
        // Present the scene.
        [skView presentScene:scene];
    }
}

Notice that you’ll also add an if() statement to check to see if the scene is already attached to the view and you’ll ignore the scene creation code if it already exists. This is because this method can be called more than once.

More Unneeded Template Text

Now you’ll remove the “Hello World” text from the template code. In the SKBMyScene.m file, take a look at the code inside the first initWithSize method and specifically look at the following code inside the if statement:

/* Set up your scene here */
        
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
        
SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        
myLabel.text = @"Hello, World!";
myLabel.fontSize = 30;
myLabel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
        
[self addChild:myLabel];

This is where both the background color and text block is created, configured, and added to the scene. You’re going to replace this with your own splash screen!

Images Available for Download

I have provided readers with a complete set of images and source code, which you can download from the Apress website. Here’s a link to the book’s page: http://www.apress.com/9781430264392. Look for the Source Code link in the Book Resources section on the left side of the page. Expand the archive and download the project folder to a convenient location. Inside this folder, you can browse to the folder labeled Images and look for the images whose names begin with SewerSplash. These are the files that you will now add to your game project.

From Xcode, choose Add Files to “Sewer Bros…” from the File menu. Navigate to the file SewerSplash_480.png and click once to select it. Then Shift-click on the second file called SewerSplash_568.png, so that both files are selected. Make sure that “Destination: Copy Items into Destination’s Group Folder (if Needed)” is checked, then make sure that “Add to Targets: Sewer Bros” is checked. Click the Add button (see Figure 2-2).

9781430264392_Fig02-02.jpg

Figure 2-2. Adding files to a project

If it’s successful, you will see the files added to the Project Navigator on the left side of the window. If you wish, you can click on them one at a time to see what they look like.

Background Color

Here, you’ll make the background color solid black. UIColor gives you some convenience methods to make common colors quickly by name instead of trying to determine the RGB values. For your reference, I list them here:

blackColor;      // 0.0 white
darkGrayColor;   // 0.333 white
lightGrayColor;  // 0.667 white
whiteColor;      // 1.0 white
grayColor;       // 0.5 white
redColor;        // 1.0, 0.0, 0.0 RGB
greenColor;      // 0.0, 1.0, 0.0 RGB
blueColor;       // 0.0, 0.0, 1.0 RGB
cyanColor;       // 0.0, 1.0, 1.0 RGB
yellowColor;     // 1.0, 1.0, 0.0 RGB
magentaColor;    // 1.0, 0.0, 1.0 RGB
orangeColor;     // 1.0, 0.5, 0.0 RGB
purpleColor;     // 0.5, 0.0, 0.5 RGB
brownColor;      // 0.6, 0.4, 0.2 RGB
clearColor;      // 0.0 white, 0.0 alpha

Let’s modify the initWithSize method by replacing the original code with your own in the SKBMyScene.m file so that it matches the following:

-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        /* Setup your scene here */
        self.backgroundColor = [SKColor blackColor];
    }
    return self;
}

Pretty straightforward—your SKScene will now have a solid black background.

The Splash Screen

You have the images inside your project that you’ll use for a splash screen already, so now you add one onto the screen. You do this by adding the following code immediately after the self.backgroundColor line you just added:

/* Set up your scene here */
self.backgroundColor = [SKColor blackColor];
        
NSString *fileName = @"";
if (self.frame.size.width == 480) {
   fileName = @"SewerSplash_480"; // iPhone Retina (3.5-inch)
} else {
   fileName = @"SewerSplash_568"; // iPhone Retina (4-inch)
}
SKSpriteNode *splash = [SKSpriteNode spriteNodeWithImageNamed:fileName];
splash.name = @"splashNode";
splash.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
        
[self addChild:splash];

You first created an empty NSString that will hold the filename. Then you determined if the user has a 3.5-inch screen or 4-inch screen. You checked the height of the view instead of the width because, at this point in the application, the device has not yet been rotated—almost, but not yet.

Once you have determined which filename to use, you create an SKSpriteNode object using the convenience method spriteNodeWithImageNamed.

You then set the name property to give the node a string name that you can use later to reference the node as needed in other methods.

Anchor Points

By default, a sprite’s frame is centered on its position and its position is the center of the graphic/frame. A sprite node’s anchorPoint property can be modified to move its position to a different point on the frame. Figure 2-3 illustrates this point.

9781430264392_Fig02-03.jpg

Figure 2-3. Compare two anchor point settings

In the example shown in Figure 2-3, you can see that moving the anchor point makes a big difference when the sprite is rotated, as it becomes the point of rotation. However, it can also make a difference when handling collisions and contacts, as you’ll see in a later chapter. Notice that that the values used for the anchor point are in the unit coordinate system, so the bottom-left corner is (0.0, 0.0) and the upper-right corner is (1.0, 1.0), no matter the size of the sprite.

Back to the Splash Screen

When you set the position of the new sprite in the scene, you need to keep in mind that the anchor point of the sprite by default is (0.5, 0.5), meaning the center of the image. Therefore, you want the image to be centered in the scene as well. The position property expects a CGPoint, and you can use a couple of convenience methods (CGRectGetMidX and CGRectGetMidY) to determine the center of the X- and Y-axes. For those with sharp eyes, you may have noticed that you seem to have reversed the order inside the CGPointMake method. You did indeed, and this is because the device has not officially rotated into landscape mode yet, as you have already determined that you want to have happen. Thus at this point in the code, you still need to check the height of the SKView to determine both the screen size and the position of the sprite, which is drawn in landscape orientation.

Now that the sprite has been created and configured, it’s time to get it into the scene and draw it, adding it as a child to the SKScene (as you did in the previous code).

Now if you build and run the program, you should see your lovely splash screen.

Moving Between Scenes

Now you’ll make it so that when the users touch the screen, it doesn’t spawn spinning spaceships, but instead takes them to the main game screen (the next scene).

Let’s start by removing the code that you won’t need anymore. Remove everything inside the for loop of the touchesBeganWithEvent method in the SKBMyScene.m file, so that it will now look like this:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */
    
    for (UITouch *touch in touches) {
 
    }
}

Rename this SKScene object to better represent what it will be in your project. In the SKBMyScene.m file, near the top, double-click on SKBMyScene (found after @implementation). Then right-click on it to open a sub-menu, scroll down to Refactor and over to Rename (or select Refactor in the Edit menu and then Rename). Once the Rename sheet is displayed, you can change the name to something more descriptive, such as SKBSplashScene (see Figure 2-4). Make sure Rename Related Files is checked and click the Preview button. Review the changes if you wish and click the Save button.

9781430264392_Fig02-04.jpg

Figure 2-4. The Rename sheet

The next option is up to you. You will be given the option of having Xcode take automatic snapshots before refactoring. This would allow you, in a sense, to undo major changes like this by reverting back to a prior snapshot. Make your choice by clicking the Enable or Disable button to continue.

The object and related files have all been renamed to something more descriptive. For those with an eye for detail, you may notice that one area was not affected by the change: the comments in both file headers. Feel free to change these manually if you want to be consistent.

Creating a New Scene

Select New File from the File menu. Make sure that iOS and Cocoa Touch are selected on the left side, select Objective-C class from the list of icons, and click the Next button. Click on the drop-down arrow called Subclass of, change it to SKScene, and then give the new class the name SKBGameScene. Click the Next button, and the standard Save dialog will appear. The default location is perfect. Just verify that Targets - Sewer Bros is checked and click the Create button. Two new files—SKBGameScene.h and SKBGameScene.m—are created and shown in your Project Navigator on the left.

It is pretty bare bones isn’t it? Copy and paste some code from the SplashScene, so that it will be ready for use. You’ll add all three methods to SKBGameScene.m so that the end result is as follows:

#import "SKBGameScene.h"
 
@implementation SKBGameScene
 
-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        /* Setup your scene here */
        self.backgroundColor = [SKColor blackColor];
    }
    return self;
}
 
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */
    for (UITouch *touch in touches) {
        
    }
}
 
-(void)update:(CFTimeInterval)currentTime {
    /* Called before each frame is rendered */
}
 
@end

Now you’ll see how to add a fancy transition between scenes, triggered when the users touch the screen. You will use the SKAction and SKTransition classes to create some animation effects.

Animated Transitions Using SKActions

Most of the time, you get sprites to move around the scene by using actions. Most actions in Sprite Kit make changes to a node, like the SKSpriteNode you created earlier. Let’s create an SKAction to describe the changes you want to apply to that node and then tell the node to run the action. When the scene is rendered, the action will be performed and it will be animated over time until the action is complete.

In the first scene implementation of SKBSplashScene.m, add one line of code right underneath the #import line at the top of the editor so that it reads like this:

#import "SKBSplashScene.h"
#import "SKBGameScene.h"
 
@implementation SKBSplashScene

Add the following lines of code inside the for loop of the touchesBeganWithEvent method like this:

for (UITouch *touch in touches) {
   SKNode *splashNode = [self childNodeWithName:@"splashNode"];
   if (splashNode != nil) {
      splashNode.name = nil;
      SKAction *zoom = [SKAction scaleTo: 4.0 duration: 1];
      [splashNode runAction: zoom completion:^{
         SKBGameScene *nextScene  = [[SKBGameScene alloc] initWithSize:self.size];
         SKTransition *doors = [SKTransition doorwayWithDuration:0.5];
         [self.view presentScene:nextScene transition:doors];
      }];
   }
}

Let’s go over what happened here. You used a convenience method (childNodeWithName) for the SKScene object to create an SKNode named splashNode. Now you can see why you named the node when you created it in the initWithSize method; now you can create a reference object to it.

You verified that the object, which you assumed exists, in fact really does. If it doesn’t, it will be a nil pointer and you can skip the touch event and continue.

If the object does exist, you set the name property to nil, because you don’t want this transition to occur more than once, which could happen if the user touched the screen several times in a row.

Then you create an SKAction object, use a convenience method to create the SKAction, and set two parameters—a float value to which you want the texture (image) to be scaled up and the duration of the scaling animation in seconds. In other words, scale the texture up to 400% over a timeframe of one second.

Next you tell the node to run the action on itself. You have three method calls from which you may choose when you want a node to run an action:

  • runAction—Simply execute the action.
  • runAction: completion—Execute the action and call a provided block when completed.
  • runAction: withKey—Execute the action and pass in a string to use as a reference to the action. This could be used later to see if a referenced action is still running.

In this case, you will use the second option, runAction: completion, and have the action execute and run a block of code when it has completed.

Looking at the code block that runs when the zoom action is finished, you see that you create an object of your own custom class SKBGameScene. Then you create an SKTransition using one of many types of transitions available (see the Xcode documentation for SKTransition for a complete listing) and set its duration to 0.5 seconds.

Finally, you tell the current scene to present the new scene you just made, using the transition you just made.

Now build and run the program. You have a splash screen, and when the screen is tapped, you have a transition to the new scene, which is solid black at this point. However, you’ll notice that the zoom animation came first and then the doorway transition.

Grouping Multiple Actions

It is completely possible to group several actions to be performed at the same time. Let’s add a fadeAway action to the existing zoom action to see how this is done.

Below the zoom action creation, change the following code so that it looks like this:

SKAction *zoom = [SKAction scaleTo: 4.0 duration: 1];
SKAction *fadeAway = [SKAction fadeOutWithDuration: 1];
SKAction *grouped = [SKAction group:@[zoom, fadeAway]];
[splashNode runAction: grouped completion:^{

Here you create an action that will cause the corresponding node to fade out over one second. Then you create an action that is simply an array of other actions already created. Then you tell the node to run the grouped action instead of the zoom action.

Build and run the program to see the result. You’ll probably notice that, with the second scene being currently solid black, the doorway transition has become invisible. Now let’s see if that changes once additional sprites are added into the game scene.

Animation Frames Using SKTextures

When you created the splash screen SKSpriteNode object, you used the convenience method spriteNodeWithImageNamed to create a static sprite that you don’t plan on changing. This is fine when you want static images but not useful at all when you want sprites that have some sort of animation attached to them, like a running character with several image frames. For sprites that will use more than one frame (or image) for the purpose of animation, you need to use an SKTexture object to create the sprite.

The hero character has four frames that you will use to animate his running behavior. You will create a separate SKTexture for each of these four images, and then create an NSArray of all the textures, and finally create an SKAction that will animate the sprite using the array of textures.

First, you’ll add the four images to the project. From Xcode, choose Add Files to “Sewer Bros” from the File menu. Navigate to the Player_Right1.png file and click once to select. Then Shift-click on the third file, called Player_Right4.png, so that all four files are selected. Make sure that “Destination: Copy Items Into Destination’s Group Folder (if Needed)” is checked and that “Add to Targets: Sewer Bros” is checked. Click the Add button.

If it’s successful, you will see the files added to the Project Navigator on the left side of the window. If you want, you can click on them one at a time to see what they look like.

Next, in the SKBGameScene.h file, you’ll add a property for the player’s SKSpriteNode object, right between the @interface and the @end, as shown here:

@interface SKBGameScene : SKScene
 
@property (strong, nonatomic) SKSpriteNode *playerSprite;
 
@end

Now, in the SKBGameScene.m file, add the following code to the for loop inside the touchesBeganWithEvent method, as shown here:

for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        
        // 4 animation frames stored as textures
        SKTexture *f1 = [SKTexture textureWithImageNamed: @"Player_Right1.png"];
        SKTexture *f2 = [SKTexture textureWithImageNamed: @"Player_Right2.png"];
        SKTexture *f3 = [SKTexture textureWithImageNamed: @"Player_Right3.png"];
        SKTexture *f4 = [SKTexture textureWithImageNamed: @"Player_Right4.png"];
        
        // an array of these textures
        NSArray *textureArray = @[f1,f2,f3,f4];
        
        // our player character sprite &starting position in the scene
        _playerSprite = [SKSpriteNode spriteNodeWithTexture:f1];
        _playerSprite.position = location;
        
        // an Action using our array of textures with each frame lasting 0.1 seconds
        SKAction *runRightAction = [SKAction animateWithTextures:textureArray timePerFrame:0.1];
        
        // don't run just once but loop indefinetely
        SKAction *runForever = [SKAction repeatActionForever:runRightAction];
        
        // attach the completed action to our sprite
        [_playerSprite runAction:runForever];
        
        // add the sprite to the scene
        [self addChild:_playerSprite];
    }

Now if you build and run the program, tap once to dismiss the splash screen, and tap on the black scene to spawn a running character (see Figure 2-5). Multiple taps at this stage will spawn multiple player characters. You’ll learn how to fix this problem later.

9781430264392_Fig02-05.jpg

Figure 2-5. A spawned, animating sprite

Now let’s elaborate on what you did.

CGPoint location = [touch locationInNode:self];

This captures the users’ touch point so that you can use it later as the location where the character may spawn.

SKTexture *f1 = [SKTexture textureWithImageNamed: @"Player_Right1.png"];
SKTexture *f2 = [SKTexture textureWithImageNamed: @"Player_Right2.png"];
SKTexture *f3 = [SKTexture textureWithImageNamed: @"Player_Right3.png"];
SKTexture *f4 = [SKTexture textureWithImageNamed: @"Player_Right4.png"];

This creates four SKTexture objects—one for each animation frame. These are created from images stored inside the project, and therefore they have to be spelled exactly to avoid errors.

NSArray *textureArray = @[f1,f2,f3,f4];

You create an NSArray of the textures.

Note  It’s important to note that the order in which you list the textures objects in the array determines the resulting frame sequence. Also, you can use objects more than once. For example, you could have done this instead:

NSArray *textureArray = @[f2,f1,f2,f3,f1,f4];

In doing so, the four image frames would be displayed, in this given order, for a total of six frames drawn to the screen for each loop of the animation.

_playerSprite = [SKSpriteNode spriteNodeWithTexture:f1];
_playerSprite.position = location;

Then you create your SKSpriteNode (using the instance variable that you created with the @property statement in the interface file), applying just one of the textures. It doesn’t really matter which one you use, but it kind of makes sense to use the first animation frame.

SKAction *runRightAction = [SKAction animateWithTextures:textureArray timePerFrame:0.1];
SKAction *runForever = [SKAction repeatActionForever:runRightAction];

This is where you create an SKAction using the complete set of textures inside the array and set each frame to be drawn every 0.1 seconds. Then you create another SKAction that is set to repeat the single action indefinitely.

[_playerSprite runAction:runForever];
[self addChild:_playerSprite];

This adds the repeating-forever action to the sprite and finally adds the sprite to the scene.

Summary

This chapter is designed to help you to understand a little more about the role that SKSpriteNodes, SKTextures, and SKActions have in creating visual components in your game. It’s not much of a game if you don’t have something visual to look at and with which to interact, right? Sprites and their associated actions are major foundational elements to every game. You will continue to use these objects as you add more game components to the game in the following chapters.

In this chapter, you also added a second SKScene and were introduced to SKTransitions, which can add some nice visual effects when switching between scenes.

In the next chapter, I will add sprite movements to go along with the running animation. You can’t have your character just running in place and not going anywhere! You’ll implement user touch events to determine where your character will go and add the ability to jump as well. Along the way, I will introduce you to the physics engine included with Sprite Kit, which adds major functionality to your game design. The physics engine provides the added bonus of relieving you from having to write all of the code to handle physics-based constraints and reactions in your game environment.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.45.185