【完整APP】SpriteKit引擎開發iOS小遊戲之三(遊戲組件與遊戲場景的實現)

【遊戲組件的實現】

回顧最開始的內容不難想到,我們需要封裝一些遊戲組件以便於在各類場景中加載和管理。SpriteKit框架下我們只需要實現SKNode的子類,或者NSObject的子類來,最終把需要的節點以合適的時機通過addChild的形式加入到SKScene場景中。

  • 遊戲的地板
    地板部分是最簡單的。我們在外部不需要考慮它的生命週期,只要封裝一個創建函數即可。
  1. 首先我們通過一張自己的圖片初始化SKTexture類型的對象只要如下一行代碼:
SKTexture *terrainTexture = [SKTexture textureWithImageNamed:@"terrain"];
  1. 接着利用這個SKTexture類型實例,去初始化SKSpriteNode類型的對象。之後便可以設置這個對象的各個屬性,基本都是佈局相關。
SKSpriteNode *node1 = [SKSpriteNode spriteNodeWithTexture:terrainTexture];`
  1. 然後創建一個SKNode類型變量設置其物理屬性。最終給地板添加SKAciton使他由右向左的移動,達到動態的外觀。

地板組件的實現代碼如下:

#import "LJZTerrain.h"
#import "LJZConstants.h"

@implementation LJZTerrain

 + (void)addNewNodeTo:(SKNode *)parentNode withType:(NSInteger)type
{
    SKTexture *terrainTexture;
    if(type == 1){
        terrainTexture = [SKTexture textureWithImageNamed:@"terrain"];
    } else if(type == 2){
        terrainTexture = [SKTexture textureWithImageNamed:@"terrain_2"];
    } else {
        terrainTexture = [SKTexture textureWithImageNamed:@"terrain_3"];
    }
    
    SKSpriteNode *node1 = [SKSpriteNode spriteNodeWithTexture:terrainTexture];
    node1.anchorPoint = CGPointMake(0, 1);
    node1.position = CGPointMake(0, 0);
    SKSpriteNode *node2 = [SKSpriteNode spriteNodeWithTexture:terrainTexture];
    node2.anchorPoint = CGPointMake(0, 1);
    node2.position = CGPointMake(320, 0);
    
    CGSize size = CGSizeMake(640, 60);
    SKSpriteNode *terrain = [SKSpriteNode spriteNodeWithTexture:terrainTexture size:size];
    terrain.zPosition = 1;
    CGPoint location = CGPointMake(0.0f, 60);
    terrain.anchorPoint = CGPointMake(0, 1);
    terrain.position = location;
    [terrain addChild:node1];
    [terrain addChild:node2];
    [parentNode addChild:terrain];
    
    
    SKNode *terrainBody = [SKNode node];
    terrainBody.position = CGPointMake(160.0f, 35);;
    terrainBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(320, 20)];
    terrainBody.physicsBody.dynamic = NO;
    terrainBody.physicsBody.affectedByGravity = NO;
    terrainBody.physicsBody.collisionBitMask = 0;
    terrainBody.physicsBody.categoryBitMask = terrainType;
    terrainBody.physicsBody.contactTestBitMask = heroType;
    [parentNode addChild:terrainBody];
    
    [terrain runAction:[SKAction repeatActionForever:
                             [SKAction sequence:@[
                                                  [SKAction moveToX:-320 duration:5.0f],
                                                  [SKAction moveToX:0 duration:.0f]
                                                  ]]]
     ];
    
}
@end
  • 遊戲障礙的實現
  1. 不同遊戲難度的場景中,障礙的外觀會有所不同,並且出現頻率會不同,這裏使用的辦法是在場景中使用定時器控制障礙的創建,因此在障礙組件中實現創建方法。其.h文件如下
#import <Foundation/Foundation.h>
#import <SpriteKit/SpriteKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface LJZPipe : NSObject

+ (void)addNewNodeTo:(SKNode *)parentNode withType:(NSInteger)type;

@end

NS_ASSUME_NONNULL_END
  1. 障礙的添加有上下方兩部分、位置在一定範圍內隨機。
+ (void)addNewNodeTo:(SKNode *)parentNode withType:(NSInteger)type
{
    CGFloat offset = 660;
    CGFloat startY = 10.0f + (CGFloat)arc4random_uniform(4) * 70.0f;
    
    [parentNode addChild:[self createPipeAtY:(startY + offset) isTopPipe:YES withType:type]];
    [parentNode addChild:[self createPipeAtY:startY isTopPipe:NO withType:type]];
}
  1. 實現單側障礙的創建,根據圖片資源初始化SKSpriteNode對象,接着指定其屬性。並執行SKAction完成由右向左的移動障礙。完整的.m內容如下
#import "LJZPipe.h"
#import "LJZConstants.h"

#define ScreenWidth [UIScreen mainScreen].bounds.size.width
static const CGFloat finalPosition = -50;
static const CGFloat duration = 6.0;

@implementation LJZPipe

+ (void)addNewNodeTo:(SKNode *)parentNode withType:(NSInteger)type
{
    CGFloat offset = 660;
    CGFloat startY = 10.0f + (CGFloat)arc4random_uniform(4) * 70.0f;
    
    [parentNode addChild:[self createPipeAtY:(startY + offset) isTopPipe:YES withType:type]];
    [parentNode addChild:[self createPipeAtY:startY isTopPipe:NO withType:type]];
}

+ (id)createPipeAtY:(CGFloat)startY isTopPipe:(BOOL)isTopPipe withType:(NSInteger)type
{
    SKSpriteNode *pipeNode;
    if (type == 1){
        pipeNode = [SKSpriteNode spriteNodeWithImageNamed:@"pipe"];
    }else if(type == 2 ){
        pipeNode = [SKSpriteNode spriteNodeWithImageNamed:@"pipe_2"];
    }else{
        pipeNode = [SKSpriteNode spriteNodeWithImageNamed:@"pipe_3"];
    }
    pipeNode.position = CGPointMake(ScreenWidth + pipeNode.size.width, startY);
    pipeNode.yScale = (isTopPipe) ? 1.0f : -1.0f;
    pipeNode.zPosition = 0;
    pipeNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:pipeNode.size];
    pipeNode.physicsBody.dynamic = NO;
    pipeNode.physicsBody.collisionBitMask = heroType;
    pipeNode.physicsBody.categoryBitMask = pipeType;
    pipeNode.physicsBody.contactTestBitMask = heroType;
    [self animate:pipeNode];
    return pipeNode;
}

+ (void)animate:(SKNode *)node
{
    [node runAction:
      [SKAction sequence:@[
                           [SKAction moveToX:finalPosition duration:duration],
                           [SKAction removeFromParent]
                           ]
       ]
     ];
}

@end
  • 遊戲中的道具組件
  1. 隨機出現的道具也和障礙類似,由場景中的定時器控制。區別在於道具在角色觸碰時需要提前移除,所以額外提供一個移除實例的方法。遊戲道具有三種,每種對應的加分和道具外觀不同,這裏以LJZToolOne、LJZToolTwo、LJZToolThree三個類控制。以LJZToolOne類爲說明。以下的類的.h文件

#import <Foundation/Foundation.h>
#import <SpriteKit/SpriteKit.h>

@interface LJZToolOne : NSObject

- (void)addNewNodeTo:(SKNode *)parentNode;
- (void)FinishBitAnimate;

@end
  1. 在實現上與障礙組件並無很大區別,出現的位置也是Y軸一定範圍隨機。實現的具體.m文件如下
#import "LJZToolOne.h"
#import "LJZConstants.h"

#define ScreenWidth [UIScreen mainScreen].bounds.size.width
static const CGFloat finalPosition = -50;
static const CGFloat duration = 6.0;

@interface LJZToolOne()
@property (nonatomic, strong)SKSpriteNode *toolNode;
@end
@implementation LJZToolOne

- (void)addNewNodeTo:(SKNode *)parentNode;
{
    CGFloat startY = 100.0f + (CGFloat)arc4random_uniform(8) * 70.0f;
    [parentNode addChild:[self createPipeAtY:startY]];
}

- (id)createPipeAtY:(CGFloat)startY
{
    self.toolNode = [SKSpriteNode spriteNodeWithImageNamed:@"tool_100"];
    _toolNode.position = CGPointMake(ScreenWidth + _toolNode.size.width, startY);
    _toolNode.zPosition = 0;
    _toolNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_toolNode.size];
    _toolNode.physicsBody.dynamic = NO;
    _toolNode.physicsBody.collisionBitMask = heroType;
    _toolNode.physicsBody.categoryBitMask = toolTypeOne;
    _toolNode.physicsBody.contactTestBitMask = heroType;
    [self animate:_toolNode];
    return _toolNode;
}

- (void)animate:(SKNode *)node
{
    [node runAction:
      [SKAction sequence:@[
                           [SKAction moveToX:finalPosition duration:duration],
                           [SKAction removeFromParent]
                           ]
       ]
     ];
}

- (void)FinishBitAnimate
{
    [self.toolNode runAction:[SKAction removeFromParent]];
}


@end
  • 遊戲中的角色組件
  1. 遊戲組件主要提供了創建、更新狀態、上升、下落這些方法,菜單創建的角色不需要有物理特徵,因此區分了兩種創建方法。其頭文件內容如下:
#import <SpriteKit/SpriteKit.h>

@interface LJZHero : SKSpriteNode

+ (id)createNodeOn:(SKNode *)parent;
+ (id)createSpriteOn:(SKNode *)parent;
- (void)update;
- (void)goDown;
- (void)flap;

@end
  1. 動態的角色外觀,是由3個SKTexture組成的數組替換,使用SKAction的對應方法實現如下:
- (void)animate
{
    NSArray *animationFrames = @[
                                 [SKTexture textureWithImageNamed:@"hero1"],
                                 [SKTexture textureWithImageNamed:@"hero2"],
                                 [SKTexture textureWithImageNamed:@"hero3"],
                                 ];
    [self runAction:[SKAction repeatActionForever:
                     [SKAction animateWithTextures:animationFrames
                                      timePerFrame:0.1f
                                            resize:NO
                                           restore:YES]] withKey:@"flyingHero"];
}
  1. 創建角色類是SKSpriteNode子類,重寫了init方法並實現初始化方法如下:
- (id)init
{
    self = [super initWithImageNamed:@"hero1"];
    if (self) {
        self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:
                            CGSizeMake(self.size.width * .95f,
                                       self.size.height * .95f)];
        self.physicsBody.dynamic = YES;
        self.physicsBody.collisionBitMask = pipeType;
        self.physicsBody.categoryBitMask = heroType;
        
        [self animate];
    }
    return self;
}
+ (id)createNodeOn:(SKNode *)parent
{
    id hero = [LJZHero new];
    [parent addChild:hero];
    return hero;
}

+ (id)createSpriteOn:(SKNode *)parent
{
    SKNode *hero = [self createNodeOn:parent];
    hero.physicsBody = nil;
    return hero;
}

  1. 實現角色的動作效果,基於physicsBody屬性進行向量的操作,改變物理特徵。
- (void)update
{
    if (self.physicsBody.velocity.dy > 30.0) {
        self.zRotation = (CGFloat) M_PI/ 6.0f;
    } else if (self.physicsBody.velocity.dy < -100.0) {
        self.zRotation = -(CGFloat) M_PI/ 4.0f;
    } else {
        self.zRotation = 0.0f;
    }
}

- (void)goDown
{
    [self.physicsBody applyImpulse:CGVectorMake(0, -20)];
}

- (void)flap
{
    self.physicsBody.velocity = CGVectorMake(0, 0);
    [self.physicsBody applyImpulse:CGVectorMake(0, 10)];
}

【遊戲場景的實現】

  1. 遊戲場景類大概涉及到如下屬性:音樂播放器、用於音效播放的SKAction、分數展示的SKLabelNode對象、記錄分數的NSInteger類型變量、NSTimer類型的定時器用於控制障礙與道具的產生。
@interface LJZGameScene () <SKPhysicsContactDelegate>
@property (nonatomic, strong) AVAudioPlayer *bgmPlayer;
@property (nonatomic, strong) SKAction *getScoreSound;
@property (nonatomic, strong) SKAction *ggSound;
@property (nonatomic, strong) SKLabelNode *scoreLabel;
@property (nonatomic, assign) NSInteger score;

@property (nonatomic, strong) LJZHero *hero;
@property (nonatomic, strong) NSTimer *pipeTimer;
@property (nonatomic, strong) NSTimer *toolTimerOne;
@property (nonatomic, strong) NSTimer *toolTimerTwo;
@property (nonatomic, strong) NSTimer *toolTimerThree;
@property (nonatomic, strong) LJZToolOne *toolOfOne;
@property (nonatomic, strong) LJZToolTwo *toolOfTwo;
@property (nonatomic, strong) LJZToolThree *toolOfThree;
@end

  1. 一些資源初始化:應用bgm、完成資源的初始化、遊戲組件的創建。
- (void)didMoveToView:(SKView *)view
{
    [super didMoveToView:view];
    [self ApplyMusic];
    [self setup];
}

- (void)ApplyMusic
{
    NSString *bgmPath = [[NSBundle mainBundle] pathForResource:@"round1_bgm" ofType:@"mp3"];
    self.bgmPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:bgmPath] error:NULL];
    self.bgmPlayer.numberOfLoops = -1;
    [self.bgmPlayer play];
}

- (void)setup
{
    [self preloadSounds];
    [self createWorld];
    [self createScoreLabel];
    [self createHero];
    [self createTerrain];
    [self schedulePipe];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self scheduleTool];
    });
}
  1. 一些事件的處理:角色死亡、難度升級、遊戲結束。SKTransition用於場景的轉換。
- (void)die
{
    [self.pipeTimer invalidate];
    [self.toolTimerOne invalidate];
    [self.toolTimerTwo invalidate];
    [self.toolTimerThree invalidate];
    
    [self.bgmPlayer stop];
    self.bgmPlayer = nil;
    [self changeToResultSceneWithScore:self.score];
}

- (void)changeToResultSceneWithScore:(NSUInteger)gameScore
{
    LJZResultScene *resultScene = [[LJZResultScene alloc] initWithSize:self.size score:gameScore];
    SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionUp duration:1.0];
    [self.scene.view presentScene:resultScene transition:reveal];
}

- (void)changeToNextGameSceneWithScore:(NSUInteger)gameScore
{
    [self.pipeTimer invalidate];
    [self.toolTimerOne invalidate];
    [self.toolTimerTwo invalidate];
    [self.toolTimerThree invalidate];
    [self.bgmPlayer stop];
    self.bgmPlayer = nil;
    
    LJZGameSceneTwo *nextScene = [[LJZGameSceneTwo alloc] initWithSize:self.size score:gameScore];
    SKTransition *reveal = [SKTransition doorwayWithDuration:1.0];
    [self.scene.view presentScene:nextScene transition:reveal];
}
  1. 碰撞處理:角色可以與地面、障礙物、道具發生碰撞,就要進行碰撞的檢測與回調處理。檢測使用的是位運算的方式、還記得之前創建組件時都指定了它們的physicsBody屬性。physicsBody依然擁有它的屬性,collisionBitMaskcategoryBitMaskcontactTestBitMask。它們分別用與指定允許與哪些物理體發生碰撞、自己的物理體類別、可以碰撞的物理體。首先實現一個類別32位:
typedef NS_OPTIONS(NSUInteger, CollisionCategory) {
    heroType       = (1 << 0),
    terrainType    = (1 << 1),
    pipeType       = (1 << 2),
    toolTypeOne    = (1 << 3),
    toolTypeTwo    = (1 << 4),
    toolTypeThree  = (1 << 5)
};

接着實現場景遵守的SKPhysicsContactDelegate中的方法:contact參數表示兩個物理體之間的聯繫,通過或運算判斷是哪些物理體之間發生了碰撞。

- (void)didBeginContact:(SKPhysicsContact *)contact
{
    uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
    if (collision == (heroType | pipeType)) {
        [self.hero goDown];
        [self runAction:self.ggSound completion:^{
            [self die];
        }];
    } else if (collision == (heroType | terrainType)) {
        [self runAction:self.ggSound completion:^{
            [self die];
        }];
    } else if (collision == (heroType | toolTypeOne)) {
        [self.toolOfOne FinishBitAnimate];
        [self runAction:self.getScoreSound completion:^{
            [self upScore100];
        }];
    } else if (collision == (heroType | toolTypeTwo)) {
        [self.toolOfTwo FinishBitAnimate];
        [self runAction:self.getScoreSound completion:^{
            [self upScore500];
        }];
    } else if (collision == (heroType | toolTypeThree)) {
        [self.toolOfThree FinishBitAnimate];
        [self runAction:self.getScoreSound completion:^{
            [self upScore1000];
        }];
    }
}

完整的遊戲場景實現文件內容如下:

#import "LJZGameScene.h"
#import "LJZGameSceneTwo.h"
#import "LJZMenuScene.h"
#import "LJZResultScene.h"
#import "LJZConstants.h"
#import "LJZTerrain.h"
#import "LJZHero.h"
#import "LJZPipe.h"
#import "LJZToolOne.h"
#import "LJZToolTwo.h"
#import "LJZToolThree.h"
#import <AVFoundation/AVFoundation.h>

@interface LJZGameScene () <SKPhysicsContactDelegate>
@property (nonatomic, strong) AVAudioPlayer *bgmPlayer;
@property (nonatomic, strong) SKAction *getScoreSound;
@property (nonatomic, strong) SKAction *ggSound;
@property (nonatomic, strong) SKLabelNode *scoreLabel;
@property (nonatomic, assign) NSInteger score;

@property (nonatomic, strong) LJZHero *hero;
@property (nonatomic, strong) NSTimer *pipeTimer;
@property (nonatomic, strong) NSTimer *toolTimerOne;
@property (nonatomic, strong) NSTimer *toolTimerTwo;
@property (nonatomic, strong) NSTimer *toolTimerThree;
@property (nonatomic, strong) LJZToolOne *toolOfOne;
@property (nonatomic, strong) LJZToolTwo *toolOfTwo;
@property (nonatomic, strong) LJZToolThree *toolOfThree;
@end

@implementation LJZGameScene

#pragma mark - Scene Cycle

- (id)initWithSize:(CGSize)size
{
    if (self = [super initWithSize:size]) {
    }
    return self;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.hero flap];
}

- (void)update:(CFTimeInterval)currentTime
{
    _score += 1;
    if (_score > 2000){
        [self changeToNextGameSceneWithScore:_score];
    }
    [self renderScore];
    [self.hero update];
}

- (void)didMoveToView:(SKView *)view
{
    [super didMoveToView:view];
    [self ApplyMusic];
    [self setup];
}

#pragma mark - Creators
- (void)ApplyMusic
{
    NSString *bgmPath = [[NSBundle mainBundle] pathForResource:@"round1_bgm" ofType:@"mp3"];
    self.bgmPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:bgmPath] error:NULL];
    self.bgmPlayer.numberOfLoops = -1;
    [self.bgmPlayer play];
}

- (void)setup
{
    [self preloadSounds];
    [self createWorld];
    [self createScoreLabel];
    [self createHero];
    [self createTerrain];
    [self schedulePipe];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self scheduleTool];
    });
}

- (void)preloadSounds
{
    self.getScoreSound = [SKAction playSoundFileNamed:@"ding.mp3" waitForCompletion:YES];
    self.ggSound = [SKAction playSoundFileNamed:@"duang.mp3" waitForCompletion:YES];
}

- (void)createScoreLabel
{
    self.scoreLabel = [[SKLabelNode alloc] initWithFontNamed:@"Helvetica"];
    [self.scoreLabel setPosition:CGPointMake(self.size.width/2, self.size.height-100)];
    [self.scoreLabel setText:[NSString stringWithFormat:@"Score %ld",self.score]];
    self.scoreLabel.zPosition = 100;
    [self addChild:self.scoreLabel];
}

- (void)createWorld
{
    SKTexture *backgroundTexture = [SKTexture textureWithImageNamed:@"background"];
    SKSpriteNode *background = [SKSpriteNode spriteNodeWithTexture:backgroundTexture size:self.view.frame.size];
    background.position = (CGPoint) {CGRectGetMidX(self.view.frame), CGRectGetMidY(self.view.frame)};
    [self addChild:background];

    self.scaleMode = SKSceneScaleModeAspectFit;
    self.physicsWorld.contactDelegate = self;
    self.physicsWorld.gravity = CGVectorMake(0, -3);
}

- (void)createHero
{
    self.hero = [LJZHero createNodeOn:self];
    self.hero.position = CGPointMake(50.0f, 450.0f);
}

- (void)createTerrain
{
    [LJZTerrain addNewNodeTo:self withType:1];
}

#pragma mark - Timer Manager Methods

- (void)schedulePipe
{
    self.pipeTimer = [NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(addPipe:) userInfo:nil repeats:YES];
    [self addPipe:nil];
}

- (void)addPipe:(NSTimer *)timer
{
    [LJZPipe addNewNodeTo:self withType:1];
}

- (void)scheduleTool
{
    self.toolTimerOne = [NSTimer scheduledTimerWithTimeInterval:9.0 target:self selector:@selector(addToolone) userInfo:nil repeats:YES];
    self.toolTimerTwo = [NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:@selector(addToolTwo) userInfo:nil repeats:YES];
    self.toolTimerThree = [NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:@selector(addToolThree) userInfo:nil repeats:YES];
}

- (void)addToolone
{
    self.toolOfOne = [[LJZToolOne alloc] init];
    [self.toolOfOne addNewNodeTo:self];
}

- (void)addToolTwo
{
    self.toolOfTwo = [[LJZToolTwo alloc] init];
    [self.toolOfTwo addNewNodeTo:self];
}

- (void)addToolThree
{
    self.toolOfThree = [[LJZToolThree alloc] init];
    [self.toolOfThree addNewNodeTo:self];
}

- (void)upScore100
{
    self.score += 100;
}

- (void)upScore500
{
    self.score += 500;
}

- (void)upScore1000
{
    self.score += 1000;
}

#pragma mark - Call back methods

- (void)die
{
    [self.pipeTimer invalidate];
    [self.toolTimerOne invalidate];
    [self.toolTimerTwo invalidate];
    [self.toolTimerThree invalidate];
    
    [self.bgmPlayer stop];
    self.bgmPlayer = nil;
    [self changeToResultSceneWithScore:self.score];
}

- (void)changeToResultSceneWithScore:(NSUInteger)gameScore
{
    LJZResultScene *resultScene = [[LJZResultScene alloc] initWithSize:self.size score:gameScore];
    SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionUp duration:1.0];
    [self.scene.view presentScene:resultScene transition:reveal];
}

- (void)changeToNextGameSceneWithScore:(NSUInteger)gameScore
{
    [self.pipeTimer invalidate];
    [self.toolTimerOne invalidate];
    [self.toolTimerTwo invalidate];
    [self.toolTimerThree invalidate];
    [self.bgmPlayer stop];
    self.bgmPlayer = nil;
    
    LJZGameSceneTwo *nextScene = [[LJZGameSceneTwo alloc] initWithSize:self.size score:gameScore];
    SKTransition *reveal = [SKTransition doorwayWithDuration:1.0];
    [self.scene.view presentScene:nextScene transition:reveal];
}

- (void)renderScore
{
    [self.scoreLabel setText:[NSString stringWithFormat:@"%ld", self.score]];
}

#pragma mark - SKPhysicsContactDelegate

- (void)didBeginContact:(SKPhysicsContact *)contact
{
    uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
    if (collision == (heroType | pipeType)) {
        [self.hero goDown];
        [self runAction:self.ggSound completion:^{
            [self die];
        }];
    } else if (collision == (heroType | terrainType)) {
        [self runAction:self.ggSound completion:^{
            [self die];
        }];
    } else if (collision == (heroType | toolTypeOne)) {
        [self.toolOfOne FinishBitAnimate];
        [self runAction:self.getScoreSound completion:^{
            [self upScore100];
        }];
    } else if (collision == (heroType | toolTypeTwo)) {
        [self.toolOfTwo FinishBitAnimate];
        [self runAction:self.getScoreSound completion:^{
            [self upScore500];
        }];
    } else if (collision == (heroType | toolTypeThree)) {
        [self.toolOfThree FinishBitAnimate];
        [self runAction:self.getScoreSound completion:^{
            [self upScore1000];
        }];
    }
}

- (void)didEndContact:(SKPhysicsContact *)contact
{
    
}

@end

【結束場景的實現】

遊戲場景不同難度的話,創建世界的方法參數不同、控制障礙與道具的定時器設置有所不同。它們都會在失敗時導向結束場景。

  1. 結束場景是由一個分數、兩個按鈕組成。場景創建的時候需要知道當前的分數。頭文件內容如下
#import <SpriteKit/SpriteKit.h>

@interface LJZResultScene : SKScene
- (instancetype)initWithSize:(CGSize)size score:(NSInteger)score;

@end
  1. 內容上實現了三個SKLabelNode的外觀與點擊事件,上傳成績接觸到了網絡部分在之後介紹。這部分之間看實現代碼吧:
#import "LJZResultScene.h"
#import "LJZMenuScene.h"
#import "LJZToast.h"
#import "LJZLoading.h"

@interface LJZResultScene()
@property (nonatomic, strong)SKLabelNode *resultLabel;
@property (nonatomic, strong)SKLabelNode *retryLabel;
@property (nonatomic, strong)SKLabelNode *uploadLabel;
@property (nonatomic, assign)NSInteger resultScore;
@end

@implementation LJZResultScene

- (instancetype)initWithSize:(CGSize)size score:(NSInteger)score
{
    if (self = [super initWithSize:size]) {
        self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
        [self SetUpResultLabel:score];
        [self SetUpRetryLabel];
        [self SetUpUploadLabel];
    }
    return self;
}
#pragma mark - SetUp UI

- (void)SetUpResultLabel:(NSUInteger)score
{
    self.resultScore = score;
    self.resultLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
    _resultLabel.text = [NSString stringWithFormat:@"score is %lu",score];
    _resultLabel.fontSize = 30;
    _resultLabel.fontColor = [SKColor blackColor];
    _resultLabel.position = CGPointMake(CGRectGetMidX(self.frame),
                                   CGRectGetMidY(self.frame));
    [self addChild:_resultLabel];
}

- (void)SetUpRetryLabel
{
    self.retryLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
    _retryLabel.text = @"Try again";
    _retryLabel.fontSize = 20;
    _retryLabel.fontColor = [SKColor blueColor];
    _retryLabel.position = CGPointMake(_resultLabel.position.x, _resultLabel.position.y * 0.8);
    _retryLabel.name = @"retryLabel";
    [self addChild:_retryLabel];
}

- (void)SetUpUploadLabel
{
    self.uploadLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
    _uploadLabel.text = @"Upload your score";
    _uploadLabel.fontSize = 20;
    _uploadLabel.fontColor = [SKColor blueColor];
    _uploadLabel.position = CGPointMake(_resultLabel.position.x, _resultLabel.position.y * 0.6);
    _uploadLabel.name = @"uploadLabel";
    [self addChild:_uploadLabel];
}

#pragma mark - handleTouch
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches) {
        CGPoint touchLocation = [touch locationInNode:self];
        SKNode *node = [self nodeAtPoint:touchLocation];
        if ([node.name isEqualToString:@"retryLabel"]) {
            [self changeToMenuScene];
        }
        else if([node.name isEqualToString:@"uploadLabel"]){
            [self handleUpload];
        }
    }
}

- (void)changeToMenuScene
{
    SKTransition *reveal = [SKTransition fadeWithDuration:.5f];
    LJZMenuScene *menu = [[LJZMenuScene alloc] initWithSize: self.size];
    [self.scene.view presentScene: menu transition: reveal];
}

- (void)handleUpload
{
	//上傳成績處理
}
@end

本文主要介紹了遊戲組件的實現以及其他場景的實現。下一節內容介紹服務端的搭建,在服務端之後將介紹客戶端的網絡請求以及其他細節優化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章