
在本教程的前三部分中,我们使用了简单明了的方法,只需使用SCNAction即可运行游戏。 现在改变了。 在专业游戏开发中,有一种叫做游戏循环的东西。 尽管SceneKit本身的方法略有不同,但我们也可以实现游戏循环。 因此,我们在本教程的这一章中会稍微更改游戏的体系结构和设计,并使其更接近真实游戏所需的内容。
游戏循环
什么是游戏循环? 定义很简单:
在游戏过程中,游戏循环会连续运行。 每次循环,它都会处理用户输入而不会阻塞,更新游戏状态并渲染游戏。 它跟踪时间的流逝,以控制游戏的速度。
现代图形UI应用程序与此惊人地相似。 您的应用程序只是等待那里什么都不做,直到您按下一个键或单击某项为止:
while (true) {
Event* event = waitForEvent() dispatchEvent(event)
}
游戏循环看起来像最简单的形式:
while (true) {
processInput()
update()
render()
}
SceneKit已经为我们做到了,并允许在框架中的多个时间通过设置委托并实现相关方法进行挂接。

在我们的游戏中,我们使用
func renderer(_ renderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: TimeInterval)
一旦在游戏中使用了物理原理,就不要在游戏循环(SceneKit中称为渲染循环)之前就迷上它,这一点很重要。 否则会导致奇怪的行为。 同样重要的是要知道:
如果有事情要做,SceneKit只会每帧调用一次所有这些方法。
这意味着,如果所有动作都已完成,但两帧之间没有任何变化,SceneKit将停止调用它。 因此,我们的游戏循环(在GameViewController.swift中定义)如下所示:
func renderer(_ renderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: TimeInterval) {
if _level != nil {
_level.update(atTime: time)
}
renderer.loops = true
}
它将工作委托给GameLevel ,我们在其中完成所有重要的工作。 让我们一起检查我们在那里执行的步骤。
func update(atTime time: TimeInterval) {
if self.state != .play {
return
}
这是本章介绍的第一件事: 游戏状态 。 游戏在很大程度上像状态机一样工作,而枚举中定义的不同游戏语句如下:
- 初始化 :游戏(关卡类)刚刚创建但尚未开始
- 玩 :游戏目前正在玩
- 赢 :比赛以赢游戏而告终
- 松散 :游戏以松散游戏结束
- 已停止 :游戏已停止(所有操作已终止)
我们为什么要这样做? 好吧,显然,我们必须在游戏的不同状态下在游戏循环中做不同的事情。 实现此目的的最简单方法是引入状态变量。 当然,状态机可能要复杂得多,但是对于我们的教程而言,这已经足够了。
var missedRings = 0
for object in _gameObjects {
object.update(atTime: time, level: self)
if let ring = object as? Ring {
if (ring.presentation.position.z + 5.0) <
_player!.presentation.position.z {
if ring.state == .alive {
missedRings += 1
}
}
}
}
这是我们游戏循环中一个有趣的部分,本章对此进行了介绍。 我们不仅计算飞过谷底的环,还算出我们错过的环。 相比计算飞行槽中的环数,这要复杂一些,因为SceneKit对此没有帮助。
首先,您会看到一系列新的游戏对象。
private var _gameObjects = Array()
在此,我们保留了所有游戏对象(如戒指,玩家和新的让分盘),因此我们可以轻松地访问它们。 要查看是否通过了没有飞槽的环,我们只需将对象的z坐标与播放器的z坐标进行比较。 如果较小,那么我们已经通过了。 除此之外,我们还检查状态(请参阅下文),以查看环是否仍在运行(意味着还没有飞过谷)。 我们在每个帧中都执行此操作,如果错过的振铃次数与前一帧不同,则将更新HUD。
if missedRings > _missedRings {
_missedRings = missedRings
_hud?.missedRings = _missedRings
}
我们还在这里测试游戏是否完成。 之前,当玩家对象的SCNAction结束时,我们才结束游戏。 现在我们使用一个特定的标准:当我们通过所有环(通过飞槽或错过环)时,游戏结束。 如果发生这种情况,我们将更改游戏状态并向用户显示一条消息。
if _missedRings + _touchedRings == _numberOfRings {
if _missedRings < 3 {
_hud?.message("YOU WIN", information: "- Touch to restart - ")
}
else {
_hud?.message("TRY TO IMPROVE",
information: "- Touch to restart - ")
}
self.state = .win
}
这就是游戏循环和实现游戏玩法的全部秘密。 当然,我们可以将其扩展更多的标准和状态,但是逻辑保持不变。
到目前为止,我们还没有涉及到一个主题。 现在,我们还有一个标准可以更改游戏状态,但发生在游戏循环之外。
func touchedHandicap(_ handicap: Handicap) {
_hud?.message("GAME OVER", information: "- Touch to restart - ")
self.state = .loose
}
每当我们碰到障碍物( 撞到 )时,游戏立即结束。 立即? 并非如此……正如您所看到的,我们只是向用户显示一条消息,然后将游戏的状态更改为“ 松散” 。 这意味着,在下一帧中,我们不再进行前面描述的工作,因为self.state != .play
。 这个概念也很重要。
永远不要直接在动作中或在游戏循环外执行任何操作
只需更改游戏状态,然后在以后的游戏循环中进行操作即可。 否则会导致图形,物理和/或多线程问题。
游戏对象
在上一个主题中,我们已经介绍了本部分最重要的事情。 但是还有一件事: GameObject这个新类本身并不引人注目 ,但它是游戏开发中的重要概念。 有了这个,我们对所有类型的游戏对象都有一个通用的基类,因此我们可以将它们全部存储在一个列表中,并在每一帧中对其进行迭代。 现在,我们不仅拥有游戏本身的状态,而且还拥有每个游戏对象的状态。
- 初始化:游戏对象刚刚创建
- 活着的:游戏对象还活着
- 死亡:游戏对象已死亡,被杀死,被触摸(无论您想要叫什么)
- 停止:最后但并非最不重要的停止(所有操作和资源的释放都结束了)
当然可以有更多的状态。 例如, 攻击状态(当玩家处于战斗模式时)或空闲状态 (当对象刚刚等待时)。 但我希望你明白这一点。 现在,每个游戏对象都可以通过拥有自己的状态和行为来独立于另一个对象进行操作。
更多细节
游戏循环和(基本类) GameObject是本章中最重要的内容。 但是像往常一样,到目前为止我还没有涉及一些小事情
差点类
虽然我们已经在游戏循环主题中讨论了此类,但我没有描述它的工作原理。 有一个很好的理由:它也是GameObject的派生类,其行为类似于环。 有一个重要的区别:游戏结束就是玩家触摸它。 但是您现在应该能够理解概念,并且还可以将自己的游戏对象添加到游戏中。
HUD扩展
HUD本质上与上一章相同,但是我们现在在其中显示更多信息。 另外,只要这些信息发生更改,我们就会为它们设置动画。
天空盒
如您所见, GameLevel.swift中还有另一行代码,如下所示:
self.background.contents = UIImage(named: "art.scnassets/skybox")
SceneKit允许我们设置用作背景的图像。 但是背景表现得像真实的天空。 这意味着,根据我们朝哪个方向看,外观会有所不同。
为了简化教程中的代码,我还向项目添加了一些扩展和类。 我自己在不同的游戏中使用它们来简化编程时的工作。
- RB + CGPoint.swift :使用点时的一些附加数学
- RB + SKAction.swift :HUD中使用的一些不错的动作(稍后将进行扩展)
- RBLog.swift :请参见下文
记录中
我还添加了一个简单的类,即日志记录类RBLog 。 它或多或少是Swift中print
功能的包装。 它的主要目的是在控制台中显示一些信息。 通过按顺序显示游戏的整体行为来测试游戏玩法时,这很有用。 调试时可能很难(或不可能)看到的东西。 我在项目中使用了类似的功能,但功能更多,稍后还将在本教程中添加这些功能。
别忘了……再次链接到源代码
教程章节:
–第1部分–建立地形
–第2部分–创建一个真实的玩家游戏对象
–第3部分–为您的地形增添生命
–第4部分–实施游戏循环
–第5部分–使用CoreMotion平稳地飞行
–第六部分–完成游戏(缺少的部分)
–第7部分– ARKit:在您的环境中玩
–第8部分–多平台:我们在macOS上的游戏
–第9部分– tvOS:出色的游戏平台
–第10部分-高级SceneKit(有待改进)
最初于 2017年12月26日 发布在 https://rogerboesch.github.io/scenekit/tutorial/games/2017/12/26/scenekit-zerotohero-IV.html 。