如何在 iOS 上构建 Infinite Runner:Cocos2D、自动化等

已发表: 2022-03-11

就个人和财务增长而言,开发 iOS 游戏可以是一种丰富的体验。 今年早些时候,我在 App Store 中部署了一款基于 Cocos2D 的游戏 Bee Race。 它的游戏玩法很简单:一个无限的跑步者,玩家(在本例中为蜜蜂)收集积分并避开障碍物。 请参阅此处进行演示。

在本教程中,我将解释为 iOS 开发游戏的过程,从 Cocos2D 到发布。 作为参考,这里有一个简短的目录:

  • 精灵和物理对象
  • Cocos2D 简介
  • 将 Cocos2D 与情节提要一起使用
  • 游戏玩法和(简要)项目描述
  • 自动化作业。 使用工具。 冷静点。
  • 应用内结算
  • 使用 Game Center 进行多人游戏
  • 改进空间
  • 结论

精灵和物理对象

在我们深入细节之前,了解精灵和物理对象之间的区别会很有帮助。

对于出现在无尽赛跑游戏屏幕上的任何给定实体,该实体的图形表示称为精灵,而该实体在物理引擎中的多边形表示称为物理对象

因此,精灵被绘制在屏幕上,由其对应的物理对象支持,然后由您的物理引擎处理。 该设置可以在此处可视化,其中精灵显示在屏幕上,其物理多边形对应物以绿色勾勒:

在 iOS 无限奔跑游戏中,精灵和物理对象共存。

默认情况下,物理对象不会连接到它们各自的精灵,这意味着作为 iOS 开发人员,您可以选择使用哪个物理引擎以及如何连接精灵和身体。 最常见的方法是继承默认精灵并为其添加一个具体的物理体。

考虑到这一点…

Cocos2D iOS游戏开发简明教程

Cocos2D-iphone 是一个 iOS 开源框架,使用 OpenGL 进行硬件图形加速,支持 Chipmunk 和 Box2D 物理引擎。

首先,我们为什么需要这样一个框架? 好吧,对于初学者来说,框架实现了游戏开发中经常使用的组件。 例如,Cocos2D 可以加载精灵(特别是精灵表(为什么?)),启动或停止物理引擎,并正确处理时间和动画。 它使用经过广泛审查和测试的代码来完成这一切——为什么要花时间重写可能较差的代码?

然而,也许最重要的是——Cocos2D游戏开发使用图形硬件加速。 如果没有这样的加速,任何具有中等数量精灵的 iOS 无限奔跑游戏都会以显着较差的性能运行。 如果我们尝试制作一个更复杂的应用程序,那么我们可能会开始在屏幕上看到“子弹时间”效果,即每个精灵在尝试动画时都有多个副本。

最后,Cocos2D 优化了内存使用,因为它缓存了精灵。 因此,任何重复的精灵都需要最少的额外内存,这显然对游戏很有用。

将 Cocos2D 与情节提要一起使用

在我对 Cocos2D 赞不绝口之后,建议使用 Storyboard 似乎不合逻辑。 为什么不直接用 Cocos2D 等来操作你的对象呢? 好吧,老实说,对于静态窗口,使用 Xcode 的 Interface Builder 和它的 Storyboard 机制通常更方便。

首先,它允许我用鼠标拖动和定位我的无尽跑步游戏的所有图形元素。 其次,Storyboard API 非常非常有用。 (是的,我知道 Cocos Builder)。

这是我的故事板的快速浏览:

要学习如何制作无尽的跑步游戏,请从一个好的故事板开始。

游戏的主视图控制器只包含一个 Cocos2D 场景,顶部有一些 HUD 元素:

我们的 Cocos2D 教程从视图控制器开始。

注意白色背景:这是一个 Cocos2D 场景,它将在运行时加载所有必要的图形元素。 其他视图(实时指示器、蒲公英、按钮等)都是标准的 Cocoa 视图,使用 Interface Builder 添加到屏幕上。

我不会详述细节——如果你有兴趣,可以在 GitHub 上找到示例。

游戏玩法和(简要)项目描述

(为了提供更多动力,我想稍微详细地描述一下我的无尽奔跑游戏。如果您想继续进行技术讨论,请随意跳过本节。)

在现场游戏中,蜜蜂一动不动,而场地本身实际上是在奔跑,带来各种危险(蜘蛛和毒花)和特权(蒲公英和它们的种子)。

Cocos2D 有相机对象,旨在跟随角色; 在实践中,操作包含游戏世界的 CCLayer 并不复杂。

控件很简单:点击屏幕将蜜蜂向上移动,再一次点击将其向下移动。

世界层本身实际上有两个子层。 游戏开始时,第一个子层从 0 填充到 BUF_LEN 并最初显示。 第二个子层预先从 BUF_LEN 填充到 2*BUF_LEN。 当蜜蜂到达 BUF_LEN 时,第一个子层被清理并立即从 2*BUF_LEN 重新填充到 3*BUF_LEN,并呈现第二个子层。 通过这种方式,我们在层之间交替,从不保留过时的对象,这是避免内存泄漏的重要部分。

我的无限亚军游戏由多层世界组成。

在物理引擎方面,我使用 Chipmunk 有两个原因:

  1. 它是用纯 Objective-C 编写的。
  2. 我以前使用过 Box2D,所以我想比较两者。

物理引擎实际上只用于碰撞检测。 有时,有人问我,“你为什么不编写自己的碰撞检测?”。 实际上,这样做并没有多大意义。 物理引擎就是为此目的而设计的:它们可以检测复杂形状的物体之间的碰撞并优化该过程。 例如,物理引擎经常将世界分割成多个单元,并且只对相同或相邻单元中的物体执行碰撞检查。

自动化作业。 使用工具。 冷静点。

独立无限奔跑游戏开发的一个关键组成部分是避免因小问题而绊倒。 开发应用程序时,时间是至关重要的资源,而自动化可以非常节省时间。

但有时,自动化也可能是完美主义和满足最后期限之间的折衷。 从这个意义上说,完美主义可以成为愤怒的小鸟杀手。

例如,在我目前正在开发的另一个 iOS 游戏中,我构建了一个框架来使用特殊工具(可在 GitHub 上获得)创建布局。 这个框架有其局限性(例如,它在场景之间没有很好的过渡),但使用它可以让我在十分之一的时间内制作我的场景。

因此,虽然您无法使用特殊的超级工具构建自己的超级框架,但您仍然可以并且应该尽可能多地自动化这些小任务。

完美主义可能是愤怒的小鸟杀手。 时间是 iOS 游戏开发的重要资源。
鸣叫

在构建这个无限的跑步者时,自动化再次成为关键。 例如,我的艺术家会通过一个特殊的 Dropbox 文件夹向我发送高分辨率图片。 为了节省时间,我编写了一些脚本来为 App Store 所需的各种目标分辨率自动构建文件集,同时添加了 -hd 或 @2x(所述脚本基于 ImageMagick)。

在其他工具方面,我发现 TexturePacker 非常有用——它可以将精灵打包到精灵表中,这样您的应用程序将消耗更少的内存并更快地加载,因为您的所有精灵都将从单个文件中读取。 它还可以以几乎所有可能的框架格式导出纹理。 (请注意,TexturePacker 不是免费工具,但我认为物有所值。您还可以查看 ShoeBox 等免费替代品。)

与游戏物理相关的主要困难是为每个精灵创建合适的多边形。 换句话说,创建一些形状不明显的蜜蜂或花朵的多边形表示。 甚至不要尝试手动执行此操作 - 始终使用特殊应用程序,其中有很多。 有些甚至非常……异国情调——比如用 Inkspace 创建矢量蒙版,然后将它们导入到游戏中。

对于我自己的无尽跑步游戏开发,我创建了一个工具来自动化这个过程,我称之为 Andengine Vertex Helper。 顾名思义,它最初是为 Andengine 框架设计的,尽管它现在可以与多种格式一起正常工作。

在我们的例子中,我们需要使用 plist 模式:

 <real>%.5f</real><real>%.5f</real>

接下来,我们创建一个带有对象描述的 plist 文件:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>jet_ant</key> <dict> <key>vertices</key> <array> <real>-0.18262</real><real>0.08277</real> <real>-0.14786</real><real>-0.22326</real> <real>0.20242</real><real>-0.55282</real> <real>0.47047</real><real>0.41234</real> <real>0.03823</real><real>0.41234</real> </array> </dict> </dict> </plist>

还有一个对象加载器:

 - (void)createBodyAtLocation:(CGPoint)location{ float mass = 1.0; body = cpBodyNew(mass, cpMomentForBox(mass, self.sprite.contentSize.width*self.sprite.scale, self.sprite.contentSize.height*self.sprite.scale)); body->p = location; cpSpaceAddBody(space, body); NSString *path =[[NSBundle mainBundle] pathForResource:@"obj _descriptions" ofType:@"plist"]; // <- load plist NSDictionary *objConfigs = [[[NSDictionary alloc] initWithContentsOfFile:path] autorelease]; NSArray *vertices = [[objConfigs objectForKey:namePrefix] objectForKey:@"vertices"]; shape = [ChipmunkUtil polyShapeWithVertArray:vertices withBody:body width:self.sprite.contentSize.width height:self.sprite.contentSize.height]; shape->e = 0.7; shape->u = 1.0; shape->collision_type = OBJ_COLLISION_TYPE; cpSpaceAddShape(space, shape); }

要测试精灵如何对应于它们的物理身体,请参见此处。

好多了,对吧?

总之,尽可能自动化。 即使是简单的脚本也可以为您节省大量时间。 而且重要的是,这段时间可以用于编程而不是鼠标点击。 (为了获得额外的动力,这里有一个代币 XKCD。)

应用内结算

游戏中收集的吹球作为应用内货币,允许用户为他们的蜜蜂购买新皮肤。 但是,这种货币也可以用真钱购买。 关于应用内计费,需要注意的重要一点是您是否需要执行服务器端检查以确保购买有效性。 由于所有可购买的商品在游戏性方面基本相同(只是改变了蜜蜂的外观),因此无需执行服务器检查购买有效性。 但是,在许多情况下,您肯定需要这样做。

更多信息,Ray Wenderlich 拥有完美的应用内计费教程。

使用 Game Center 进行多人游戏

在手机游戏中,社交不仅仅是添加 Facebook 的“赞”按钮或设置排行榜。 为了让游戏更精彩,我实现了多人游戏版本。

它是如何工作的? 首先,两个玩家使用 iOS 游戏中心的实时匹配进行连接。 由于玩家实际上是在玩同一个无限奔跑游戏,因此只需要一组游戏对象。 这意味着一个玩家的实例需要生成对象,而另一个玩家的实例将读取它们。 换句话说,如果两个玩家的设备都在生成游戏对象,则很难同步体验。

考虑到这一点,在建立连接后,双方玩家都会互相发送一个随机数。 编号较大的玩家充当“服务器”,创建游戏对象。

你还记得关于分块世界生成的讨论吗? 我们有两个子层,一个从 0 到 BUF_LEN,另一个从 BUF_LEN 到 2*BUF_LEN? 这种架构并不是偶然使用的——它必须在延迟的网络上提供流畅的图形。 当生成对象的一部分时,它会被打包到一个 plist 中并发送给其他玩家。 缓冲区足够大,即使网络延迟也可以让第二个玩家玩。 双方玩家以半秒的时间互相发送他们当前的位置,同时也立即发送他们的上下动作。 为了平滑体验,位置和速度每 0.5 秒通过平滑动画进行校正,因此在实践中看起来其他玩家正在逐渐移动或加速。

关于多人无尽奔跑游戏当然还有更多的考虑因素,但希望这能让你对所涉及的挑战类型有所了解。

改进空间

游戏永远不会结束。 诚然,有几个方面我想改进自己的,即:

  1. 控制问题:对于喜欢滑动的玩家来说,点击通常是一种不直观的手势。
  2. 使用 CCMoveBy 动作移动世界层。 当世界层的速度恒定时,这很好,因为 CCMoveBy 动作是用 CCRepeatForever 循环的:

     -(void) infiniteMove{ id actionBy = [CCMoveBy actionWithDuration: BUFFER_DURATION position: ccp(-BUFFER_LENGTH, 0)]; id actionCallFunc = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)]; id actionSequence = [CCSequence actions: actionBy, actionCallFunc, nil]; id repeateForever = [CCRepeatForever actionWithAction:actionSequence]; [self.bufferContainer runAction:repeateForever]; }

    但后来,我添加了世界速度增加,使游戏更难进行:

     -(void) infiniteMoveWithAccel { float duration = BUFFER_DURATION-BUFFER_ACCEL*self.lastBufferNumber; duration = max(duration, MIN_BUFFER_DURATION); id actionBy = [CCMoveBy actionWithDuration: duration position: ccp(-BUFFER_LENGTH, 0)]; id restartMove = [CCCallFunc actionWithTarget:self selector:@selector(infiniteMoveWithAccel)]; id fillBuffer = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)]; id actionSequence = [CCSequence actions: actionBy, restartMove, fillBuffer, nil]; [self.bufferContainer runAction:actionSequence]; }

    此更改导致动画在每次动作重新启动时破灭。 我试图解决这个问题,但无济于事。 但是,我的 beta 测试人员没有注意到这种行为,所以我推迟了修复。

  3. 一方面,在使用 Game Center 或运行自己的游戏服务器时,无需编写自己的多人游戏授权。 另一方面,它使得创建机器人变得不可能,这是我可能想要改变的。

结论

创建自己的独立无限亚军游戏可能是一种很棒的体验。 一旦您进入该过程的发布步骤,当您将自己的创作发布到野外时,这会是一种美妙的感觉。

审查过程可能从几天到几周不等。 更多信息,这里有一个有用的网站,它使用众包数据来估计当前的审查时间。

此外,我建议使用 AppAnnie 检查有关 App Store 中所有应用程序的各种信息,并且注册一些分析服务(如 Flurry Analytics)也会很有帮助。

如果这款游戏引起了您的兴趣,请务必在商店中查看 Bee Race。