How to Make a Game like Space Invaders - Ray Wenderlich (why do my space invaders scroll off screen)

Posted by Erv Noel on Stack Overflow See other posts from Stack Overflow or by Erv Noel
Published on 2013-11-09T03:27:45Z Indexed on 2013/11/09 3:53 UTC
Read the original article Hit count: 246

Filed under:
|
|
|

I'm following this tutorial(http://www.raywenderlich.com/51068/how-to-make-a-game-like-space-invaders-with-sprite-kit-tutorial-part-1) and I've run into a problem right after the part where I add [self determineInvaderMovementDirection]; to my GameScene.m file (specifically to my moveInvadersForUpdate method)

The tutorial states that the space invaders should be moving accordingly after adding this piece of code but when I run they move to the left and they do not come back. I'm not sure what I am doing wrong as I have followed this tutorial very carefully. Any help or clarification would be greatly appreciated. Thanks in advance !

Here is the full GameScene.m

#import "GameScene.h"
#import <CoreMotion/CoreMotion.h>

#pragma mark - Custom Type Definitions
/*
 The type definition and constant definitions 1,2,3 take care of 
 the following tasks:

 1.Define the possible types of invader enemies. This can be used in switch 
 statements later when things like displaying different sprites images for each enemy type. The typedef makes InvaderType a formal Obj-C type that is type checked for method arguments and variables.This is so that the wrong
 method argument is not used or assigned to the wrong variable.

 2. Define the size of the invaders and that they'll be laid out in 
 a grid of rows and columns on the screen.

 3. Define a name that will be used to identify invaders when searching for them.

 */


//1
typedef enum InvaderType {
    InvaderTypeA,
    InvaderTypeB,
    InvaderTypeC
} InvaderType;


/*
 Invaders move in a fixed pattern: right, right, down, left, down, right
 right. InvaderMovementDirection tracks the invaders' progress through this pattern
 */
typedef enum InvaderMovementDirection {
    InvaderMovementDirectionRight,
    InvaderMovementDirectionLeft,
    InvaderMovementDirectionDownThenRight,
    InvaderMovementDirectionDownThenLeft,
    InvaderMovementDirectionNone
} InvaderMovementDirection;

//2
#define kInvaderSize CGSizeMake(24,16)
#define kInvaderGridSpacing CGSizeMake(12,12)
#define kInvaderRowCount 6
#define kInvaderColCount 6

//3
#define kInvaderName @"invader"
#define kShipSize CGSizeMake(30, 16) //stores the size of the ship
#define kShipName @"ship" // stores the name of the ship stored on the sprite node
#define kScoreHudName @"scoreHud"
#define kHealthHudName @"healthHud"



/*
 this class extension allows you to 
 add “private” properties to GameScene class, without revealing the properties to other classes or code. You still get the benefit of using Objective-C properties, but your GameScene state is stored internally and can’t be modified by other external classes.
 As well, it doesn’t clutter the namespace of datatypes that your other classes see. This class extension is used in the method didMoveToView
 */
#pragma mark - Private GameScene Properties

@interface GameScene ()
@property BOOL contentCreated;

@property InvaderMovementDirection invaderMovementDirection;
@property NSTimeInterval timeOfLastMove;
@property NSTimeInterval timePerMove;

@end


@implementation GameScene



#pragma mark Object Lifecycle Management

#pragma mark - Scene Setup and Content Creation


/*This method simply invokes createContent using the
 BOOL property contentCreated to make sure you
 don’t create your scene’s content more than once. 
 This property is defined in an Objective-C Class Extension found near the top of the file()*/
- (void)didMoveToView:(SKView *)view

{
    if (!self.contentCreated) {
        [self createContent];
        self.contentCreated = YES;
    }
}

- (void)createContent {
    //1 - Invaders begin by moving to the right
    self.invaderMovementDirection = InvaderMovementDirectionRight;

    //2 - Invaders take 1 sec for each move. Each step left, right or down
    // takes 1 second.
    self.timePerMove = 1.0;

    //3 - Invaders haven't moved yet, so set the time to zero
    self.timeOfLastMove = 0.0;


    [self setupInvaders];
    [self setupShip];
    [self setupHud];
}

/*
Creates an invade sprite of a given type

 1. Use the invadeType parameterr to determine color of the invader
 2. Call spriteNodeWithColor:size: of SKSpriteNode to alloc and init a sprite
    that renders as a rect of the given color invaderColor with size kInvaderSize
*/
-(SKNode*)makeInvaderOfType:(InvaderType)invaderType {
    //1
    SKColor* invaderColor;
    switch (invaderType) {
        case InvaderTypeA:
            invaderColor = [SKColor redColor];
            break;
        case InvaderTypeB:
            invaderColor = [SKColor greenColor];
            break;
        case InvaderTypeC:
            invaderColor = [SKColor blueColor];
            break;
    }

    //2
    SKSpriteNode* invader = [SKSpriteNode spriteNodeWithColor:invaderColor size:kInvaderSize];
    invader.name = kInvaderName;

    return invader;
}

-(void)setupInvaders {
    //1 - loop over the rows
    CGPoint baseOrigin = CGPointMake(kInvaderSize.width / 2, 180);
    for (NSUInteger row = 0; row < kInvaderRowCount; ++row) {

        //2 - Choose a single InvaderType for all invaders
        // in this row based on the row number
        InvaderType invaderType;
        if (row % 3 == 0)       invaderType = InvaderTypeA;
        else if (row % 3 == 1)  invaderType = InvaderTypeB;
        else                    invaderType = InvaderTypeC;

        //3 - Does some math to figure out where the first invader
        // in the row should be positioned
        CGPoint invaderPosition = CGPointMake(baseOrigin.x, row * (kInvaderGridSpacing.height + kInvaderSize.height) + baseOrigin.y);

        //4 - Loop over the columns
        for (NSUInteger col = 0; col < kInvaderColCount; ++col) {

            //5 - Create an invader for the current row and column and add it
            // to the scene
            SKNode* invader = [self makeInvaderOfType:invaderType];
            invader.position = invaderPosition;
            [self addChild:invader];

            //6 - update the invaderPosition so that it's correct for the
            //next invader
            invaderPosition.x += kInvaderSize.width + kInvaderGridSpacing.width;
        }

    }
}

-(void)setupShip {
    //1 - creates ship using makeShip. makeShip can easily be used later
    // to create another ship (ex. to set up more lives)
    SKNode* ship = [self makeShip];

    //2 - Places the ship on the screen. In SpriteKit the origin is at the lower
    //left corner of the screen. The anchorPoint is based on a unit square with (0, 0) at the lower left of the sprite's area and (1, 1) at its top right. Since SKSpriteNode has a default anchorPoint of (0.5, 0.5), i.e., its center, the ship's position is the position of its center. Positioning the ship at kShipSize.height/2.0f means that half of the ship's height will protrude below its position and half above. If you check the math, you'll see that the ship's bottom aligns exactly with the bottom of the scene.
    ship.position = CGPointMake(self.size.width / 2.0f, kShipSize.height/2.0f);
    [self addChild:ship];
}

-(SKNode*)makeShip {
    SKNode* ship = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:kShipSize];
    ship.name = kShipName;
    return ship;
}

-(void)setupHud {
    //Sets the score label font to Courier
    SKLabelNode* scoreLabel = [SKLabelNode labelNodeWithFontNamed:@"Courier"];

    //1 - Give the score label a name so it becomes easy to find later when
    // the score needs to be updated.
    scoreLabel.name = kScoreHudName;
    scoreLabel.fontSize = 15;

    //2 - Color the score label green
    scoreLabel.fontColor = [SKColor greenColor];
    scoreLabel.text = [NSString stringWithFormat:@"Score: %04u", 0];

    //3 - Positions the score label near the top left corner of the screen
    scoreLabel.position = CGPointMake(20 + scoreLabel.frame.size.width/2, self.size.height - (20 + scoreLabel.frame.size.height/2));
    [self addChild:scoreLabel];

    //Applies the font of the health label
    SKLabelNode* healthLabel = [SKLabelNode labelNodeWithFontNamed:@"Courier"];


    //4 - Give the health label a name so it can be referenced later when it needs
    // to be updated to display the health
    healthLabel.name = kHealthHudName;
    healthLabel.fontSize = 15;

    //5 - Colors the health label red
    healthLabel.fontColor = [SKColor redColor];
    healthLabel.text = [NSString stringWithFormat:@"Health: %.1f%%", 100.0f];

    //6 - Positions the health Label on the upper right hand side of the screen
    healthLabel.position = CGPointMake(self.size.width - healthLabel.frame.size.width/2 - 20, self.size.height - (20 + healthLabel.frame.size.height/2));
    [self addChild:healthLabel];
}

#pragma mark - Scene Update

- (void)update:(NSTimeInterval)currentTime {
    //Makes the invaders move
    [self moveInvadersForUpdate:currentTime];

}

#pragma mark - Scene Update Helpers
//This method will get invoked by update
-(void)moveInvadersForUpdate:(NSTimeInterval)currentTime {

    //1 - if it's not yet time to move, exit the method. moveInvadersForUpdate:
    // is invoked 60 times per second, but you don't want the invaders to move
    // that often since the movement would be too fast to see
    if (currentTime - self.timeOfLastMove < self.timePerMove) return;

    //2 - Recall that the scene holds all the invaders as child nodes; which were
    // added to the scene using addChild: in setupInvaders identifying each invader
    // by its name property. Invoking enumerateChildNodesWithName:usingBlock only loops over the invaders because they're named kInvaderType; which makes the loop skip the ship and the HUD. The guts og the block moves the invaders 10 pixels either right, left or down depending on the value of invaderMovementDirection
    [self enumerateChildNodesWithName:kInvaderName usingBlock:^(SKNode *node, BOOL *stop) {
        switch (self.invaderMovementDirection) {
            case InvaderMovementDirectionRight:
                node.position = CGPointMake(node.position.x - 10, node.position.y);
                break;
            case InvaderMovementDirectionLeft:
                node.position = CGPointMake(node.position.x - 10, node.position.y);
                break;
            case InvaderMovementDirectionDownThenLeft:
            case InvaderMovementDirectionDownThenRight:
                node.position = CGPointMake(node.position.x, node.position.y - 10);
                break;
            InvaderMovementDirectionNone:
            default:
                break;
        }
    }];

    //3 - Record that you just moved the invaders, so that the next time this method is invoked (1/60th of a second from when it starts), the invaders won't move again until the set time period of one second has elapsed.
    self.timeOfLastMove = currentTime;

    //Makes it so that the invader movement direction changes only when the invaders are actually moving. Invaders only move when the check on self.timeOfLastMove passes (when conditional expression is true)
    [self determineInvaderMovementDirection];



}





#pragma mark - Invader Movement Helpers
-(void)determineInvaderMovementDirection {
    //1 - Since local vars accessed by block are default const(means they cannot be changed), this snippet of code qualifies proposedMovementDirection with __block so that you can modify it in //2
    __block InvaderMovementDirection proposedMovementDirection = self.invaderMovementDirection;

    //2 - Loops over the invaders in the scene and refers to the block with the invader as an argument
    [self enumerateChildNodesWithName:kInvaderName usingBlock:^(SKNode *node, BOOL *stop) {

        switch (self.invaderMovementDirection) {

            case InvaderMovementDirectionRight:

                //3 - If the invader's right edge is within 1pt of the right edge of the scene, it's about to move offscreen. Sets proposedMovementDirection so that the invaders move down then left. You compare the invader's frame(the frame that contains its content in the scene's coordinate system) with the scene width. Since the scene has an anchorPoint of (0,0) by default and is scaled to fill it's parent view, this comparison ensures you're testing against the view's edges.
                if (CGRectGetMaxX(node.frame) >= node.scene.size.width - 1.0f) {
                    proposedMovementDirection = InvaderMovementDirectionDownThenLeft;
                    *stop = YES;
                }
                break;

            case InvaderMovementDirectionLeft:
                //4 - If the invader's left edge is within 1 pt of the left edge of the scene, it's about to move offscreen. Sets the proposedMovementDirection so invaders move down then right
                if (CGRectGetMinX(node.frame) <= 1.0f) {
                    proposedMovementDirection = InvaderMovementDirectionDownThenRight;
                    *stop = YES;
                }
                break;

            case InvaderMovementDirectionDownThenLeft:
                //5 - If invaders are moving down then left, they already moved down at this point, so they should now move left.
                proposedMovementDirection = InvaderMovementDirectionLeft;
                *stop = YES;
                break;

            case InvaderMovementDirectionDownThenRight:
                //6 - if the invaders are moving down then right, they already moved down so they should now move right.
                proposedMovementDirection = InvaderMovementDirectionRight;
                *stop = YES;
                break;
            default:
                break;
        }
    }];

     //7 - if the proposed invader movement direction is different than the current invader movement direction, update the current direction to the proposed direction
     if (proposedMovementDirection != self.invaderMovementDirection) {
         self.invaderMovementDirection = proposedMovementDirection;

     }
}



#pragma mark - Bullet Helpers

#pragma mark - User Tap Helpers

#pragma mark - HUD Helpers

#pragma mark - Physics Contact Helpers

#pragma mark - Game End Helpers

@end

© Stack Overflow or respective owner

Related posts about objective-c

Related posts about xcode