Search code examples
iosobjective-csprite-kit2d-games

How to speed up a Sprite Kit game after every additional 50 points


I am making a 2D game with Sprite Kit and I need to add a method to speed up the game after every 50 points.

I worked out a method that adds the speed as wanted, but I have to copy, paste and adjust an if-statement in the update method for every additional 50 points.

Another problem with my solution is that I have to give my points an increment right after speeding it up, because if not the game speeds up completely, though I don't know why.

Here is my solution so far:

// inside the update method
// points are added when an enemy is destroyed
// pointSpeed is the float variable that is used to change positions

if (points == 4) {
    pointSpeed++;
    points++;
}
if (points == 8) {
    pointSpeed++;
    points++;
}
if (points == 12) {
    pointSpeed++;
    points++;
}

I would like to implement this feature without being forced to do all increments manually and without the game speeding up without points increments.


Solution

  • Swift way

    You can use didSet property observer to change the game speed based on the current score:

    import SpriteKit
    
    class GameScene: SKScene {
    
        var score:Int = 0 {
    
            didSet{
    
                if score % 10 == 0 {
                   gameSpeed += 0.5
                }
    
                label.text = "Score : \(score), Speed : \(gameSpeed)"
            }
    
        }
    
        var gameSpeed:Double = 1.0
    
        var label:SKLabelNode = SKLabelNode(fontNamed: "ArialMT")
    
        override func didMoveToView(view: SKView) {
            /* Setup your scene here */
    
            label.text = "Score : \(score), Speed : \(gameSpeed)"
            label.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidX(frame))
            label.fontSize = 18
    
            addChild(label)
    
        }
    
        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    
            score++
        }
    
    }
    

    From the docs:

    didSet is called immediately after the new value is stored.

    So, immediately after the new score is set, you should update the gameSpeed variable.

    Objective-C way

    In Objective-C there is no such a thing which can provide the exact same behaviour like Swift's didSet and willSet property observers. But there are another ways, for example you can override a score's setter, like this:

    #import "GameScene.h"
    
    @interface GameScene()
    
    @property(nonatomic, strong) SKLabelNode *label;
    
    @property(nonatomic, assign) NSUInteger score;
    
    @property(nonatomic, assign) double gameSpeed;
    
    @end
    
    @implementation GameScene
    
    -(instancetype)initWithCoder:(NSCoder *)aDecoder{
    
    NSLog(@"Scene's initWithSize: called");
    
    if(self = [super initWithCoder:aDecoder]){
    
            /*
    
             Instance variables in Obj-C should be used only while initialization, deallocation or when overriding accessor methods.
             Otherwise, you should use accessor methods (eg. through properties).
    
            */
    
            _gameSpeed  = 1;
            _score      = 0;
    
           NSLog(@"Variables initialized successfully");
        }
    
        return self;
    }
    
    -(void)didMoveToView:(SKView *)view {
    
        self.label      = [SKLabelNode labelNodeWithFontNamed:@"ArialMT"];
        self.label.fontSize = 18;
        self.label.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
        self.label.text = [NSString stringWithFormat:@"Score : %lu, Speed %f", self.score, self.gameSpeed];
        [self addChild:self.label];
    
    }
    
    //Overriding an actual setter, so when score is changed, the game speed will change accordingly
    -(void)setScore:(NSUInteger)score{
    
        /*
    
         Make sure that you don't do assigment like this, self.score = score, but rather _score = score
         because self.score = somevalue, is an actual setter call - [self setScore:score]; and it will create an infinite recursive call.
    
         */
        _score          = score;
    
    
        if(_score % 10 == 0){
            self.gameSpeed += 0.5;
        }
    
        // Here, it is safe to to write something like self.score, because you call the getter.
    
        self.label.text = [NSString stringWithFormat:@"Score : %lu, Speed %f", self.score, self.gameSpeed];
    
    }
    
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
        self.score++;
    }
    
    @end
    

    Or, I guess, you can do the same using KVO, but for your example because of simplicity, you can just override a setter. Or maybe you could just make a custom method which will increase the score, handle the gameSpeed's updating logic, and update the speed if necessary:

    //Whenever the player scores, you will do this
    [self updateScore: score];
    

    So, you will have an additional method definition, which must be called when player scores, in compare to this (assuming that score's setter is overridden):

    self.score = someScore; 
    

    It's really up to you.

    Hope this make sense and it helps!

    EDIT:

    I've changed the initWithSize: to initWithCoder, because initWithSize is not called when the scene is loaded from sks, which is probably how you are doing it, due to fact that in Xcode 7 scene is loaded by default from .sks file.