iOS 7 Sprite Kit游戏——探险
利用Sprite工具包(Kit)工具包轻松打造令人信服的、 高性能的游戏,iOS 和 OS X 使用相同的面向对象的原则、 设计模式以及您使用生成其他应用程序的和Objective-C语言语言。为了证明你可以完成用Sprite工具包(Kit)工具包,我们创建了完整的探险游戏:
这段代码: 通过对探险 Xcode 项目,概述介绍和引导。通过这个文件,你会看到我们如何使用Sprite工具包(Kit)套装节点、行动和物理,建立一个关卡中,你扮演一个勇敢的英雄走过的森林迷宫的墙壁。有几个不同的人物和基本的精灵,弥补游戏,包括你的英雄,一路上你会遇到邪恶的妖精洞穴产卵,更何况俱乐部挥舞着首领在年底的水平。
你从此代码的介绍中,将会学到什么?
本文档旨在一起探险 Xcode 项目,给你上下文和附加说明的代码示例通过在工作时读取。
(1) "项目的快速之旅"
我们建立了使用的是单一的 Xcode 项目针对 iOS 和 OS X,这意味着我们可以在两个平台之间共享代码和资产的探险。将这两个目标组合成一个项目也使得很容易在 OS X 上的游戏在测试 iPhone 模拟器,在 iOS 设备上,只需通过选择之前建立的有关目标。
有 17 类用来表示不同的元素在探险,包括现场、 动画的角色、 静态树和洞穴,人工智能,控制敌人如何追你通过迷宫的英雄。此外,你会发现几个平台特定的类和资源,代表游戏,以及大量的共享的艺术和视觉效果资产的简单容器。
(2) "构建世界"
在推出,我们预加载播放探险,所以,我们不需要在玩游戏 (这可能导致令人讨厌的暂停或丢弃帧) 期间从磁盘读取所需的所有美术资源。加载资源后,我们在不同的元素构成了一级,包括背景瓷砖、 物理机构的迷宫墙,产卵妖精、 水平的首领和各地的水平的静态树的洞穴中添加。
1. "保持最新"
我们已经加载的所有资源并添加元素到现场后,其余的为游戏工作大多是在更新循环中发生了。为每个帧,Sprite工具包(Kit)工具包现场通过几个步骤按顺序,包括评估行动和模拟物理之前它将呈现提出视图中的内容, 将自动运行。我们绑到此循环来执行任务,如更新的每个人物角色、 更新敌人 AI,和移动照相机为中心的世界里英雄的各个阶段。我们需要移动照相机,每次我们更新树木和洞穴的视差偏移的量。
(3) "控制人物角色"
在探险的英雄人物角色是由你控制的。当在 OS X 上玩游戏,你控制键盘 ; 从英雄在 iOS,你触摸屏幕来指明您要移动的英雄。小妖精们和首领人物角色有人工智能,这导致它们追逐和攻击你,当你在一个预定义的距离内。
(4) "动画人物"
英雄,小妖精和级首领是所有动画,即使在空闲时。每个这些人物角色有不同的序列动画帧用于站立、 行走、 攻击、 受到攻击,和死。动画是以各种方式等由人工智慧和物体之间的碰撞口授的用户输入,操作触发的。
(5) "处理碰撞"
每个探险人物角色都有一个相应的物理机构,检测到碰撞与其他人物角色的迷宫,墙壁或射弹。在游戏中的物理行为的一些 — — 例如,阻止穿墙 — — 将自动处理Sprite工具包(Kit)工具包 ;我们需要做的就是配置上物理机构的位掩码。
一些碰撞触发其他操作。例如,当一个英雄子弹头与地精相碰撞,它启动那小妖精死亡序列并增加你的分数。对于这些序列,我们依靠Sprite工具包(Kit)工具包物理委托机制 — — 每次重要的碰撞发生时现场接收指示涉及的不同机构的回调。
阅读本文件之前,你需要知道什么?
建设一个与Sprite工具包(Kit)工具包游戏涉及所有相同的工具和技术构建使用目标 C 和可可碰其他应用程序时,您使用。例如,探险使用演示图板 iOS 和 OS X,一个 MainMenu.xib 文件,以创建一个简单的应用程序界面,包含该视图,其中显示的游戏内容。如果你不已经为 iOS 和 OS X 的应用程序开发好的理解,您应该阅读开始开发 iOS 应用程序今天或开始开发 Mac 应用程序今天。
若要获得最大的此文档和探险示例项目,至少阅读早期章节的Sprite工具包(Kit)工具包编程指南。你会获取Sprite工具包(Kit)工具包框架的一般理解,并了解用于在屏幕上显示信息的基本元素 — — 例如视图、 场景、 节点、 行动和物理机构,所有这一切中使用的探险。
第1章 快速浏览项目
探险是完全使用 Sprite 工具包框架而构建一个单机别游戏。不必维护多个项目,我们创建了探险使用单个Xcode 项目,目标是 iOS 和 OS X,这样,所有的游戏特定类这两个平台之间共享。除了共享游戏代码,每个目标都特定于平台的资源 (演示图板和 xib 文件) 和类负责设置准备好显示探险场景的基本应用程序容器。
第一,玩游戏,得到的一切是相生相克的想法。下一步,阅读这一章,了解我们为您遇到的角色使用的术语的第一节。当你准备深入研究该项目以查看如何的一切工作时,继续此快速教程,其中介绍 Xcode 项目的重要部分,并说明进入建筑背后的探险类层次结构的决定。
1.1 探险中的人物角色
当你玩探险,你可以选择两种不同的英雄人物,例如勇士或弓箭手,如图1-1所示的两个人物角色,一个勇士抛出一锤,一个弓箭手射出箭。
图1-1 勇士和弓箭手
当你行走各地的迷宫,你碰上像图1-2所示的妖精。一个地精会自动寻找和追逐你的英雄人物,直到你杀了它与弹丸。
图1-2 妖精
最后,当你到达年底的水平,您会遇到关卡首领的性格,如图2-4所示。关卡首领不从它的位置移动,直到你的英雄附近,然后它追逐和攻击你。正如你可能想到,受关卡首领的减少您的健康。杀死关卡首领,完成了水平,结束了比赛。
图1-3 关卡首领
虽然他们不是技术上角色,探险还设有树木左右的水平,如在图2-5所示。
图1-4 妖精洞穴
树木构造层叠在彼此的顶部从多个影像。当你走动的水平和摄像机移动到按照你的性格,你会发现,这些图像层以不同的速度移动,造成的视差效果,使世界成为一个三维质量。
一些树木发出个别叶节点来模拟落叶,增加了运动感,将世界带入生活。树木也淡出时,会自动的英雄是他们下面,你可以看到你要去哪里。
妖精洞穴中还内置多个视差图像层,再移动照相机移动时,以不同的速率。
图1-5 树
1.2 探险中类的层次结构
创建类时的探险,我们试图利用继承,尽量减少代码重复,而同时保持层次结构相对简单,所以您可以快速找出一切相生相克之间取得平衡。我们故意考虑共享或泛型行为抽象类,以便很容易地将游戏在将来扩展与您自己的附加功能。
图 2-1 显示共享的所有游戏类的层次结构。蓝框表示抽象类,旨在创建子类 ;tan 的框表示,我们直接在运行时进行实例化的具体类。
图1-6 游戏的类层次结构
注: 我们已经遵循最佳做法的命名用三个字母前缀的探险中的每个类"APA,"代表苹果探险"。
1.2.1 场景
在探险,转化为一个单一的场景在Sprite工具包(Kit)套件条款中,只有一个关卡,但我们已经实施拆分为两个类。APAMultiplayerLayeredCharacterScene 类是 SKScene 的一个泛型子类,并实现了将所需的任何场景在游戏中的行为。如果你想要扩展的探险与另一个关卡,您将创建此抽象场景类的子类。
APAMultiplayerLayeredCharacterScene 跟踪在游戏中,玩家并处理用户的输入来控制英雄。它还会生成空节点 (节点树) 来表示不同的层数在游戏中基本树。这些层口述子画面的绘制顺序。例如,下面的角色,始终绘制地面瓷砖和叶面总是出现高于一切。有关节点树的详细信息,请参阅"A 节点树定义什么会出现在现场"Sprite工具包(Kit)工具包编程指南中。
APAMultiplayerLayeredCharacterScene 的具体子类 — — APAAdventureScene 类 — — 是负责创建水平特定的内容。除了预加载必要图像、 排放国和动画的帧,此类是负责确定的水平,包括地面瓷砖、 撞墙、 角色和像洞穴和树木 ; 静态元素内的所有魍魉初始放置中详细描述了这一过程在"构建空间"。APAAdventureScene 是也在现场内, 碰撞的物理委托中所述"处理冲突"
每次通过更新循环 (每帧的一次调用)APAAdventureScene 更新现场,以及层偏移量的视差子画面中的所有角色。要保持内存和 CPU 使用量降到最低,粒子发射器是禁用,删除从现场,如果他们是太远了从默认播放器的英雄,看到的和英雄获取足够近时再次添加。更新循环中详细讨论了在"保持向上到日期"
1.2.2 精灵
在探险中的角色和树类都是 APAParallaxSprite 的最终后裔。此类继承自 SKSpriteNode,但添加可选的视差行为使用的树木和小妖精洞。
顾名思义,APATree 类表示关卡中的一棵树。此类中添加一个方法,updateAlphaWithScene:,由现场一次每个框架调用。我们使用此方法更新Sprite工具包(Kit)的 alpha 值,使树几乎透明,每当它下面是一个英雄。
1. 角色
在探险中的所有其他子画面继承 APACharacter 类,该类是负责所有基本角色运动和动画。此类还将额外shadowBlob 节点添加到现场来表示一个角色的阴影,并控制角色的健康,触发死亡序列,如果健康降到零。
在运动上的详细信息,请参见"控制角色";有关详细信息动画,请参阅"设置动画效果的角色"。
2. 英雄
APAHeroCharacter 类将常见的基本行为添加到战士和弓箭手的英雄人物。这种行为包括配置粒子发射器以显示损坏,当英雄被杀,报警现场对象和发射弹丸时触发火警的行动。APAHeroCharacter 类有负责提供一个合适的子弹头的两个子类 — — APAWarrior,抛出一把锤子和 APAArcher,射一箭。
3. 敌人
(妖精、 地精的洞穴和级关卡首领),场景中的敌人全部从APAEnemyCharacter 类中继承。此类通过添加一个 APAArtificialIntelligence 对象,使其人工智能 (AI) 的这些角色的单一情报属性扩展的 APACharacter类。
APAArtificialIntelligence 基类只需跟踪的相关的敌人,以及由 AI 子类用于指当前英雄目标目标属性。APAEnemyCharacter 更新 AI 对象每次角色更新 (每帧)。
两个类,APAGoblin 和 APABoss,初始化实例的 APAChaseAI 类,该类用于跟踪的最接近的英雄,以确定它是否是在追逐或攻击的范围之内的位置的智能的财产权。最后但不是最少,APACave 类使用 APASpawnAI 类来确定如何经常地精洞穴应生成新的妖精,再根据接近英雄角色的接近。
执行的选择: 不的所有角色都是 SKSpriteNode 的子类中,我们可以改为使用模型背后的每个角色 (NSObject子类) 类。现场实例将使用这些模型类基本的 SKSpriteNode 实例,可以在场景中显示的每个角色进行操作。我们选择了移动每个Sprite工具包(Kit)Sprite工具包(Kit)节点子类里面的逻辑,以便每个角色可以确定它自己的行为。这意味着现场类简单得多,并且在每个角色的基础上的游戏逻辑划分。
1.3 Xcode项目
跨多个组,使它易于浏览的所有不同文件通过划分了类和 Xcode 项目中的资源。所有游戏特定的类都是在探险乐园-共享组,与相关的现场、 子画面、 AI,最后是一些通用的图形使用整个游戏的实用程序类中又包含组中。
提示: 要快速概览在项目中的文件结构,请按住 Option 键并单击组三角形,以扩大它和它的所有子组。
我们在对相关行为组方法的每个类中使用了 #pragma 标记指示。这意味着您可以使用跳转栏中,一个类实现快速大纲在图 2-7 所示。
图1-7 源文件的代码布局
当你想要在不同的设备上测试的探险时,你可以切换两个目标 — — iOS 和 OS X — — 通过使用计划下拉菜单图 2-1 所示。
1.3.1 类和资源
具体到 iOS 目标的文件是在探险-iOS 组。应用程序的用户界面,从演示图板文件,生成包含一个单个视图控制器,是APAViewController 类的一个实例。视图控制器内容视图包含相同的项目作为 OS X 窗口: SKView对象、 图像视图、 活动指示灯和两个按钮。
APAViewController 类负责加载场景的资源和处理的用户界面元素,如中所述"构建空间"。
1.3.2 艺术和特效效果资源
探险包含大量的图像、 声音、 纹理地图集和粒子排放国。这些资产资产集团内,所载,OS X 与 iOS 版本的游戏之间共享。如果展开这一组,你会发现这些文件按类型组织到子群。
4. 纹理地图集
探险使用纹理地图集对于所有纹理,包括人物角色动画帧,组成的背景以及使用的元素,如树木、 洞穴和射弹的图像的瓷砖。我们把这些图像放在.atlas 文件夹 ;在编译时,Xcode 成纹理地图集生成图像的每个的文件夹。
当生成纹理地图集包含至少一个图像文件,具有多个联想里面,显示在图 2-9。相应的.plist 文件描述偏移、 大小和旋转的此文件内的每个元素以便Sprite工具包(Kit)工具包可以在运行时绘制只是相关的纹理。
图1-8 纹理地图集图像
它是使用纹理地图集比使用多个单独的文件,因为Sprite工具包(Kit)工具包可以绘制分享对 GPU 的单一呈现调用同一 atlas 的对象更多有效。例如,树木和洞穴所有使用环境地图集,这意味着他们可以全部绘制在同一时间从纹理。如果纹理在单独的文件,Sprite工具包(Kit)工具包会使一个呈现给每个组使用每个纹理对象的GPU 打电话。
5. 粒子释放
Xcode 提供粒子发射器编辑器,它允许您创建和调整Sprite工具包(Kit)工具包粒子发射器使用可视化的界面,在图 2-10 所示
图1-9 粒子发射器存档
第2章 空间(World)的构建及更新处理
在探险,只有一个关卡,但它充满了各式各样的精灵,包括树木、 产卵妖精、 燃烧的火把和首领角色的洞穴。应用程序发布时,我们加载所需的现场的所有共享的资源,然后通过添加背景瓷砖、 精灵和碰撞墙中构建空间。图 3-1 显示了此过程的概要。
图1-1 创建空间
1.1 探险资产(assets)的异步加载
1.1.1 创建场景
Adventure: APAAppDelegateOSX.m -applicationDidFinishLaunching:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
... (Show the progress indicator)
[APAAdventureScene loadSceneAssetsWithCompletionHandler:^{
APAAdventureScene *scene = [APAAdventureScene
sceneWithSize:CGSizeMake(1024, 768)];
[self.skView presentScene:scene];
... (Hide the progress indicator)
... (Show the Warrior/Archer buttons)
[scene configureGameControllers];
}];
... (Show debug info)
}
完成处理程序调用 configureGameControllers 来检查有任何已配置游戏控制器的设备,如所述在"支持外部游戏控制器。
1.1.2 加载场景资产
LoadSceneAssetsWithCompletionHandler: 方法由 APAMultiplayerLayeredCharacterSceneclass 来实现。它使用 dispatch_async() 来调用 loadSceneAssets 方法中背景 ;资产加载完成时,它调用完成处理程序返回的主线程上。
APAAdventureScene 来加载场景特定资产,包括预先配置的粒子发射器是重写 loadSceneAssets 方法。
Adventure: APAAdventureScene.m +loadSceneAssets
+ (void)loadSceneAssets {
sSharedProjectileSparkEmitter = [SKEmitterNode
apa_emitterNodeWithEmitterNamed:@"ProjectileSplat"];
... (Load other emitters and sprites)
[self loadWorldTiles];
[APACave loadSharedAssets];
... (Load other characters' assets)
}
最佳实践: 与类中的所有名称的探险,一样我们一直遵循命名上框架类的分类方法的最佳做法。为避免潜在的碰撞与苹果可能会在未来将添加到 SKEmitterNode 的任何方法,我们命名方法 apa_emitterNodeWithEmitterNamed: 而不是只是 emitterNodeWithEmitterNamed:。
加载后粒子发射器,该方法将调用 loadWorldTiles。现场有很大,方形背景 (4096 x 4096 像素为单位)。而不是问了 GPU 的加载此整个图像,当我们只需要绘制背景的一小部分时,我们分为纹理的瓷砖32 x 32、 网格,如图 3-2 所示,从而导致 1024年各个图块。
图1-2 背景图像的划分
我们可以把这些瓦片作为单独的文件,但这将意味着从磁盘加载 1024年文件,导致大量的 GPU 渲染电话作为每个拼贴不得不单独绘制。相反,我们可以将图像存储在项目中的Tiles.atlas 文件夹中。在编译时,Xcode使用此文件夹生成纹理地图集,分裂的 1024年瓷砖跨 5 图像,图 3-3 所示。并尽量减少我们要加载的文件的数目,这也意味着可见部分的背景绘制只使用一个或两个 GPU 呈现的调用。
图1-3 五个背景瓷片纹理成的地图集图像
LoadWorldTiles 方法创建一个 SKSpriteNode 实例的每个图像,并设置的相对位置,以便瓷砖时的布局正确地添加到空间后,在现场初始化过程。瓷砖被加载后,loadSharedSceneAssets 方法由现场使用每个人物角色类上调用 loadSharedAssets 方法。
1.1.3 加载共享的人物角色资产
探险中的人物角色类的大多数在现场内实例化多次 — — 例如,有许多的小妖精和洞穴,和尽可能多英雄作为那里是球员。为了尽量减少占用的内存量,每个类的人物角色共享一组的资产。对于探险,我们假定这些人物角色会使用任何额外的场景所以资产是加载一次,驻留在内存中,只要游戏正在运行,您可能会添加到游戏中,可使用。
每个人物角色类的 loadSharedAssets 方法使用 dispatch_once 块,确保只有一次执行的代码。APABoss类实现此方法,以加载动画帧用于进行动画处理关卡首领空闲时,行走,攻击,命中,或死亡 ;它还将加载Xcode 创建粒子发射器和 flash 着色时要显示的损害英雄弹击中首领的行动。
Adventure: APABoss.m +loadSharedAssets
+ (void)loadSharedAssets {
[super loadSharedAssets];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sSharedIdleAnimationFrames = APALoadFramesFromAtlas(@"Boss_Idle",
@"boss_idle", kBossIdleFrames);
... (Load other animation frames)
sSharedDamageEmitter = [SKEmitterNode
apa_emitterNodeWithEmitterNamed:@"BossDamage"];
sSharedDamageAction = [SKAction sequence:@[
[SKAction colorizeWithColor:[SKColor whiteColor]
colorBlendFactor:1.0 duration:0.0],
[SKAction waitForDuration:0.5],
[SKAction colorizeWithColorBlendFactor:0.0 duration:0.1]]];
}
}。
APALoadFramesFromAtlas() 函数是在 APAGraphicsUtilities.m 中定义的。它从磁盘加载纹理地图集、 提取的命名和编号的纹理,并返回一个 SKTexture 实例的数组。
提示: 要迅速跳到 Xcode 中的符号的定义,按住命令键并单击该符号名称,如方法、 函数、 变量、 类名称或定义的值。
最共享的资产探险类中用一种简单的访问器方法返回的值存储在静态变量中。
Adventure: APABoss.m -damageEmitter
static SKEmitterNode *sSharedDamageEmitter = nil;
- (SKEmitterNode *)damageEmitter {
return sSharedDamageEmitter;
}
对于sSharedDamageEmitter而言,在文件范围内的静态变量是指同一变量的类的所有实例之间共享,但无法访问此文件之外。资产加载后,它停留在内存中为使用由任何未来的类的实例。
其他人物角色类实现 loadSharedAssets 方法中一样作为APABoss 加载其动画帧、 排放国和共同的行动。
1.2 构建场景
所有的资产都加载后,该应用程序委托创建 APAAdventureScene 类的一个实例。此类继承的APAMultiplayerLayeredCharacterScene,这是负责配置环境的任何场景内的探险。
如果你看一下 initWithSize: APAMultiplayerLayeredCharacterScene 的方法,你会看到它初始化一个数组来跟踪的球员。
Adventure: APAMultiplayerLayeredCharacterScene.m -initWithSize:
- (instancetype)initWithSize:(CGSize)size {
self = [super initWithSize:size];
if (self) {
_players = [[NSMutableArray alloc] initWithCapacity:kNumPlayers];
_defaultPlayer = [[APAPlayer alloc] init];
[(NSMutableArray *)_players addObject:_defaultPlayer];
for (int i = 1; i < kNumPlayers; i++) {
[(NSMutableArray *)_players addObject:[NSNull null]];
}
1.2.1 配置探险场景
在配置 APAMultiplayerLayeredCharacterScene 后,初始化落到 APAAdventureScene。
APAdventureScene 类 initWithSize: 方法设置了不同的阵列用于跟踪在场景中的不同元素,然后创建水平和树地图数据结构。
Adventure: APAAdventureScene.m -initWithSize:
- (id)initWithSize:(CGSize)size {
self = [super initWithSize:size];
if (self) {
_heroes = [[NSMutableArray alloc] init];
... (Create the other arrays)
_levelMap = APACreateDataMap(@"map_collision.png");
_treeMap = APACreateDataMap(@"map_foliage.png");
当我们开始将子画面添加到空间后,使用的关卡和树的地图。InitWithSize: 方法的调用 buildWorld 继续启动建设进程的空间。然后,此方法使用centerWorldOnPosition: 方法设置空间的立场,以使照相机居中产卵点英雄将出现在用户启动游戏时的位置上。
Adventure: APAAdventureScene.m -initWithSize:
[self buildWorld];
CGPoint startPosition = self.defaultSpawnPoint;
[self centerWorldOnPosition:startPosition];
}
return self;
}
1.2.2 构建空间(World)
BuildWorld 方法配置基本物理模拟设置,然后添加子画面并设置的开始位置的人物角色水平范围内:
Adventure: APAAdventureScene.m -buildWorld
- (void)buildWorld {
self.physicsWorld.gravity = CGPointZero;
self.physicsWorld.contactDelegate = self;
[self addBackgroundTiles];
[self addSpawnPoints];
[self addTrees];
[self addCollisionWalls];
}
Sprite Kit包物理引擎模拟重力游戏,例如,人物角色运行和跳过障碍。但是,探险是一种游戏哪里你垂直往下看从上面,所以无需为重力。我们将重心位置设置为 CGPointZero,以指示没有重力。
探险不会使用 Sprite Kit物理引擎来自动处理的冲突,因此,例如,一个人物角色不能通过一堵墙,或者步行的洞穴上方。我们将 contactDelegate 设置为现场接收回调,当发生某些冲突 ;这些都涵盖在"处理冲突"中详细”。
AddBackgroundTiles 方法只是预装的瓷砖节点的散步,并将它们添加到空间 ;方法的其余部分将在以下各节将讨论。
1.2.3 读取关卡地图
若要使它易于配置的不同人物角色的起始位置在游戏中,我们创建了一个关卡的地图,显示在图 3-4。这张地图是.png 文件使用四个像素的颜色来表示不同的项目,在游戏中:
a) 一个透明的像素表示的关卡老大的位置。
b) 一个红色像素指示一堵墙。
c) 一个绿色的像素指示一个哥布林洞穴。
d) 一个蓝色的像素表示的开始位置的英雄。
这一战略轻松平实的设计师专注游戏图稿而不必担心如精确屏幕尺寸的问题。
图1-4 关卡数据地图
AddSpawnPoints 方法使用由从 initWithSize 中调用 APACreateDataMap() 函数加载的较早前_levelMap:。
APACreateDataMap() 是在APAGraphicsUtilities.m 中定义的。它加载,并将该图像绘制到 ARGB 位图上下文。如果您跟踪通过APACreateDataMap() 函数,你就会看到它调用 APACreateARGBBitmapContext(),也在APAGraphicsUtilities.m,以创建具有 8 位每个组件的上下文中定义。
Adventure: APAGraphicsUtilities.m APACreateARGBBitmapContext()
context = CGBitmapContextCreate(bitmapData,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
(CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
如果你看在 APAGraphicsUtilities.h 中,您会看到使用四个 uint8_t 字段定义的 APADataMap 结构:
Adventure: APAGraphicsUtilities.h
typedef struct {
uint8_t bossLocation, wall, goblinCaveLocation, heroSpawnLocation;
} APADataMap;
ARGB 上下文中的"每像素 4 x 8 位"数据直接转化为在 APADataMap 结构中的四个字段。
APACreateDataMap() 函数只是返回一个指针到原始数据,但可以 APAAdventureScene 类访问这些数据通过将它当作一个 C 的 APADataMap 结构的数组。这意味着,代码更容易阅读,因为我们是指的.goblinCaveLocation 而不是看着原始字节的"绿色通道"。
将翻译成场景坐标的像素关卡的地图中,通过使用的 APADataMap 结构的 addSpawnPoints 方法循环嵌套的for 循环。每次循环会检查像素的APADataMap,看看是否什么应创建在当前的位置。
Adventure: APAAdventureScene.m -addSpawnPoints
- (void)addSpawnPoints {
for (int y = 0; y < kLevelMapSize; y++) {
for (int x = 0; x < kLevelMapSize; x++) {
CGPoint location = CGPointMake(x, y);
APADataMap spot = [self queryLevelMap:location];
CGPoint worldPoint = [self
convertLevelMapPointToWorldPoint:location];
if (spot.boss <= 200) {
... (Create a boss at this location)
}
if (spot.goblinCaveLocation >= 200) {
... (Create a goblin cave at this location)
}
if (spot.heroSpawnLocation >= 200) {
... (Set the hero spawn point)
}
}
}
}
KLevelMapSize 常数是 256 — — 的高度和水平地图图像的宽度。每个这些像素转化为在现场,通过一系列的不同坐标系之间转换的帮助器方法内的坐标。
首领菌种位置代表的 alpha 通道。如果你非常密切地看看地图图 3-4 中,您将看到一个末尾的迷宫,这就是为什么该代码检查的 alpha 值是否小于 200 的透明像素。
小妖精洞穴由碰撞地图中绿色像素表示。任何用 200 或更多的绿颜色值的像素转化为在游戏中的 APACave实例。
英雄菌种位置由大于 200 像素的蓝色通道中的值表示。
(红色通道用于指示的迷宫,墙上"添加碰撞墙壁"中所述)
实现详细信息:APADataMap 和 APATreeMap 的结构定义封装在 #pragma pack(1) 和 #pragma pack () 指令。默认情况下,编译器包中可能效率更高的 CPU 在运行时访问方式的结构中的字段。要这样做,可能需要插入额外的空白,以便字段对齐针对 32 位或 64 位地址。#pragma pack(1) 指令告诉编译器指定的结构布局和从来没有插入任何额外的填充,以便保证 4 × 8 位像素数据的数组是直接转化为结构的字段。
1.2.4 读取树地图
在场景中树木的位置指定在叶面图中,图 3-5 所示。
图1-5 叶子地图
这张地图再转化成数据使用 APACreateDataMap() 函数,但这一次 APAAdventureScene 将数据解释为APATreeMap 结构的数组。
在叶面映射中的 alpha 和蓝色通道未使用,但的红色和绿色通道的用法如下:
l 红色通道指示一棵小树。
l 绿色通道指示一个大的树。
Adventure: APAGraphicsUtilities.h
typedef struct {
uint8_t unusedA, bigTreeLocation, smallTreeLocation, unusedB;
} APATreeMap;
探险的树木都是由多个图像图层,移动以不同的比率作为照相机移动在空间各地展出视差效果。更详细地描述了这种效果在"创建具有视差效应"。
1.2.5 添加碰撞壁
AddCollisionWalls 方法使用在层次图中每个像素的红色通道中编码的数据确定放置位置在迷宫中的墙。此方法中的代码是相当致密,所以您可能会发现它有些直观如果你不习惯原始 C 位操作。
Adventure: APAAdventureScene.m -addCollisionWalls
- (void)addCollisionWalls {
//Alloca() 呼叫分配内存足够大以容纳在图像地图中的像素的块
unsigned char *filled = alloca(kLevelMapSize * kLevelMapSize);
//此块然后用零填充
memset(filled, 0, kLevelMapSize * kLevelMapSize);
...
AddCollisionWalls 方法继续做两次长什么样子同样的事情 — — 有两个长代码块的涉及嵌套的 for 循环。这些循环的第一是负责创建水平墙 ;第二个处理垂直墙。
每个循环遍历碰撞地图,找个连续的像素墙 (红色) 通道值大于 200 组中的所有像素。每个连续块然后变成使用addCollisionWallAtWorldPoint:withWidth:height 的碰撞墙: 方法,添加一个基本子与配置、 矩形的物理身体。
Adventure: APAAdventureScene.m -addCollisionWallAtWorldPoint:withWidth:height:
- (void)addCollisionWallAtWorldPoint:(CGPoint)worldPoint
withWidth:(CGFloat)width height:(CGFloat)height {
//物理身体是一个简单、 非动态的矩形的机构。当与墙碰撞其他精灵在游戏中的时,他们将自动
//阻止民宅内的矩形区域,给行走的墙在迷宫的错觉。
CGRect rect = CGRectMake(0, 0, width, height);
SKNode *wallNode = [SKNode node];
wallNode.position = CGPointMake(worldPoint.x + rect.size.width * 0.5,
worldPoint.y - rect.size.height * 0.5);
wallNode.physicsBody = [SKPhysicsBody
bodyWithRectangleOfSize:rect.size];
wallNode.physicsBody.dynamic = NO;
// CategoryBitMask 为一堵墙是 APAColliderTypeWall。我们使用此值来设置是否一种类
//型的物理机构碰撞与物理机构的另一种类型 ;碰撞中"处理冲突"的更多详细介绍
wallNode.physicsBody.categoryBitMask = APAColliderTypeWall;
[self addNode:wallNode atWorldLayer:APAWorldLayerGround];
}
尽快添加墙壁,空间是完整的 !
1.3 游戏更新
Sprite Kit包游戏都是从传统的 iOS 或 OS X 应用程序在你看见屏幕上正在不断地绘第二多次的一切都不同。从开发人员的角度来看,更新和显示一个场景节点例如,子画面或粒子发射器的工作主要被进行自动Sprite Kit包。当你已经将Sprite工具包(Kit)添加到场景时,它继续进行重绘,直到您删除它。同样,粒子排放继续发出的粒子,直到您显式暂停他们或从场景中删除它们。
一个静态的场景变成更有趣的东西,你上创建和运行操作子画面。物理模拟照顾之间的相互作用 (如碰撞) 物理机构,并防止子画面的重叠。为每个帧,Sprite Kit包将自动更新更改触发的操作或应用物理模拟的结果后的每个节点的位置。
在探险,我们也进行方案调整到节点在场景中。例如,我们移动英雄对用户输入的响应,我们移动照相机这样的人物角色是始终可见。每当我们移动照相机,我们还必须调整视差精灵层偏移的量。所有的这种编程更新逻辑Sprite Kit包更新循环的一部分,作为发生。
1.3.1 更新循环处理
对于每个帧,Sprite Kit包通过了六个关键阶段的更新循环运行图 4-1 所示。
图1-6 更新循环
在这些阶段内, 有三次机会,使方案调整前Sprite Kit包呈现帧:
1. 循环的开始是的更新:,调用的方法的现场评估任何行动或模拟物理之前。
2. Sprite Kit包已计算节点上,但之前看所涉问题从物理模拟任何未决操作后在现场上调用didEvaluateActions 方法。
3. Sprite Kit包作出任何调整从物理仿真后在现场上调用 didSimulatePhysics 方法。这是最后一次重写的方法调用前视图呈现现场和循环再继续。
在探险,我们绑到这个循环在两个地方:
1. 我们执行更新: 在APAMultiplayerLayeredCharacterScene 移动基于用户输入的英雄。更新: 方法反过来调用updateWithTimeSinceLastUpdate:,它由 APAAdventureScene 更新现场,以及所有的敌人的人工智能中的单个人物角色来实现。用户输入和人工智能中所述"控制人物角色"。
2. 我们在 APAMultiplayerLayeredCharacterScene 以移动的照照相机,实施 didSimulatePhysics,如果有必要,基于英雄位置。APAAdventureScene 类重写此方法,以隐藏或显示粒子排放国基于英雄的位置,然后它会更新的不同的图像图层的视差树木和洞穴组成的相对位置。
我们可以处理照相机运动、 粒子发射器和视差更新初始更新的过程中得到: 的阶段,但是这些任务的每个绑到英雄的位置。在更新期间: 阶段,Sprite Kit包的节点不一定在它为当前帧的最后位置 — — 它可以从物理模拟移动因为的行动或副作用。例如,如果英雄是参与碰撞与另一个人物角色或一堵墙,将会更改英雄位置之间更新: 和最终的呈现。
通过延迟直到 didSimulatePhysics 位置相关的工作,我们保证英雄位置是最终为当前帧
1.3.2 移动照相机
探险场景是很大 — — 全面的背景是 4096 x 4096 — — 所以只有现场的一小部分是可见的在任何时间。词照相机涉及到的现场 ; 可见区域的概念没有任何明确的Sprite工具包(Kit)套件摄像机对象涉及,但你会经常遇到引用到"移动照相机"。
在探险空间相关的所有节点,包括背景瓷砖、 人物角色和叶面,都是场景的节点的儿童的空间,这又是场景的节点的一个孩子。我们改变在现场给影响的跨水平移动照相机内此树顶部的空间节点的位置。相比之下,弥补 HUD 的节点是直接子现场,而不是空间节点中,单独节点的子节点,以便 HUD 中的元素不动时我们"移动照相机"。
我们本可以选择以调整空间节点的位置,以便英雄总是出现在现场,这将意味着英雄移动时移动照相机的可见部分的中心。相反,我们决定要定义一个矩形区域中,可以移动的英雄,并将移动照照相机仅当英雄误入外面那些边界,如图 4-2 所示。
图1-7 照相机边缘内边距
Adventure: APAMultiplayerLayeredCharacterScene.m -didSimulatePhysics
- (void)didSimulatePhysics {
// 如果有多个播放器,照相机始终遵循默认玩家的英雄。
APAHeroCharacter *defaultHero = self.defaultPlayer.hero;
if (defaultHero) {
CGPoint heroPosition = defaultHero.position;
CGPoint worldPos = self.world.position;
CGFloat yCoordinate = worldPos.y + heroPosition.y;
if (yCoordinate < kMinHeroToEdgeDistance) {
worldPos.y = worldPos.y - yCoordinate + kMinHeroToEdgeDistance;
self.worldMovedForUpdate = YES;
} else if (yCoordinate > (self.frame.size.height –
kMinHeroToEdgeDistance)) {
worldPos.y = worldPos.y + self.frame.size.height - yCoordinate –
kMinHeroToEdgeDistance;
self.worldMovedForUpdate = YES;
}
... (Repeat for horizontal axis)
self.world.position = worldPos;
}
//使用 performSelector:withObject:afterDelay: 特别延迟值为 0.0 意味着选择器调
//用发生在当前后通过运行循环。使用此值确保 didSimulatePhysics 子类执行会出现之前的
//worldMovedForUpdate 属性重置为号
[self performSelector:@selector(clearWorldMoved)
withObject:nil afterDelay:0.0f];
}
我们 worldMovedForUpdate 属性设置为是无论何时移动照相机。DidSimulatePhysics APAAdventure 执行检查 worldMovedForUpdate 来确定我们是否需要更新的视差精灵,或删除任何不再可见的粒子发射器。
1.3.3 创建视差效果
探险是从二维图形元素创建的。以给人错觉的一个三维的空间,我们建立一些精灵 (树木和小妖精洞穴) 从多个图像的图层 ;当镜头移动时跟随英雄,我们调整这些图层在不同的利率以模拟深度使用一种技术称为视差滚动的立场。
若要了解使用真人秀的大示例的视差,想象你是一名乘客在行驶的车,透过窗户,路旁的树木与建筑。当车移动时,你最亲近的树木似乎穿越你的视野更快做建筑的远的距离,如图 4-3 所示。
图1-8 真实空间的视差
我们模仿这种效果在探险通过移动,顶部图层树或地精的洞穴更远较低层相对于照相机的中心的距离。APAParallaxSprite 类,从其继承的所有人物角色和树木所提供的视差支持。大部分的人物角色子类将usesParallaxEffect 设置为 NO,但是 APATree 和 APACave 类的属性设置为是。
每当我们初始化为视差的Sprite工具包(Kit),我们使用APAParallaxSprite,initWithSprites:usingOffset 指定初始值设定项:,其中采用Sprite工具包(Kit)节点的数组,并将它们添加作为儿童的容器Sprite工具包(Kit)在减少 z 职位。
Adventure: APAParallaxSprite.m -initWithSprites:usingOffset:
- (id)initWithSprites:(NSArray *)sprites usingOffset:(CGFloat)offset {
self = [super init];
if (self) {
_usesParallaxEffect = YES;
CGFloat zOffset = 1.0f / (CGFloat)[sprites count];
CGFloat ourZPosition = self.zPosition;
NSUInteger childNumber = 0;
for (SKNode *node in sprites) {
//较高的 z 位置值,早在子画面的绘制顺序绘制了。
node.zPosition = ourZPosition + (zOffset + (zOffset * childNumber));
[self addChild:node];
childNumber++;
}
_parallaxOffset = offset;
}
return self;
}
我们创建共享的 APATree 实例,当加载 APAAdventureScene,在资产和供应子Sprite工具包(Kit)节点为不同的层数。
Adventure: APAAdventureScene.m +loadSceneAssets
+ (void)loadSceneAssets {
SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:@"Environment"];
...
sSharedSmallTree = [[APATree alloc] initWithSprites:@[
[SKSpriteNode spriteNodeWithTexture:
[atlas textureNamed:@"small_tree_base.png"]],
[SKSpriteNode spriteNodeWithTexture:
[atlas textureNamed:@"small_tree_middle.png"]],
[SKSpriteNode spriteNodeWithTexture:
[atlas textureNamed:@"small_tree_top.png"]]]
usingOffset:25.0f];
...
}
我们初始化使用指定的初始值设定项,initAtPosition 的 APACave 的小妖精洞穴:,其中初始化洞穴中相同的方式,用 cave_base 和 cave_top 纹理使用子画面。
我们将视差Sprite工具包(Kit)添加到 APAAdventureScene,在空间的每个时间我们将其添加到一个数组中的parallaxSprites。
更新的视差偏移量。我们通过更新循环每次更新视差精灵偏移的量。因为偏移的变化基于摄像机的位置,和因为摄像机的位置取决于英雄的位置,视差偏移量在 didSimulatePhysics,APAMultiplayerLayeredCharacterScene 类实施后的 APAAdventureScene 类更新方法调整了空间的地位。
Adventure: APAAdventureScene.m -didSimulatePhysics
- (void)didSimulatePhysics {
[super didSimulatePhysics];
... (Get the position of the default hero, or hero spawn point)
//需要更新的偏移量,只当空间节点已经移动。
if (!self.worldMovedForUpdate) {
return;
}
...
for (APAParallaxSprite *sprite in self.parallaxSprites) {
//需要更新只有当前可见的 sprite 的偏移量或很快就是可见的。因此,我们检查
//Sprite工具包(Kit)和英雄 ; 之间的距离如果不是英雄是可见的我们使用的英雄重生点 —— 中水平的起
//始点。
if (APADistanceBetweenPoints(sprite.position, position) >= 1024) {
continue;
}
[sprite updateOffset];
}
}
UpdateOffset 方法是由要调整的每个儿童Sprite工具包(Kit)节点相对于屏幕的中心偏移量的APAParallaxSprite 类来实现的。
Adventure: APAParallaxSprite.m -updateOffset
- (void)updateOffset {
SKScene *scene = self.scene;
SKNode *parent = self.parent;
... (Return early if parallax is disabled)
CGPoint scenePos = [scene convertPoint:self.position fromNode:parent];
//偏置到 (–0.5,0.5) 的偏移的方向范围相对于屏幕的中心。
CGFloat offsetX = (-1.0f + (2.0 * (scenePos.x / scene.size.width)));
CGFloat offsetY = (-1.0f + (2.0 * (scenePos.y / scene.size.height)));
CGFloat delta = self.parallaxOffset / (CGFloat)self.children.count;
int childNumber = 0;
for (SKNode *node in self.children) {
//每个子节点是偏移量的日益增加的数额 ;最顶尖的孩子因此偏移量。
node.position = CGPointMake(offsetX*delta*childNumber,
offsetY*delta*childNumber);
childNumber++;
}
}
1.3.4 游戏的顺畅运行
把一切都看顺利向用户,优化重绘速率是每秒 60 帧。这意味着每个框架的整个更新循环必须采取少于16.7 毫秒为单位)。在优化的代码,我们写了更新: didSimulatePhysics,我们通过各种战略,设法减低的Sprite工具包(Kit)套件来更新一切场景中花费的时间量。
发射器是Sprite Kit包游戏的性能最密集的方面之一所以我们用 Xcode 粒子发射器编辑器,尽量减少任何时间,同时仍然实现所需的效果在屏幕上的粒子的数量。每次更新循环时,我们也暂停任何发射器不可见:
Adventure: APAAdventureScene.m -didSimulatePhysics
- (void)didSimulatePhysics {
[super didSimluatePhysics];
...
if (!self.worldMovedForUpdate) {
return;
}
for (SKEmitterNode *particles in self.particleSystems) {
BOOL particlesAreVisible =
APADistanceBetweenPoints(particles.position, position) < 1024;
if (!particlesAreVisible && !particles.paused) {
particles.paused = YES;
} else if (particlesAreVisible && particles.paused) {
particles.paused = NO;
}
}
...
}
很多在 didSimulatePhysics 工作需要去做只有照相机移动,所以我们检查 worldMovedForUpdate 属性(由APAMultiplayerLayeredCharacterScene 设置) 并提早回来如果空间节点都没动。
1.1 人物角色的控制
1.1.1 接收键盘和触摸事件
Sprite工具包(Kit)工具包的任何节点可以从响应方链接收用户输入。在 OS X 中的 SKNode 类NSResponder 类,从继承和 iOS,它继承了 UIResponder。
在探险,我们实现响应程序链和游戏控制器中的 APAMultiplayerLayeredCharacterScene 类处理的事件。我们使用条件编译器指令来实现 keyDown: 和 keyUp: 构建为 OS X 和touchesBegan:withEvent时:,touchesMoved:withEvent:,和 touchesEnded:withEvent: ios。
处理 iOS 触摸事件。当用户触摸屏幕的iOS 设备时,我们收到对touchesBegan:withEvent 的调用: 在APAMultiplayerLayeredCharacterScene 上。我们实现此方法可设置的目标位置,而英雄将应移动 ;如果触摸发生的敌人,如小妖精或洞穴,我们反而解释触摸为打击敌人火行动。
OS X 事件处理中,我们使用 APAPlayer 实例来桥之间触摸和更新循环,但这一次我们使用的moveRequested 和targetLocation 的属性。
Adventure: APAMultiplayerLayeredCharacterScene.m -touchesBegan:withEvent:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
... (Return early if we don't have any heroes in play, or if we're already
tracking a touch)
UITouch *touch = [touches anyObject];
APAPlayer *defaultPlayer = self.defaultPlayer;
defaultPlayer.targetLocation = [touch
locationInNode:defaultPlayer.hero.parent];
BOOL wantsAttack = NO;
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
for (SKNode *node in nodes) {
if (node.physicsBody.categoryBitMask & (APAColliderTypeCave |
APAColliderTypeGoblinOrBoss) {
//设置 wantsAttack 为 YES 如果触摸点击任何小妖精、 水平的首领或山洞里,然后使用
//此值来确定是否播放机正试图移动人物角色或执行攻击行动。
wantsAttack = YES;
}
}
defaultPlayer.fireAction = wantsAttack;
//如果英雄攻击一个敌人,不会误以为触摸运动。
defaultPlayer.moveRequested = !wantsAttack;
defaultPlayer.movementTouch = touch;
iOS 更新进行编译时: 在 APAMultiplayerLayeredCharacterScene 方法中包含此额外的代码来处理人物角色运动块:
Adventure: APAMultiplayerLayeredCharacterScene.m -update:
- (void)update:(NSTimeInterval)currentTime {
...
#if TARGET_OS_IPHONE
APAPlayer *defaultPlayer = self.defaultPlayer;
if ([self.heroes count] > 0) {
APAHeroCharacter *hero = defaultPlayer.hero;
if (defaultPlayer.fireAction) {
//如果玩家攻击,把他们的英雄,脸朝着敌人目标。
[hero faceTo:defaultPlayer.targetLocation];
}
if (defaultPlayer.moveRequested) {
//通过检查来看看英雄是否已经在请求的位置 ;如果不是,我们向位置移动的英雄。
if (!CGPointEqualToPoint(defaultPlayer.targetLocation,
hero.position)) {
[hero moveTowards:defaultPlayer.targetLocation
withTimeInterval:timeSinceLast];
} else {
//否则,就取消请求的移动。
defaultPlayer.moveRequested = NO;
}
}
}
#endif
...
循环遍历以后中更新的 APAPlayer 实例的跨平台代码: 负责触发实际消防行动:
for (APAPlayer *player in self.players) {
...
APAHeroCharacter *hero = player.hero;
...
if (player.fireAction) {
[hero performAttackAction];
}
...
}
}
1.1.2 支持外接游戏控制器
探险使用游戏控制器框架与外部的游戏控制器 ; 进行通信我们支持最多四个不同的球员。如果您连接了一个新的控制器,创建一个新的 APAPlayer 实例和一个新的英雄添加到游戏。
你还会记得从"创建现场"app 委托 (OS X) 或视图控制器 (iOS) 创建一个 APAAdventure 现场的实例、 将其添加到视图中,和现场然后调用 configureGameControllers。我们在 APAMultiplayerLayeredCharacterScene 注册以接收通知,每当游戏控制器连接或断开连接时实现此方法。我们还配置控制器的游戏发布时,已连接,然后开始搜索任何无线控制器:
Adventure: APAMultiplayerLayeredCharacterScene.m -configureGameControllers
- (void)configureGameControllers
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(gameControllerDidConnect:)
name:GCControllerDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(gameControllerDidDisconnect:)
name:GCControllerDidDisconnectNotification object:nil];
[self configureConnectedGameControllers];
[GCController startWirelessControllerDiscoveryWithCompletionHandler:^{
NSLog(@"Finished finding controllers");
}
}
1. 将控制器分配给玩家
每当检测到控制器,我们检查控制器的 playerIndex 属性。如果控制器以前用探险,该属性对应于玩家的APAPlayer 实例 ; 数组中的索引如果它是一个新的控制器,该属性设置为 GCControllerPlayerIndexUnset。
如果已经设置的球员索引,我们要检查是否已与相关的 APAPlayer 实例 ; 关联现有控制器如果如此,我们对待控制器,如果它已永远不被用于之前,避免多个控制器控制一个单一的英雄。否则为我们创建一个新的英雄为球员,并将其添加到世界:
Adventure: APAMultiplayerLayeredCharacterScene.m -assignPresetController:toIndex:
- (void)assignPresetController:(CGController *)controller
toIndex:(NSInteger)playerIndex {
APAPlayer *player = self.players[playerIndex];
... (Check if the player is NSNull and create a new APAPlayer if so)
if (player.controller && player.controller != controller) {
//AssignUnknownController 方法枚举通过 APAPlayer 实例,寻找还没有分配的控制
//器的球员。
[self assignUnknownController:controller];
return;
}
[self configureController:controller forPlayer:player];
}
2. 为控制器事件做准备
ConfigureController:forPlayer: 方法我们认识到的所有控制器输入都设置的 valueChangedHandler 块属性。例如,如果玩家操纵 thumbstick 或按方向垫按钮之一,我们会将 heroMoveDirection 属性设置上APAPlayer 实例 ;如果播放机按下按钮 A,我们将 fireAction 属性设置。
- (void)configureController:(GCController *)controller forPlayer:(APAPlayer
*)player {
player.controller = controller;
GCControllerDirectionPadValueChangedHandler dpadMoveHandler =
^(GCControllerDirectionPad *dpad, float xValue, float yValue) {
float length = hypotf(xValue, yValue);
if (length > 0.0f) {
float invLength = 1.0f / length;
player.heroMoveDirection = CGPointMake(xValue * invLength, yValue
* invLength);
} else {
player.heroMoveDirection = CGPointZero;
}
};
controller.extendedGamepad.leftThumbstick.valueChangedHandler =
dpadMoveHandler;
controller.gamepad.dpad.valueChangedHandler = dpadMoveHandler;
GCControllerButtonValueChangedHandler fireButtonHandler =
^(GCControllerButtonInput *button, float value, BOOL pressed) {
player.fireAction = pressed;
};
controller.gamepad.buttonA.valueChangedHandler = fireButtonHandler;
controller.gamepad.buttonB.valueChangedHandler = fireButtonHandler;
... (Add a hero for the player, if necessary)
}
作为与键盘和触摸屏输入,我们使用 APAPlayer 对象作为我们接收控制器输入的时间和我们在更新循环期间操纵有关英雄的时间之间的桥梁。
更新: 方法是负责检查这些的 APAPlayer 属性,并触发任何有关的行动或运动对球员的英雄。
Adventure: APAMultiplayerLayeredCharacterScene.m -update:
- (void)update:(NSTimeInterval)currentTime {
...
for (APAPlayer *player in self.players) {
...
APAHeroCharacter *hero = player.hero;
...
if (player.fireAction) {
[hero performAttackAction];
}
CGPoint heroMoveDirection = player.heroMoveDirection;
//使用 hypotf() 来计算的三角形的斜边长度与侧长度 x 和 y。如果这一结果大于零,则我们需要
//移动的英雄。
if (hypotf(heroMoveDirection.x, heroMoveDirection.y) > 0.0f) {
[hero moveInDirection:heroMoveDirection withTimeInterval:timeSinceLast];
}
}
}
而不是向前走预设的量按键,或从一个触摸往一个特定的点走后,heroMoveDirection 表示 x 和 y –1.0和 1.0,取决于用户如何远和朝哪个方向移动 thumbstick 之间的浮点值。
MoveInDirection: 上APAHeroCharacter 的方法使用这些值来生成基于当前英雄位置的目标位置。
Adeventure: APACharacter.m -moveInDirection:withTimeInterval:
- (void)moveInDirection:(CGPoint)direction
withTimeInterval:(NSTimeInterval)timeInterval {
CGPoint curPosition = self.position;
CGFloat movementSpeed = self.movementSpeed;
CGFloat dx = movementSpeed * direction.x;
CGFloat dy = movementSpeed * direction.y;
CGFloat dt = movementSpeed * kMinTimeInterval;
CGPoint targetPosition = CGPointMake(curPosition.x + dx, curPosition.y +
dy);
CGFloat ang = APA_POLAR_ADJUST(APARadiansBetweenPoints(targetPosition,
curPosition));
self.zRotation = ang;
CGFloat distRemaining = hypotf(dx, dy);
if (distRemaining < dt) {
self.position = targetPosition;
} else {
self.position = CGPointMake(curPosition.x - sinf(ang)*dt,
curPosition.y + cosf(ang)*dt);
}
... (Set up the walk animation)
}
APA_POLAR_ADJUST 是一个预处理器宏,在调整角度的 APAGraphicsUtilities.h 或定义的二分之一 pi 弧度 90 °。
所有中探险的资产创建与"转发"正面朝上,换句话说,面向沿 y 轴。在游戏中的坐标系统对待作为面临向右,向前面向沿 x 轴,所以我们需要通过旋转 90 ° 向右调整所有Sprite工具包(Kit)纹理方向。
1.2 敌人的人工智能化控制
在探险的敌人 — — 妖精、 水平的首领和地精的洞穴 — — 都由单独的人工智能对象控制。妖精和关卡首领使用 APAChaseAI 的一个实例来追在预定义的距离 ; 是任何英雄小妖精洞穴使用 APASpawnAI 听写如何频繁地生成新的妖精。
所有敌人人物角色在探险下降从 APAEnemyCharacter 类,该类将重写更新: 提供的 APACharacter 来调用update 方法: 人工智能上:
Adventure: APAEnemyCharacter.m -update:
- (void)update:(CFTimeInterval)interval {
[super update:interval];
[self.intelligence update:interval];
}
这意味着人工智能的对象进行更新每次通过循环时更新。
1.2.1 奔跑的人工智能(AI)化处理
APAChaseAI 类重写抽象的 APAArtificialIntelligence 类更新: 如果它是在敌人的 kEnemyAlertRadius距离内追逐最接近的英雄,但唯一的方法。如果英雄是近距离的攻击,我们旋转的敌人面对英雄和触发的攻击行动。
Adventure: APAChaseAI.m -update:
- (void)update:(CFTimeInterval)interval {
APACharacter *ourCharacter = self.character;
... (Return early if our enemy character is dying)
CGPoint position = ourCharacter.position;
APAMultiplayerLayeredCharacterScene *scene = [ourCharacter
characterScene];
CGFloat closestHeroDistance = MAXFLOAT;
for (APAHeroCharacter *hero in scene.heroes) {
CGPoint heroPosition = hero.position;
CGFloat distance = APADistanceBetweenPoints(position, heroPosition);
if (distance < kEnemyAlertRadius && distance < closestHeroDistance
&& !hero.dying) {
closestHeroDistance = distance;
self.target = hero;
}
}
APACharacter *target = self.target;
... (Return early if there's no target)
CGPoint heroPosition = target.position;
CGFloat chaseRadius = self.chaseRadius;
if (closestHeroDistance > self.maxAlertRadius) {
self.target = nil;
} else if (closestHeroDistance > chaseRadius) {
[self.chararacter moveTowards:heroPosition];
} else if (closestHeroDistance < chaseRadius) {
[self.character faceTo:heroPosition];
[self.character performAttackAction];
}
}
1.2.2 产卵的人工智能化(AI)处理
APASpawnAI 类重写该更新: 方法来确定如何频繁地精洞穴应产卵新妖精。随着大通人工智慧,我们寻找的最接近的英雄。我们然后调整小妖精菌种与洞穴和最接近英雄之间的距离之间的时间,以便洞穴作为英雄获取更密切更频繁地生成妖精。
Adventure: APASpawnAI.m -update:
- (void)update:(CFTimeInterval)interval {
APACave *cave = (id)self.character;
... (Return early if our cave is destroyed)
CGFloat closestHeroDistance = kMinimumHeroDistance;
CGPoint closestHeroPosition = CGPointZero;
... (Calculate the distance to, and get the location of, the closest hero)
CGFloat distScale = (closestHeroDistance / kMinimumHeroDistance);
cave.timeUntilNextGenerate -= interval;
NSUInteger goblinCount = [cave.activeGoblins count];
if (goblinCount < 1 || cave.timeUntilNextGenerate <= 0.0f || (distScale <
0.35f && cave.timeUntilNextGenerate > 5.0f)) {
if (goblinCount < 1 || (!CGPointEqualToPoint(closestHeroPosition,
CGPointZero) && [scene closestHeroPosition
from:cave.position])) {
[cave generate];
}
cave.timeUntilNextGenerate = (4.0f * distScale);
}
}
插个me的广告
iOS 7 Sprite Kit游戏——探险相关推荐
- 初探使用iOS 7 Sprite Kit与Cocos2d开发游戏的对比(一家之言)
初探使用iOS 7 Sprite Kit与Cocos2d开发游戏的对比 初探使用iOS 7 Sprite Kit与Cocos2d开发游戏的对比 发布于:2013-07-18 11:00阅读数:1984 ...
- 初探使用iOS 7 Sprite Kit与Cocos2d开发游戏的对比
前言 iOS7 beta发布后,大部分开发者和用户的注意力都集中在了iOS 7的全新UI交互设计界面上.一直负责硬件工业设计的Jony Ive首次全面负责苹果的软件和硬件设计工作,自然要把他自己的设计 ...
- Swift版iOS游戏框架Sprite Kit基础教程下册
Swift版iOS游戏框架Sprite Kit基础教程下册 试读下载地址:http://pan.baidu.com/s/1qWBdV0C 介绍:本教程是国内唯一的Swift版的Spritekit教程 ...
- iOS游戏框架Sprite Kit基础教程——Swift版上册
iOS游戏框架Sprite Kit基础教程--Swift版上册 试读下载地址:http://pan.baidu.com/s/1qWBdV0C 介绍:本教程是国内唯一的Swift版的Spritekit ...
- 苹果官方《Sprite Kit Programming Guide》翻译
http://www.cocoachina.com/newbie/basic/2013/0822/6845.html 本文翻译自Apple官方的<Sprite Kit Programming G ...
- sprite Kit Actions(三)
感觉一辈子都没打过这么多字了... Scale action 缩放动作 You now have an animated zombie and some crazy cat ladies, but t ...
- swift语言注册非免费苹果账号iOS游戏框架Sprite Kit基础教程
swift语言注册非免费苹果账号iOS游戏框架Sprite Kit基础教程 1.2.3 注册非免费苹果账号swift语言注册非免费苹果账号iOS游戏框架Sprite Kit基础教程 免费的苹果账号在 ...
- iOS游戏框架Sprite Kit基础教程第1章编写第一个Sprite Kit程序
iOS游戏框架Sprite Kit基础教程第1章编写第一个Sprite Kit程序 程序是为了实现特定目标或解决特定问题而用计算机语言编写的命令序列的集合.本章将以编写第一个Sprite Kit程序为 ...
- ios游戏开发 Sprite Kit教程:初学者 2
2019独角兽企业重金招聘Python工程师标准>>> 注:本文译自Sprite Kit Tutorial for Beginners 目录 Sprite Kit的优点和缺点 Spr ...
最新文章
- SQL语句统计错误率
- python ai应用开发_AI应用开发实战 - 从零开始搭建macOS开发环境
- java多图片上传json_SpringMVC框架五:图片上传与JSON交互
- 枚举类型是怎样定义的?有什么用途?_新型合金材料——什么是液态金属、液态金属的定义、发展以及用途...
- 100以内 蝗 靓耸 6的c语言怎,C语言学习C6.ppt
- 衡量时间序列相似度的方法:从欧氏距离到DTW及其变种
- Django(补充CBV,FBV)
- eclipse java maven_java – 非常轻量级的Eclipse-Maven集成 – 仅...
- 牛顿法python实现_牛顿法求极值及其Python实现
- 安全bios手册(5)
- 阈值处理(Threshold processing)
- UE4内容浏览器改文件夹名称后无法删除空文件夹
- 15 个最佳开源设计工具
- Vue指令之条件渲染
- dq坐标系下无功功率表达式_一种单相脉冲整流器电网电压估算方法与流程
- thymeleaf: th:src=@{}
- 36-雷达图像基本处理
- stm32实用篇3: 字符显示字库生成
- 添加winods临时字体的方法
- 获取不同hold rpt中的endpoint,并输出在同一个文件中------Perl+tcl实现