版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.10.20 星期六 |
前言
SceneKit
使用高级场景描述创建3D游戏并将3D内容添加到应用程序。 轻松添加动画,物理模拟,粒子效果和逼真的基于物理性的渲染。接下来这几篇我们就详细的解析一下这个框架。感兴趣的看下面几篇文章。
1. SceneKit框架详细解析(一) —— 基本概览(一)
2. SceneKit框架详细解析(二) —— 基于SceneKit的简单游戏示例的实现(一)
3. SceneKit框架详细解析(三) —— 基于SceneKit的简单游戏示例的实现(二)
4. SceneKit框架详细解析(四) —— 基于SceneKit的简单游戏示例的实现(三)
5. SceneKit框架详细解析(五) —— 基于SceneKit的简单游戏示例的实现(四)
开始
在这部分中,您将为游戏添加一些很酷的粒子系统并将其包装起来!
想象自己在电影院里手持爆米花;在屏幕上,一个来自“速度与激情”的坏人将他咆哮的高性能高速赛车撞到一辆高度不稳定的油轮上,这辆油轮在一场巨大的死亡和大屠杀火球中爆炸。
现在,想想同样的场景,但没有大规模的火球爆炸。 你几乎可以感受到世界各地观众的集体失望。
就像好莱坞大片一样,你的游戏需要特效来提升兴奋程度。 这些特殊效果以所谓的粒子系统(particle systems)
的形式出现。 您可以使用粒子系统来实现多种效果,从移动星空,燃烧火箭助推器,到雨雪,到金属火花 - 甚至是大型爆炸火球!
现在是时候看看如何将这些整洁的特效添加到Geometry Fighter
中。
Particle Systems - 粒子系统
在SceneKit
中,SCNParticleSystem
管理场景图中粒子的创建,动画和移除。
粒子本质上是一个小图像精灵。粒子系统不会将单个粒子添加到场景图本身中,因此您无法直接访问每个粒子;粒子系统管理粒子,包括它们的外观,大小和位置。
但是,您可以通过修改粒子系统上的各种属性来影响粒子系统,例如:
- Appearance - 外观:系统的每个粒子都可以渲染为单个图像或动画图像序列。您可以调整生成的粒子的大小,色调颜色,混合模式和其他渲染参数。
-
Life Span - 寿命:系统使用粒子发射器
(particle emitter)
,它产生每个单独的粒子。粒子的寿命决定了它在场景中保持可见的时间。 - Emitter behavior - 发射器行为:您可以控制发射器的各种参数,例如粒子生成的位置和生成率。
- Variation - 变化:在粒子系统中引入变体可以使其看起来更多或更少,随机。
- Movement - 移动:您可以调整粒子在生成后的移动方式。粒子使用简化的物理模拟来加速性能,但粒子仍然可以与物理引擎管理的对象进行交互。
1. Creating a Trail Particle System - 创建跟踪粒子系统
在将粒子系统添加到游戏世界之前,您需要一个组来容纳此粒子系统以保持项目的有序性。 右键单击GeometryFighter
组并选择New Group
,如下所示:
将此新组命名为Particles
。 右键单击该组,然后选择New File
。 选择iOS/Resource/SceneKit Particle System
,然后单击Next
继续:
在下一个屏幕上,选择Particle system template
的Fire
,然后单击Next
。 将文件另存为Trail.scnp
,然后单击Create
。 完成后,您应该在场景中看到以下内容:
以下是上面注释的编辑器各个部分的快速概述:
- 1) Center stage - 中心面板:中心拥有您的粒子系统的直观表示。您可以使用它来了解最终结果的样子。
- 2) Gesture controls - 手势控件:您可以使用手势来操作相机视图;它类似于你在场景中移动相机的方式。
- 3) Pause/Play button - 暂停/播放按钮:您可以暂停粒子系统模拟并更详细地检查它。暂停时,停按钮变为播放按钮,您可以使用该按钮恢复模拟。
- 4) Restart button - 重启按钮:这使您可以从头开始重新启动粒子模拟。
- 5) Camera reset button - 相机重置按钮:使用此按钮将相机视图重置为默认位置。
- 6) Color button - 颜色按钮:这可让您为编辑器设置合适的背景颜色;例如,在黑色背景下看雪花更容易。
- 7) Particle system properties - 粒子系统属性:选择
Attributes Inspector
会显示一系列属性,您将在下一节中了解这些属性。
2. Configuring the Trail Particle System - 配置跟踪粒子系统
在本节中,您将深入了解编辑器右侧的粒子系统属性。 在浏览每个部分时,将屏幕截图中每个设置的值复制到您自己的粒子系统中。
在更改每个属性时,请密切关注粒子系统编辑器;您将看到每个参数如何影响粒子系统的行为。 稍后,您将在游戏中使用此粒子效果来创建从生成的对象落下的粒子痕迹。
3. Emitter Attributes - 发射器属性
粒子发射器是所有粒子产生的原点。 以下是发射器属性:
- Birth rate - 出生率:控制粒子的排放率。将其设置为25,指示粒子引擎以每秒25个粒子的速率生成新粒子。
- Warmup duration - 预热持续时间:模拟在渲染粒子之前运行的秒数。这可以用于在开始时显示充满粒子的屏幕,而不是等待粒子填满屏幕。将其设置为0,以便可以从一开始就查看模拟。
-
Location - 位置:相对于形状的位置,发射器产生其粒子的位置。将其设置为
Vertex
,这意味着粒子将使用几何顶点作为生成位置。 -
Emission space - 发射空间:发射粒子所在的空间。将其设置为
World Space
,以便将发射的粒子发射到世界空间,而不是对象节点本身的局部空间。 - Direction mode - 方向模式:控制衍生粒子的传播方式;你可以将它们全部移动到一个恒定的方向,让它们从形状表面径向向外移动,或者只是随机移动它们。将其设置为常量,使所有发射的粒子保持恒定方向。
-
Direction - 方向:指定方向模式恒定时使用的初始方向向量。将此向量设置为
(x:0,y:0,z:0)
,将方向设置为空。 - Spreading angle - 扩散角度:随机化产生的粒子的发射角度。将其设置为0°,从而精确地在先前设定的方向上发射粒子。
- Initial angle - 初始角度:发射粒子的初始角度。将其设置为0°,因为这与零方向矢量无关。
-
Shape - 形状:发射粒子的形状。将形状设置为球体
(Sphere)
,因此使用球体形状作为几何体。 - Shape radius - 形状半径:此属性的存在取决于您使用的形状;对于球形发射器,这决定了球体的大小。将其设置为0.2,它定义了一个足够大的球体,以满足您的需要。
注意:请注意,某些属性有两个输入区域,其中一个区域旁边有一个
Δ=
符号(请参阅Birth Rate and Initial angle
)。第一个输入区域包含基值,Δ=
输入区域包含delta值。每次生成粒子时,它都使用基值加上范围内的随机值(-delta
值,+ delta值
)。这允许您为这些属性获得一些随机方差。
4. Simulation Attributes - 模拟属性
模拟属性管理粒子在其生命周期内的运动。 这使您无需使用物理引擎即可管理其移动:
- Life span - 寿命:以秒为单位指定粒子的寿命。将此值设置为1,以便单个粒子仅存在一秒钟。
- Linear velocity - 线速度:指定发射粒子的线速度。将此值设置为0,这样粒子就会产生没有方向或速度的粒子。
- Angular velocity - 角速度:指定发射粒子的角速度。将其设置为0,这样粒子就不会旋转。
-
Acceleration - 加速度:指定应用于发射粒子的力矢量。将其设置为
(x:0,y:-5,z:0)
- 这是向下矢量 - 一旦产生,以模拟粒子上的软重力效应。 - Speed factor - 速度系数:设置粒子模拟速度的乘数。将其设置为1可以正常速度运行模拟。
- Stretch factor - 拉伸因子:在运动方向上拉伸粒子的乘数。将其设置为0以不拉伸粒子图像。
5. Image Attributes - 图像属性
图像属性控制粒子的视觉方面。它们还控制着这些粒子的外观在其生命周期中如何变化:
-
Image - 图像:指定将用于渲染每个粒子的图像。选择
CircleParticle.png
图像,为粒子赋予其主要形状。 - Color - 颜色:设置指定图像的色调。将颜色设置为白色,为粒子系统提供白色的基色。
- Animate color - 动画颜色:使粒子在其生命周期内改变颜色。取消选中此项,因为粒子颜色根本不会改变。
-
Color variation - 颜色变化:为粒子颜色添加一点随机性。您可以将其设置为
(h:0,s:0,b:0,a:0)
,因为粒子颜色不会变化。 - Size - 大小:指定粒子的大小。将此值设置为0.1,以便发射的粒子尺寸较小。
6. Image Sequence Attributes - 图像序列属性
要为粒子创建动画图像,可以将动画的每个帧排列到单个图像上的网格中(如游戏中的精灵表)。然后,您只需将该网格图像用作粒子发射器的图像。使用图像序列属性可以控制粒子的基本动画属性:
- Initial frame - 初始帧:设置动画序列的第一个从零开始的帧。 第0帧对应于网格中的左上图像。 您正在使用单帧图像,因此将其设置为0。
- Frame rate - 帧速率:以每秒帧数控制动画的速率。 将其设置为0,因为这仅适用于使用包含多个帧的图像。
-
Animation - 动画:指定动画序列的行为。 重复循环动画,
Clamp
只播放一次,Auto Reverse
从开始到结束播放,然后再播放。 你可以将它保留在Repeat
上,因为这在使用单帧图像时无关紧要。 -
Dimensions - 尺寸:指定动画网格中的行数和列数。 将其设置为
(Rows: 1, Columns: 1)
,因为您使用的是单帧图像。
7. Rendering Attributes - 渲染属性
渲染属性定义渲染阶段如何处理粒子:
-
Blending - 混合:在将粒子绘制到场景中时指定渲染器的混合模式。 将其设置为
Alpha
,它将使用图像Alpha通道信息进行透明。 -
Orientation - 方向:控制粒子的旋转。 将此设置为
Billboard screen-aligned
,这将使扁平粒子始终面向摄像机视图,因此您不会注意到粒子确实是平面图像。 -
Sorting - 排序:设置粒子的渲染顺序。 此属性与混合模式一起使用,并影响混合的应用方式。 将其设置为
None
,以便粒子系统不会使用排序。 -
Lighting - 光照:控制
SceneKit
是否将光照应用于粒子。 取消选中此选项,粒子系统将忽略场景中的任何灯光。
8. Physics Attributes - 物理属性
物理属性允许您指定粒子在物理模拟中的行为方式:
- Affected by gravity - 受重力影响:使场景的重力影响粒子。 取消选中此项,因为您不希望粒子系统参与物理模拟。
- Affected by physics fields - 受物理场影响:导致场景中的物理场影响粒子。 取消选中此项,因为您不希望物理场对粒子产生影响。
- Die on Collision - 死于碰撞:让场景中的物理体碰撞并破坏粒子。 取消选中此项,因为您不希望在与场景中的节点对象发生碰撞时删除粒子。
- Physics Properties - 物理属性:在物理模拟过程中控制粒子物理行为的基本物理属性。 您可以将所有这些保留为默认值,因为粒子系统不会使用它。
9. Life Cycle Attributes - 生命周期属性
生命周期属性可让您控制粒子系统的整个生命周期:
- Emission duration - 发射持续时间:控制发射器发射新粒子的时间长度。 将其设置为1,这将激活粒子发射器,总长度为1秒。
- Idle duration - 空闲持续时间:循环粒子系统在指定的发射持续时间内发射粒子,然后在指定的空闲持续时间内空闲,之后循环重复。 将其设置为0,这样粒子系统将只发射一次。
-
Looping - 循环:指定粒子系统是否发射一次粒子,如爆炸,或连续发射,如火山。 将其设置为
Loops continuously
,以便发射器在再次从场景中移除之前尽可能长时间地发射。
对于单个粒子系统,需要考虑很多属性,但这可以让您获得很多控制以获得您正在寻找的特别的特效。
如果您努力将屏幕截图中的值复制到粒子系统,您将拥有一个代表以下效果的系统:
如果您的不是这样,请尝试旋转相机。 它也可能有助于将背景颜色更改为深蓝色,就像您在此处看到的那样,可以更容易地看到粒子系统。
现在终于可以为游戏添加炫酷粒子效果了。 将以下内容添加到GameViewController.swift
类:
// 1
func createTrail(color: UIColor, geometry: SCNGeometry) ->
SCNParticleSystem {
// 2
let trail = SCNParticleSystem(named: "Trail.scnp", inDirectory: nil)!
// 3
trail.particleColor = color
// 4
trail.emitterShape = geometry
// 5
return trail
}
以下是上面发生的事情:
- 1) 这定义了
createTrail(_:geometry :)
,它接受color
和geometry
参数来设置粒子系统。 - 2) 这将从您之前创建的文件加载粒子系统。
- 3) 在这里,您可以根据传入的参数修改粒子的色调颜色。
- 4) 这使用
geometry
参数指定发射器的形状。 - 5) 最后,这将返回新创建的粒子系统。
此方法可帮助您创建SCNParticleSystem
的实例,但仍需要将粒子系统添加到生成的形状对象。
请注意,createTrail(_:geometry :)
接受一个颜色参数并使用它来为粒子着色。 您将粒子系统的颜色设置为与形状的颜色相同。
在spawnShape()
中找到在其中设置形状的材质漫反射内容的那一行,然后将其拆分,使随机颜色存储在常量中,如下所示:
let color = UIColor.random()
geometry.materials.first?.diffuse.contents = color
接下来,在将力强加到geometryNode
的物理体之后,在spawnShape()
中进一步向下添加以下行:
let trailEmitter = createTrail(color: color, geometry: geometry)
geometryNode.addParticleSystem(trailEmitter)
这使用createTrail(_:geometry :)
创建粒子系统并将其附加到geometryNode
。
构建并运行,看看你的辛勤工作!
它看起来很棒 !
Heads-up Displays
在这个简短的部分中,您将使用Game Utils
快速添加一个小小的抬头显示器来显示您的玩家的剩余生命,最佳分数和当前分数。 幕后代码使用SpriteKit
标签,并使用标签的输出作为平面的纹理。 这是一种强大的技术!
将以下新属性添加到gamewnController.swift
,就在spawnTime
下面:
var game = GameHelper.sharedInstance
这使您可以快速访问GameHelper
共享实例,该实例包含一组方法来为您完成繁重的工作。
将以下方法添加到GameViewController
的底部,在createTrail()
下面:
func setupHUD() {
game.hudNode.position = SCNVector3(x: 0.0, y: 10.0, z: 0.0)
scnScene.rootNode.addChildNode(game.hudNode)
}
在这里,您可以使用帮助程序库中的game.hudNode
。 您设置HUD节点的位置并将其添加到场景中。
接下来,您需要从某个地方调用setupHUD()
。 将以下行添加到viewDidLoad()
的底部:
setupHUD()
现在你有一个抬头显示器,你需要保持它是最新的。 将以下调用game.updateHUD()
添加到到renderer(_: updateAtTime:)
的底部。
game.updateHUD()
构建并运行,您将在屏幕顶部看到您的显示,如下所示:
你的游戏现在有一个漂亮的小HUD,有生命计数器,高分和当前分数。
好的,抬头显示很好,但现在是时候为你的游戏添加一些互动了。
Touch Handling - 触摸处理
通常情况下,在您的应用中启用触摸并不像人们希望的那样简单。
第一步是了解触摸处理在3D中的工作原理。 下图显示了场景侧视图中的触摸点以及SceneKit
如何将该触摸点转换为3D场景以确定您正在触摸的对象:
那么您采取了哪些步骤来处理用户的触摸事件?
- Get touch location - 获取触摸位置:首先,您需要在屏幕上获取用户触摸的位置。
-
Convert to view coordinates - 转换为视图坐标:之后,您需要将该触摸位置转换为相对于呈现场景的
SCNView
实例的位置。 - Fire a ray for a hit test - 为命中测试射出一条光线:一旦你建立了一个相对于视图的触摸位置,SceneKit就可以通过向你的场景发射一条光线(不是,不是那个Ray!)来为你执行命中测试并返回一个 与光线相交的对象列表。
1. Naming Nodes - 命名节点
在激活触摸死亡之前,您需要一种方法来识别每个衍生对象。 最简单的方法是给他们起名字。
将粒子系统添加到geometryNode
后立即将以下内容添加到spawnShape()
:
if color == UIColor.black {
geometryNode.name = "BAD"
} else {
geometryNode.name = "GOOD"
}
秉承古老西方电影黑帽恶魔的精神,你将绰号BAD
分配给黑色物体,并将GOOD
分配给所有其他物体。
2. Adding Touch Handling - 添加触摸处理
接下来,您需要编写一个方法,以便在检测到用户已触发特定节点时稍后调用该方法。
将以下方法添加到GameViewController
的底部,在setupHUD()
下面:
func handleTouchFor(node: SCNNode) {
if node.name == "GOOD" {
game.score += 1
node.removeFromParentNode()
} else if node.name == "BAD" {
game.lives -= 1
node.removeFromParentNode()
}
}
该方法检查被触摸节点的名字;良好的节点会增加分数,而糟糕的(黑色)节点会将生命数量减少一个。 在任何一种情况下,您都会从屏幕中删除节点,因为它已被销毁。
3. Using the Touch Handler - 使用触控处理程序
要捕获用户的触摸,您将使用touchesBegan(_:withEvent :)
,每次玩家触摸屏幕时都会调用它。
要实现您自己的版本,请在GameViewController
中handleTouchFor(_ :)
下面添加以下内容:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 1
let touch = touches.first!
// 2
let location = touch.location(in: scnView)
// 3
let hitResults = scnView.hitTest(location, options: nil)
// 4
if let result = hitResults.first {
// 5
handleTouchFor(node: result.node)
}
}
下面进行细分:
- 1) 抓住第一个可用的
touch
。 如果玩家使用多个手指,则可以有多个。 - 2) 将触摸位置转换为相对于
scnView
坐标的位置。 - 3)
hitTest(_:options :)
为您提供了一个SCNHitTestResult
对象数组,这些对象表示从用户触摸的视图内的点开始并远离相机的光线的任何交叉点。 - 4) 检查命中测试的第一个结果。
- 5) 最后,您将第一个结果节点传递给您的触摸处理程序,这将增加您的分数 - 或者让您失去生命!
最后一步。 您不再需要摄像头控制,因此请更改setupView()
中的行,如下所示:
scnView.allowsCameraControl = false
构建并运行,准备好释放你的手指!
点击产生的物体,使它们分解在稀薄的空气中。
Challenge - 挑战
是时间到很酷的因素 - 什么比爆炸更酷? 绝对没有,对吧?
这将带您接受本教程的挑战,即创建另一个粒子系统并将其命名为Explode.scnp
。 看看你是否可以找出要修改的属性以使这些粒子爆炸。
效果看起来应该与此类似:
您可以使用以下图像作为粒子系统的起点:
Shaping Particle Explosions - 塑造粒子爆炸
现在您已经创建了爆炸粒子系统,您需要添加一些代码来使这些节点爆炸。 您将使用一些特殊属性来使爆炸与您触摸的任何节点具有相同的形状。
将以下内容添加到GameViewController
的底部,在touchesBegan(_:withEvent)
下面:
// 1
func createExplosion(geometry: SCNGeometry, position: SCNVector3,
rotation: SCNVector4) {
// 2
let explosion =
SCNParticleSystem(named: "Explode.scnp", inDirectory:
nil)!
explosion.emitterShape = geometry
explosion.birthLocation = .surface
// 3
let rotationMatrix =
SCNMatrix4MakeRotation(rotation.w, rotation.x,
rotation.y, rotation.z)
let translationMatrix =
SCNMatrix4MakeTranslation(position.x, position.y,
position.z)
let transformMatrix =
SCNMatrix4Mult(rotationMatrix, translationMatrix)
// 4
scnScene.addParticleSystem(explosion, transform: transformMatrix)
}
以下是上述代码的细分:
- 1)
createExplosion(_:position:rotation :)
有三个参数:geometry
定义粒子效果的形状,而position
和rotation
帮助将爆炸放入场景中。 - 2) 这会加载
Explode.scnp
并使用它来创建一个发射器。 发射器使用geometry
作为emitterShape
,因此粒子将从形状的表面发射。 - 3) 输入矩阵!不要被这三条线吓到;它们只是为
addParticleSystem(_:withTransform :)
提供了一个组合的旋转和位置(或转换)转换矩阵。 - 4) 最后,在
scnScene
上调用addParticleSystem(_:wtihTransform)
将爆炸添加到场景中。
你是如此接近复制那些伟大的好莱坞爆炸! 在handleTouchFor(_ :)
中两次添加以下行 - 一次到“good”if
块,一次到“bad”else
块,就在从父节点移除节点之前:
createExplosion(geometry: node.geometry!,
position: node.presentation.position,
rotation: node.presentation.rotation)
这使用presentation
属性来检索节点的position
和rotation
参数。 然后使用这些参数调用createExplosion(_:position:rotation :)
。
注意:您正在使用
presentation
,因为物理模拟当前正在移动节点。
构建并运行,点击,让这些节点爆炸!
注意:您可以在
projects / challenge / GeometryFighter
文件夹下找到此挑战的项目。
Adding Juice
你的游戏尚未完成;还有很大的改进空间,对吧?要将游戏推向更高级别,您必须添加一些称为juice
的东西。juice
会给你的游戏带来一些特别的东西,只是为了让它在其余部分中脱颖而出。
这里有一些想法肯定会让事情变得更好:
-
Game state management - 游戏状态管理:通过基本的游戏状态管理,您将能够根据
TapToPlay
,Playing
或GameOver
等游戏状态控制某些游戏机制。 - Splash screens - 启动画面:使用漂亮的溅射屏幕。它们为玩家提供当前游戏状态的视觉线索。
- Sound effects - 声音效果:添加炫酷的声音效果,为玩家提供与游戏元素良好互动的重要音频反馈。
- Camera shakes - 相机震动:真正的大爆炸产生了非常大的冲击波。添加摇动相机效果,为您的游戏增添一些额外的东西。
后记
本篇主要讲述了基于SceneKit的简单游戏示例的实现,感兴趣的给个赞或者关注~~~