美文网首页
SpriteKit 实现暗黑侠客小游戏

SpriteKit 实现暗黑侠客小游戏

作者: 时光啊混蛋_97boy | 来源:发表于2021-01-12 18:33 被阅读0次

原创:知识探索型文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 一、准备工作

    • 1、什么是SpriteKit
    • 2、建立SpriteKit项目
    • 3、创建Scene场景
    • 4、添加游戏角色图片
    • 5、放入游戏背景音乐
  • 二、进行展示

  • 三、在ResultScene里实现大侠请重新来过界面

    • 1、初始化方法
    • 2、创建视图方法
    • 3、在重新来过Label上添加点击效果
    • 4、重新进入游戏界面
  • 四、在GameScene里实现游戏界面

    • 1、准备工作
    • 2、初始化方法
    • 3、添加游戏里的玩家角色
    • 4、添加游戏里的小怪兽
    • 5、添加背景音乐
    • 6、向手指点击方向释放子弹
    • 7、子弹击中后删除子弹和小怪兽
    • 8、切换到游戏结束场景
  • Demo

  • 参考文献


一、准备工作

1、什么是SpriteKit

SpriteKit是一个功能强大的基于2D精灵的框架,适用于Apple的游戏开发。SpriteKit使用SKView作为场景,即你在屏幕上看到的视觉效果,对于熟悉制作iOS App的人来说,它类似于Storyboard


2、建立SpriteKit项目

选中Game 使用SpriteKit框架

3、创建Scene场景


4、添加游戏角色图片


5、放入游戏背景音乐


二、进行展示

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 配置视图
    SKView *skView = (SKView *)self.view;
    // 显示FPS
    skView.showsFPS = YES;
    // 显示节点数量
    skView.showsNodeCount = YES;
    
    // 创建场景
    SKScene *scene = [GameScene sceneWithSize:skView.bounds.size];
    // 展现场景
    [skView presentScene:scene];
    // 展示时适配屏幕大小
    scene.scaleMode = SKSceneScaleModeAspectFill;
}

三、在ResultScene里实现大侠请重新来过界面

1、初始化方法

- (instancetype)initWithSize:(CGSize)size won:(BOOL)won
{
    if (self = [super initWithSize:size])
    {
        self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0f blue:1.0f alpha:1.0f];
        NSLog(@"视图的宽:%f,高:%f",size.width,size.height);
        
        [self createSubviews:won];
    }
    
    return self;
}

2、创建视图方法

- (void)createSubviews:(BOOL)won
{
}
将结果label显示到屏幕中央
// 开辟空间,设置字体
SKLabelNode *resultLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
// 设置text内容
resultLabel.text = won ? @"大侠真是天赋异禀" : @"大侠请重新来过";
// 设置字体大小
resultLabel.fontSize = 30;
// 设置字体颜色
resultLabel.fontColor = [SKColor blackColor];
// 设置位置为屏幕中央
resultLabel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
// 添加子节点
[self addChild:resultLabel];
添加重新来过Label
SKLabelNode *retryLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
retryLabel.text = @"重出江湖";
retryLabel.fontSize = 20;
retryLabel.fontColor = [SKColor blueColor];
retryLabel.position = CGPointMake(resultLabel.position.x, resultLabel.position.y * 0.8);
// 给节点命名,方便找到节点
retryLabel.name = @"retryLabel";
[self addChild:retryLabel];

3、在重新来过Label上添加点击效果

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches)
    {
        // 获取当前点击位置
        CGPoint touchuLocation = [touch locationInNode:self];
        // 根据点击的位置获取点击到的节点,如果有则返回对应节点,否则返回空
        SKNode *node = [self nodeAtPoint:touchuLocation];
        // 判断当前点击的节点是否是retryLabel
        if ([node.name isEqualToString:@"retryLabel"])
        {
            // 重新进入游戏界面
            [self changeToGameScene];
        }
    }
}

4、重新进入游戏界面

- (void)changeToGameScene
{
    GameScene *gameScene = [GameScene sceneWithSize:self.size];
    SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:1.0];
    [self.scene.view presentScene:gameScene transition:reveal];
}

四、在GameScene里实现游戏界面

1、准备工作

导入框架
#import <AVFoundation/AVFoundation.h>
#import <Foundation/NSObjCRuntime.h>
私有属性
@property (nonatomic, strong) NSMutableArray *monsters;// 怪物数组
@property (nonatomic, strong) NSMutableArray *projectiles;// 弹药数组
@property (nonatomic, strong) AVAudioPlayer *bgmPlayer;// 背景播放Player
@property(nonatomic,strong) SKAction *projectileSoundEffectAction;// 子弹声音Action
@property(nonatomic,assign) int monstersDestroyed;// 消灭怪物个数

2、初始化方法

- (instancetype)initWithSize:(CGSize)size
{
}
❶ 给Scene设置背景颜色,默认颜色是黑色

SKColor只是一个define定义而已,在iOS平台下被定义为UIColor,在Mac平台下被定义为NSColor。在SpriteKit开发时,尽量用SK开头的对应的UI类,这样可以统一代码而减少跨iOS和Mac平台的成本。类似的定义还有SKView,它在iOS下是UIView的子类,在Mac下是NSView的子类。

self.backgroundColor = [SKColor colorWithRed:1.0f green:1.0 blue:1.0f alpha:1.0f];
❷ 添加游戏里的玩家角色
[self addPlayer:size];
❸ 间隔1秒后创建下一个小怪兽,如此反复,使游戏里有多个小怪兽
SKAction *actionAddMonster = [SKAction runBlock:^{ [self addMonster]; }];
SKAction *actionWaitNextMonster = [SKAction waitForDuration:1];
SKAction *repeatAddMonsterAction = [SKAction repeatActionForever:[SKAction sequence:@[actionAddMonster,actionWaitNextMonster]]];
[self runAction:repeatAddMonsterAction];
❹ 添加背景音乐
[self addBGM];

3、添加游戏里的玩家角色

- (void)addPlayer:(CGSize)size
{
}
❶ 初始化一个精灵

实际上一个SKSpriteNode中包含了贴图(SKTexture对象)、颜色、尺寸等参数。同过这个简便方法我们可以读取图片,生成SKTexture,并设定精灵尺寸和图片大小一致。

SKSpriteNode * player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];
❷ 设置精灵玩家的位置

SpriteKit中的坐标系和其他OpenGL游戏坐标系是一致的,屏幕左下角为(0,0)。不过需要注意的是不论是横屏还是竖屏游戏,view的尺寸都是按照竖屏进行计算的,即对于iPhone来说,在这里传入的sizewidth是320,height是480或者568,而不会因为横屏而发生交换。因此在开发时,请千万不要使用绝对数值来进行位置设定及计算(否则你会死的很难看啊)。

player.position = CGPointMake(player.size.width/2, size.height/2);
❸ 将精灵玩家添加当前的场景中
[self addChild:player];

4、添加游戏里的小怪兽

添加怪物和添加主角的方式是一样的,同样生成精灵,设定位置,加到Scene中。区别就是怪物会移动,并且会每隔一段时间随机出现。

❶ 初始化并添加一个小怪兽
SKSpriteNode *monster = [SKSpriteNode spriteNodeWithImageNamed:@"monster"];
[self addChild:monster];
❷ 计算小怪兽的出生点

出生点的Y坐标指的就是怪兽开始移动时候的位置的Y值。由于怪兽从右侧屏幕外随机的高度处进入屏幕,需要指定最小/最大的Y值,然后在这个范围内随机选取一个Y值作为出生点。为了保证怪物图像都在屏幕范围内,出生点的X坐标需要大于屏幕宽度。

CGSize windowSize = self.size;
int minY = monster.size.height/2;
int maxY = windowSize.height - monster.size.height/2;
int rangeY = maxY - minY;// Y值的范围
int actualY = (arc4random() % rangeY)+minY;// 将范围内产生的随机Y值作为实际Y值
monster.position = CGPointMake(windowSize.width + monster.size.width/2, actualY);// 设定出生点恰好在屏幕右侧外面一点
❸ 设置小怪兽的移动速度

速度如果是匀速,游戏会显得非常死板,所以将速度设置为随机值。

int minDuration = 2.0;
int maxDuration = 4.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
❹ 创建小怪兽移动的动作
  • SKAction可以操作SKNode精灵进行移动/消失/旋转
  • location:节点新位置的坐标
  • duration:动画的持续时间,以秒为单位
// 将精灵在持续时间内移动到结束点(直线横穿屏幕)
SKAction *actionMove = [SKAction moveTo:CGPointMake(-monster.size.width/2, actualY) duration:actualDuration];
❺ 从屏幕中删除移动完成的小怪兽并宣告游戏失败

runAction方法可以让精灵执行某个操作。

SKAction *actionMoveDone = [SKAction runBlock:^{
    // 从父节点视图中移除
    [monster removeFromParent];
    // 从monsters数组中移除
    [self.monsters removeObject:monster];
    // 当小怪兽顺利移动到屏幕左侧消失掉的时候就意味着大侠的子弹没有击中它,所以要显示游戏失败的界面
    [self changeToResultSceneWithWon:NO];
}];
❻ 按照数组中的顺序调用Action

我们要做的是先将精灵移动到结束点,当移动结束后再移除精灵。由于需要按照顺序执行,所以创建了一个sequence,这样可以按照顺序调用多个action

[monster runAction:[SKAction sequence:@[actionMove,actionMoveDone]]];
❼ 将小怪兽添加到数组中
[self.monsters addObject:monster];

5、添加背景音乐

- (void)addBGM
{
    NSString *bgmPath = [[NSBundle mainBundle] pathForResource:@"background-music-aac" ofType:@"caf"];
    self.bgmPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:bgmPath] error:nil];
    self.bgmPlayer.numberOfLoops = -1;// 无限循环
    [self.bgmPlayer play];
}

6、向手指点击方向释放子弹

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches)
    {
        ...
    }
}
❶ 创建子弹节点并设置其初始位置
SKSpriteNode *projectile = [SKSpriteNode spriteNodeWithImageNamed:@"projectile.png"];
CGSize windowSize = self.size;
projectile.position = CGPointMake(projectile.size.width/2, windowSize.height/2);
❷ 获取场景中的手指触摸位置并计算和子弹节点位置之间的偏移量

如果偏移量的x值为负数则表明子弹的射出方向向左,自然不可能打到小怪兽,无视掉此子弹则直接返回。

CGPoint location = [touch locationInNode:self];
CGPoint offset = CGPointMake(location.x - projectile.position.x, location.y - projectile.position.y);
if (offset.x <= 0) return;
❸ 将子弹节点添加到场景中并计算实际的x和y的位置

实际上子弹在X轴方向上会射穿整个屏幕,所以子弹在X轴上的实际位置为屏幕宽度+子弹宽度的一半。

int realX = windowSize.width + projectile.size.width/2;// 实际上X的位置
float ratio = offset.y/offset.x;// 比率
int realY = realX * ratio + projectile.position.y;// 实际上Y的位置
CGPoint realDest = CGPointMake(realX, realY);// 获取到实际位置
❹ 获取子弹移动耗时

获取子弹结束位置与侠客位置在x轴和y轴上的距离,构成直角三角形使用勾股定理计算两个点之间的距离,再除以速度获取到移动耗时。

int offRealX = realX - projectile.position.x;
int offRealY = realY - projectile.position.y;
float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));// 路径长度
float velocity = self.size.width/1;// 速度为1秒内穿越整个屏幕
float realMoveDuration = length/velocity;// 时间=路程/速度
❺ 为子弹添加移动动作
SKAction *moveAction = [SKAction moveTo:realDest duration:realMoveDuration];
❻ 将移动和子弹声音动画组合使播放音效的action和移动精灵的action同时执行
SKAction *projectileCastAction = [SKAction group:@[moveAction,self.projectileSoundEffectAction]];
❼ 执行动画
[projectile runAction:projectileCastAction completion:^{
    // 动画执行完毕后从父节点中移除子弹
    [projectile removeFromParent];
    // 动画执行完毕后将子弹从子弹数组中移除
    [self.projectiles removeObject:projectile];
}];

// 往子弹数组中添加刚刚产生的子弹
[self.projectiles addObject:projectile];

7、子弹击中后删除子弹和小怪兽

// 系统自动不断进行调用的方法
- (void)update:(NSTimeInterval)currentTime
{
}

如果子弹和怪兽的位置区域发生了重叠则说明二者相撞子弹成功击中了怪物,于是需要将此子弹和怪兽均从场景和数组中移除掉。

移除子弹
❶ 定义将要删除的子弹组成的数组
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
❷ 遍历子弹数组

如果将要删除的怪物数量大于0,说明有子弹击中了,则需要将该子弹加入到子弹删除数组。

for (SKSpriteNode *projectile in self.projectiles)
{
    ......
    if (monsterToDelete.count > 0)
    {
        [projectilesToDelete addObject:projectile];
    }
}
❸ 遍历将要删除的子弹组成的数组
for (SKSpriteNode *projectile in projectilesToDelete)
{
    // 将子弹从子弹数组删除
    [self.projectiles removeObject:projectile];
    // 将该子弹从父节点中移除(子弹消失)
    [projectile removeFromParent];
}
移除怪兽
❶ 定义将要删除的怪兽组成的数组
NSMutableArray *monsterToDelete = [[NSMutableArray alloc] init];
❷ 遍历怪物数组判断子弹是否和怪物相交(碰撞检测)
for (SKSpriteNode *monster in self.monsters)
{
    if (CGRectIntersectsRect(projectile.frame, monster.frame))
    {
        [monsterToDelete addObject:monster];
    }
}
❸ 遍历将要删除的怪兽组成的数组
for (SKSpriteNode *monster in monsterToDelete)
{
    // 从怪物数组删除该怪物
    [self.monsters removeObject:monster];
    // 将该怪物从父节点中移除(怪物消失)
    [monster removeFromParent];
    ...
}
❹ 记录战绩,如果战绩大于30则切换到成功的界面
self.monstersDestroyed++;
if (self.monstersDestroyed >= 30)
{
    [self changeToResultSceneWithWon:YES];
}

8、切换到游戏结束场景

- (void)changeToResultSceneWithWon:(BOOL)won
{
}
❶ 停止背景音乐
[self.bgmPlayer stop];
self.bgmPlayer = nil;
❷ 切换到结果场景
ResultScene *resultScene = [[ResultScene alloc] initWithSize:self.size won:won];
❸ 设置转场动画
SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionUp duration:1.0];
❹ 切换场景
[self.scene.view presentScene:resultScene transition:reveal];

Demo

Demo在我的Github上,欢迎下载。
GameDemo

参考文献

相关文章

网友评论

      本文标题:SpriteKit 实现暗黑侠客小游戏

      本文链接:https://www.haomeiwen.com/subject/tfleaktx.html