【完整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 
#import 

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 
#import 

@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 

@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 

@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 

@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

本文主要介绍了游戏组件的实现以及其他场景的实现。下一节内容介绍服务端的搭建,在服务端之后将介绍客户端的网络请求以及其他细节优化。

你可能感兴趣的:(小项目)