Monday, August 13, 2018

Objective-C Protocols (Part 2): Applications for iOS Gaming

*For a more holistic understanding of this mini-game with specific emphasis on the application of iOS protocols, please go to main page for this tutorial here.   Otherwise, if you are interested in more specific details about the implementation of the different classes in this game, feel free to continue reading.
In the previous section, we defined a Letter class that would be responsible for managing the SKSpriteNode for displaying the sprite in our game as well as for storing state information relevant to a given letter object, such as health, wordIndex, and whether or not the the letter is in a state of recovery after contacting an enemy object.
We continue by defining a LetterManager class, whose header and implementation files are provided below.   The LetterManager spawns the letters for a given target word, adding them to the scene and randomizing their positions at regular update intervals. It also handles some of the contact logic between letters and other game objects. The letter manager should not be confused with the WordManager (another class that we will define later), which manages the target word (i.e. the word that the user aims to spell correctly) and word in progress (i.e. the word currently being spelled by the user).
LetterManager.h
@interface LetterManager: NSObject 

@property NSString* targetWord;


-(instancetype)initWithSpawnPoints:(NSArray<NSValue*>*)spawnPoints andWithTargetWord:(NSString*)targetWord;

-(void)update:(NSTimeInterval)currentTime;

-(void)addLettersTo:(SKScene*)scene;
-(void)clearLetters;

-(void)handleContactForLetterWith:(NSString*)letterIdentifier andWithContactedObjectName:(NSString*)contactedObjectName;

@end

LetterManager.m
#import <Foundation/Foundation.h>
#import <SpriteKit/SpriteKit.h>
#import <UIKit/UIKit.h>
#import "RandomPointGenerator.h"
#import "LetterManager.h"
#import "Letter.h"
#import "LetterDelegate.h"



@interface LetterManager() 

@property NSMutableArray<Letter*>* letters;
@property NSArray<NSValue*>* spawnPoints;

@property NSTimeInterval repositionInterval;
@property NSTimeInterval frameCount;
@property NSTimeInterval lastUpdateTime;

@property BOOL lettersAreHidden;
@property RandomPointGenerator* pointGenerator;

@end


@implementation LetterManager



-(instancetype)initWithSpawnPoints:(NSArray<NSValue*>*)spawnPoints andWithTargetWord:(NSString*)targetWord{

    self = [super init];
    
    if(self){
        self.spawnPoints = spawnPoints;
        self.targetWord = targetWord;
        self.letters = [[NSMutableArray alloc] initWithCapacity:self.targetWord.length];
        self.lettersAreHidden = YES;
        self.pointGenerator = [[RandomPointGenerator alloc] init];
        
        self.frameCount = 0.00;
        self.repositionInterval = 4.00;
        self.lastUpdateTime = 0.00;
    }
    
    return self;

}


-(void)addLettersTo:(SKScene*)scene{
    
    for (int charIndex = 0; charIndex < self.targetWord.length; charIndex++) {                  char wordChar = [self.targetWord characterAtIndex:charIndex];                  Letter* newLetter = [[Letter alloc] initWithLetter:wordChar andWithWordIndex:charIndex];                  newLetter.delegate = self;                  CGPoint randomPos = [self getRandomSpawnPointPosition];                  [newLetter addLetterTo:scene atPosition:randomPos];                  [self.letters addObject:newLetter];              } } -(void)update:(NSTimeInterval)currentTime{                    if(self.frameCount == currentTime){         self.frameCount = 0;     }               self.frameCount += currentTime - self.lastUpdateTime;          //Update all of the letters     for (Letter*letter in self.letters) {                  [letter update:currentTime];     }     if(self.frameCount > self.repositionInterval){
        
        
        if(self.lettersAreHidden){
            
            [self revealLetters];
            
        } else {
        
            [self hideLetters];

        }
        
        self.frameCount = 0;
    }
    
    self.lastUpdateTime = currentTime;
}



-(void)revealLetters{
    
    for (Letter*letter in self.letters) {
        CGPoint randomOnScreenPos = [self getRandomOnScreenPosition];
        [letter setLetterPositionTo:randomOnScreenPos];
    }
    
    self.lettersAreHidden = NO;

}

-(void)hideLetters{
    for (Letter*letter in self.letters) {
        
        CGPoint randomPos = [self getRandomSpawnPointPosition];
        [letter setLetterPositionTo: randomPos];
        
    
    }
    self.lettersAreHidden = YES;
}

-(CGPoint)getRandomOnScreenPosition{
    
    return [self.pointGenerator getRandomOnScreenCoordinate];
}

-(CGPoint)getRandomSpawnPointPosition{
    
    NSUInteger randomIndex = arc4random_uniform((UInt32)self.spawnPoints.count);
    
    NSValue* positionVal = [self.spawnPoints objectAtIndex:randomIndex];
    
    return positionVal.CGPointValue;
}


/** The contacted letter can take different amounts of damage based on the enemy that contacts it **/

-(void)handleContactForLetterWith:(NSString*)letterIdentifier andWithContactedObjectName:(NSString*)contactedObjectName{
    
    NSLog(@"The letter identifiers is: %@",letterIdentifier);
    
    NSPredicate* predicate = [NSPredicate predicateWithFormat:@"identifier LIKE %@",letterIdentifier];
    
   Letter* contactedLetter = [[self.letters filteredArrayUsingPredicate:predicate] firstObject];
    
    NSString* contactedLetterInfo = [contactedLetter debugDescription];
    
    NSLog(@"Contacted letter info: %@",contactedLetterInfo);
    
    [contactedLetter takeDamage:1];
    

}

-(void)clearLetters{
    
  
    self.letters = nil;
    
    self.letters = [[NSMutableArray alloc] init];

}

/** Conformance to LetterDelegate protocol **/

-(void)didDestroyLetter:(Letter *)letter{
    
    [self.letters removeObject:letter];
}




@end

It will be noted that the implementation file for the LetterManager requires the RandomPointGenerator, which we defined in the previous section, as a dependency, and also defines a property for a pointGenerator in an extension.  The pointGenerator instance method getRandomOnScreenCoordinate is called in the function getRandomOnScreenPosition, which in turn is used to generate random on-screen points that are used to reposition the letters at random positions after the letters have been hiding for fixed interval.
During initialization of the LetterManager class, an array of NSValue objects containing a set of spawn points is used to initialize the spawnPointsproperty.  This is basically an array of points representing the positions of the all the clouds on the screen, whose positions themselves are determined randomly using a RandomPointGenerator in the GameScene class.  This is why the LetterManager class has another instance method getRandomSpawnPointPosition which randomly selects a spawn point from the array of spawn points in self.spawnPoints in order to cause the on-screen letters to hide behind a random on-screen cloud after appearing on-screen for a fixed period of time.  Hence, the revealLetters and hideLetters instance methods are called alternately in the update function, causing letters to appear at random on-screen positions and then hide randomly from one among any one of a number of the spawn positions represented by the on-screen background clouds.
**The didDestroyLetter method is implemented in order to make the LetterManager class conform to the LetterDelegate protocol, which we defined in the previous section.  Each letter that is spawned by the LetterManager has its delegate set to the LetterManager in order that the LetterManager can perform any tasks associated with a letter being destroyed.  In this version of the game, however, we won’t be concerned with letters being destroyed or damaged in any way, so this can basically be ignored.  Likewise, the instance method handleContactForLetterWith can also be ignored,  since it is only relevant for games where collision logic is implemented.  But in case you were curious, if you will recall, the interface for a Letter has an identifier property, whose implementation involves retrieving the name of sprite node managed by the letter.  This identifier is retrieved in a collision handler (i.e. specifically the SKPhysicsContactDelegatemethod didBeginContact) and used to identify the specific letter involved in a collision so that the LetterManager can process any changes in state accordingly (e.g. decreased health, temporary invulnerabilitiy, etc.).  For this reason,  we defined a class method in in the Letter class getLetterCharacterFromPhysicsBody, which is utility method for accessing the node name of the node with which a physics body is associated.  Since we only have access to physics bodies, this method enables us to gain quick access to the node name, which is a unique identifier that can be used to identify the specific letter involved in the collision.   If you are interested, an alternative implementation involves adding a category to the SKPhysicsBody class – that is, we define a category that provides additional functionality for an SKPhysicsBody and then import the header file for this category in our main GameScene file.
Now, going back to our LetterManger class, let’s take a look at another important method addLettersTo, shown below:
 


-(void)addLettersTo:(SKScene*)scene{
    
    for (int charIndex = 0; charIndex < self.targetWord.length; charIndex++) {
        
        char wordChar = [self.targetWord characterAtIndex:charIndex];
        
        Letter* newLetter = [[Letter alloc] initWithLetter:wordChar andWithWordIndex:charIndex];
        
        newLetter.delegate = self;
        
        CGPoint randomPos = [self getRandomSpawnPointPosition];
        
        [newLetter addLetterTo:scene atPosition:randomPos];
        
        [self.letters addObject:newLetter];
        
    }
}


In order to understand this method, we have to remember that, in addition to an array of NSValues representing the spawn points where letters can hide at regular intervals, we provided a variable targetWord of type NSString to our initializer for the LetterManager.  This is the word that must be spelled by the user, and in our addLettersTo function we use a for-loop to iterate through all of the characters in the target word, using the index for the position of the character as well as the character itself to instantiate a Letter object, whose delegate we set to the LetterManager class. This letter also is added to the node hierarchy of the scene as well as the self.letterarray, which is a property defined in an extension for the LetterManger class and which used to keep track of all the Letter whose sprites are being currently displayed on screen.
Now that we have our LetterManager class defined, let’s move on to the heart of this tutorial, the WordManager, for which we will define a data source and delegate protocols, and which will be responsible for handling the main logic of the game.

Objective-C Protocols: Applications for iOS Gaming (Part I)





*For a more holistic understanding of this mini-game with specific emphasis on the application of iOS protocols, please go to main page for this tutorial here.   Otherwise, if you are interested in more specific details about the implementation of the different classes in this game, feel free to continue reading.

In this tutorial, we will develop a small demo game to illustrate the utility of Objective-C protocols in iOS game design. In the game, a collection of letters is spawned, and the letters appear and disappear at regular intervals.  When the letters appear, they are scattered at random positions on the screen.  When they disappear, they hide behind clouds in the night sky.  The user is given a target word displayed at the bottom of the screen, and the goal of the game is to spell the target word by tapping on the letters in the order in which they appear in the word.  Here is a video screen shot of the game:
Video.  Galactic Letters





You can clone the project or download the source code for the final project at this Github repository.  Otherwise, you can just follow along and hopefully everything will make sense.
First, we will create a helper class RandomPointGenerator, which will generate random points on screen so that we can scatter our letters across the screen at regular intervals and make it more challenging for the user to spell the target word.  The header and implementation files for this class are show below:
RandomPointGenerator.h
 

#import <Foundation/Foundation.h>

@interface RandomPointGenerator : NSObject

-(instancetype)init;
-(instancetype)initForDebugSpawnArea;

-(CGPoint)getRandomOnScreenCoordinate;
-(NSArray<NSValue*>*)getArrayOfOnScreenPoints:(NSInteger)numberOfPoints;

@end

RandomPointGenerator.m
#import <Foundation/Foundation.h>
#import <GameplayKit/GameplayKit.h>
#import "RandomPointGenerator.h"
#import "ScreenConstants.h"
#import "Constants.h"

@interface RandomPointGenerator()

@property GKMersenneTwisterRandomSource* randomXSource;
@property GKMersenneTwisterRandomSource* randomYSource;

@property GKRandomDistribution* randomXDist;
@property GKRandomDistribution* randomYDist;

@end



@implementation RandomPointGenerator


-(NSArray<NSValue*>*)getArrayOfOnScreenPoints:(NSInteger)numberOfPoints{
    
    NSMutableArray<NSValue*>* pointArray = [NSMutableArray arrayWithCapacity:numberOfPoints];
    
    for (int i = 0; i < numberOfPoints; i++) {
        
        CGPoint randomPoint = [self getRandomOnScreenCoordinate];
        
        NSValue* randomPointVal = [NSValue valueWithCGPoint:randomPoint];
        
        [pointArray addObject:randomPointVal];
        
    }
    
    return [NSArray arrayWithArray:pointArray];
}

-(CGPoint)getRandomOnScreenCoordinate{
    
    NSInteger xCoord = [self getRandomOnScreenXCoord];
    NSInteger yCoord = [self getRandomOnScreenYCoord];
    
    return CGPointMake(xCoord, yCoord);
}

-(NSInteger)getRandomOnScreenXCoord{
    return [self.randomXDist nextInt];
}

-(NSInteger)getRandomOnScreenYCoord{
    return [self.randomYDist nextInt];
}


-(instancetype)initForDebugSpawnArea{
    
    self = [super init];
    
    if(self){
        _randomXSource = [[GKMersenneTwisterRandomSource alloc] init];
        _randomYSource = [[GKMersenneTwisterRandomSource alloc] init];
        
        int lowestValX = kDebugSpawnArea_LowerX;
        int highestValX = kDebugSpawnArea_UpperX;
        
        int lowestValY = kDebugSpawnArea_LowerY;
        int highestValY = kDebugSpawnArea_UpperY;
        
        self.randomXDist = [[GKRandomDistribution alloc] initWithRandomSource:_randomXSource lowestValue:lowestValX highestValue:highestValX];
        
        self.randomYDist = [[GKRandomDistribution alloc] initWithRandomSource:_randomYSource lowestValue:lowestValY highestValue:highestValY];
        
    }
    
    return self;
}

-(instancetype)init{
    
    self = [super init];
    
    if(self){
        
        _randomXSource = [[GKMersenneTwisterRandomSource alloc] init];
        _randomYSource = [[GKMersenneTwisterRandomSource alloc] init];

        int lowestValX = (int)kLeftXBoundary;
        int highestValX = (int)kRightXBoundary;
        
        int lowestValY = (int)kLowerYBoundary;
        int highestValY = (int)kUpperYBoundary;
        
        self.randomXDist = [[GKRandomDistribution alloc] initWithRandomSource:_randomXSource lowestValue:lowestValX highestValue:highestValX];
        
        self.randomYDist = [[GKRandomDistribution alloc] initWithRandomSource:_randomYSource lowestValue:lowestValY highestValue:highestValY];

    }
    
    return self;
}

@end

The interface for this class provides two initializers init and initForDebugSpawnArea,  as well as two methods that we will use to position our game objects- that is, getRandomOnScreenCoordinate, which returns a random point on screen, and getArrayOfOnScreenPoints, which returns an array of random on-screen points wrapped in NSValue objects.
In the implementation file, you can see that we define several properties: randomXSource and randomYSource, which are used to initialize GKRandomDistribution objects, as required by GameplayKit. The other properties include randomXDist and randomYDist, which basically correspond to the Gaussian distributions that will be used to generate random on-screen X-coordinates and Y-coordinates, respectively. Please note that the initializer for the GKRandomDistribution object also requires a lower and an upper bounds, which, in this case, are provided by global constants that we define in a ScreenConstants.h file, shown below:
 

#define kScreenWidth UIScreen.mainScreen.bounds.size.width;
#define kScreenHeight UIScreen.mainScreen.bounds.size.height;
#define kLeftXBoundary -UIScreen.mainScreen.bounds.size.width/2.00+50;
#define kRightXBoundary UIScreen.mainScreen.bounds.size.width/2.00-50;
#define kUpperYBoundary UIScreen.mainScreen.bounds.size.height/2.00-50;
#define kLowerYBoundary -UIScreen.mainScreen.bounds.size.height/2.00+50;

The implementation file for the RandomPointGenerator defines two helper methods getRandomOnScreenXCoord and getRandomOnScreenYCoord, which are used to get a random X-coordinate and a random Y-coordinate respectively:
-(NSInteger)getRandomOnScreenXCoord{
    return [self.randomXDist nextInt];
}

-(NSInteger)getRandomOnScreenYCoord{
    return [self.randomYDist nextInt];
}


These helper methods are then called in additional helper methods to generate random points on-screen, as well as an array of random points on-screen:
-(NSArray<NSValue*>*)getArrayOfOnScreenPoints:(NSInteger)numberOfPoints{
    
    NSMutableArray<NSValue*>* pointArray = [NSMutableArray arrayWithCapacity:numberOfPoints];
    
    for (int i = 0; i < numberOfPoints; i++) {
        
        CGPoint randomPoint = [self getRandomOnScreenCoordinate];
        
        NSValue* randomPointVal = [NSValue valueWithCGPoint:randomPoint];
        
        [pointArray addObject:randomPointVal];
        
    }
    
    return [NSArray arrayWithArray:pointArray];
}

-(CGPoint)getRandomOnScreenCoordinate{
    
    NSInteger xCoord = [self getRandomOnScreenXCoord];
    NSInteger yCoord = [self getRandomOnScreenYCoord];
    
    return CGPointMake(xCoord, yCoord);
}

This is relatively straightforward, and it will come in handy when we are implementing our LetterManager class.  Before we embark upon the journey of making our LetterManager class, we need to create a Letter class that will manage the node for displaying a letter sprite and for storing other state information about the letter, such as its its recovery state (i.e. whether or not the letter is in a state of temporary invulnerability due to recent contact with an enemy object), its health (if you intend to create a version of the game where the letter can be damaged via contact with other game objects),  or its index within a given word.
You can get the sprites for these letters for free by going to Kenney.nl.  The clouds used for creating the night sky background are also from Kenney as well.  It a great site for public domain game assets that can be used in not just games but other projects as well:
/** The letter object manages an SKSpriteNode which provides the visual representation for the letter in question; the letter object can also contain additional data, such as the number of points associated with a letter **/

@interface Letter: NSObject

-(instancetype)initWithLetter:(char)letter;
-(instancetype)initWithLetter:(char)letter andWithWordIndex:(NSUInteger)wordIndex;
-(instancetype)initWithLetter:(char)letter andWithStartingHealth: (int)startingHealth andWithWordIndex:(NSUInteger)wordIndex;

-(void)addLetterTo:(SKScene*)scene atPosition:(CGPoint)position;
-(void)setLetterPositionTo:(CGPoint)position;
-(void)removeLetter;

@property (readonly)NSString* identifier;
@property (readonly)int pointValue;

@property (weak) id<LetterDelegate> delegate;

-(void)update:(NSTimeInterval)currentTime;
-(void)takeDamage:(int)damageAmount;

+(int)pointsForLetter:(char)wordChar;

@end

You will notice that the interface includes a class method pointsForLetter, which allows us to get the point value for a specific letter.  If you scroll back up to the pictures of the letter sprites, you will notice a small number in the lower right hand corner, which corresponds to the point value of the letter.  By extension, a given word has a point value equal to the sum of the individual point values of each letter, so that when user successfully spell different words, they earn different amounts of points.  This class method is implemented in the implementation section of the Letter class, as show below:
Letter.m
+(int)pointsForLetter:(char)wordChar{
    
    switch (wordChar) {
        case 'A':
            return 1;
        case 'B':
            return 3;
        case 'C':
            return 3;
        case 'D':
            return 2;
        case 'E':
            return 1;
        case 'F':
            return 4;
        case 'G':
            return 2;
        case 'H':
            return 4;
        case 'I':
            return 1;
        case 'J':
            return 5;
        case 'K':
            return 8;
        case 'L':
            return 1;
        case 'M':
            return 3;
        case 'N':
            return 1;
        case 'O':
            return 1;
        case 'P':
            return 3;
        case 'Q':
            return 1;
        case 'R':
            return 1;
        case 'S':
            return 1;
        case 'T':
            return 1;
        case 'U':
            return 1;
        case 'V':
            return 4;
        case 'W':
            return 4;
        case 'X':
            return 8;
        case 'Y':
            return 4;
        case 'Z':
            return 10;
        default:
            return 1;
    }
}

For this method, we pass in a character as an argument. All that’s required is that we switch on the character and return the appropriate point value accordingly. Here, it’s good to note that Objective-C is a superset of C, so the libraries and data types used in a C/C++ project can also be conveniently used in an Objective-C project.
In the public interface for the Letter class, you will notice that we defined a readonly property whereby we can represent the point value of a given letter.
Letter.h
 

@property (readonly)int pointValue;

In our implementation of the letter class, we call the class method pointsForLetter, to implement this readonly property. Note the letter class also has private property (i.e. a property defined in an extension) of type char that is used to represent the character corresponding to the Letterobject in question. This letter is passed in as an argument to the function pointsForLetter, to get the pointValue for the Letter object in question.
Letter.m
-(int)pointValue{
    return [Letter pointsForLetter:self.letterChar];
}

Before we get too far ahead of ourselves, let’s back-track a little bit and go back to the public interface for the Letter class. There we noticed that there were several initializers that could be invoked to instantiate a letter object. The corresponding implementations for these initializers are shown in the implementation file for the letter below:
Letter.m
#import <Foundation/Foundation.h>
#import <SpriteKit/SpriteKit.h>
#import "Letter.h"
#import "ContactBitMasks.h"
#import "Constants.h"

@interface Letter()

@property SKSpriteNode* sprite;
@property int health;
@property NSUInteger wordIndex;
@property char letterChar;

@property BOOL isRecovering;
@property NSTimeInterval recoveryInterval;
@property NSTimeInterval frameCount;
@property NSTimeInterval lastUpdateTime;

@end


@implementation Letter

const static double recoveryTime = 0.60;


-(instancetype)initWithLetter:(char)letter{
    
    self = [self initWithLetter:letter andWithWordIndex:0];
    
    return self;
}

-(instancetype)initWithLetter:(char)letter andWithWordIndex:(NSUInteger)wordIndex{
    
    self = [self initWithLetter:letter andWithStartingHealth:3 andWithWordIndex:wordIndex];
    
    return self;
}

-(instancetype)initWithLetter:(char)letter andWithStartingHealth: (int)startingHealth andWithWordIndex:(NSUInteger)wordIndex{

    self = [super init];
    
    if(self){
        
        letter = toupper(letter);
        
        self.letterChar = letter;
        self.health = startingHealth;
        self.wordIndex = wordIndex;
        self.isRecovering = NO;
        self.frameCount = 0.00;
        self.recoveryInterval = recoveryTime;
        
        NSString* spriteName = [NSString stringWithFormat:@"letter_%c",letter];

        SKTexture* letterTexture = [SKTexture textureWithImageNamed:spriteName];
        
        self.sprite = [SKSpriteNode spriteNodeWithTexture:letterTexture];
        
        
        [self configureSpriteWithNodeName:spriteName];
        
        [self configurePhysicsProperties:letterTexture];
        
    }
    
    return self;
}

These initializers provide initial values not only for properties defined in the public interface but also for those in the extension defined in the implementation.  If you are not familiar with the concept of an extension in Objective-C, well, it basically is a way of defining private properties that can be accessed in the implementation of a class.  In our implementation file for the Letter class, you can see that we have define additional properties in an extension, as shown below:
Letter.m
 

@interface Letter()

@property SKSpriteNode* sprite;
@property int health;
@property NSUInteger wordIndex;
@property char letterChar;

@property BOOL isRecovering;
@property NSTimeInterval recoveryInterval;
@property NSTimeInterval frameCount;
@property NSTimeInterval lastUpdateTime;

@end

This extension includes several properties whose usage in the implementation of the Letter class can be hidden from the user (i.e. the programmer). The sprite will hold the SKSpriteNode that is displays the sprite for the letter; health is of integer type and will be used to track the health of the letter (which will be relevant for versions of the game where the letter can take damage via collisions); wordIndex, of type NSUInteger, represents the position of the letter in a word; letterChar, of type char, represents the actual character this Letter object represents; isRecovering, a Boolean flag, represents the damage state of the letter, which is used to give a letter temporary invulnerability after it’s been damaged (this way, the letter does suffer excessive damage and immediately die upon a simple contact with an enemy object). The variables recoveryIntervalframeCount, and lastUpdate time, all of which are of type NSTimeInterval, are all used to implement a timer functionality whereby the letter appears and disappears behind on-screen clouds at regular intervals.
In the implementation section, we’ve also defined a constant of type double for the time interval at which the letter changes between appearing and hiding states.
Letter.m
 

const static double recoveryTime = 0.60;

This constant can just as easily be defined in a separate constants file. All of the properties defined in both the extension and the interface have to be initialized, and of all the initializers shown, one would be considered the designated initializer (the main initializer on which all the other ones depend)while the others would be considered convenience initializers (as they all call or delegate to the designated initializer):
Letter.m
 

-(instancetype)initWithLetter:(char)letter andWithStartingHealth: (int)startingHealth andWithWordIndex:(NSUInteger)wordIndex{

    self = [super init];
    
    if(self){
        
        letter = toupper(letter);
        
        self.letterChar = letter;
        self.health = startingHealth;
        self.wordIndex = wordIndex;
        self.isRecovering = NO;
        self.frameCount = 0.00;
        self.recoveryInterval = recoveryTime;
        
        NSString* spriteName = [NSString stringWithFormat:@"letter_%c",letter];

        SKTexture* letterTexture = [SKTexture textureWithImageNamed:spriteName];
        
        self.sprite = [SKSpriteNode spriteNodeWithTexture:letterTexture];
        
        
        [self configureSpriteWithNodeName:spriteName];
        
        [self configurePhysicsProperties:letterTexture];
        
    }
    
    return self;
}


This designated initializer calls the C-function toupper() to ensure thatself.letter is upper-cased. It uses the self.letter property to construct the string that is used for the name of the texture that is used in turn to get the sprite for the letter character in question. In addition, this designated initializer calls upon two helper methods, configureSpriteWithNodeName and configurePhysicsProperties, which perform some additional configuration work for the sprite node and the physics body associated with the sprite node.
In the implementation file, we also implement the functionaddLetterTo:atPosition, which adds the sprite for a letter to the GameScene.  Note this function sets the zPosition of the letter sprite using kZPositionLetter, which is a constant defined in our Constants.h file and therefore available for use here.  We also implement setLetterPositionTo, which sets the position of the sprite node for the letter, and update, which toggles the isRecovering variable at regular intervals.
Letter.m
 


-(void)addLetterTo:(SKScene*)scene atPosition:(CGPoint)position{
    
    [self.sprite moveToParent:scene];
    [self.sprite setZPosition:kZPositionLetter];
    [self.sprite setPosition:position];
}

-(void)setLetterPositionTo:(CGPoint)position{
    
    SKAction* moveAction = [SKAction moveTo:position duration:0.5];
    [self.sprite runAction:moveAction];
    
}

-(void)update:(NSTimeInterval)currentTime{
    
    if(currentTime == 0){
        self.frameCount = currentTime;
    }
    
    self.frameCount += currentTime - self.lastUpdateTime;
    
    if(self.frameCount > self.recoveryInterval){
        
        self.isRecovering = !self.isRecovering;
        
        self.frameCount = 0.00;
    }
    
    self.lastUpdateTime = currentTime;
    
}



The implementation section also includes implementations of removeLetter, which removes the Letter object’s sprite node from its parent and also calls the Letter object’s delegate method didDestroyLetter, which informs the Letter’s delegate that the letter has been removed from its node hierarchy (We will get to the delegate shortly!).  It also includes implementation of runDamageAnimation, which runs a fadeAction on the Letter’s sprite node when it has been damaged via contact with an enemy, and die, which like the removeLetter method, removes the Letter’s sprite node from the node hierarchy and calls the delegate method didDestroyLetter.  The die method can in fact be refactored so as to call the removeLetter method, if you want to make your code less repetitive.
Letter.m
 


-(void)removeLetter{
    [self.sprite removeFromParent];
    [self.delegate didDestroyLetter:self];

}

-(void)runDamageAnimation{
    NSLog(@"Letter has been damaged...");
    
    SKAction* fadeAction = [SKAction fadeAlphaBy:0.20 duration:0.10];
    
    [self.sprite runAction:fadeAction];
}



-(void)die{
    NSLog(@"Letter is dead...");

    [self.sprite removeFromParent];
    [self.delegate didDestroyLetter:self];

}

Finally, for versions of the game where the letter can take damage such that it’s health value increases or decreases, we implement a takeDamagefunction, which first checks if the Letter is in a recovering state (in which case we return from the method immediately), then decreases the health value of the letter, and then runs an animation based on the health value of the letter by switching on the value of self.health.
Letter.m
-(void)takeDamage:(int)damageAmount{
    
    if(self.isRecovering){
        NSLog(@"Letter is recovering...");
        return;
    }
    
    NSLog(@"Letter is taking damage...");
    
    self.health -= damageAmount;
    
    
    switch (self.health) {
        case 3:
            [self runDamageAnimation];
            break;
        case 2:
            [self runDamageAnimation];
            break;
        case 1:
            [self runDamageAnimation];
            break;
        case 0:
            [self die];
            
            break;
        default:
            break;
    }
    
   
    
}


If you scroll back up to the interface for the Letter class, you should notice that we define a property for a delegate that conforms to the LetterDelegateprotocol.   This protocol defines methods that are called when a letter is damaged or destroyed, and it will become important in versions of this game where the letter is subject to damage, but you can ignore it for now, since it’s not essential to our purposes here. You will have noticed that the above methods have often called delegate methods as well.  The LetterDelegatemethods are defined below:
LetterDelegate.h
#import <Foundation/Foundation.h>

@class Letter;

@protocol LetterDelegate 


@optional

-(void)didDamageLetter:(Letter*)letter;

@required

-(void)didDestroyLetter:(Letter*)letter;

@end


Wednesday, August 8, 2018

Objective-C Tutorials: Protocols in Gaming Applications

In this tutorial, we will look at some ways that Objective-C protocols can be helpful in designing iOS mini-games.   A link to the Github repo for this project is provided here if you would like to download or clone the original source code and follow along.  A video screen shot for the final result of our mini-game is shown below:


Basically, the user is shown a “target word” at the bottom of the screen, which must be spelled by tapping on letter sprites on screen.  The catch is that these letter sprites appear and disappear at regular intervals, sometimes appearing at random on-screen positions, other times hiding behind cloud sprites randomly scattered across the background.  Before we begin, we will do some preliminary setup in our GameScene class.  The preliminary header and implementation files are show below:
GameScene.h
@import SpriteKit;

@interface GameScene : SKScene

@end


GameScene.m
@import Foundation;
@import SpriteKit;

#import "GameScene.h"
#import "RandomPointGenerator.h"
#import "Constants.h"
#import "LetterManager.h"
#import "WordManager.h"
#import "TargetWordArray.h"
#import "StatTracker.h"
#import "HUDManager.h"
#import "Cloud.h"

@interface GameScene() <SKPhysicsContactDelegate,WordManagerDelegate>

@property NSArray<NSValue*>*randomSpawnPoints;
@property LetterManager* letterManager;
@property NSString* targetWord;

@property WordManager* wordManager;
@property TargetWordArray* debugTargetWordArray;
@property StatTracker* statTracker;
@property HUDManager* hudManager;

@end
 
The GameScene interface is pretty straightforward – we notice that SpriteKit is included via an import statement, and that our GameScene class inherits from SKScene.  In our implementation file, we notice several private properties defined in the class extension.  Among them are targetWord, of type NSString, which is basically a reference to the word that must be spelled by the user. There is also spawnPoints, of type NSArray<NSValue*>*,  which is basically an array of CGPoint values wrapped in NSValue objects where the CGPoint values represent random positions used to scatter the cloud sprites across the screen randomly.
Other important properties defined in the extension require corresponding import statements at the top of the implementation file: wordManagerstatTrackerhudManagerletterManager, and debugTargetWordArray.  All of these properties are custom data types defined in other files – hence, we need to import the appropriate header files where these classes are defined accordingly.
The hudManager is responsible for managing the accumulated score indicator at the top of the screen as well as the text displays for the target word and the user’s progress towards spelling the target word.  The letterManager object is responsible for randomizing the positions of the on-screen letter sprites as well as for adding letter sprites to the scene based on the current target word.  The statTracker object is responsible for collecting game statistics and data, such as the number of misspellings, the number of target words spelled correctly, the total points accumulated by the user, etc.
wordManager is basically responsible for keeping track of the word that is currently being spelled by the user, comparing each letter tapped by the user to a corresponding letter in the target word to determine whether or not the user is spelling the word correctly.  Since the user can theoretically spell more than a single target word correctly, the wordManager must have access to additional target words, which is provided by the debugTargetWordArray. This is basically a wrapper class for a simple NSArrayobject and conforms to the WordManagerDataSource protocol, which is a custom Objective-C protocol that stipulates the methods that must be provided by any object that will act as a data source (i.e. a source of target words) for the wordManager object, regardless of how that data source is implemented (i.e. the data source can be just as easily implemented as a stack, linked list, etc.).  The WordManagerDataSource protocol is fairly simple and shown below:
#import <Foundation/Foundation.h>

@protocol WordManagerDataSource 

-(NSString*)getInitialTargetWord;

-(NSString*)getNextTargetWord;

@optional

-(BOOL)hasAcquiredAllTargetWords;


@end

As you can see, it’s relatively straightforward.  It just requires that the data source provide the initial target word as well as the next target word after the current target word.  In this way, the wordManager can continually iterate through a collection of strings that function as target words.  This collection of target words can be implemented  by means of a variety of data structures (e.g. a stack, linked list, array, etc.).  The optional protocol method hasAcquiredAllTargetWords can be implemented if we only wish to iterate through the collection of target words once.
The extension for our GameScene also indicates that the GameScene class conforms to two protocols:  SKPhysicsContactDelegate and WordManagerDelegate.  The former basically means that our GameSceneclass can act as an SKPhysicsContactDelegate for the physicsWorld property of the SKScene and that it will implement two required methods, didBeginContact and didEndContact, which are called when the physics bodies of different game objects collide with each other.  The latter, WordManagerDelegate,  is our own custom protocol, which requires that several methods be implemented:
WordManagerDelegate.h
#import <Foundation/Foundation.h>

/** The WordManager will inform its delegate when it has updated the word in progress;  it will also inform its delegate when it has cleared or completed its word in progress, as well as when it has updated the target word and when the user has earned points **/

@protocol WordManagerDelegate 


-(void)didUpdateWordInProgress:(NSString*)wordInProgress;


@optional

-(void)didExtendWordInProgress:(NSString*)extendedWordInProgress;

-(void)didMisspellWordInProgress:(NSString*)misspelledWordInProgress;

-(void)didClearWordInProgress:(NSString*)deletedWordInProgress;

-(void)didCompleteWordInProgress:(NSString*)completedWordInProgress;

-(void)didUpdateTargetWordTo:(NSString*)updatedTargetWord fromPreviousTargetWord:(NSString*)previousTargetWord;



@end


In order to understand this custom protocol, it should be understood that the WordManager stores a reference not only to the target word (i.e. the word that the user is trying to spell) but also to an NSString property wordInProgress, which is a temporary string that tracks the user’s progress towards spelling the target word.
Our custom protocol defines one required method didUpdateWordInProgress, which is  called every time the temporary string that tracks the user’s attempted spelling of the target word undergoes some kind of change – that is, this method is called if the user misspells the target word, resulting in the temporary word in progress being deleted, or if the user spells a letter correctly, resulting in the temporary word being lengthened or in the current target word being swapped for a new target word if the user has completed spelling the existing target word.  Additionally, several optional methods can be implemented: didUpdateTargetWordTo:fromPreviousTargetWord (which is called every time the user successfully completes the spelling of a target word), didCompleteWordInProgress (which is called after the user correctly spells the target word but before the current target word is swapped for an updated target word), didClearWordInProgress (which is called either when the user misspells the target word, requiring that the user start spelling the target word again  or when the the user successfully spells the target word, resulting in the word in progress being cleared, in which cases it is called after the current target word is swapped for an updated target word), didMisspellWordInProgress (which is called every time the user misspells a target word, resulting the word in progress being cleared), and didExtendWordInProgress (which is called every time the user correctly spells a letter in the target word, resulting in the temporary word in progress being extended).
Now that we have a rough understanding of all the private properties defined via this extension, let’s start to examine the meat of the the implementation file:
const static CGFloat kHUDXPosition = 0.0;
const static CGFloat kHUDYPosition = 0.0;


- (void)didMoveToView:(SKView *)view {
    // Setup your scene here
    
    [self configureScene];
    [self setupBackground];
    [self setupBGMusic];
    [self setupSpawnPoints];
    [self setupStatTracker];
    [self setupWordManager];
    [self acquireTargetWord];
    [self setupHUDManager];
    [self setupLetterManager];
    [self createClouds];
    [self addTargetWordLetters];
    
    
}

At the top of the implementation section,  we’ve defined static constants for the X and Y coordinates of the position of the main HUD display.  These constants can just as easily be provided via the Constants.h file.  In the didMoveToView method, we call a series of  helper methods responsible for setting up and configuring the scene for game play.  The order of these methods is important, since the methods are called on the assumption that certain properties have already been initialized (i.e.  a hudManager can only be initialized with a targetWord, which is why it is called after the method acquireTargetWord, which itself is called after setupWordManger, since the initial target word is provided by the wordManager).
Now that we have a holistic understanding of how these setup methods are called, let’s look at their specific implementations below:
GameScene.m
-(void)configureScene{
    self.physicsWorld.contactDelegate = self;
    self.anchorPoint = CGPointMake(0.5, 0.5);
}

-(void)setupBackground{
    
    SKEmitterNode* starEmitter = [SKEmitterNode nodeWithFileNamed:@"stars"];
    [self addChild:starEmitter];
    starEmitter.position = CGPointZero;
}


-(void)setupBGMusic{
    SKAudioNode* bgNode = [SKAudioNode nodeWithFileNamed:@"Mishief-Stroll.mp3"];
    
    if(bgNode){
        [self addChild:bgNode];
        
    }
}


-(void)setupSpawnPoints{
    
    RandomPointGenerator* pointGen = [[RandomPointGenerator alloc] init];
    int numPoints = kNumberOfOnScreenDebugPoints;
    self.randomSpawnPoints = [pointGen getArrayOfOnScreenPoints: numPoints];

    
}

-(void)setupStatTracker{
    
    self.statTracker = [[StatTracker alloc] init];
}

-(void)setupWordManager{
    
    self.debugTargetWordArray = [[TargetWordArray alloc] initDebugArray];
    
    self.wordManager = [[WordManager alloc] initWith:self.debugTargetWordArray];
    
    self.wordManager.delegate = self;
    
    
}

-(void)acquireTargetWord{
    
    self.targetWord = [self.wordManager targetWord];
}


-(void)setupHUDManager{
    
    self.hudManager = [[HUDManager alloc] initWithTargetWord:self.targetWord];
    
    [self.hudManager addHUDNodeTo:self atPosition:CGPointMake(kHUDXPosition, kHUDYPosition)];
    
}



-(void)setupLetterManager{
    
    self.letterManager = [[LetterManager alloc] initWithSpawnPoints:self.randomSpawnPoints andWithTargetWord:self.targetWord];
    
}


-(void)createClouds{
    
    
    
    for (NSValue*pointVal in self.randomSpawnPoints) {
        
        Cloud* randomCloud = [Cloud getRandomCloud];
        [randomCloud addSpriteTo:self atPosition:pointVal.CGPointValue];
    }
}

-(void)addTargetWordLetters{
    
    if([self.letterManager targetWord]){
        [self.letterManager addLettersTo:self];
    }
    
}

Again, the order in which these methods is called is important.  For example, we first call the method setupSpawnPoints to initialize the array of random spawn points (each of which is wrapped in an NSValue object), which we in turn use to provide the positions for each of the cloud sprites that are added to the scene in the createClouds method. Finally, letterManager adds the letter sprites to the scene based on the targetWord obtained from the wordManager in the method acquireTargetWord.
In the course of gameplay, the letter sprites on-screen are constantly appearing and disappearing at regular cycles, alternately scattering to random on-screen positions where they can be tapped by the user and then hiding behind on-screen cloud sprites, respectively.  In order to achieve this effect, we need to call the update method on the letterManager, which we do in the GameScene’s update function:
-(void)update:(CFTimeInterval)currentTime {
    
    if(self.letterManager){
        
        [self.letterManager update:currentTime];
        
    }
}

In addition, during the course of gameplay, the user can tap on different on-screen letter sprites in order to spell the target word displayed by the HUD display at the bottom of the screen.  Therefore, in our touchesBeganfunction, we need to call the evaluateNextLetter method on the wordManager object in order to determine whether the user tapped on the correct letter, in which case the word in progress will be extended, or the wrong letter, in which case the user has misspelled the target word, requiring that the target word be cleared:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    for (UITouch *t in touches) {
        
        
        CGPoint touchPoint = [t locationInNode:self];
        
        SKSpriteNode* node = (SKSpriteNode*)[self nodeAtPoint:touchPoint];
        
        if(node && [node.name containsString:@"letter"]){
            NSString* nodeName = node.name;
            
            char lastChar = [nodeName characterAtIndex:nodeName.length-1];
            
            NSLog(@"You touched the letter %c", lastChar);
            
            [self.wordManager evaluateNextLetter:lastChar];
            
        }
        
    }

}

So far, we understood how the initial setup and configuration takes place.  We also  have a way of randomizing the letter sprites on-screen via the update function, which basically calls the corresponding update instance method defined for the WordManager class.   We also have a way of handling the user’s interaction with letters on the screen.  However, there’s still no communication between the wordManager object and the hudManagerstatTracker, and the letterManager (which must be notified whenever a new target word has been set so that it can add new letter sprites to the screen appropriately).  In order to accomplish this, we make use of the WordManagerDelegate protocol we defined earlier and to which our GameScene class is in conformance.  To this end, we implement all of the WordManagerDelegate protocol methods in our GameScene class, as shown below:
GameScene.m


/** Word Manager delegate methods **/

-(void)didUpdateWordInProgress:(NSString *)wordInProgress{
    NSLog(@"The word in progress has been updated - the current word in progress is now: %@", wordInProgress);
    
    [self.hudManager updateWordInProgressNode:wordInProgress];
}

-(void)didClearWordInProgress:(NSString *)deletedWordInProgress{
    
    NSLog(@"The word in progress %@ has been cleared.  You must start over in order to spell the target word",deletedWordInProgress);
    
    [self.hudManager updateWordInProgressNode:@""];
}

-(void)didMisspellWordInProgress:(NSString*)misspelledWordInProgress{
    
    self.statTracker.numberOfMisspellings += 1;
    
}


-(void)didCompleteWordInProgress:(NSString *)completedWordInProgress{
    
    NSLog(@"The user has completed the word in progress: %@",completedWordInProgress);
    
    
}

-(void)didUpdateTargetWordTo:(NSString*)updatedTargetWord fromPreviousTargetWord:(NSString*)previousTargetWord{
    
    NSLog(@"The target word has been updated.  The new target word is: %@",updatedTargetWord);
    
    self.statTracker.numberOfTargetWordsCompleted += 1;
    [self.statTracker addPointsForTargetWord:previousTargetWord];
    
    [self.hudManager updateTargetWordNode:updatedTargetWord];
    [self.hudManager updateScoreNode:self.statTracker.totalPointsAccumulated];
    
    
    /** Acquire the next target word form the WordManager **/
    [self acquireTargetWord];
    
    
    /** Remove letter nodes from the scene **/
    [self removeLetterNodes];
    
    /** Clear all the letters currently managed by the LetterManager **/
    [self.letterManager clearLetters];
    
    /** Reset the target word for the letter manager **/
    [self.letterManager setTargetWord:self.targetWord];
    
    /** Add the new letters from the letter manager to the scene **/
    [self.letterManager addLettersTo:self];
}

-(void)didExtendWordInProgress:(NSString*)extendedWordInProgress{
    
    self.statTracker.numberOfLettersSpelledCorrectly += 1;
    
}

//MARK: ******* Helper Function for Removing Excess Letters

-(void)removeLetterNodes{
    for (SKSpriteNode* node in self.children) {
        if([node.name containsString:@"letter"]){
            [node removeFromParent];
        }
    }
}



As you can see, these delegate methods allow us to update the HUD display, keep track of game statistics, and update the target word in a logical order, all the while maintaining the modularity of our game design and avoiding tight coupling among the different objects responsible for handling different game functionalities (i.e. tracking game statistics, update the HUD display, etc.).
This section of the tutorial is intended to provide a holistic overview of how we use protocol to achieve modular game design.  To continue with some of the finer points of how these different classes are implemented, you can continue with the tutorial here. Otherwise, please leave any comments or feedbacks below.

Objective-C Tutorials: Categories in Gaming Applications

Objective-C categories are often used to provide additional methods for supplementing the functionality of a given class.  When the header file for the category is imported in the implementation file for a given class, all of the supplementary category methods become available in the main implementation file.  The project for this tutorial AnimationsTester (*source code available here),  will provide a demonstration of how categories can be useful for providing specific functionalities for game characters.   Clicking on a menu button in the AnimationsController for the project will activate different animations, which are then displayed in an embedded SKView (i.e. SpriteKit view).  Above the embedded SKView is a segmented control that is used to toggle the orientation of the animation (i.e. switch between left-facing and right-facing animations).
* Please be aware that the game assets for this project are not available due to copyright concerns.  However, the source code can easily be adapted for the game assets specific for your project.




In order to begin, create a header file (DesertHero.h) that will define the public interface for our main game character:
@import SpriteKit;

#import "AnimationType.h"
#import "DesertCharacterOrientation.h"

@interface DesertHero: NSObject

-(instancetype)init;

-(void)addDesertHeroTo:(SKScene*)scene atPosition:(CGPoint)position;

-(void)runAnimationWithAnimationType:(AnimationType)animationType andWithOrientation:(DesertCharacterOrientation)orientation;

@end

First, please take note of the import statements at the top of the file.  Aside from SpriteKit, our DesertHero class has some dependencies – namely, the enums  AnimationType and DesertCharacterOrientation, which are defined in the header files AnimationType.h and DesertCharacterOrientation.h, respectively.  These two enums will be useful for defining methods for creating and retrieving animations, as will be explained shortly.
The DesertHero class inherits from NSObject, but it will be responsible for managing a SpriteKit node that will display the character animations, as well as other character data relevant to the game (i.e. health, bullets, etc.).  As far as interface methods go, we define a default initializer (to make things simple), and also a method addDesertHeroTo:atPosition, which will add the character to a SpriteKit scene, and runAnimationWithAnimationType:andWithOrientation:, which will be change the animations currently be executed by the character’s sprite node.
Animations for this project include idle, walk, jump, run and shoot, and each of these animations can have a left-facing or right-facing orientation.  To represent these various combinations of orientation and animation type, we define the aforementioned AnimationType and DesertCharacterOrientationenums in separate header files, as show below:

typedef enum{
    IDLE,
    WALK,
    RUN,
    JUMP,
    SHOOT
}AnimationType;

typedef enum{
    LEFT,
    RIGHT,
    SYMMETRICAL
}DesertCharacterOrientation;

In addition to the LEFT and RIGHT cases for the DesertCharacterOrientationenum, I’ve also included a SYMMETRICAL case to account for animations that don’t have a distinct right-facing or left-facing orientation in case we want to include additional animations (e.g. climb, crouch, etc.) later. This will be relevant for future expansions of this project and can be ignored for now.
Now it’s time to implement our DesertHero class.  Let’s create an implementation file for the DesertHero class and add the following import statements at the top:
#import 
#import 

#import "DesertHero.h"
#import "DesertHero+AnimationsGenerator.h"
#import "AnimationType.h"

As you will see, we’ve imported the header file for an Objective-C category(i.e. DesertHero+AnimationsGenerator.h), which we will define shortly. This category provides the methods that we need to generate animations that can be used in the main implementation of the DesertHero class.
In addition, we use Objective-C extensions (indicated via the “()” used in the@interface pre-processor directive)to define what is roughly the equivalent of private variables for our DesertHero.  While extensions are not of primary interest here, it worth discussing them in case the concept is new. Whereas categories are often used to inject new methods into the implementation of a class, extensions are often used to provide additional variables needed to provide additional state information about the class we are implementing:
 


@interface DesertHero()

@property SKSpriteNode* spriteNode;

@property DesertCharacterOrientation currentOrientation;
@property AnimationType currentAnimationType;

@end


@interface DesertHero()

/** Stored references to animations **/

@property SKAction* idleLeftAnimation;
@property SKAction* idleRightAnimation;
@property SKAction* runLeftAnimation;
@property SKAction* runRightAnimation;
@property SKAction* walkLeftAnimation;
@property SKAction* walkRightAnimation;
@property SKAction* jumpLeftAnimation;
@property SKAction* jumpRightAnimation;
@property SKAction* shootLeftAnimation;
@property SKAction* shootRightAnimation;
@property SKAction* climbAnimation;


@end


The extension at the top defines several properties:  spriteNode, which is used to display the character sprite and run the different animations for the character; currentOrientation, which obviously represent the current value of the orientation of the character sprite; and currentAnimationType, which obviously represent the current value of the animationType enum.  The extension at the bottom defines variables that will be used to store the different animations for the DesertHero character.
Now let’s turn to the implementation section of the DesertHero class:
@implementation DesertHero

static NSString* const kDefaultTexture = @"IdleLeft_000";


-(instancetype)init{
    
    self = [super init];
    
    if(self){
        
        SKTexture* texture = [SKTexture textureWithImageNamed:kDefaultTexture];
        
        self.spriteNode = [SKSpriteNode spriteNodeWithTexture:texture];
        
        self.currentAnimationType = IDLE;
        self.currentOrientation = LEFT;
        
        [self configureSpriteNode];
        
        [self configureAnimations];
        
    }
    
    return self;
}


-(void)addDesertHeroTo:(SKScene*)scene atPosition:(CGPoint)position{
    
    [self.spriteNode moveToParent:scene];
    [self.spriteNode setPosition:position];
    
    
}

@end

Above, we have defined a simple default initializer that instantiates a new SKSpriteNode with a default sprite based on a texture whose filename is stored in a constant defined at the top of the implementation file – that is, kDefaultTexture.  The initializer also defines starting values for the currentAnimationType and currentOrientation, and calls some private helper methods that are used to configure the sprite node and animations.  These private helper methods are shown below:
-(void)configureSpriteNode{
    
    NSLog(@"Configuring sprite node...");
    self.spriteNode.anchorPoint = CGPointMake(0.5, 0.5);
    self.spriteNode.xScale *= 0.25;
    self.spriteNode.yScale *= 0.25;
    
    
}



-(void)configureAnimations{
    
    NSLog(@"Configuring animations...");

    
    self.idleLeftAnimation = [self generateIdleAnimation:LEFT];
    self.idleRightAnimation = [self generateIdleAnimation:RIGHT];
    
    self.walkLeftAnimation = [self generateWalkAnimation:LEFT];
    self.walkRightAnimation = [self generateWalkAnimation:RIGHT];
    
    self.runLeftAnimation = [self generateRunAnimation:LEFT];
    self.runRightAnimation = [self generateRunAnimation:RIGHT];
    
    self.jumpLeftAnimation = [self generateJumpAnimation:LEFT];
    self.jumpRightAnimation = [self generateJumpAnimation:RIGHT];
    
    self.shootLeftAnimation = [self generateShootAnimation:LEFT];
    self.shootRightAnimation = [self generateShootAnimation:RIGHT];
    
    
}



The above methods are pretty self-explanatory.  Notice that in our configureAnimations: method, we assign animations to each of the animation properties that we defined in the extension above (we are actually using accessors generated by the compiler via @property keyword, and not assigning directly to the underlying instance variables themselves, which in this case are accessible via variable names beginning with an underline – e.g. as  _idleLeftAnimation_idleRightAnimation, etc.).  The methods used to generate the animations are still somewhat of an enigma – and that’s because we haven’t defined them yet.  Where do they come from? They are from the category that we imported via the #import “DesertHero+AnimationsGenerator.h” statement at the top of our implementation file., which we will get to shortly.
In addition, we implement the interface method that will be responsible for running a specific animation based on the animation type and orientation parameters passed into the function, as shown below:
-(void)runAnimationOnNode:(SKSpriteNode*)node ForAnimationType:(AnimationType)animationType andOrientation:(DesertCharacterOrientation)orientation{
    
    
    SKAction* selectedAnimation = nil;
    
    switch (animationType) {
        case IDLE:
            if(orientation == LEFT){
                selectedAnimation = self.idleLeftAnimation;
            } else if(orientation == RIGHT){
                selectedAnimation = self.idleRightAnimation;
            } else {
                selectedAnimation = nil;
            }
            break;
        case WALK:
            if(orientation == LEFT){
                selectedAnimation = self.walkLeftAnimation;
            } else if(orientation == RIGHT){
                selectedAnimation = self.walkRightAnimation;
            } else {
                selectedAnimation = nil;
            }
            break;
        case RUN:
            if(orientation == LEFT){
                selectedAnimation = self.runLeftAnimation;
            } else if(orientation == RIGHT){
                selectedAnimation = self.runRightAnimation;
            } else {
                selectedAnimation = nil;
            }
            break;
        case SHOOT:
            if(orientation == LEFT){
                selectedAnimation = self.shootLeftAnimation;
            } else if(orientation == RIGHT){
                selectedAnimation = self.shootRightAnimation;
            } else {
                selectedAnimation = nil;
            }
            break;
        case JUMP:
            if(orientation == LEFT){
                selectedAnimation = self.jumpLeftAnimation;
            } else if(orientation == RIGHT){
                selectedAnimation = self.jumpRightAnimation;
            } else {
                selectedAnimation = nil;
            }
            break;
        default:
            break;
    }
    
    NSString* animationKey = [self generateAnimationKeyFor:animationType andFor:orientation];
    
    [node runAction:selectedAnimation withKey:animationKey];
}



The use of if-then statements nested within a switch statement may not be the most elegant way to implement this method, but it suits our purposes here.   This method will ultimately allow us to run any given animation based on the specific orientation and animation type requested via user interaction with UI elements in the AnimationsController.
To make things easier, we can hide the implementation details of the method above and wrap the above method in another method that doesn’t take a node parameter, since we already know that the node being used to execute animations is the spriteNode that is responsible for displaying the character.  To that end, the helper method above will be called in the interface method which we will use in our AnimationsController class to switch between different animations that will be displayed in an embedded SpriteKit view:
-(void)runAnimationWithAnimationType:(AnimationType)animationType andWithOrientation:(DesertCharacterOrientation)orientation{
    
    [self runAnimationOnNode:self.spriteNode ForAnimationType:animationType andOrientation:orientation];
}



Finally, we turn to the the category itself, which will provide the methods needed by our DesertHero class for configuring the different animations for the character.  Below is the public interface for the category, which basically shows method signatures for a series of methods that, given a given orientation, will generate the different animations for the character.
#import "DesertHero.h"
#import "DesertCharacterOrientation.h"
#import "AnimationType.h"


@interface DesertHero (AnimationsGenerator)


-(NSString*)generateAnimationKeyFor:(AnimationType)animationType andFor:(DesertCharacterOrientation)orientation;

-(SKAction*)generateIdleAnimation:(DesertCharacterOrientation)orientation;
-(SKAction*)generateRunAnimation:(DesertCharacterOrientation)orientation;
-(SKAction*)generateJumpAnimation:(DesertCharacterOrientation)orientation;
-(SKAction*)generateShootAnimation:(DesertCharacterOrientation)orientation;
-(SKAction*)generateWalkAnimation:(DesertCharacterOrientation)orientation;


@end

Next, we create the implementation(DesertHere+AnimationsGenerator.m) file for our category. At the top of the implementation, we define static constants for the time between frames for each of the animations:
#import "DesertHero+AnimationsGenerator.h"



@implementation DesertHero (AnimationsGenerator)

const static double kIdleActionDuration = 0.40;
const static double kRunActionDuration = 0.30;
const static double kShootActionDuration = 0.30;
const static double kJumpActionDuration = 0.20;
const static double kWalkActionDuration = 0.30;


@end 

In the implementation file, we will create a helper function which will return a block of signature (SKAciton*(^)(DesertCharacterOrientation)).  This helper function is basically a wrapper for a switch statement.  It takes arrays of SKTextures for left-facing animations, right-facing animations, and symmetric animations.  It then uses the texture arrays to create a block that can be called to generate an SKAction for a given orientation, based on the array of textures passed in as arguments to the wrapper function.
-(SKAction*(^)(DesertCharacterOrientation))getAnimationGeneratorWithLeftOrientationTextures:(NSArray*)leftOrientationTextures andWithRightOrientationTexture:(NSArray*)rightOrientationTextures andWithSymmetricOrientationTextures:(NSArray*)symmetricOrientationTextures andWithTimePerFrame:(NSTimeInterval)timePerFrame{
    
    return ^(DesertCharacterOrientation orientation){
        
        SKAction* baseAction;
        
        switch (orientation) {
            case LEFT:
                baseAction =  [SKAction animateWithTextures:leftOrientationTextures timePerFrame:timePerFrame];
                break;
            case RIGHT:
                baseAction =  [SKAction animateWithTextures:rightOrientationTextures timePerFrame:timePerFrame];
                break;
            case SYMMETRICAL:
                baseAction =  [SKAction animateWithTextures:symmetricOrientationTextures timePerFrame:timePerFrame];
                break;
            default:
                break;
        }
        
        SKAction* repeatingAnimation = [SKAction repeatActionForever:baseAction];
        
        return repeatingAnimation;
        
        
    };
}




Next we define a method that will generate a string key for an animation based on its animationType and its orientation.  This method is not absolutely necessary for our purposes here and can be skipped if it has no use for your project.  However, sometimes its convenient to not only run animations but also to provide a string key that can be used as an identifier for that animation.  This can come in handy if you have several different animations running on the same node and you wish to selectively remove a specific animation based on a specific event or condition in the game.
-(NSString*)generateAnimationKeyFor:(AnimationType)animationType andFor:(DesertCharacterOrientation)orientation{
    
    NSString* baseStr = [[NSString alloc] init];
    
    switch (animationType) {
        case IDLE:
            baseStr = @"idle";
            break;
        case WALK:
            baseStr = @"walk";
            break;
        case RUN:
            baseStr = @"run";
            break;
        case SHOOT:
            baseStr = @"shoot";
            break;
        case JUMP:
            baseStr = @"jump";
            break;
        default:
            break;
    }
    
    NSString* orientationStr;
    
    switch (orientation) {
        case LEFT:
            orientationStr = @"Left";
            break;
        case RIGHT:
            orientationStr = @"Right";
            break;
        case SYMMETRICAL:
            orientationStr = @"Symmetric";
            break;
        default:
            break;
    }
    
    
    return [baseStr stringByAppendingString:orientationStr];
    
}

If we didn’t define the helper method that generates an animation generator function, we could have alternatively defined a function like the one show below for generating idle animations. This function implementation first creates an SKAction instance based on a given orientation and set of pre-defined “idle” textures. It then passes in this SKAction instance to the SKAction class method, repeatActionForever:, which runs the idle animation on a loop:
-(SKAction *)generateIdleAnimation:(DesertCharacterOrientation)orientation{
    
    SKAction* idleAction;
    
    switch (orientation) {
        case LEFT:
            idleAction = [SKAction animateWithTextures:@[
             [SKTexture textureWithImageNamed:@"IdleLeft_000"],
             [SKTexture textureWithImageNamed:@"IdleLeft_001"],
             [SKTexture textureWithImageNamed:@"IdleLeft_002"],
             [SKTexture textureWithImageNamed:@"IdleLeft_003"],
             [SKTexture textureWithImageNamed:@"IdleLeft_004"],
             [SKTexture textureWithImageNamed:@"IdleLeft_005"],
             [SKTexture textureWithImageNamed:@"IdleLeft_006"],
             [SKTexture textureWithImageNamed:@"IdleLeft_007"],
             [SKTexture textureWithImageNamed:@"IdleLeft_008"],
             [SKTexture textureWithImageNamed:@"IdleLeft_009"],

             ] timePerFrame:kIdleActionDuration];
            break;
        case RIGHT:
            idleAction = [SKAction animateWithTextures:@[
             [SKTexture textureWithImageNamed:@"IdleRight_000"],
             [SKTexture textureWithImageNamed:@"IdleRight_001"],
             [SKTexture textureWithImageNamed:@"IdleRight_002"],
             [SKTexture textureWithImageNamed:@"IdleRight_003"],
             [SKTexture textureWithImageNamed:@"IdleRight_004"],
             [SKTexture textureWithImageNamed:@"IdleRight_005"],
             [SKTexture textureWithImageNamed:@"IdleRight_006"],
             [SKTexture textureWithImageNamed:@"IdleRight_007"],
             [SKTexture textureWithImageNamed:@"IdleRight_008"],
             [SKTexture textureWithImageNamed:@"IdleRight_009"],

                 ] timePerFrame:kIdleActionDuration];
            break;
        case SYMMETRICAL:
            break;
        default:
            break;
    }
    
    SKAction* idleAnimation = [SKAction repeatActionForever:idleAction];
    
    return idleAnimation;
    
}

However, rather than implementing the animation generator functions the way we’ve shown above, we instead call our helper function getAnimationGeneratorWithLeftOrientationTextures:andWithRightOrientationTexture:andWithSymmetricOrientationTextures:andWithTimePerFrame:, which return a block that is called in the return statement of our animation generator function so as to generate the final, repeating animation:
-(SKAction *)generateRunAnimation:(DesertCharacterOrientation)orientation{
    
    SKAction*(^getRunAnimation)(DesertCharacterOrientation) = [self getAnimationGeneratorWithLeftOrientationTextures:@[
           [SKTexture textureWithImageNamed:@"RunLeft_000"],
           [SKTexture textureWithImageNamed:@"RunLeft_001"],
           [SKTexture textureWithImageNamed:@"RunLeft_002"],
           [SKTexture textureWithImageNamed:@"RunLeft_003"],
           [SKTexture textureWithImageNamed:@"RunLeft_004"],
           [SKTexture textureWithImageNamed:@"RunLeft_005"],
           [SKTexture textureWithImageNamed:@"RunLeft_006"],
           [SKTexture textureWithImageNamed:@"RunLeft_007"],
           [SKTexture textureWithImageNamed:@"RunLeft_008"],
           [SKTexture textureWithImageNamed:@"RunLeft_009"]
           
       ]
      
      andWithRightOrientationTexture:@[
      
           [SKTexture textureWithImageNamed:@"RunRight_000"],
           [SKTexture textureWithImageNamed:@"RunRight_001"],
           [SKTexture textureWithImageNamed:@"RunRight_002"],
           [SKTexture textureWithImageNamed:@"RunRight_003"],
           [SKTexture textureWithImageNamed:@"RunRight_004"],
           [SKTexture textureWithImageNamed:@"RunRight_005"],
           [SKTexture textureWithImageNamed:@"RunRight_006"],
           [SKTexture textureWithImageNamed:@"RunRight_007"],
           [SKTexture textureWithImageNamed:@"RunRight_008"],
           [SKTexture textureWithImageNamed:@"RunRight_009"]
      ] andWithSymmetricOrientationTextures: nil
     andWithTimePerFrame:kRunActionDuration];
    
    return getRunAnimation(orientation);
    
}

-(SKAction *)generateJumpAnimation:(DesertCharacterOrientation)orientation{
    
    SKAction*(^getJumpAnimation)(DesertCharacterOrientation) = [self getAnimationGeneratorWithLeftOrientationTextures:@[
        
    [SKTexture textureWithImageNamed:@"JumpLeft_000"],
    [SKTexture textureWithImageNamed:@"JumpLeft_001"],
    [SKTexture textureWithImageNamed:@"JumpLeft_002"],
    [SKTexture textureWithImageNamed:@"JumpLeft_003"],
    [SKTexture textureWithImageNamed:@"JumpLeft_004"],
    [SKTexture textureWithImageNamed:@"JumpLeft_005"],
    [SKTexture textureWithImageNamed:@"JumpLeft_006"],
    [SKTexture textureWithImageNamed:@"JumpLeft_007"],
    [SKTexture textureWithImageNamed:@"JumpLeft_008"],
    [SKTexture textureWithImageNamed:@"JumpLeft_009"]
                                                                                                            
        ] andWithRightOrientationTexture:@[
       
   [SKTexture textureWithImageNamed:@"JumpRight_000"],
   [SKTexture textureWithImageNamed:@"JumpRight_001"],
   [SKTexture textureWithImageNamed:@"JumpRight_002"],
   [SKTexture textureWithImageNamed:@"JumpRight_003"],
   [SKTexture textureWithImageNamed:@"JumpRight_004"],
   [SKTexture textureWithImageNamed:@"JumpRight_005"],
   [SKTexture textureWithImageNamed:@"JumpRight_006"],
   [SKTexture textureWithImageNamed:@"JumpRight_007"],
   [SKTexture textureWithImageNamed:@"JumpRight_008"],
   [SKTexture textureWithImageNamed:@"JumpRight_009"]
       ] andWithSymmetricOrientationTextures:nil andWithTimePerFrame:kJumpActionDuration];
    
    return getJumpAnimation(orientation);
}



-(SKAction *)generateShootAnimation:(DesertCharacterOrientation)orientation{
    
    SKAction*(^getShootAnimation)(DesertCharacterOrientation) = [self getAnimationGeneratorWithLeftOrientationTextures:
  @[
    [SKTexture textureWithImageNamed:@"ShootLeft_000"],
    [SKTexture textureWithImageNamed:@"ShootLeft_001"],
    [SKTexture textureWithImageNamed:@"ShootLeft_002"],
    [SKTexture textureWithImageNamed:@"ShootLeft_003"],
    [SKTexture textureWithImageNamed:@"ShootLeft_004"],
    [SKTexture textureWithImageNamed:@"ShootLeft_005"],
    [SKTexture textureWithImageNamed:@"ShootLeft_006"],
    [SKTexture textureWithImageNamed:@"ShootLeft_007"],
    [SKTexture textureWithImageNamed:@"ShootLeft_008"],
    [SKTexture textureWithImageNamed:@"ShootLeft_009"],


    
    ]
    andWithRightOrientationTexture:
  @[
    [SKTexture textureWithImageNamed:@"ShootRight_000"],
    [SKTexture textureWithImageNamed:@"ShootRight_001"],
    [SKTexture textureWithImageNamed:@"ShootRight_002"],
    [SKTexture textureWithImageNamed:@"ShootRight_003"],
    [SKTexture textureWithImageNamed:@"ShootRight_004"],
    [SKTexture textureWithImageNamed:@"ShootRight_005"],
    [SKTexture textureWithImageNamed:@"ShootRight_006"],
    [SKTexture textureWithImageNamed:@"ShootRight_007"],
    [SKTexture textureWithImageNamed:@"ShootRight_008"],
    [SKTexture textureWithImageNamed:@"ShootRight_009"],


    ]
   andWithSymmetricOrientationTextures: nil
   andWithTimePerFrame: kShootActionDuration];
    
    return getShootAnimation(orientation);
    
}



-(SKAction *)generateWalkAnimation:(DesertCharacterOrientation)orientation{
    
    SKAction*(^getWalkAnimation)(DesertCharacterOrientation) = [self getAnimationGeneratorWithLeftOrientationTextures:
  @[
    [SKTexture textureWithImageNamed:@"WalkLeft_000"],
    [SKTexture textureWithImageNamed:@"WalkLeft_001"],
    [SKTexture textureWithImageNamed:@"WalkLeft_002"],
    [SKTexture textureWithImageNamed:@"WalkLeft_003"],
    [SKTexture textureWithImageNamed:@"WalkLeft_004"],
    [SKTexture textureWithImageNamed:@"WalkLeft_005"],
    [SKTexture textureWithImageNamed:@"WalkLeft_006"],
    [SKTexture textureWithImageNamed:@"WalkLeft_007"],
    [SKTexture textureWithImageNamed:@"WalkLeft_008"],
    [SKTexture textureWithImageNamed:@"WalkLeft_009"],


    ]
   andWithRightOrientationTexture:
  @[
    [SKTexture textureWithImageNamed:@"WalkRight_000"],
    [SKTexture textureWithImageNamed:@"WalkRight_001"],
    [SKTexture textureWithImageNamed:@"WalkRight_002"],
    [SKTexture textureWithImageNamed:@"WalkRight_003"],
    [SKTexture textureWithImageNamed:@"WalkRight_004"],
    [SKTexture textureWithImageNamed:@"WalkRight_005"],
    [SKTexture textureWithImageNamed:@"WalkRight_006"],
    [SKTexture textureWithImageNamed:@"WalkRight_007"],
    [SKTexture textureWithImageNamed:@"WalkRight_008"],
    [SKTexture textureWithImageNamed:@"WalkRight_009"],

    ]
                                                                
  andWithSymmetricOrientationTextures:nil andWithTimePerFrame:kWalkActionDuration];
    
    return getWalkAnimation(orientation);
    
};



Now we will create the interface for the SKScene that will be presented by the SKView embedded in our AnimationsController.   This interface will have a single method  runAnimationWithAnimationType:andWithOrientation:, which can be called to activate different character animations from our AnimationsController.  It will basically act as a wrapper for the DesertHeroclass instance method runAnimationWithAnimationType:andWithOrientation:.   The implementation file for the animation scene will also configure the background and add the DesertHero character to the scene in the didMoveToView function.
AnimationScene.h
@import SpriteKit;
#import "AnimationType.h"
#import "DesertCharacterOrientation.h"

@interface AnimationScene: SKScene

-(void)runAnimationWithAnimationType:(AnimationType)animationType andWithOrientation:(DesertCharacterOrientation)orientation;


@end


AnimationScene.m 

#import "AnimationScene.h"
#import "DesertHero.h"

@interface AnimationScene()

@property DesertHero* hero;

@end



@implementation AnimationScene


-(void)didMoveToView:(SKView *)view{
    
    self.anchorPoint = CGPointMake(0.5, 0.5);
    
    [self configureBackground];
    
    self.hero = [[DesertHero alloc] init];
    
    [self.hero addDesertHeroTo:self atPosition:CGPointZero];
    
}

-(void)configureBackground{
    

    SKSpriteNode* backgroundSprite = [SKSpriteNode spriteNodeWithImageNamed:@"desert_background"];
    
    backgroundSprite.anchorPoint = CGPointMake(0.5, 0.5);
    
    backgroundSprite.zPosition = -10;
    
    [backgroundSprite setScale:0.5];
    
    [backgroundSprite moveToParent:self];
}

-(void)runAnimationWithAnimationType:(AnimationType)animationType andWithOrientation:(DesertCharacterOrientation)orientation{
    
    [self.hero runAnimationWithAnimationType:animationType andWithOrientation:orientation];

    
}


@end


Now we are ready to create a header file for the AnimationsController, which will also act as the backing class for the view controller that we configure in our Main.storyboard file.   The AnimationsController is configured in the Main.storyboard, a screenshot for which is shown below.  Basically, it has segmented control for toggling the orientation of the character at the top, an embedded SKView (which will display the DesertHero character in different animation states), and a set of buttons embedded in a UIStackViewthat can be used to select from different animations that can be displayed in the SKView:
AnimationsController.h
@import UIKit;

@interface AnimationsController: UIViewController


@end


Main.storyboard
animationsController_storyboard
AnimationsController.m
#import 
#import 
#import "AnimationsController.h"
#import "AnimationScene.h"
#import "AnimationType.h"
#import "DesertCharacterOrientation.h"


@interface AnimationsController()

@property (weak, nonatomic) IBOutlet SKView *skView;

@property AnimationScene* animationScene;
@property DesertCharacterOrientation currentOrientation;
@property AnimationType currentAnimationType;

@property (weak, nonatomic) IBOutlet UISegmentedControl *orientationSegmentedControl;



@end

As shown above, the implementation file for our AnimationsControllerdefines properties for the animationScene, the SKScene being displayed by the SKView view, and also for currentOrientation and currentAnimationType, which are used to toggle between different animation states.  Below is the viewDidLoad function for the AnimationsController:
@implementation AnimationsController

-(void)viewDidLoad{
    
    CGSize size = self.skView.bounds.size;
    
    self.animationScene = [[AnimationScene alloc] initWithSize:size];
    
    [self.skView presentScene:self.animationScene];
}

We also define a helper method runAnimation, that gets called each time the user toggles to a different animation state, either as a result of tapping the segmented control for character orientation or by tapping on a button to a select a different animation type:
-(void)runAnimation{
    
    [self.animationScene runAnimationWithAnimationType:self.currentAnimationType andWithOrientation:self.currentOrientation];
}

Below are the IBActions connected to the different UI elements in the AnimationsController. These IBActions change the value stored in currentOrientation and currentAnimation properties defined above, and they also call the runAnimation method so as to display the new animation for the character.
- (IBAction)changeOrientation:(UISegmentedControl *)sender {
    
    self.currentOrientation = (DesertCharacterOrientation)self.orientationSegmentedControl.selectedSegmentIndex;
    
    [self runAnimation];

}



- (IBAction)showIdleAnimation:(UIButton *)sender {
    
    self.currentAnimationType = IDLE;

    [self runAnimation];
}

- (IBAction)showWalkAnimation:(UIButton *)sender {
    
    self.currentAnimationType = WALK;

    [self runAnimation];

}


- (IBAction)showRunAnimation:(UIButton *)sender {
    
    self.currentAnimationType = RUN;

    [self runAnimation];

}

- (IBAction)showJumpAnimation:(UIButton *)sender {
    
    self.currentAnimationType = JUMP;

    [self runAnimation];

}
- (IBAction)showShootAnimation:(UIButton *)sender {
    
    self.currentAnimationType = SHOOT;

    [self runAnimation];

}


@end