How to speed up a Sprite Kit game after every addi

2020-02-14 17:09发布

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.

2条回答
时光不老,我们不散
2楼-- · 2020-02-14 17:57

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.

查看更多
太酷不给撩
3楼-- · 2020-02-14 18:01

if you want to update the speed at regular intervals, you can use the modulus operator - take the remainder after dividing by your interval - if that equals zero, take action. The reason you find the game speeding up completely is that the next time through the update function, it's still on the right points level, and it just increments the speed - you could add another variable to track if you're ready for an increment

// as part of class declaration
var bReadyForIncrement : Bool = true
let pointIncrement : Int = 4  // or whatever you need


// 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) 
{
    if bReadyForIncrement
    {
       pointSpeed++;
       bReadyForIncrement = false // set this to true wherever you add points
    }
}
查看更多
登录 后发表回答