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

版本记录

版本号 时间
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 neededCreate GroupsGeometryFighter目标,然后单击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表示,其xPovyPov属性可让您调整视野,而zNearzFar可让您调整视锥体。

要记住的一个关键点是摄像机本身不会做任何事情,除非它是节点层次结构的一部分。

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对象并将其分配给cameraNodecamera属性。
  • 3) 然后,将摄像机的位置设置为(x:0,y:0,z:10)
  • 4) 最后,将cameraNode添加到场景中,作为场景根节点的子节点。

通过调用viewDidLoad()中刚刚添加的方法来完成任务,就在setupScene()下面:

setupCamera()

没有必要构建和运行。 即使你刚刚在场景中添加了一个摄像头,仍然没有什么可看的,这意味着你不会看到任何不同的东西。 但那即将改变!


Geometry - 几何

要创建可见内容,您需要将几何对象添加到节点。 几何对象表示三维形状,并且由称为定义多边形polygonsvertices的许多点创建。

此外,几何对象可以包含修改几何体表面外观的材质对象。 通过材质,您可以指定几何体表面的颜色和纹理等信息,以及几何体应如何响应光线以及其他视觉效果。 顶点和材质的集合称为模型或网格(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) 在这里,您将创建名为geometryNodeSCNNode实例。 这次,您使用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的简单游戏示例的实现,感兴趣的给个赞或者关注~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容