SceneKit框架详细解析(五) —— 基于SceneKit的简单游戏示例的实现(四)

版本记录

版本号 时间
V1.0 2018.10.20 星期六

前言

SceneKit使用高级场景描述创建3D游戏并将3D内容添加到应用程序。 轻松添加动画,物理模拟,粒子效果和逼真的基于物理性的渲染。接下来这几篇我们就详细的解析一下这个框架。感兴趣的看下面几篇文章。
1. SceneKit框架详细解析(一) —— 基本概览(一)
2. SceneKit框架详细解析(二) —— 基于SceneKit的简单游戏示例的实现(一)
3. SceneKit框架详细解析(三) —— 基于SceneKit的简单游戏示例的实现(二)
4. SceneKit框架详细解析(四) —— 基于SceneKit的简单游戏示例的实现(三)

开始

在这部分中,您将学习如何通过Scene Kit渲染循环使几何体逐渐产生。

在上一篇中,您为生成的对象启用了基本物理,并施加了冲击以将其踢到空中。最终,由于重力的模拟效应,物体倒下并消失。

尽管效果很好,但是产生多个彼此碰撞的物体会更加cool。这肯定会让兴奋因素上升一个档次!

现在,你的游戏只调用一次spawnShape()。要生成多个对象,您需要重复调​​用spawnShape()

正如您在之前的教程中所了解到的,SceneKit使用SCNView对象渲染场景的内容。 SCNView有一个delegate属性,您可以将其设置为符合SCNSceneRendererDelegate协议的对象;然后,当每个帧的动画和渲染过程中发生某些事件时,SCNView将调用该委托上的方法。

通过这种方式,您可以轻松进入SceneKit渲染场景的每个帧时所采用的步骤。这些渲染步骤构成了渲染循环。

那么 - 这些步骤究竟是什么?好吧,这是渲染循环的快速细分:

这是命运之轮吗? 不,它只是渲染循环的九个步骤的描述。在一个以60 fps运行的游戏中,所有这些步骤都会按顺序运行 - 你猜对了 - 每秒60次。

这些步骤始终按以下顺序执行,这使您可以将游戏逻辑准确地注入所需的位置:

  • 1) Update - 更新:视图在其委托上调用renderer(_: updateAtTime:)。这是放置基本场景更新逻辑的好地方。
  • 2) Execute Actions & Animations - 执行动作和动画:SceneKit执行所有动作并执行所有附加动画到场景图中的节点。
  • 3) Did Apply Animations - 应用动画:视图调用其委托的renderer(_: didApplyAnimationsAtTime:)。此时,场景中的所有节点都根据应用的动作和动画完成了一个帧的动画。
  • 4) Simulates Physics - 模拟物理:SceneKit将物理模拟的一个步骤应用于场景中的所有物理实体。
  • 5) Did Simulate Physics - 完成模拟物理:视图在其委托上调用renderer(_: didSimulatePhysicsAtTime:)。此时,物理模拟步骤已完成,您可以添加任何依赖于上面应用的物理的逻辑。
  • 6) Evaluates Constraints - 评估约束:SceneKit评估并应用约束,这些约束是您可以配置的规则,以使SceneKit自动调整节点的转换。
  • 7) Will Render Scene - 将渲染场景:视图在其委托上调用renderer(_: willRenderScene: atTime:)。此时,视图即将渲染场景,因此应在此处执行任何最后一分钟的更改。
  • 8) Renders Scene In View - 在视图中渲染场景:SceneKit在视图中渲染场景。
  • 9) Did Render Scene - 完成渲染场景:最后一步是视图调用其委托的renderer(_: didRenderScene: atTime:)。这标志着渲染循环的一个循环的结束;你可以把任何游戏逻辑放在这里,需要在进程重新开始之前执行。

因为渲染循环是一个循环,所以它是调用spawnShape()的最佳位置。你的工作是决定在哪里注入spawn逻辑。


The Renderer Delegate - 渲染代理

现在是时候将这个很酷的功能用于游戏中。

首先,通过将以下内容添加到GameViewController.swift的底部,使GameViewController类符合SCNSceneRendererDelegate协议:

// 1
extension GameViewController: SCNSceneRendererDelegate {
  // 2
  func renderer(_ renderer: SCNSceneRenderer, 
    updateAtTime time: TimeInterval) {
    // 3
    spawnShape()
  }
}

细分上面的代码:

  • 1) 这为GameViewController添加了一个扩展,遵循协议,并允许您在单独的代码块中维护协议方法。
  • 2) 这增加了renderer(_: updateAtTime:)协议方法的实现。
  • 3) 最后,调用spawnShape()在委托方法中创建一个新形状。

这将为您提供第一次挂钩到SceneKit的渲染循环。 在视图可以调用此委托方法之前,首先需要知道GameViewController将充当视图的委托。

通过在setupView()的底部添加以下行来完成此操作:

scnView.delegate = self

这会将SceneKit视图的代理设置为self。 现在,视图可以在渲染循环运行时调用您在GameViewController中实现的委托方法。

最后,通过删除viewDidLoad()中对spawnShape()的单个调用,稍微清理一下代码;因为你现在在渲染循环中调用方法,所以不再需要它了。

构建并运行

游戏开始并产生了大量的物体,导致了碰撞物体的撞击。

那么这里发生了什么? 由于您在渲染循环的每个更新步骤中调用spawnShape(),因此您每秒会生成60个对象 - 如果您运行的设备可以以60 fps支持您的游戏。 但是功能较弱的设备(包括模拟器)无法支持该帧速率。

随着游戏的运行,您会注意到帧速率的快速下降。 图形处理器不仅必须处理越来越多的几何体,物理引擎必须处理越来越多的碰撞,这也会对帧速率产生负面影响。

目前情况有点失控,因为你的游戏在所有设备上的表现都不尽如人意。


Spawn Timers - 产生定时器

要使设备之间的游戏体验保持一致,您需要利用时间。 不,我不是说花更多的时间来写你的游戏!相反,你需要使用时间的推移作为设备间的一个常数;这使您可以以一致的速率设置动画,无论设备可以支持的帧速率如何。

定时器是许多游戏中的常用技术。 还记得传递给update delegate方法的updateAtTime参数吗? 该参数表示当前系统时间。 如果您监视此参数,则可以计算游戏的已用时间等内容,或者每三秒生成一个新对象,而不是尽可能快无限的生产。

Geometry Fighter将使用一个简单的计时器以任意处理器应该能够处理的随机时间间隔生成对象。

将以下属性添加到GameViewControllercameraNode下面:

var spawnTime: TimeInterval = 0

您将使用它来确定生成另一个形状之前的时间间隔。

要修复连续生成,请使用以下内容替换整个renderer(_: updateAtTime:)

// 1
if time > spawnTime {
  spawnShape()

  // 2
  spawnTime = time + TimeInterval(Float.random(min: 0.2, max: 1.5))
}

下面进行细分:

  • 1) 您检查time(当前系统时间)是否大于spawnTime。 如果是这样,产生一个新的形状;否则,什么也不做。
  • 2) 生成对象后,下次更新spawnTime以生成新对象。 下一个生成时间只是当前时间增量随机量。 由于TimeInterval以秒为单位,因此您会在当前时间之后的0.2秒到1.5秒之间生成下一个对象。

构建并运行,检查你的计时器的差异:

事情看起来更容易管理,形状随机产生。 但是,你难道不是很好奇所有这些物体掉出视线后会发生什么?


Removing Child Nodes - 删除子节点

spawnShape()不断地将新的子节点添加到场景中 - 但是它们永远不会被移除,即使它们不在视线之外。 SceneKit做了很棒的工作,让事情尽可能长时间保持平稳运行,但这并不意味着你可以忘记你的子节点。

要以最佳性能级别和帧速率运行,您必须删除看不见的对象。 还有什么比这更好的地方 - 渲染循环! 很好的处理地方,不是吗?

一旦对象达到其边界的极限,您应该将其从场景中删除。

将以下内容添加到GameViewController类的末尾,就在spawnShape()下面:

func cleanScene() {
  // 1
  for node in scnScene.rootNode.childNodes {
    // 2
    if node.presentation.position.y < -2 {
      // 3
      node.removeFromParentNode()
    }
  }
}

这是上面代码中所做的事情:

  • 1) 首先,您只需创建一个for循环,逐步遍历场景根节点中的所有可用子节点。
  • 2) 由于此时物理模拟正在进行中,您不能简单地查看对象的位置,因为这反映了动画开始前的位置。 SceneKit在动画期间维护对象的副本并进行,直到动画完成。 一开始理解这是一个奇怪的概念,但你不久就会看到它是如何工作的。 要在动画制作动画时获取对象的实际位置,请使用presentationNode属性。 这纯粹是只读的 - 不要试图修改此属性的任何值!
  • 3) 这行代码使一个对象不存在。

要使用上面的方法,在renderer(_: updatedAtTime:)内部if语句之后调用cleanScene()

cleanScene()

还有最后一件事要补充。 默认情况下,如果没有要播放的动画,SceneKit会进入“ paused”状态。 要防止这种情况发生,您必须在SCNView实例上启用playing属性。

将以下代码行添加到setupView()的底部:

scnView.isPlaying = true

这会强制SceneKit视图进入无限play的模式。

构建并运行;随着你的物体开始下降,捏合缩小,看看它们在哪里消失:

落在较低y边界上的对象(在上面的屏幕截图中用红线表示)将从场景中删除。 这比将所有物体放在设备的暗凹处更好。

后记

本篇主要讲述了基于SceneKit的简单游戏示例的实现,感兴趣的给个赞或者关注~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容