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






4.95/5 (9投票s)
本系列第八篇文章,讨论 Unity 3D 以及如何开始您自己的 3D 项目。
引言
在本文的第八部分,我们将探讨如何改善游戏体验,使我们的游戏创意更具吸引力。我们将在游戏中添加障碍物。此外,我们还将引入音效和灯光效果,这将极大地增强环境效果,使其栩栩如生。
如果您还没有阅读,请花点时间阅读
- Unity 3D – 游戏编程 – 第 1 部分
-
Unity 3D – 游戏编程 – 第 8 部分
Unity 3D 网络文章
Unity 3D Leap Motion 和 Oculus Rift 文章
在本系列的第一部分,我们从 Unity 3D 环境的最基础知识开始。我们熟悉了 IDE 以及您将在项目过程中使用的各个部分。我们还介绍了如何使用设计器中的工具对选定的游戏对象应用不同的变换:定位、旋转和缩放。最后,我们研究了如何创建第一个脚本,并使用该脚本对立方体沿 Y 轴应用旋转变换。
在本系列的第二部分中,我们更多地研究了通过编码对给定对象进行变换。我们还研究了如何创建对场景中对象的渲染至关重要的光源。
在本系列的第三部分中,我们研究了如何通过键盘处理用户输入,并根据键码采取特定操作。
在本系列的第四部分,我们研究了创建简单的用户界面。我们开发的界面为用户提供了反馈机制,也为用户提供了输入到我们的游戏或模拟中的另一种方法。
在第五部分,我们提出了一个简单游戏的构想。我们还研究了如何将 3D 模型导入游戏引擎。
在第六部分,我们扩展了我们的游戏构想,使其更有趣、更完整。我们引入了几个关于游戏玩法和游戏设计的概念,并为我们的游戏创建了一个简单的用户界面。
在第七部分,我们回答了第六部分提出的一些问题。
在第八部分,我们将为玩家创建更好的用户体验和视觉效果。
Windows Phone 8.x 演示
我提供了一个免费的手机应用程序,您可以在 Windows Phone 上下载并预览演示。要下载该移动应用程序,请访问链接:CodeProjectArticleSample
文章代码和视觉效果的实时预览
实时预览链接:http://www.noorcon.com/CodeProject/CodeProjectArticlePreview.html
背景
注意:对于本篇文章,我将使用 SketchUp 创建一些简单的积木,并将其导入 Unity!我不是 3D 建模师或设计师,所以请耐心等待并原谅我的不足!
假设本文的读者熟悉一般的编程概念。也假设读者对 C# 语言有理解和经验。还建议本文的读者熟悉面向对象编程和设计概念。我们将在文章中根据需要简要介绍它们,但不会深入细节,因为它们是完全独立的主题。我们还假设您热衷于学习 3D 编程,并对 3D 图形和向量数学有基本的理论概念。
最后,本文使用了 Unity 3D 版本 4.6.1,这是初始发布日期时最新的公开版本。本系列讨论的大多数主题都将与游戏引擎的旧版本兼容,也许也与今年晚些时候将要发布的那个新版本兼容。然而,在当前 4.6.1 版本中,有一个主题与旧版本游戏引擎相比有显著不同,那就是 UI(用户界面)管道。这是因为引擎中新的 UI 架构比我们发布之前的架构要优越得多。我个人对新的 UI 架构非常满意。
使用代码
下载本系列文章的项目/源代码:下载源代码。
随着后续文章的提交,项目/源代码也将随之扩展。新的项目文件和源文件将包含系列中较旧的部分。
注意:要获取最新代码,请转到本系列最新发布的部分并下载代码。
改进游戏玩法
我们的游戏中缺少的一个主要元素是某种形式的障碍物。事实上,我们的游戏中已经定义了一个障碍物,那就是时间。玩家受到游戏开始时给定时间的限制,在这个时间内可以到处跑动并收集金币。这很有挑战性,但玩久了会变得乏味。
玩家通常希望面对一些挑战,克服挑战是他们在游戏中实现目标时获得的满足感。因此,作为一名游戏设计师,您必须能够创造足够具有挑战性的障碍物,让玩家保持警惕和投入,但又不过于复杂以至于让玩家感到沮丧。
对于我们这个小示例游戏,我想引入一个这样的挑战。我的想法很简单,但它将稍微增强游戏玩法。我想引入一个用作发射加农炮球的 GameObject。如果这些加农炮球击中玩家,玩家将失败。
一如既往,由于我不是图形设计师,我将坚持使用简单的模型来演示和实现我们的加农炮!
建模加农炮和加农炮球
对于加农炮,我将使用一个立方体原始体。底座的尺寸将是10 厘米 x 10 厘米 x 10 厘米。换句话说,在 (x,y,z) 轴上的缩放比例为(0.1,0.1,0.1)。这就是我们加农炮 3D 可视化方面的全部。
图 1 - 加农炮底座
我还为加农炮 GameObject 创建了一个材质。该材质可以附加到新创建的立方体原始体上,为其着色。
模型层级结构介绍
我们的加农炮 GameObject 在视觉上看起来很简单,但从层级结构的角度来看,我的设计方法会稍微复杂一些。为了让我的加农炮按照我设想的那样工作,我需要在加农炮的结构中引入另外两个GameObject。我称其中一个为CannonBase,另一个为CannonStart。
这两个 GameObject 是所谓的空GameObject。也就是说,它们没有任何视觉组件。默认情况下,它们只附加了一个Transform 组件。
我们将为加农炮的几个重要部分使用空 GameObject。首先,CannonBase 将用于旋转我们加农炮的炮管。其次,CannonStart 将用于指示加农炮球的起始位置。
图 2 - 加农炮 GameObject 的结构
看看图 2。请注意,加农炮 GameObject 有两个子GameObject。这些子对象是我们的空 GameObject,代表CannonBase 和CannonStart。
注意:子 GameObject 的位置很重要。当您将一个 GameObject 设为另一个 GameObject 的子项时,子 GameObject 的变换相对于父 GameObject。这一点非常重要,您必须了解这种关系和相对位置。
以下图形将说明代表加农炮的每个 GameObject 的 Inspector 窗口。
|
|
|
|
|
|
我提供了图形作为视觉提示。请注意,作为**父**GameObject的加农炮 GameObject 位于(-3.892, 0.05, 1.082) 的位置,作为加农炮 GameObject 的**子项**的CannonBase GameObject 位于(0, 0.55, 0) 的位置。此位置是相对于*父 GameObject*的。这意味着,**CannonBase** 位于父GameObject中心上方0.55 米处。然后,作为CannonBase GameObject 子项的CannonStart GameObject 位于(0, 0.1, 0) 的位置,相对于其*父对象*。另请注意,*CannonStart GameObject*在 X 轴上具有347 度的旋转。您很快就会明白原因。
注意:通常,在将子 GameObject 附加到父项时,最好对其执行一次**重置**。这是通过使用**Inspector 窗口**中选定 GameObject 的**Transform 组件**的**设置**下拉菜单来完成的。这将将其移动到父 GameObject 的中心。然后您可以相应地进行调整。
图 9 - 重置子 GameObject
现在我们对加农炮模型满意了,是时候用魔法让它活起来了!是的,您猜对了,就是编程!
using UnityEngine; using System.Collections; public class LaunchCannon : MonoBehaviour { public float canonBallSpeed = 0.5f; // spped of our cannon ball public float rateOfFire = 5.5f; // cannon blast rate in seconds public float fireDelay; public GameObject cannonBall; // used to store our cannon ball prefab public GameObject cannonBase; // used to store our cannon base public GameObject cannonStart; // used to store our cannon nozle starting point // Use this for initialization void Start () { this.canonBallSpeed = Random.Range (3, 4); // randomize cannon ball speed between 3 and 4 this.rateOfFire = Random.Range (3, 6.5f); // randomize rate of fire between 3 and 6.5 seconds // set a fire delay time for the cannon this.fireDelay = Time.time + this.rateOfFire; } // Update is called once per frame void Update () { // if the current game running time is larger then our fire delay time, then fire! if (Time.time > this.fireDelay){ // cannon ball speed and rate of fire will be randomly generated based // on the defined boundaries ... this.canonBallSpeed = Random.Range (3, 4); this.rateOfFire = Random.Range (3, 6.5f); // re-set our fire delay this.fireDelay = Time.time + this.rateOfFire; // instantiate our cannon ball at the specified location GameObject fire = GameObject.Instantiate(this.cannonBall, this.cannonStart.transform.position, this.cannonStart.transform.rotation) as GameObject; // self-destruct our cannon ball after 10 seconds Destroy(fire, 10.0f); // use the physics engine to push/give velocity to our cannon ball fire.rigidbody.velocity = this.cannonStart.transform.TransformDirection( new Vector3(0,this.canonBallSpeed,0)); } // rotate the cannon base around its Y-Axis continiously this.cannonBase.transform.Rotate (Vector3.up, 10 * Time.deltaTime); } }
脚本相当简单。我们来看一下。我们定义了以下GameObject变量:cannonBall、cannonBase 和cannonStart。变量名应该能够自描述。cannonBall 变量是对将代表我们加农炮球的预制件的引用,cannonBase 变量是对加农炮模型中的加农炮底座空 GameObject 的引用,而cannonStart 是对加农炮模型中的加农炮起始空 GameObject 的引用。
cannonball 变量用于在加农炮准备就绪时引用并实例化我们的加农炮球。cannonStart 变量用于指定实例化时加农炮球的初始位置。cannonBase 变量用于在 Y 轴上连续旋转自身以及附加的子对象 cannonStart。
请注意,在Start() 和Update() 函数中,我们将 *cannonBallSpeed* 和 *rateOfFire* 变量设置为随机生成的数字。这样做是为了让加农炮的行为和感觉独立且随机,使其更有趣。
另一个需要注意的元素是,我们设置了一个计时器,并且只有在满足计时器条件时,加农炮才会开火。**if (Time.time > this.fireDelay)** 语句负责处理此事。**Time.time** 是游戏的实际运行时间!它从 0 开始...N,N 是您退出或终止游戏时的时间。*fireDelay* 变量被设置为*rateOfFire* 和当前*Time.time* 的总和。这将确保加农炮不会连续发射加农炮球,从而淹没游戏!要看到区别,只需注释掉**if()** 语句。
还有一件非常重要的事情要注意:**Destroy(fire, 10.0f);** 用于销毁指定的游戏对象。您能猜到为什么这很重要吗?
想象一下这种情况,假设您的加农炮每 2 秒发射一次加农炮球。这意味着每两秒钟我们都会创建一个新的加农炮球 GameObject。所以在 10 秒内,我们将有 5 个加农炮球在场景中,在 60 秒内将有 30 个,在 5 分钟内,每个加农炮将在场景中生成 150 个加农炮球!
现在假设我们在关卡中放置了 5 个加农炮,这将在 5 分钟内生成 750 个加农炮球!场景中有很多加农炮球!它不仅会从游戏玩法的角度阻碍玩家,还会浪费大量内存和 CPU 资源。请记住,这些对象是rigidbody 对象,每个对象都与物理引擎交互,因此,游戏引擎必须计算和计算所有这些游戏对象在它们驻留在内存中的时候的实际交互!因此,您在开发关卡时需要考虑这一点。
因此,**Destroy(GameObject,TimeInSeconds)** 函数将在指定的秒数后从内存中移除引用的 GameObject!在我们的例子中,每个加农炮球在游戏中的寿命为 10 秒!
加农炮球
加农炮球本身就是一个球体原始体。我已经将其在所有三个轴上缩小到了 (0.05, 0.05, 0.05)。给它应用一个Rigidbody 并将插值选项设置为 Interpolate。我引入的另一个组件是Audio Source。当您将 Audio Source 附加到 GameObject 时,您就可以从该 GameObject 发出声音,在这种情况下,我们希望我们的加农炮能够发出射击加农炮球的音效,因此,应用声音的最简单方法就是将其应用于加农炮球本身。现在您可以将音频剪辑应用到音频源,并在需要时播放它。
|
|
增强场景并整合一切
我已经继续添加了四个额外的光源,并将它们分别放置在四个角落。由于我的关卡没有任何纹理,我认为添加一些灯光效果可以增强其视觉效果,还可以用作视觉提示,让玩家知道他们在关卡中的位置。
图 12 - 添加光源等...
这在您开始扩展想法并引入新功能时会很有用。我添加了四个点光源,分别带有独特的颜色:红色、蓝色、橙色和绿色。
下一步是将脚本附加到加农炮 GameObject。附加后,我继续为各自的元素分配了以下变量:cannonBall、cannonBase、cannonStart。如果您还记得,cannonball 是对CannonBall Prefab 的引用,cannonBase 和 cannonStart 是对加农炮 GameObject 层级结构上子对象的引用!完成后,我还将加农炮 GameObject 制成了一个 Prefab。
现在,您可以将您的加农炮 Prefab 任意次数地拖放到场景中。我已经放置了 5 个加农炮。从图 12 中可以看到它们的位置,因为它们带有粉红色调。
我做的最后一件事是向 CP(角色玩家)添加了另一个 Audio Source,并将背景关卡音乐附加到了这个 Audio Source。因此,现在当您开始游戏时,您会听到背景关卡音乐以及加农炮球发射时的音效。
关注点
到目前为止,我们还没有为加农炮球创造一种能够摧毁玩家或对玩家造成伤害的机制。下一步将是想出一个机制,以便在玩家被加农炮球击中时对其造成伤害。
历史
这是我将缓慢贡献给 Code Project 社区的系列文章中的第八篇。
- Unity 3D – 游戏编程 – 第 1 部分
-
Unity 3D – 游戏编程 – 第 8 部分
Unity 3D 网络文章
Unity 3D Leap Motion 和 Oculus Rift 文章