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的简单游戏示例的实现,感兴趣的给个赞或者关注~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容