版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.10.20 星期六 |
前言
SceneKit
使用高级场景描述创建3D游戏并将3D内容添加到应用程序。 轻松添加动画,物理模拟,粒子效果和逼真的基于物理性的渲染。接下来这几篇我们就详细的解析一下这个框架。感兴趣的看下面几篇文章。
1. SceneKit框架详细解析(一) —— 基本概览(一)
2. SceneKit框架详细解析(二) —— 基于SceneKit的简单游戏示例的实现(一)
开始
在这部分中,您将开始制作游戏,顺路学习Scene Kit
节点。
我们再回来吧!
在上一个教程中,您了解到SceneKit
将游戏组件组织为一个称为scene graph
的层次结构。
游戏的每个元素 - 例如灯光,照相机,几何体和粒子发射器 - 都称为节点nodes
,节点存储在这种树状结构中。
为了说明这是如何工作的,回想一下你可能听过的童年童谣......
🎵 The hip bone’s connected to the back bone 🎵 The back bone’s connected to the shoulder bone… 🎵
你是对的! 这是经典歌曲Dem Dry Bones
。 如果你知道一个特别好用这个概念的经典视频游戏,那么奖励积分。
考虑到这些歌词,请看一下罕见的四指骨架的以下解剖学上正确的结构:
为了帮助说明如何从此骨架构建基于节点的层次结构,请将骨架中的每个骨骼视为节点node
。
正如歌曲指出的那样,肩骨与背骨相连。因此,将后骨骼视为肩骨的父节点,将肩骨骨骼视为后骨骼的子节点。
要将肩骨添加到场景中,请将其添加为后骨骼的子项。您可以继续以这种方式构建整个手臂,将子骨骼添加到父骨骼。
要定位骨骼,请相对于其父骨骼定位骨骼。例如,要挥动骨架的左臂,只需按照小蓝箭头的指示来回旋转肩节点即可。肩节点的所有子节点将与其父节点一起旋转。
恭喜!你刚刚通过骨骼解剖学!
从技术角度来看,单个节点由SCNNode
类表示,并表示相对于其父节点在3D空间中的位置。节点本身没有可见内容,因此,当作为场景的一部分呈现时,它是不可见的。要创建可见内容,您必须向节点添加其他组件,如灯光,相机或几何图形(如骨骼)。
场景图(scene graph)
包含一个特殊节点,它构成了基于节点的层次结构的基础:根节点。要构建场景,可以将节点添加为根节点的子节点,也可以添加为根节点后代之一的子节点。
在本教程中,您将开始使用SceneKit
中的一些简单节点,例如相机和几何节点。在本教程结束时,您将在屏幕上呈现一个简单的3D立方体!
Asset Catalogs
一旦你成为一名成功且富有经验的3D游戏设计师,你将有足够的资金聘请你自己的图形艺术家和音响工程师,这将让你自由地专注于游戏代码。SceneKit asset catalog
专门设计用于帮助您与代码分开管理游戏资产。
通过资产目录,您可以在单个文件夹中管理游戏资产。 要使用它,只需将带有.scnassets
扩展名的文件夹添加到项目中,并将所有游戏资源保存在该文件夹中。 Xcode会在构建时将目录中的所有内容复制到您的应用包中。 Xcode保留您的资产文件夹层次结构;这使您可以完全控制文件夹结构。
通过与您的艺术家共享您的资产文件夹,他们可以快速解决任何问题,例如一个不那么可怕的跨眼怪物,并为下一次构建做好准备,而无需将更改的资产复制回项目。
1. Adding an Asset Catalog - 添加资产目录
现在您已了解资产目录的全部内容,您将向Geometry Fighter
添加一个。
将GeometryFighter.scnassets
文件夹从resources
文件夹拖放到Xcode
中的游戏项目中。 在出现的弹出窗口中,确保选中Copy items if needed
,Create Groups
和GeometryFighter
目标,然后单击Finish
。
在项目导航器中选择GeometryFighter.scnassets
文件夹。 请注意右侧面板中资产目录的其他独有设置。 展开GeometryFighter.scnassets
文件夹和子文件夹以查看有关资产的更多详细信息:
资产目录中有两个文件夹:
-
Sounds
:包含游戏所需的所有声音资源。 -
Textures
:包含您需要的所有图像。
随意偷看每个文件夹里面的内容。
2. Adding a Launch Screen - 添加启动屏幕
现在您已导入资产目录,您将负责一些基本的内部处理步骤,包括在启动屏幕上添加适当的图像。
首先,单击项目导航器中的Assets.xcassets
。 将GeometryFighter.scnassets / Textures / Logo_Diffuse.png
拖放到AppIcon
下面的资源中。
接下来,单击项目导航器中的LaunchScreen.storyboard
。 选择主视图并将Background
属性设置为深蓝色:
接下来,将Logo_Diffuse
图像从Media Library
拖动到视图的中心。 将新图像的Content Mode
属性设置为Aspect Fit
:
你的启动屏幕差不多完成了。 您需要做的就是添加一些约束,以便启动图像适用于所有设备。 单击底部的Pin
按钮,切换所有四条边的约束,然后单击Add 4 Constraints
,如下所示:
您已完成设置启动屏幕! 构建并运行您的应用程序; 你会看到你闪亮的新发布屏幕出现:
3. Adding a Background Image - 添加背景图像
一旦你的启动画面消失,你就会被转回空白的屏幕。 是时候添加一个漂亮干净的背景,这样你就不会觉得自己正盯着一个黑洞。
为此,请将以下代码行添加到GameViewController.swift中setupScene()
的底部:
scnScene.background.contents = "GeometryFighter.scnassets/Textures/Background_Diffuse.png"
此行代码指示场景从资产目录中加载Background_Diffuse.png
图像,并将其用作场景背景的材质属性。
构建并运行;你现在应该在游戏开始时看到蓝色背景图像:
您已完成项目的所有基本内部处理任务。 你的游戏现在有一个华丽的应用程序图标,一个启动画面和漂亮的背景,它们都准备好显示你将要添加到场景中的节点。
The SceneKit Coordinate System - SceneKit坐标系
在开始向场景添加节点之前,首先需要了解SceneKit
的坐标系如何工作,以便将节点定位在所需位置。
在诸如UIKit或SpriteKit的2D系统中,您使用一个点来描述x和y轴上的视图或sprite的位置。 要在3D空间中放置对象,还需要描述对象在z轴上的位置深度。
请考虑以下简单说明:
SceneKit使用这个三轴系统来表示3D空间中的位置。 红色块沿x轴放置,绿色块沿y轴放置,蓝色块沿z轴放置。 轴正中心的灰色立方体表示原点,坐标为(x:0,y:0,z:0)
。
SceneKit
使用SCNVector3
数据类型将三维坐标表示为三分量向量。 以下是在代码中创建向量的方法:
let position = SCNVector3(x: 0, y: 5, z: 10)
这将使用向量(x:0,y:5,z:10)
声明position
位置。 您可以轻松访问矢量的各个属性,如下所示:
let x = position.x
let y = position.y
let z = position.z
如果您之前使用过CGPoint
,则可以轻松地在它与SCNVector3
之间进行比较。
注意:添加到场景中的节点的默认位置为(x:0,y:0,z:0),它始终相对于父节点。 要将节点放置在所需位置,您需要调整节点相对于其父节点的位置(本地坐标) - 而不是原点(世界坐标)。
Cameras - 相机
既然您已经了解了如何在SceneKit
中定位节点,那么您可能想知道如何在屏幕上实际显示某些内容。 回想一下本教程系列第1部分中电影集的类比。 要拍摄场景,您需要放置一个观察场景的摄像机,并且从摄像机的角度来看该场景的结果图像。
SceneKit
以类似的方式工作;包含摄像机的节点的位置决定了您从中查看场景的视点。
下图演示了摄像机在SceneKit
中的工作原理:
上图中有几个关键点:
- 1) 摄像机的视线方向始终沿着包含摄像机的节点的负z轴。
- 1) 视野
( field of view)
是相机可视区域的限制角度。 紧密的角度提供了狭窄的视野,而广角提供了宽阔的视野。 - 1) 视锥体
(viewing frustum)
确定了相机的可见深度。 此区域外的任何东西 - 即距离相机太近或太远 - 都将被剪裁,不会出现在屏幕上。
SceneKit
摄像机由SCNCamera
表示,其xPov
和yPov
属性可让您调整视野,而zNear
和zFar
可让您调整视锥体。
要记住的一个关键点是摄像机本身不会做任何事情,除非它是节点层次结构的一部分。
1. Adding a Camera - 添加相机
是时候尝试一下了。 打开GameViewController.swift
并在scnScene
下面添加以下属性:
var cameraNode: SCNNode!
接下来,在setupScene()
下面添加以下方法:
func setupCamera() {
// 1
cameraNode = SCNNode()
// 2
cameraNode.camera = SCNCamera()
// 3
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
// 4
scnScene.rootNode.addChildNode(cameraNode)
}
仔细看看代码:
- 1) 首先,创建一个空的
SCNNode
并将其分配给cameraNode
。 - 2) 接下来,您将创建一个新的
SCNCamera
对象并将其分配给cameraNode
的camera
属性。 - 3) 然后,将摄像机的位置设置为
(x:0,y:0,z:10)
。 - 4) 最后,将
cameraNode
添加到场景中,作为场景根节点的子节点。
通过调用viewDidLoad()
中刚刚添加的方法来完成任务,就在setupScene()
下面:
setupCamera()
没有必要构建和运行。 即使你刚刚在场景中添加了一个摄像头,仍然没有什么可看的,这意味着你不会看到任何不同的东西。 但那即将改变!
Geometry - 几何
要创建可见内容,您需要将几何对象添加到节点。 几何对象表示三维形状,并且由称为定义多边形polygons
的vertices
的许多点创建。
此外,几何对象可以包含修改几何体表面外观的材质对象。 通过材质,您可以指定几何体表面的颜色和纹理等信息,以及几何体应如何响应光线以及其他视觉效果。 顶点和材质的集合称为模型或网格(model
或者 mesh
)。
SceneKit
包含以下内置几何形状:
在前排,从左侧开始,您有一个圆锥,一个圆环,一个胶囊和一个管子。 在后排,从左侧开始,你有一个金字塔,一个盒子,一个球体和一个圆柱体。
1. Adding ShapeTypes - 添加ShapeTypes
在开始向场景添加几何形状之前,请创建一个新的Swift文件,以定义您将在游戏中使用的不同形状的ShapeType
枚举。
右键单击GeometryFighter
组并选择New File ....
选择iOS / Source / Swift File
文件模板,然后单击Next
:
将文件命名为ShapeType.swift
,确保它包含在项目中,然后单击Create
。
创建文件后,打开ShapeType.swift
并使用以下内容替换其内容:
import Foundation
// 1
enum ShapeType:Int {
case box = 0
case sphere
case pyramid
case torus
case capsule
case cylinder
case cone case tube
// 2
static func random() -> ShapeType {
let maxValue = tube.rawValue
let rand = arc4random_uniform(UInt32(maxValue+1))
return ShapeType(rawValue: Int(rand))!
}
}
上面的代码相对简单:
- 1) 您创建一个名为
ShapeType
的新枚举,枚举各种形状。 - 2) 您还定义了一个名为
random()
的静态方法,该方法生成随机ShapeType
。 此功能将在您的游戏中稍后派上用场。
2. Adding a Geometry Node - 添加几何节点
您的下一个任务是创建一个方法,该方法生成ShapeType
中定义的各种随机形状。
将以下方法添加到GameViewController.swift
,就在setupCamera()
下面:
func spawnShape() {
// 1
var geometry:SCNGeometry
// 2
switch ShapeType.random() {
default:
// 3
geometry = SCNBox(width: 1.0, height: 1.0, length: 1.0,
chamferRadius: 0.0)
}
// 4
let geometryNode = SCNNode(geometry: geometry)
// 5
scnScene.rootNode.addChildNode(geometryNode)
}
依次记录每个编号的评论:
- 1) 首先,创建占位符
geometry
变量以供稍后使用。 - 2) 接下来,定义一个
switch
语句来处理ShapeType.random()
返回的形状。 目前它还不完整,只创造了一个盒子形状;在本教程结束时,您将在挑战中添加更多内容。 - 3) 然后,创建一个
SCNBox
对象并将其存储在geometry
中。 您可以指定宽度,高度和长度以及倒角半径(这是一种说明圆角的奇特方式)。 - 4) 在这里,您将创建名为
geometryNode
的SCNNode
实例。 这次,您使用SCNNode
初始化程序,它使用geometry
参数来创建节点并自动附加提供的几何体。 - 5) 最后,将节点添加为场景根节点的子节点。
现在你需要调用这个方法。 将以下行添加到setupCamera()
下面的viewDidLoad()
:
spawnShape()
构建并运行;你会看到屏幕上显示一个白色方块:
这里有几点需要注意:
- 1)
box
节点是spawnShape()
的默认形状,它位于场景中的(x:0,y:0,z:0)
。 - 2) 您正在通过
cameraNode
查看场景。 由于摄像机节点位于(x:0,y:0:z:10)
,因此该box
位于摄像机可视区域的中心。
好吧,这不是很令人兴奋,而且它几乎不是三维的 - 但不要害怕......下一部分会改变所有这些!
Built-in View Features - 内置视图功能
SCNView
具有一些开箱即用的功能,可帮助您轻松生活。
将以下行添加到GameViewController.swift
中的setupView()
,就在当前实现的下方:
// 1
scnView.showsStatistics = true
// 2
scnView.allowsCameraControl = true
// 3
scnView.autoenablesDefaultLighting = true
以下是对上述代码的解释:
- 1)
showStatistics
在场景底部启用实时统计面板。 - 2)
allowsCameraControl
允许您通过简单的手势手动控制活动相机。 - 3)
autoenablesDefaultLighting
在场景中创建一个通用的全向灯,因此您不必担心添加自己的光源。
构建并运行;这次事情应该看起来更令人兴奋!
您可以使用以下手势来控制场景中的活动相机:
- Single finger swipe - 单指滑动:围绕场景内容旋转活动摄像机。
- Two finger swipe - 双指滑动:在场景中向左,向右,向上或向下移动或平移相机。
- 双指捏 - Two finger pinch:将相机放入和移出场景。
- 双击 - Double-tap:如果您有多个摄像头,则会在场景中的摄像头之间切换。 当然,由于场景中只有一台摄像机,因此不会这样做。 但是,它还具有将相机重置为其原始位置和设置的效果。
1. Working with Scene Statistics - 使用场景统计
找到屏幕底部的统计信息面板:
以下是每个元素含义的快速细分:
-
fps
:代表每秒帧数。这是在一秒内完成的连续帧重绘总量的测量。这个数量越低,你的游戏表现就越差。您通常希望您的游戏以60fps的速度运行,这将使您的游戏看起来更加流畅。 - ◆:代表每帧的总绘图调用。这通常是每个帧绘制的可见对象的总量。影响对象的灯光也会增加对象的绘制调用量。这个数量越低越好。
- ▲:代表每帧的总多边形。这是用于为所有可见几何体绘制单个帧的多边形总数。这个数量越低越好。
- ✸:代表全部可见光源。这是当前影响可见对象的光源总量。 SceneKit指南建议一次不要使用3个以上的光源。
单击+
按钮展开面板并显示更多详细信息:
此面板为您提供以下信息:
-
Frame time - 帧时间:这是绘制单帧所花费的总时间。 需要
16.7ms
的帧时间来实现60fps
的帧速率。 - The color chart: - 颜色图表:这为您提供了SceneKit渲染管道中每个组件的粗略帧时间百分比细分。
从这个例子中,您现在知道绘制一个帧的时间为22.3ms,其中±75%
用于渲染,±25%
用于GL Flush
。
注意:稍后将更详细地讨论
SceneKit
渲染管道。
您可以单击-
按钮以再次最小化面板。
所有这些功能都是内置是不是很好?
Challenges - 挑战
对您来说,练习您自己学到的知识非常重要,本系列的许多部分都有一个或多个与之相关的挑战。
我强烈建议尝试所有的挑战。虽然按照分步教程进行,但您可以通过自己解决问题来学习更多知识。
1. Your First Challenge - 你的第一个挑战
本教程中只有一个挑战,但它很有趣。
您的挑战是改进spawnShape()
中的switch
语句以处理枚举器中的其余形状。
使用Apple的官方SceneKit文档(http://apple.co/2aDBgtH)作为各种几何形状的指南。另外,看一下ShapeType
枚举,看看要创建哪些形状;他们的名字应该让你知道从哪里开始。
不要过分担心使用的尺寸;试着让它们与你之前制作的盒子大小相同。
在这次挑战之后,您将牢牢掌握SceneKit中的一些最基本的概念!
后记
本篇主要讲述了基于SceneKit的简单游戏示例的实现,感兴趣的给个赞或者关注~~~