UNITY 3D – 游戏编程 – 第三部分






4.94/5 (40投票s)
本系列第三篇文章,旨在讨论Unity 3D以及如何开始你的3D项目。
引言
在本系列的第三篇文章中,我们将继续扩展我们对环境的总体认识。我们还将探讨如何处理来自键盘的用户输入。我们需要某种方式来处理用户输入,并将其内部转换为期望的动作或输出。如果你还没有这样做,请花点时间阅读
-
Unity 3D – 游戏编程 – 第 3 部分
Unity 3D 网络文章
Unity 3D Leap Motion 和 Oculus Rift 文章
在本系列的第一个部分,我们从Unity 3D环境的最基础内容开始。了解IDE以及你将在项目中使用的不同部分。我们还讨论了如何在设计器中使用工具对选定的游戏对象应用不同的变换:定位、旋转和缩放。最后,我们研究了如何创建第一个脚本,并使用该脚本在立方体的Y轴上应用旋转变换。
在本系列的第二部分中,我们更多地研究了通过编码对给定对象进行变换。我们还研究了如何创建对场景中对象的渲染至关重要的光源。
在本系列第三部分,我们将研究如何处理用户输入,并根据输入执行特定操作。此时,我们将只关注用户的键盘输入。未来的文章将涵盖其他类型的输入。
Windows Phone 8.x 演示
我提供了一个免费的手机应用程序,你可以下载并在Windows Phone上预览演示。要下载移动应用程序,请访问以下链接:CodeProjectArticleSample
文章代码和视觉效果的实时预览
实时预览链接:http://www.noorcon.com/CodeProject/CodeProjectArticlePreview.html
背景
假定本文读者对编程概念有普遍的了解。也假定读者对C#语言有理解和经验。还建议本文读者熟悉面向对象编程和设计概念。我们将在文章中根据需要简要涵盖它们,但不会深入细节,因为它们是完全独立的主题。我们还假定你对学习3D编程充满热情,并具备3D图形和向量数学的基本理论概念。
最后,本文使用Unity 3D版本4.6.1,这是初始发布日期时的最新公开版本。系列中讨论的大部分主题将与游戏引擎的旧版本兼容,也许也与今年将要发布的新版本兼容。然而,有一个主题与当前4.6.1版本相比,与游戏引擎的旧版本有显著不同,那就是UI(用户界面)管线。这是因为引擎中新的UI架构比我们在此版本之前拥有的要优越得多。我个人对新的UI架构非常满意。
使用代码
下载文章系列的工程/源代码:下载源代码。
随着后续文章的提交,项目/源代码也将随之扩展。新的项目文件和源文件将包含系列中较旧的部分。
Unity 3D中的输入处理 - 输入管理器
你编写的每个程序,无论是手机应用、Web应用、游戏还是嵌入式系统,都需要某种形式的输入。总的来说,每个程序都需要某种输入和某种输出。输出是用户看到的东西,输入是用户输入或提供给程序以根据某些算法生成输出的内容。
Unity 3D也不例外。游戏引擎需要能够处理来自不同源的输入
-
键盘
-
鼠标
-
游戏杆(们)
Unity 3D通过输入管理器管理其输入。可以通过从主菜单栏选择“编辑”->“项目设置”->“输入”来访问输入管理器。
图1-输入管理器 |
图2-水平配置 |
图3-垂直配置 |
图1列出了游戏引擎中定义的所有现成输入配置。让我们展开第一个输入定义,称为Horizontal。查看图2,您会注意到在Horizontal Axis输入配置下定义的属性。以下是输入配置中需要注意的重要属性:
-
名称: 输入管理器中定义的轴的名称。这是你可以在C#代码中引用的名称,以便检测并按需执行操作。
-
Positive Button: 定义将提供正方向/力的主要按钮。
-
Negative Button: 定义将提供负方向/力的主要按钮。
-
Alt. Positive Button: 与Positive Button相同,但定义了次要输入。
-
Alt. Negative Button: 与Negative Button相同,但定义了次要输入。
-
Type: 定义输入源来自哪里;(键盘/鼠标/游戏杆)。
-
Axis: 应该将力施加到世界上的哪个轴。
目前我们不打算修改任何配置。我只是想向您展示输入管理器是什么样子,并简要介绍一下属性。
Unity 3D中的输入处理 - 键盘输入
回顾第二部分,我们希望动态创建立方体原语,并将它们放置在设计时立方体的相对位置。为了实现这一点,我们必须创建一个新脚本并将其附加到场景中任何活动的Game Object上。我们将脚本附加到了相机对象上。
注意:这样做是安全的,因为我们的脚本不与它附加到的对象交互。
结果是,当我们运行程序时,原语将立即创建并显示在场景中。
现在让我们考虑以下情况:如果我们希望在按下特定键盘按键(例如空格键)后才创建原语,会怎样?
using UnityEngine; using System.Collections; public class createPrimitivesFromInput : MonoBehaviour { private GameObject cube1; // represents our Cube1' private GameObject cube2; // represents our Cube2' private GameObject cube3; // represents our Cube3' private bool PRIMITIVES_CREATED; // Use this for initialization void Start () { this.PRIMITIVES_CREATED = false; } // Update is called once per frame void Update () { if (Input.GetKey (KeyCode.Space)) { this.CreateMyPrimitives(); } if (PRIMITIVES_CREATED) { // apply the y-axis transform to Cube1' this.cube1.transform.Rotate(new Vector3(0,1,0), 1); // apply the x-axis transform to Cube2' this.cube2.transform.Rotate(new Vector3(1,0,0), 1); // apply the z-axis transform to Cube3' this.cube3.transform.Rotate(new Vector3(0,0,1), 1); } } // This function will be called when the Space Bar on the keyboard is pressed void CreateMyPrimitives(){ // check to see if the object is null before instantiating if (this.cube1 == null) { // initialize our Cube1 primitive and place it at location (0,2,0) this.cube1 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube1.transform.localPosition = new Vector3 (0, 2, 0); } // check to see if the object is null before instantiating if (this.cube2 == null) { // initialize our Cube2 primitive and place it at location (3,2,0) this.cube2 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube2.transform.localPosition = new Vector3 (3, 2, 0); } // check to see if the object is null before instantiating if (this.cube3 == null) { // initialize our Cube3 primitive and place it at location (-3,2,0) this.cube3 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube3.transform.localPosition = new Vector3 (-3, 2, 0); } this.PRIMITIVES_CREATED = true; } }
上面的列表是对我们在系列第二部分中创建的脚本的修改。让我们分解一下脚本。
-
我们需要检测用户是否按下了空格键。这在Update()函数中使用Input对象和GetKey()函数完成,即Input.GetKey (KeyCode.Space)。我们传递给函数的参数是空格键的枚举表示,KeyCode.Space。
-
然后,我们需要通过检查GetKey()函数返回的值来检测是否按下了按键。如果为true,我们将调用一个我们定义的新函数,名为CreateMyPrimitives()。
-
在CreateMyPrimitive()函数中,我们将检查原语是否为null,如果是,则实例化它们。
-
在Update()函数中,我们检查原语是否已初始化,如果是,则相应地应用旋转。
别忘了Update()函数在程序运行时会连续执行。因此,如果按下了空格键数次,我们会遇到问题。我们将多次实例化原语,因为按下了空格键,导致混乱。
为了防止这种情况发生,我们将引入一个新的布尔变量,名为PRIMITIVES_CREATED,用于控制我们是否需要实例化原语。PRIMITIVES_CREATED的初始值设置为false,这是在Start()函数中完成的。
注意:记住Start()函数只在开始时执行一次!
因此,当按下空格键时,将调用CreateMyPrimitives()函数,并实例化原语。再次注意,我们在实例化原语变量之前检查它们是否为null。最后,我们将PRIMITIVES_CREATED变量设置为true。
现在,在Update()函数中,我们必须检查并查看原语是否已初始化,然后才能将其旋转应用于Transform。
图4-运行新脚本的截图
我们能改进这段代码吗?当然可以。这是另一个实现我们想要功能的列表。
using UnityEngine; using System.Collections; public class createPrimitivesFromInput : MonoBehaviour { private GameObject cube1; // represents our Cube1' private GameObject cube2; // represents our Cube2' private GameObject cube3; // represents our Cube3' private bool PRIMITIVES_CREATED; // Use this for initialization void Start () { this.PRIMITIVES_CREATED = false; } // Update is called once per frame void Update () { if (!this.PRIMITIVES_CREATED) { if (Input.GetKey (KeyCode.Space)) { this.CreateMyPrimitives(); } }else{ // apply the y-axis transform to Cube1' this.cube1.transform.Rotate(new Vector3(0,1,0), 1); // apply the x-axis transform to Cube2' this.cube2.transform.Rotate(new Vector3(1,0,0), 1); // apply the z-axis transform to Cube3' this.cube3.transform.Rotate(new Vector3(0,0,1), 1); } } // This function will be called when the Space Bar on the keyboard is pressed void CreateMyPrimitives(){ if (!this.PRIMITIVES_CREATED) { // initialize our Cube1 primitive and place it at location (0,2,0) this.cube1 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube1.transform.localPosition = new Vector3 (0, 2, 0); // initialize our Cube2 primitive and place it at location (3,2,0) this.cube2 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube2.transform.localPosition = new Vector3 (3, 2, 0); // initialize our Cube3 primitive and place it at location (-3,2,0) this.cube3 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube3.transform.localPosition = new Vector3 (-3, 2, 0); this.PRIMITIVES_CREATED = true; } } }
正如你所见,新列表得到了改进,因为我们使用一个变量来检测原语是否已创建,并在此基础上执行我们的操作。
使用键盘输入应用旋转
现在,让我们更进一步。让我们来定义一些输入键值,以按以下顺序处理动态创建的原语的旋转:
-
A键负责在Y轴上旋转Cube1'。
-
B键负责在X轴上旋转Cube2'。
-
C键负责在Z轴上旋转Cube3'。
我们需要修改我们的代码以处理这三种新输入类型,如下所示:
using UnityEngine; using System.Collections; public class createPrimitivesFromInput : MonoBehaviour { private GameObject cube1; // represents our Cube1' private GameObject cube2; // represents our Cube2' private GameObject cube3; // represents our Cube3' private bool PRIMITIVES_CREATED; // Use this for initialization void Start () { this.PRIMITIVES_CREATED = false; } // Update is called once per frame void Update () { if (!this.PRIMITIVES_CREATED) { if (Input.GetKey (KeyCode.Space)) { this.CreateMyPrimitives(); } }else{ // apply the y-axis transform to Cube1' if(Input.GetKey(KeyCode.A)){ this.cube1.transform.Rotate(new Vector3(0,1,0), 1); } // apply the x-axis transform to Cube2' if(Input.GetKey(KeyCode.B)){ this.cube2.transform.Rotate(new Vector3(1,0,0), 1); } // apply the z-axis transform to Cube3' if(Input.GetKey(KeyCode.C)){ this.cube3.transform.Rotate(new Vector3(0,0,1), 1); } } } // This function will be called when the Space Bar on the keyboard is pressed void CreateMyPrimitives(){ if (!this.PRIMITIVES_CREATED) { // initialize our Cube1 primitive and place it at location (0,2,0) this.cube1 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube1.transform.localPosition = new Vector3 (0, 2, 0); // initialize our Cube2 primitive and place it at location (3,2,0) this.cube2 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube2.transform.localPosition = new Vector3 (3, 2, 0); // initialize our Cube3 primitive and place it at location (-3,2,0) this.cube3 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube3.transform.localPosition = new Vector3 (-3, 2, 0); this.PRIMITIVES_CREATED = true; } } }
请注意,Update()函数是您放置逻辑以执行输入和输出的地方。在上面的列表中,代码检查原语是否已初始化,如果为true,则会检查用户输入。根据键值,它将对Game Object应用适当的旋转。
思考一下:在新代码中,原语不会像在第二部分那样连续旋转。它们也不会同时同步旋转!
每个原语仅在其关联的旋转键被按下时独立旋转。例如,如果你按下A键,Cube1'将持续旋转,直到你停止按下它。当您松开手指时,它将停留在最后一次旋转角度!
图5-捕捉新旋转输入的截图
使用键盘输入应用平移
继续改进我们的脚本,现在让我们使用以下按键来向前、向后以及向两侧移动Cube1':
-
向上箭头键负责在Z轴上向前移动Cube1'。
-
向下箭头键负责在Z轴上向后移动Cube1'。
-
向左箭头键负责在X轴上向左移动Cube1'。
-
向右箭头键负责在X轴上向右移动Cube1'。
为了实现这一点,我们需要修改我们的Update()函数,如下所示:
using UnityEngine; using System.Collections; public class createPrimitivesFromInput : MonoBehaviour { private GameObject cube1; // represents our Cube1' private GameObject cube2; // represents our Cube2' private GameObject cube3; // represents our Cube3' private bool PRIMITIVES_CREATED; // Use this for initialization void Start () { this.PRIMITIVES_CREATED = false; } // Update is called once per frame void Update () { if (!this.PRIMITIVES_CREATED) { if (Input.GetKey (KeyCode.Space)) { this.CreateMyPrimitives(); } }else{ // apply the y-axis transform to Cube1' if(Input.GetKey(KeyCode.A)){ this.cube1.transform.Rotate(new Vector3(0,1,0), 1); } // apply the x-axis transform to Cube2' if(Input.GetKey(KeyCode.B)){ this.cube2.transform.Rotate(new Vector3(1,0,0), 1); } // apply the z-axis transform to Cube3' if(Input.GetKey(KeyCode.C)){ this.cube3.transform.Rotate(new Vector3(0,0,1), 1); } // code for the movement of Cube1' forward if(Input.GetKey(KeyCode.UpArrow)){ this.cube1.transform.Translate(Vector3.forward * Time.deltaTime); } // code for the movement of Cube1' backward if(Input.GetKey(KeyCode.DownArrow)){ this.cube1.transform.Translate(Vector3.back * Time.deltaTime); } // code for the movement of Cube1' left if(Input.GetKey(KeyCode.LeftArrow)){ this.cube1.transform.Translate(Vector3.left * Time.deltaTime); } // code for the movement of Cube1' right if(Input.GetKey(KeyCode.RightArrow)){ this.cube1.transform.Translate(Vector3.right * Time.deltaTime); } } } // This function will be called when the Space Bar on the keyboard is pressed void CreateMyPrimitives(){ if (!this.PRIMITIVES_CREATED) { // initialize our Cube1 primitive and place it at location (0,2,0) this.cube1 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube1.transform.localPosition = new Vector3 (0, 2, 0); // initialize our Cube2 primitive and place it at location (3,2,0) this.cube2 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube2.transform.localPosition = new Vector3 (3, 2, 0); // initialize our Cube3 primitive and place it at location (-3,2,0) this.cube3 = GameObject.CreatePrimitive (PrimitiveType.Cube); this.cube3.transform.localPosition = new Vector3 (-3, 2, 0); this.PRIMITIVES_CREATED = true; } } }
图6-对象平移截图
现在您已经了解了如何在Unity 3D中处理键盘输入,并根据输入执行操作。
关注点
利用你新掌握的技能,试着思考你的游戏或模拟将需要哪些用户输入方面的内容。如何管理更多的操作并处理多个输入?阅读有关该主题的内容,并下载本文的源代码进行更多练习。接下来,我们将演示如何创建一些简单的用户界面元素。
历史
这是我将逐渐贡献给Code Project社区的系列文章中的第三篇。
-
Unity 3D – 游戏编程 – 第 3 部分
Unity 3D 网络文章
Unity 3D Leap Motion 和 Oculus Rift 文章