ios游戏开发 Sprite Kit教程:初学者 2

article/2025/5/20 0:00:02

注:本文译自Sprite Kit Tutorial for Beginners

目录

  • Sprite Kit的优点和缺点
  • Sprite Kit vs Cocos2D-iPhone vs Cocos2D-X vs Unity
  • Hello, Sprite Kit!
  • 横屏显示
  • 移动怪兽
  • 发射炮弹
  • 碰撞检测: 概述
  • 碰撞检测: 实现
  • 收尾
  • 何去何从?

横屏显示

首先,在Project Navigator中单击SpriteKitSimpleGame工程以打开target设置,选中SpriteKitSimpleGame target。然后在Deployment Info中,不要勾选Portrait,只选中LandscapeLandscape Right,如下所示:

编译并运行工程,会看到如下运行画面:

下面我们试着添加一个忍者(ninja)。

首先,下载此工程的资源文件,并将其拖拽到Xcode工程中。确保勾选上“Copy items into destination group’s folder (if needed)”SpriteKitSimpleGame target

接着,打开MyScene.m,并用下面的内容替换之:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "MyScene.h"

// 1
@interface MyScene ()
@property (nonatomic) SKSpriteNode * player;
@end

@implementation MyScene

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

        // 2
        NSLog(@"Size: %@", NSStringFromCGSize(size));

        // 3
        self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];

        // 4
        self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];
        self.player.position = CGPointMake(100, 100);
        [self addChild:self.player];

    }
    return self;
}

@end

我们来看看上面的代码。

  1. 为了给player(例如忍者)声明一个私有变量,在这里创建了一个私有的interface,之后可以把这个私有变量添加到场景中。
  2. 在这里打印出了场景的size,至于什么原因很快你就会看到了。
  3. 在Sprite Kit中设置一个场景的背景色非常简单——只需要设置backgroundColor属性,在这里将其设置位白色。
  4. 在Sprite Kit场景中添加一个精灵同样非常简单,只需要使用spriteNodeWithImageNamed方法,并把一副图片的名称传递进去就可以创建一个精灵。接着设置一下精灵的位置,然后调用addChild方法将该精灵添加到场景中。在代码中将忍者的位置设置为(100, 100),该位置是从屏幕的左下角到右上角计算的。

编译并运行,看看效果如何…

呀!屏幕是白色的,并没有看到忍者。这是为什么呢?你可能在想设计之初就是这样的,实际上这里有一个问题。

如果你观察一下控制台输出的内容,会看到如下内容

1
SpriteKitSimpleGame[3139:907] Size: {320, 568}

可能你会认为场景的宽度是320,高度则是568——实际上刚好相反!

我们来看看具体发生了什么:定位到ViewController.mviewDidLoad方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad
{
    [super viewDidLoad];

    // Configure the view.
    SKView * skView = (SKView *)self.view;
    skView.showsFPS = YES;
    skView.showsNodeCount = YES;

    // Create and configure the scene.
    SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
    scene.scaleMode = SKSceneScaleModeAspectFill;

    // Present the scene.
    [skView presentScene:scene];
}

上面的代码中利用view的边界size创建了场景。不过请注意,当viewDidLoad被调用的时候,在这之前view已经被添加到view层次结构中了,因此它还没有响应出布局的改变。所以view的边界可能还不正确,进而在viewDidLoad中并不是开启场景的最佳时机。

提醒:要想了解更多相关内容,请看由Rob Mayoff带来的最佳解释。

解决方法就是将开启场景代码的过程再靠后一点。用下面的代码替换viewDidLoad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];

    // Configure the view.
    SKView * skView = (SKView *)self.view;
    if (!skView.scene) {
      skView.showsFPS = YES;
      skView.showsNodeCount = YES;

      // Create and configure the scene.
      SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
      scene.scaleMode = SKSceneScaleModeAspectFill;

      // Present the scene.
      [skView presentScene:scene];
    }
}

编译并运行程序,可以看到,忍者已经显示在屏幕中了!

如上图所示,可以看到坐标系已经正确了,如果想要把忍者的位置设置为其中间靠左,那么在MyScene.m中用下面的代码来替换设置忍者位置相关的代码:

1
self.player.position = CGPointMake(self.player.size.width/2, self.frame.size.height/2);

移动怪兽

接下来,我们希望在场景中添加一些怪兽,让忍者进行攻击。为了让游戏更有趣一点,希望怪兽能够移动——否则没有太大的挑战!OK,我们就在屏幕的右边,离屏的方式创建怪兽,并给怪兽设置一个动作:告诉它们往左边移动。

将下面这个方法添加到MyScene.m中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- (void)addMonster {

    // Create sprite
    SKSpriteNode * monster = [SKSpriteNode spriteNodeWithImageNamed:@"monster"];

    // Determine where to spawn the monster along the Y axis
    int minY = monster.size.height / 2;
    int maxY = self.frame.size.height - monster.size.height / 2;
    int rangeY = maxY - minY;
    int actualY = (arc4random() % rangeY) + minY;

    // Create the monster slightly off-screen along the right edge,
    // and along a random position along the Y axis as calculated above
    monster.position = CGPointMake(self.frame.size.width + monster.size.width/2, actualY);
    [self addChild:monster];

    // Determine speed of the monster
    int minDuration = 2.0;
    int maxDuration = 4.0;
    int rangeDuration = maxDuration - minDuration;
    int actualDuration = (arc4random() % rangeDuration) + minDuration;

    // Create the actions
    SKAction * actionMove = [SKAction moveTo:CGPointMake(-monster.size.width/2, actualY) duration:actualDuration];
    SKAction * actionMoveDone = [SKAction removeFromParent];
    [monster runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];

}

在上面,我尽量让代码看起来容易理解。首先是通过一个简单的计算,确定怪兽出现的位置,并将该位置设置给怪兽,然后将其添加到场景中。

接着是添加动作(actions)。跟Cocos2D一样,Sprite Kit同样提供了很多方便的内置动作,例如移动动作、旋转动作、淡入淡出动作、动画动作等。在这里我们只需要在怪兽上使用3中动作即可:

  • moveTo:duration:使用这个动作可以把怪兽从屏幕外边移动到左边。移动过程中,我们可以指定移动持续的时间,上面的代码中,指定为2-4秒之间的一个随机数。
  • removeFromParent:在Sprite Kit中,可以使用该方法,方便的将某个node从parent中移除,能有效的从场景中删除某个对象。此处,将不再需要显示的怪兽从场景中移除。这个功能非常的重要,否则当有源源不断的怪兽出现在场景中时,会耗尽设备的所有资源。
  • sequence:sequence动作可以一次性就把一系列动作串联起来按照一定顺序执行。通过该方法我们就能让moveTo:方法先执行,当完成之后,在执行removeFromParent:动作。

最后,我们需要做的事情就是调用上面这个方法addMonster,以实际的创建出怪兽!为了更加好玩,下面我们来让怪兽随着时间持续的出现在屏幕中。

在Sprite Kit中,并不能像Cocos2D一样,可以配置每隔X秒就回调一下update方法。同样也不支持将从上次更新到目前为止的时间差传入方法中。(非常令人吃惊!)。

不过,我们可以通过一小段代码来仿造这种行为。首先在MyScene.m的private interface中添加如下属性:

1
2
@property (nonatomic) NSTimeInterval lastSpawnTimeInterval;
@property (nonatomic) NSTimeInterval lastUpdateTimeInterval;

通过lastSpawnTimeInterval可以记录着最近出现怪兽时的时间,而lastUpdateTimeInterval可以记录着上次更新时的时间。

接着,我们写一个方法,该方法在画面每一帧更新的时候都会被调用。记住,该方法不会被自动调用——需要另外写一个方法来调用它:

1
2
3
4
5
6
7
8
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {

    self.lastSpawnTimeInterval += timeSinceLast;
    if (self.lastSpawnTimeInterval > 1) {
        self.lastSpawnTimeInterval = 0;
        [self addMonster];
    }
}

上面的代码中简单的将上次更新(update调用)的时间追加到self.lastSpawnTimeInterval中。一旦该时间大于1秒,就在场景中新增一个怪兽,并将lastSpawnTimeInterval重置。

最后,添加如下方法来调用上面的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)update:(NSTimeInterval)currentTime {
    // Handle time delta.
    // If we drop below 60fps, we still want everything to move the same distance.
    CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
    self.lastUpdateTimeInterval = currentTime;
    if (timeSinceLast > 1) { // more than a second since last update
        timeSinceLast = 1.0 / 60.0;
        self.lastUpdateTimeInterval = currentTime;
    }

    [self updateWithTimeSinceLastUpdate:timeSinceLast];

}

Sprite Kit在显示每帧时都会调用上面的update:方法。

上面的代码其实是来自苹果提供的Adventure示例中。该方法会传入当前的时间,在其中,会做一些计算,以确定出上一帧更新的时间。注意,在代码中做了一些合理性的检查,以避免从上一帧更新到现在已经过去了大量时间,并且将间隔重置为1/60秒,避免出现奇怪的行为。

现在编译并运行程序,可以看到许多怪兽从左边移动到屏幕右边并消失。

发射炮弹

现在我们开始给忍者添加一些动作,首先从发射炮弹开始!实际上有多种方法来实现炮弹的发射,不过,在这里要实现的方法时当用户tap屏幕时,从忍者的方位到tap的方位发射一颗炮弹。

由于本文是针对初级开发者,所以在这里我使用moveTo:动作来实现,不过这需要做一点点的数学运算——因为moveTo:方法需要指定炮弹的目的地,但是又不能直接使用touch point(因为touch point仅仅代表需要发射的方向)。实际上我们需要让炮弹穿过touch point,直到炮弹在屏幕中消失。

如下图,演示了上面的相关内容:

如图所示,我们可以通过origin point到touch point得到一个小的三角形。我们要做的就是根据这个小三角形的比例创建出一个大的三角形——而你知道你想要的一个端点是离开屏幕的地方。

为了做这个计算,如果有一些基本的矢量方法可供调用(例如矢量的加减法),那么会非常有帮助,但很不幸的时Sprite Kit并没有提供相关方法,所以,我们必须自己实现。

不过很幸运的时这非常容易实现。将下面的方法添加到文件的顶部(implementation之前):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static inline CGPoint rwAdd(CGPoint a, CGPoint b) {
    return CGPointMake(a.x + b.x, a.y + b.y);
}

static inline CGPoint rwSub(CGPoint a, CGPoint b) {
    return CGPointMake(a.x - b.x, a.y - b.y);
}

static inline CGPoint rwMult(CGPoint a, float b) {
    return CGPointMake(a.x * b, a.y * b);
}

static inline float rwLength(CGPoint a) {
    return sqrtf(a.x * a.x + a.y * a.y);
}

// Makes a vector have a length of 1
static inline CGPoint rwNormalize(CGPoint a) {
    float length = rwLength(a);
    return CGPointMake(a.x / length, a.y / length);
}

上面实现了一些标准的矢量函数。如果你看得不是太明白,请看这里关于矢量方法的解释。

接着,在文件中添加一个新的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    // 1 - Choose one of the touches to work with
    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInNode:self];

    // 2 - Set up initial location of projectile
    SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:@"projectile"];
    projectile.position = self.player.position;

    // 3- Determine offset of location to projectile
    CGPoint offset = rwSub(location, projectile.position);

    // 4 - Bail out if you are shooting down or backwards
    if (offset.x <= 0) return;

    // 5 - OK to add now - we've double checked position
    [self addChild:projectile];

    // 6 - Get the direction of where to shoot
    CGPoint direction = rwNormalize(offset);

    // 7 - Make it shoot far enough to be guaranteed off screen
    CGPoint shootAmount = rwMult(direction, 1000);

    // 8 - Add the shoot amount to the current position       
    CGPoint realDest = rwAdd(shootAmount, projectile.position);

    // 9 - Create the actions
    float velocity = 480.0/1.0;
    float realMoveDuration = self.size.width / velocity;
    SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
    SKAction * actionMoveDone = [SKAction removeFromParent];
    [projectile runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];

}

上面的代码中做了很多事情,我们来详细看看。

  1. SpriteKit为我们做了很棒的一件事情就是它提供了一个UITouch的category,该category中有locationInNode:previousLocationInNode:方法。这两个方法可以帮助我们定位到在SKNode内部坐标系中touch的坐标位置。这样一来,我们就可以寻得到在场景坐标系中touch的位置。
  2. 然后创建一个炮弹,并将其放置到忍者的地方,以当做其开始位置。注意,现在还没有将其添加到场景中,因为还需要先做一个合理性的检查——该游戏不允许忍者向后发射。
  3. 接着利用touch位置减去炮弹的当前位置,这样就能获得一个从当前位置到touch位置的矢量。
  4. 如果X值小于0,就意味着忍者将要向后发射,由于在这里的游戏中是不允许的(真实中的忍者是不回头的!),所以就return。
  5. 否则,将可以将炮弹添加到场景中。
  6. 调用方法rwNormalize,将offset转换为一个单位矢量(长度为1)。这样做可以让在相同方向上,根据确定的长度来构建一个矢量更加容易(因为1 * length = length)。
  7. 在单位矢量的方向上乘以1000。为什么是1000呢?因为着肯定足够超过屏幕边缘了 :]
  8. 将上一步中计算得到的位置与炮弹的位置相加,以获得炮弹最终结束的位置。
  9. 最后,参照之前构建怪物时的方法,创建moveTo:removeFromParent:两个actions。

编译并运行程序,现在忍者可以发射炮弹了!

……Sprite Kit教程:初学者 2 结束……


http://chatgpt.dhexx.cn/article/dHYe7SNj.shtml

相关文章

【游戏开发教程】Unity iOS平台接入微信SDK,实现微信登录等功能(教程 | 流程讲解)

文章目录 一、前言二、流程1、申请开发者账号2、创建应用3、下载SDK4、导入到Unity中5、编写Objective-C代码5.1、CustomAppController.mm5.2、WXApiManager.h5.3、WXApiManager.mm5.4、注册回调对象5.5、封装初始化接口5.6、封装登录接口5.7、其他接口封装 6、XCodeAPI7、关于…

iOS开发知识概览

1、学习ios开发的知识概览&#xff0c;经常会认为ios开发会学那些知识&#xff0c;如何把断断续续的知识连接起来&#xff0c; 这就让你有了全局的了解&#xff0c;如何去计划和安排自己的学习计划&#xff0c;有了全局的知识体系。 原文来自&#xff1a;https://github.com/sh…

IOS 开发之逆向分析

在逆向过程中很多时候需要分析 APP 和 Web 端数据交互的内容那么最简单的方式即是抓包网络分析&#xff0c;而使用 Charles、Tcpdump 也是逆袭分析最基本的手段。本文以 Charles 为例来介绍网络相关的内容。 Charles 是在 Mac 下常用的网络封包截取工具&#xff0c;在做 移动开…

ios游戏开发

知识系统 英文教程网站 http://www.csdn.net/article/2012-12-20/2813035-game-dev-guide 开发类库 http://www.csdn.net/article/2012-11-07/2811587-pop-ios-dev-library

芒果iOS开发之Swift教程01-Swift基础

【主要内容】 1.关于Swift 2.Hello World 3.常量和变量 4.类型标注 5.常量和变量的命名 6. 输出常量和变量 7.注释 8.分号 一、关于Swift 苹果在2014年WWDC&#xff08;苹果开发者大会&#xff09;发布了Swift&#xff0c;用于编写iOS&#xff0c;Mac OS X和watchOS…

ios教程,用pc开发ios游戏

原文是Thomas Henshell对手机游戏Catch the Monkey的开发总结&#xff0c;由Lyra翻译。 传智播客今年6月开始ios培训&#xff08;http://ios.itcast.cn&#xff09;的课程&#xff1b;相对于一些应用&#xff0c;我本人对ios游戏开发更感兴趣&#xff0c;这些开发总结并不拘泥…

RPG游戏开发基础教程

RPG游戏开发基础教程 第一步 下载RPG Maker 开发工具包 1.RPG Maker 是什么?RPG Maker 是由Enterbrain公司推出的RPG制作工具。 中文译名为RPG制作大师。 熟悉的人喜欢简称为RM。 根据发行版本的不同,RM在国内流行的版本有4款: 2.附上资源下载地址:点击进入网盘下载 3.关于…

ios开发快速入门教程

1.高级C语言、C语言 C语言是iOS开发的语言(Objective-C)基础&#xff0c;在iOS开发培训的课程中也会涉及到。iOS开发培训需要强调的是并不是要同学们都要精通C/C&#xff0c;而是要掌握iOS开发中要用的的C/C的核心内容。 2.Objective-C语言 Objective-C是iOS开发的标准语言&…

苹果游戏开发教程之如何使用 SpriteKit 和 GameplayKit 制作你的街机手机游戏

项目运行效果 什么是GameplayKit GameplayKit 是由 Apple 开发的框架,在 iOS 9 和 macOS X.11 中引入,它提供了许多类型游戏中常见的基础设施。 它让您专注于游戏玩法和游戏规则,以最大限度地减少意大利面条式代码的编写,并且只需要基于 Objective-C 或 Swift 的界面。 使…

ios 手游SDK 开发教程

前言&#xff1a; 各位同学大家好 &#xff0c;有一段时间没有见面了。 具体多久我也不清楚了&#xff0c; 最近在学习iOS 手游sdk 的开发 所以就想着写完 写一份教程分享给大家&#xff0c; 本人之前一直都是做安卓开发的 安卓 app和安卓手游SDK 开的都会。 需要用到的三方库…

数据分析:大数据时代的必备技能之EXCEL

文章目录 数据分析价值与数据分析思维一、数据分析概述1.什么是数据2.理解数据3.什么是数据分析3. 数据分析的步骤 二、数据分析价值1.定义和组成2. 商业价值案例--豆浆 三、数据分析思维1. 核心思维方式(1)、结构化分析思维(2)、公式化分析思维(3)、业务化分析思维 数据预处理…

#C数据结构与算法# 绪论 算法与大O时间复杂度表示法(附例题)

一 算法基本概念与特性。 1.解决问题的五个步骤 由此我们可以看出良好的解决问题离就不开算法。 2.什么是算法 算法是指在解决问题时&#xff0c;按照某种机械的步骤一定可以得到问题的结果&#xff08;有的问题有解&#xff0c;有的没有&#xff09;的处理过程。算法是对解…

云计算期末速成大法

笔记仅自用&#xff0c;杠勿cue我 1. 绪论 4V特征&#xff1a;Volume&#xff08;规模大&#xff09;&#xff0c;Variety&#xff08;种类杂&#xff09;&#xff0c;Velocity&#xff08;变化快&#xff09;&#xff0c;Value&#xff08;价值密度小&#xff09; 从抽样到全…

简单分析几十个游戏案例

文章目录 一、 介绍二、 影响游戏体验的要点三、 游戏爆火的秘诀1.解读5个关键因素2.把握玩游戏的两种经典心理3.分析几款爆款游戏Qq农场植物大战僵尸水果忍者召唤神龙羊了个羊 4.值得游戏公司学习的经验5.未来游戏面对的诸多挑战 四、 几十款游戏的多方面分析FC红白游戏机十二…

软考高级-系统分析师-案例分析-系统设计

系分-案例分析-系统设计 结构化设计SD内聚&#xff08;高内聚低耦合&#xff09;耦合 业务流程建模IDEF&#xff08;建模仿真&#xff09; 面向对象的设计OOD设计原则设计模式分类 人机界面设计架构设计Zachman 架构框架Zachman 架构框架&#xff08;案例&#xff09; 面向服务…

系统分析师【系统规划案例分析汇总】

系统规划 项目选择和确定 &#xff08;1&#xff09;选择有核心价值的项目 &#xff08;2&#xff09;评估所选择的项目 &#xff08;3&#xff09;项目优先级排序 &#xff08;4&#xff09;评估项目的多种实施方式 &#xff08;5&#xff09;平衡地选择合适的方案 可行…

数据分析师常用的商业模型

数据分析少不了商业分析思维&#xff0c;以及对业务的理解。很多时候觉得思维不够健全&#xff0c;或者分析没有思路&#xff0c;其实都可以借助思维模型的学习来不足&#xff0c;来加速分析的成功。 一、波特五种竞争力模型 波特五力模型是企业制定竞争战略时常用的战略分析…

障碍度如何分析?

通常在综合评价后&#xff0c;比如计算得到准则层和指标层的分别权重之后&#xff08;指标权重体系构建后&#xff09;&#xff0c;为了找到‘主要障碍因子’&#xff0c;此时可使用‘障碍度模型’&#xff08;obstacle degree&#xff09;进一步研究&#xff0c;以便进行障碍度…

大数据分析师技能图谱详解

全球的数据量正在以每18个月翻一倍的惊人速度增长,世界正在高速数字化,大数据堪比石油,如何掘金大数据是所有个人、企业和国家的机遇和挑战。中国是人才大国,能理解和应用大数据的创新人才更是稀缺资源。大数据分析应用已经渗透到我们生活的方方面面。 随着大数据在国内的…

大数据分析案例-基于决策树算法构建银行客户流失预测模型

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 喜欢大数据分析项目的小伙伴,希望可以多多支持该系列的其他文章 大数据分析案例合集…