所有互动游戏都有游戏开发人员必须实施的功能:游戏对象应遵循物理定律,并与其他游戏对象发生接触;游戏对象应触发事件,比如播放声音和计分;以及响应用户的输入,比如操纵杆、鼠标和键盘。通常情况下,开发人员必须针对每个目标平台反复实施这种功能,过程非常耗时。为了减轻负担,开发人员可选择使用游戏引擎,后者包含可执行常规任务的不同函数,从而将更多精力集中于增强游戏创意。
Unity 技术的交叉平台游戏引擎Unity* 3D是一款极具吸引力的解决方案。它的目标平台包括计算机、游戏控制台、移动设备和 web 浏览器。Unity 3D 还支持 C++、C#、Unity Script(类似于 JavaScript*),Boo 等不同的编程语言。
本文同时适于初学者和专家阅读。对于之前从未使用过 Unity 的开发人员,我们在本文的第一部分做了简要介绍。然后演示如何结合使用手部追踪和英特尔® 实感™ SDK,以借助 C# 和英特尔实感 3D 摄像头开发一款小游戏。如欲加入游戏开发行列,您需要: 安装于计算机的 Visual Studio和 Unity,以及英特尔实感 SDK 和英特尔实感 3D 摄像头。
下载、安装和配置 Visual Studio*
您可以免费下载 Unity。Unity 基于 Microsoft Windows* OS 和 Apple Mac* OS X* 运行。安装 Unity 后,您应完成免费注册流程。
本文所显示的代码基于 C# 编程语言编写而成。IDE Unity 提供的默认交叉平台为MonoDevelop。如果您是 .NET 开发人员,肯定十分希望 Unity 和Microsoft Visual Studio能够完美地协同工作。第一步为连接 Unity 和 Visual Studio。为此,您需要从下拉菜单中选择 Edit -> Preferences…。将打开 Unity Preferences 对话框。选择左侧的 External Tools(图 1) 对于外部脚本编辑器,点击 ComboBox 中的 MonoDevelop,并使用Browse… 按钮查找 Visual Studio .exe 文件。使用默认设置安装 Visual Studio;程序的位置路径应为:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe.
这些是成功完成连接的全部必要步骤。
图 1. 连接 Visual Studio* 和 Unity*
环境
现在是配置 Unity 环境的部分特性的最佳时机。主屏幕包含多个不同的窗口。可手动选择设置或使用默认设置,查找最理想的安排。我们从右上角的 Layout 下拉菜单中选择常用设置 Tall。
图 2. 选择窗口的位置
场景窗口和游戏窗口
第一个较大的场景窗口(图 3)主要用于定位以及布局您的游戏世界。以 3D 为标准实现内容可视化。通过拖放操作可轻松定位游戏对象。您可直接查看主屏幕上方的游戏选项卡。通过该游戏选项卡,可预览渲染后的游戏,显示在所需的平台上。
图 3. 场景窗口
图 4. 游戏窗口
层级窗口 (Hierarchy Window)
层级窗口(图 5)包含相关的内容对象,例如,游戏对象、摄像头视图、光照对象、UI 元素和音频。可在 "GameObject"下的菜单中,或直接在窗口中使用 "Create"创建新的内容对象。
图 5. 层级窗口
检查器窗口 (Inspector Window)
在层级窗口中选中内容对象后,检查器窗口将显示其属性(图 6)。通过添加 'add component'按钮提供的组件,可进行功能扩展。例如,名为 rigid body 的组件,可为游戏对象提供重力。
图 6. 检查器窗口
项目窗口
项目窗口中排列了多个项目文件,比如游戏场景、游戏对象、音频、图片和脚本文件(图 7)。选择文件时,检查器显示相应的属性和预览信息。双击文件名称,可直接打开应用的脚本文件,及其开发环境。
图 7. 项目窗口
开发简单游戏
20 世纪 70 年代早期,Atari 创始人 Nolan Bushnell 成功发布了一款名为 “Pong” 的游戏。游戏规则非常简单,与乒乓球类似:球在屏幕中来回移动。每名玩家控制一把垂直球拍。如果玩家没有成功回球,对手将获得一分。以下步骤将介绍如何在 Unity 中开发 Ping Pong 游戏。
通过 File -> New Project创建新项目 PingPongUnity。
图 8. 创建新项目
球拍
首先添加球拍,在层级窗口中点击 Create -> 3D Object -> Cube。如需改变球拍的形状和位置属性,您需要选择生成的立方体,并在检查器窗口中对其进行更改。我们将名称更改为 Paddle1,并在转换部分自定义它的形状。所需的 scale-值包括: X: 0.5,Y:3,Z:1。将 Position设置为: X: -9,Y: 1, Z: 0。现在,第一只球拍就完成了。在层级窗口中右击该球拍,然后在环境菜单中点击 Duplicate。这样可复制一把相同的球拍。现在,您只需将该复制品重命名为 Paddle2。然后将 Position。 的 X。 值改为 9。 即可。
为使球拍做出正确的物理行为,我们需要点击 “Add Components” 按钮,为每只球拍添加 RigidBody 组件。这样我们可在代码范围内移动球拍,使球拍和球发生碰撞。现在,球与球拍发生碰撞后,弹出了球场外。这不是我们想要的行为。为避免出现这种行为,我们需要为新增的 RigidBody 设置限制属性。球拍只允许上下移动,因此我们需要在冻结旋转部分选择 x、y 和 z,并在冻结位置只选择 x。
图 9. 生成的球拍
墙壁
打乒乓球时,我们需要两面墙壁:上壁和下壁,以便球反弹出去并留在球场内。因此,我们再次使用立方体。再次点击层级窗口中的 Create -> 3D Object -> Cube。命名为 WallBelow。墙壁的形状稍长一些,将 scale值设置为: X: 23,Y: 1,Z: 1。位置的值设为: X: 0,Y: -5, Z: 0。
生成第二面墙壁时,只需复制刚刚生成的墙壁,并将其命名为 WallAbove。现在将 Y-Position 更改为 7。
图 10. 生成的墙壁
球
游戏最后,也是最重要的一个对象是:球。Unity 提供了一个符合形状的球。只需点击层级窗口中的 Create -> 3D Object -> Sphere。将名称更改为 Ball,并将Position设置为 X: 0,Y: 1, Z: 0。如欲添加相应的物理属性,您需要使用 "Add Components"添加 rigid body 组件,并删除属性选择 "Use gravity”。
图 11. 生成的球
调整摄像头视角,并增加亮度。
游戏的场景显示和游戏窗口仍然不够理想。我们需要更改摄像头设置。在层级窗口中,选择标配摄像头 Main Camera。然后将当前的 3D 视图改为 2D 视图。这一步骤在摄像头 Projection属性中完成。将 Perspective (3D) 更改为 Orthographic (2D)。这样我们可获得正面的视图。并将新的 Size属性更改为 10。
如果场景窗口的显示仍然不够理想,您可以点击坐标轴旋转视图。
图 12. 点击坐标轴启用视图旋转功能。
这样,摄像头获得了比较理想的设置。但游戏对象仍然较暗。为增加亮度,需要切换至层级窗口,然后点击 Create -> Light -> Directional Light。现在,您可以看到,游戏窗口发生了变化。
图 13. 游戏窗口中的游戏
移动球
游戏场地已经准备好,但仍需一些动作。如果运行游戏,球落地,就可以了。我们需要编写几行 C# 代码,使球运动起来。为此,我们点击项目窗口中的 Create 按钮,创建名为 “Ball” 的 C# 脚本文件。然后,创建脚本文件和游戏对象之间的绑定。这一步骤的具体做法为:将文件拖放至层级或场景窗口中的游戏对象。选择球体对象后,检查器窗口将显示脚本属性。
双击 C# 文件,自动启动您设置好的开发环境:MonoDevelop 或 Microsoft Visual Studio。生成的 C# 类将以标准结构开始。它包含 MonoBehavior 基本类,是 Unity API 的一个界面。“Start” 方法可用于一次性初始化和准备。游戏框架每秒处理几张图片,就好像您正在观看电视或手翻书。单张图片称为图画。绘制图画之前,每次需调用 “Update” 方法。我们将这种反复称之为游戏循环,这意味着其他游戏对象可在不考虑玩家动作的情况下行动。
在该游戏中,我们希望设置球落地的方向,在游戏开始前设置一次。“Start” 方法似乎非常可行。您可在表 1 中查找所需的代码。
using UnityEngine; public class Ball : MonoBehaviour { private float _x; private float _y; // Use this for initialization void Start () { _x = GetRandomPositionValue(); _y = GetRandomPositionValue(); GetComponent<Rigidbody>().velocity = new Vector3(Random.Range(5,10) * _x, Random.Range(5,10) * _y, 0); Debug.Log("x: " + _x); Debug.Log("y: " + _y); } private float GetRandomPositionValue() { if (Random.Range(0, 2) == 0) { return -1; } return 1; } // Update is called once per frame void Update () { } }
表 1. 借助 C# 让球在一个方向下落
游戏对象的真正魅力隐藏于组件部分。有一款组件可实现所有游戏的逻辑。表 1 中的 C# 代码可通过 GetComponent 方法访问当前游戏对象的 Rigidbody 组件。Rigidbody 通过重力功能扩大游戏对象。设置速度属性时,会分配一个新的 Vector3 实例。它可定义当前重力的随机方向。
第二次游戏测试表明,球可随机朝不同的方向落下。但球碰到墙壁后,将沿着屏幕上行,我们不希望出现这种行为。它应该更像在墙壁之间反弹的橡皮球。
Physic Material 可帮助达到这种效果。我们点击项目窗口中的 Create 按钮,创建 Physic Material。将它重命名为 Bouncy。如需实现反弹行为,还要对属性做些调整。在检查器窗口中,将属性 Dynamic Friction和 Static Friction的值设为 0。Bounciness设为 1。Friction Combine要求设为最小值,Bounce Combine设为最大值。
图 14. 添加和设置 Physic Material
为使所有游戏对象使用我们生成的 Physic Material,必须设置通用物理设置。为此,点击菜单 Edit -> Project Settings -> Physics。将 Physic Material Bouncy拖放至 Default material属性。属性 Bounce Threshold应设为 0.1。如果现在启动游戏,球的动作将与橡皮球一样。
图 15. 设置 PhysicsManager 中的 “Bouncy”
用手控制球拍
此款游戏即将完成。游戏对象已各就各位,球也处于运动之中。剩下的一点就是控制球拍的逻辑。为此,最佳的方法是使用创新型输入设备,即 3D 摄像头,该摄像头已普遍用于 Microsoft Xbox* 和 Kinect*。部分最新平板电脑和超极本™ 设备也配备了 3D 摄像头。如果没有 3D 摄像头,也可以使用这些经济型设备。开发游戏时,您需要下载免费的英特尔实感 SDK。安装后,您可以在英特尔实感 SDK 文件夹中查找到一些 Unity 示例。
图 16– 英特尔实感 SDK 示例
Unity 3D 支持第三方插件,并为英特尔实感 SDK 创建了一个这样的插件。如欲集成英特尔实感 SDK,必须将 SDK DLL 文件添加至项目。在 Unity 中选择 Assets -> Import package -> custom package.... 然后选择 RSUnityToolkit.unitypackage文件,该文件位于 C:\Program files (x 86) \Intel\RSSDK\framework\Unity 路径下。将会弹出类似图 17 所示的对话框窗口。该对话框默认选择了所有文件。点击 None清空选项,只选择插件目录。如果基于 64 位计算机开发游戏,您还需手动替换导入的 DLL 文件。这些文件位于下列目录:C:\Program Files (x86)\Intel\RSSDK\bin\x64
图 17– 使用 "import package"将英特尔实感 SDK 集成至 Unity*
集成后,现在可开始开发球拍逻辑了。我们需要另外一个 C# 文件,可通过点击项目窗口中的 Create按钮生成。该文件名称为 Paddle。游戏过程中只需生成一个常见实例即可响应两只球拍。这一点非常重要,因为英特尔实感 3D 摄像头只能连接一个实例,无法连接多个。因此,我们在层级窗口中将新文件拖放至 "main camera"游戏对象,以实现连接。
接下来,双击 Paddles.cs 文件。即使文件连接了主摄像头,仍然需要球拍游戏对象。Unity 中多个游戏对象之间的连接通过属性就可实现。在 C#,您需要使用 “get” 和 “set” 宣布其他属性,但在 Unity 中不需要。我们需要做的只是针对每支球拍创建类型 GameObject 的公共变量,如表 2 所示。现在,可将游戏对象拖放至创建好的球拍属性,以实现连接。
public class Paddles : MonoBehaviour { public GameObject Paddle1; public GameObject Paddle2; .. Listing 2 – Self written properties
图 18. 借助 Unity* 宣布面向 Paddles.cs 文件的游戏对象
英特尔实感 SDK 提供类 PXCMSenseManager,可实现与摄像头的永久连接。该启动函数只能从 Unity 引擎中调用一次,因此是一般实例准备的理想选择。这是我们在启动函数中初始化 PXCMSenseManager 类的原因。为此,我们首先为其提供 QueryHand 模块,这样有助于我们读取手势。如欲识别面部表情和语音,需采用其他模块。摄像头的实例在启动函数外以 _pxcmSenseManager 字段的形式宣布。代码如表 3 所示。
public class Paddles : MonoBehaviour { public GameObject Paddle1; public GameObject Paddle2; private PXCMSenseManager _pxcmSenseManager; private PXCMHandModule _pxcmHandModule; // Use this for initialization private void Start() { _pxcmSenseManager = PXCMSenseManager.CreateInstance(); if (_pxcmSenseManager == null) { Debug.LogError("SenseManager Initialization Failed"); } else { pxcmStatus pxcmResult = _pxcmSenseManager.EnableHand(); if (pxcmResult != pxcmStatus.PXCM_STATUS_NO_ERROR) { Debug.LogError("EnableHand: " + pxcmResult); } else { _pxcmHandModule = _pxcmSenseManager.QueryHand(); _pxcmSenseManager.Init(); PXCMHandConfiguration configuration = _pxcmHandModule.CreateActiveConfiguration(); configuration.EnableAllGestures(); configuration.ApplyChanges(); configuration.Dispose(); } } } ...
表 3. 与英特尔实感 3D 摄像头的永久连接
采用更新函数(游戏循环)可直接查询最新的摄像头数据,我们可因此确认摄像头是否捕捉到了手部。左手用于控制左球拍,右手控制右球拍。因此,您需要将计算后的数值分别分配给各自的球拍。因为逻辑非常相似,因此我们创建私有函数 MoveBall。
如果玩家退出或重新启动游戏,需要断开摄像头的直接实例。Unity 引擎可自动调用的函数 OnDisable 非常适用于这种情况。
// Update is called once per frame private void Update() { if (_pxcmSenseManager == null) { return; } _pxcmSenseManager.AcquireFrame(false, 0); _pxcmHandModule = _pxcmSenseManager.QueryHand(); PXCMHandData handData = _pxcmHandModule.CreateOutput(); handData.Update(); MoveBall(handData, PXCMHandData.AccessOrderType.ACCESS_ORDER_LEFT_HANDS, Paddle1); MoveBall(handData, PXCMHandData.AccessOrderType.ACCESS_ORDER_RIGHT_HANDS, Paddle2); _pxcmSenseManager.ReleaseFrame(); } private void MoveBall(PXCMHandData handData, PXCMHandData.AccessOrderType accessOrderType, GameObject gameObject) { PXCMHandData.IHand pxcmHandData; if (handData.QueryHandData(accessOrderType, 0, out pxcmHandData) == pxcmStatus.PXCM_STATUS_NO_ERROR) { PXCMHandData.JointData jointData; if (pxcmHandData.QueryTrackedJoint(PXCMHandData.JointType.JOINT_CENTER, out jointData) == pxcmStatus.PXCM_STATUS_NO_ERROR) { gameObject.GetComponent<Rigidbody>().velocity = new Vector3(-9, jointData.positionWorld.y*100f, 0); } } } private void OnDisable() { _pxcmHandModule.Dispose(); _pxcmSenseManager.Dispose(); } }
表 4. 用手移动球拍
重启游戏
生成游戏对象,并编写逻辑代码后,现在可以测试游戏了。如果摄像头可在 1-5 米的范围实现手部识别,球拍将会移动。球碰到球拍后,将会反弹至对面。如果球离开球场,未触碰到球拍,玩家将无计可施。在这种情况下,如果球离开球场,应自动重启游戏。
只需几行代码就可实施 Ball.cs 文件的逻辑。如果球还在视线范围内,应检查 Update 函数。如果看不到球,Application.LoadLevel 函数将重启游戏,如 表 5 所示。
private bool _loaded; // Update is called once per frame void Update () { if (GetComponent<Renderer>().isVisible) { _loaded = true; } if (!GetComponent<Renderer>().isVisible && _loaded) { Application.LoadLevel(Application.loadedLevel); } }
表 5. Ball.cs: 看不到球时重启游戏
发布游戏
第一版游戏已就绪,当然,我们希望同大家一起分享。菜单选项 File | Build Settings (Ctrg + Shift + B) 可打开 Build 对话框。您可以从包含众多选择的 Platform 框中选择用来运行游戏的平台。我们选择 Windows,并将 x86_64 设置为架构。点击Build即可启动编译器并生成面向 Windows 的.exe 文件。如欲玩游戏,您需要配备包含预安装驱动程序的英特尔实感 3D 摄像头。
图 19. 创建 .exe 文件发布游戏
现在轮到您了
我已经向大家演示了如何创建有趣的游戏,但还需做进一步改进。那么,向我展示您的学习成果吧。构建高分、声效和炫酷特效的游戏。请将视频上传至 YouTube*,然后通过电子邮件将创建好的游戏发送给我。非常高兴看到大家的创意。现在,请用英特尔实感 3D 摄像头尽享首款 Unity 游戏的乐趣吧。
图 20. 玩游戏
图 21. 游戏 UI
关于作者
Gregor Biswanger(微软 MVP,英特尔黑带开发人员, 英特尔软件创新者)是CleverSocial.de的创始人,兼自由讲师、顾问、培训师、作家和演讲者。他为大中型企业和机构提供关于软件架构、灵活流程、XAML、Web 应用和混合应用开发、云解决方案、内容营销和社交媒体营销方面的建议。
他在 video2brain 和 Microsoft 发布了一些关于上述主题的培训视频。他还是一名自由作家,为heise Developer撰写在线文章,并为商业类杂志撰写文章。他主笔撰写过 entwickler.press 出版的《交叉平台开发》一书。
他还经常参加国际会议,或在会议上发言。英特尔曾聘请他担任英特尔® 开发人员专区的技术顾问。此外,他还是 INdotNET (Ingolstaedter .NET Developers Group) 的负责人。点击此处,查找关于他的联系方式: https://about.me/gregor.biswanger