此文章由机器翻译。
Unity
使用 Unity 和 C#(第 3 部分)开发您的首个游戏
你仍与我在本系列中。 好。 第一篇文章,来掩盖一些统一基础知识 (msdn.microsoft.com/magazine/dn759441)。第二,集中统一的 2D (msdn.microsoft.com/magazine/dn781360)。现在我去我最喜欢的游戏开发部分 — — 3D。3D 的世界是一个真正神奇的地方 — — 令人惊异的浸入式环境,丰富的声音效果和美丽的视觉效果 — — 甚至只是一个简单的益智游戏与真实世界的物理可以让你上瘾几个小时。
3D 游戏无疑增加了一层复杂性,在 2D,但以它一片一片你可以建立一个很酷的 3D 游戏。新项目设置为 2D 和 3D 团结支持 3D。你可以有三维对象在一个 2D 游戏 (反之亦然)。
什么构成了一个 3D 场景?
3D 场景主要包括三个主要的视觉组件 — — 灯光,网格渲染器和着色器。一盏灯是,嗯,一盏灯和团结支持四种不同类型。你可以发现他们都在游戏菜单下。尝试添加各种类型和更改其属性。最容易的一个,照亮你的场景是定向的光,像是天空中的太阳。
网格 (或模型) 是弥补构成对象的多边形的顶点的集合。着色器是一个包含代码来控制您的对象将如何显示或与光相互作用的编译的例程。一些着色器简单地以光和反映它像一面镜子 ; 其他人采取一种纹理 (要应用于您的网格的图像) 和可以启用阴影和深度 ; 和有些甚至允许您通过您的模型,就像篱笆视觉打孔。
模式通常是从另一个建模软件程序包导出的 FBX 或 OBJ 文件。FBX 文件也可以包含动画数据,所以您可能会收到一个 FBX 文件为您的模型和一个包含几个动画。此外支持了几个第三方文件格式,如玛雅.ma 格式和搅拌机文件。通常,您需要第三方程序安装在同一系统上,如果您要导入这些文件的统一,那么就只需拖动并将它们放入到统一的项目,就像任何其他文件一样。在幕后,统一将转换的 FBX 文件格式为其他文件格式 (在导入或检测文件更改)。
资产存储库
我的第一篇文章,谈到资产存储库,但在 3D 游戏中是不是真的派上用场。我不是艺术家,因为这是一本专业杂志,我猜你们中的大多数也不是。(如果你是,请接受我恭喜,你是一种罕见的群的一部分。)但如果我想要创建一个游戏,与郁郁葱葱的环境和被破坏的古建筑,例如,它不是一个问题。我可以买我从资产存储库的需要。有 15 个不同僵尸的想,我可以从 Mixamo 在资产存储库中购买一包。潜在的组合是几乎是无止境的所以不要担心别人的游戏看起来像你的。最重要的是,资产存储库集成到统一。您可以通过单击窗口升级您的包裹 |资产存储库,然后 bin 图标。你也可以看看评论和注释更容易确定某个特定项目是否适合您的项目,例如,其移动优化与否。桌面游戏通常可以处理更多的对象/顶点/纹理/内存比手机的游戏,虽然一些较新的芯片使移动设备今天看起来像 Xbox 360。
在典型的 3D 游戏中,很多相同的概念,从一个 2D 游戏应用 — — 碰撞、 触发器、 刚体、 游戏的对象转换、 组件和更多。无论类型的 3D 游戏,您通常会希望控制输入、 移动和字符 ; 使用动画和粒子效果 ; 共建是幻想和现实充满想象力的世界。我将讨论一些方法统一也有助于此。
输入、 运动和角色控制器
阅读输入运动变得稍微复杂的 3D 的因为,而不是只动中的 X 和 Y 平面,你现在可以动议在三个维度:X、 Y 和 Z。 三维运动的情况包括 (但不限于) 自上而下的运动,一个字符移动到哪里只有水平和垂直方向 ; 旋转照相机或字符,当读取鼠标输入,是这样做的很多第一人称射击游戏 (FPS) 游戏 ; 扫射左到右的时候阅读水平输入 ; 旋转,转过身,当阅读水平的输入 ; 或只是倒着走路。有大量优秀的移动选项可供选择。
当移动一个对象时,你不给它一个位置来移动,正如你可能期望。请记住,你正在执行的代码与每个帧,所以你仍然需要以小的增量移动该对象。要么,你可以让物理引擎处理这一支部队加入你的刚体移动它,或者您可以补间的对象。补间基本上意味着过渡之间的价值观 ; 那就,从 A 点搬到 b 点。 有很多种补间统一,包括免费的第三方库,如 iTween 中的值。图 1 演示一些手动的方式来移动对象的统一。请注意,为了简单起见,他们还没有进行优化 (要做到这一点,我认为在一个变量中,往往阻止去从托管代码到本机代码转换的引用)。
图 1 移动对象的各种方法
// Method 1
void Update()
{
// Move from point a to point b by .2 each frame - assuming called in Update.
// Will not overshoot the destination, so .2 is the max amount moved.
transform.position =
Vector3.MoveTowards(transform.position, new Vector3(10, 1, 100), .2f);
}
// Method 2
void Update()
{
// Interpolate from point a to point b by a percentage each frame,
// in this case 10 percent (.1 float).
var targetPosition = new Vector3(10,0,15);
transform.position = Vector3.Lerp(parentRig.position, targetPosition, .1f);
}
// Method 3
void Update()
{
// Teleport the object forward in the direction it is rotated.
// If you rotate the object 90 degrees, it will now move forward in the direction
// it is now facing. This essentially translates local coordinates to
// world coordinates to move object in direction and distance
// specified by vector. See the Unity Coordinate Systems section in the
// main article.
transform.Translate(Vector3.forward * Time.deltaTime);
}
// Method 4
void FixedUpdate()
{
// Cause the object to act like it's being pushed to the
// right (positive x axis). You can also use (Vector.right * someForce)
// instead of new Vector().
rigidbody.AddForce( new Vector3(7, 0, 0), ForceMode.Force);
}
// Method 5
void FixedUpdate()
{
// Cause the object to act like it's being pushed to the positive
// x axis (world coordinates) at a speed of approx 7 meters per second.
// The object will slow down due to friction.
rigidbody.velocity = new Vector3(7,0,0);
}
// Method 6
// Move the rigidbody's position (note this is not via the transform).
// This method will push other objects out of the way and move to the right in
// world space ~three units per second.
private Vector3 speed = new Vector3(3, 0, 0);
void FixedUpdate()
{
rigidbody.MovePosition(rigidbody.position + speed * Time.deltaTime);
}
// Method 7
void FixedUpdate()
{
// Vector3.forward is 0,0,1. You could move a character toward 0,0,1, but you
// actually want to move the object forward no matter its rotation.
// This is used when you want a character to move in the direction it's
// facing, no matter its rotation. You need to convert the meaning of
// this vector from local space (0,0,1) to world space,
// and for that you can use TransformDirection and assign that vector
// to its velocity.
rigidbody.velocity = transform.TransformDirection(Vector3.forward * speed);
}
每种方法各有优点和缺点。可以移动只是变换的性能命中 (方法 1-2),虽然它是一个非常简单的方法,做运动。统一假定,如果一个对象在它没有刚体组件,它可能不是一个移动的物体。它生成一个静态碰撞矩阵内部要知道对象在哪里,这可提高性能。当你移动对象移动变换时,这个矩阵已被重新计算,这将导致性能下降。对于简单的游戏,你可能从未注意到的命中和它可能是最简单的事情要做,虽然作为你的游戏变得更加复杂,就必须移动刚体本身,方法 4-6 的那样。
旋转对象
旋转对象是相当简单的就像移动的对象,除了向量现在代表度而不是位置或一个归一化的向量。归一化的向量是只是一个向量,一个对于任何值的最大值,你只是想简单地通过使用矢量引用一个方向时,可以使用。有一些矢量关键字可帮助,如 Vector3.right,回来了,向前,下来,起来,左,右,零和一。将移动或旋转在水平方向上积极的东西可以使用 Vector.right,只是一个快捷方式 (1,0,0),或者向右的一个单位。为旋转对象时,这将是一个学位。在图 2,只是旋转一点点每一帧中的对象。
图 2 方法旋转对象
// Any code below that uses _player assumes you
// have this code prior to it to cache a reference to it.
private GameObject _player;
void Start()
{
_player = GameObject.FindGameObjectWithTag("Player");
}
// Method 1
void Update () {
// Every frame rotate around the X axis by 1 degree a
// second (Vector3.right = (1,0,0)).
transform.Rotate(Vector3.right * Time.deltaTime);
}
// Method 2
void Update () {
// No matter where the player goes, rotate toward him, like a gun
// turret following a target.
transform.LookAt(_player.transform);
}
// Method 3
void Update()
{
Vector3 relativePos = _player.transform.position - transform.position;
// If you set rotation directly, you need to do it via a Quaternion.
transform.rotation = Quaternion.LookRotation(relativePos);
}
这些技术都是次要的细微差别。您应该使用哪一个?不适用于部队的刚体,如果可能的话试试我已经大概只被迷惑了你有点与该选项。好消息是几乎所有这些都能为您的现有代码。
你注意到四元数方法 3 中的吗?统一内部使用四元数来表示所有的旋转。四元数是防止叫做万向节锁,如果你使用普通的欧拉角旋转可能会发生效应的有效结构。万向节锁两个轴旋转在同一平面上,然后不能分离时发生。(在视频 bit.ly/1mKgdFI 提供了一个很好的解释。)若要避免此问题,统一使用四元数,而不是欧拉角,虽然在统一编辑器中您可以指定欧拉角,它会转换成一个在后端的四元数。很多人从来没有经历万向节锁,但我想指出一点,如果你想要直接在代码中设置一个旋转,你必须通过一个四元数,您可以从使用 Quaternion.Euler 的欧拉角转换。
现在,您已经看到很多选项,我应该注意到发现最简单的方法是使用一个刚体,并简单地套用。AddForce 的字符。我更喜欢重用代码时,并幸运地统一供应预置的数目。
让我们不重新发明轮子
Unity 提供了资产存储库中的示例资产包 (bit.ly/1twX0Kr),其中包含一个跨平台输入管理器移动操纵杆控制,一些动画和粒子,与大多数重要的是,一些预建的角色控制器。
有一些旧的资产 (撰写本文时,版本 4.6) 附带统一。现在,这些资产分布作为一个单独的软件包,团结可以单独更新。不必写的所有代码来创建一个第一人称人物在你的游戏,三人的性格或甚至是自动驾驶的汽车,你可以只使用示例资产从预置。拖和放入你的场景和瞬间你有查看具有多个动画和完全访问源代码,第三人称,如中所示图 3。
图 3 第三个人预置
Animations
一整本书可能是专用 (,) Mecanim 动画系统的统一。在 3D 动画是一般比二维空间中比较复杂。在 2D、 动画文件通常改变精灵渲染器中每个关键帧,给动画的外观。在 3D 动画数据是很多更复杂。记得从我的第二条动画文件包含关键帧。在 3D 中,可以有很多的关键帧,每个都有很多的数据点,改变一根手指,一只手臂或一条腿,移动,或执行任何数目和类型的运动。网格可以也已定义骨头,并且可以使用的组件称为皮肤网格渲染器变形网格基于骨骼是如何移动的一样的活的动物会。
虽然您可以创建他们在团结、 以及动画文件通常创建在一个第三方建模/动画系统。
3D 动画系统中的字符的基本姿势是 T 构成,这就是它听起来就像 — — 人物,站直的一面张开了双臂,和它适用于几乎任何人形机器人形状模型。然后可以通过 Mecanim 将几乎任何动画文件分配给它来搞活的基本特征。你可以有一个僵尸,精灵和人类所有跳舞以同样的方式。你可以混合和匹配的动画文件,然而你认为合适,并将它们分配通过国家多就可以在 2D。若要执行此操作,您使用动画控制器,如中所示图 4。
图 4 动画控制器用于控制字符动画状态
请记住,你可以从统一资产存储 ; 字符和动画 您可以创建它们带有建模工具 ; 还有像 Mixamo 的保险丝的第三方产品,使您能够快速生成您自己的自定义的字符。查阅我为入门动画在统一的通道 9 视频。
创造一个世界
统一有一个内置的地形系统,创造一个世界。您可以创建一种地形,然后使用包括的地形工具雕刻你的地形,使山,地方的树木和草、 油漆纹理和更多。可以通过导入天空盒包向你们的世界添加一片天空 (资产 |导入包 |天空盒) 并将其分配在编辑 |渲染设置 |天空体材料。它我只花了几分钟来创建地形与反射,动态水、 树木、 沙子、 山和草,如中所示图 5。
图 5 快速创建地形
统一坐标系统
统一已指在游戏中或在屏幕上的点,如中所示的四种不同方法图 6。还有的屏幕空间,范围为从 0 到的像素数,通常用于获取在用户接触,或单击屏幕上的位置。视口空间是简单值 0 到 1,这使得它容易说,例如,一半是.5,而不是以像素为单位) 除以 2。 所以我可以轻松地放置在屏幕的中间对象通过使用 (。 5、.5) 作为它的位置。世界空间是指在基于三坐标,一个游戏对象的绝对定位 (0,0,0)。在一个场景中的所有顶级游戏对象有他们列在世界空间中的坐标。最后,局部空间始终是相对于父游戏对象。一个顶级的游戏物体,这是世界空间相同。所有的子游戏物体列出中的编辑器中相对于其父级的坐标,所以在一栋房子,例如,您的应用程序中的模型可能有世界坐标 (200、 0、 35),同时它的前门 (假设它一个子游戏物体的房子) 可能只有 (1.5,0,0),因为这是相对于父级。在代码中,当您引用 transform.position,它老是在世界坐标,即使它是一个子对象。在示例中,门会 201.5、 0 35),但如果您改为引用 transform.localPosition,你一定会回来 (1.5,0,0)。统一具有在不同的坐标系统转换的函数。
图 6 中统一坐标
在事先移动我大多使用世界空间移动,但在某些情况下使用本地空间的例子。回指中的方法 7 图 1。在这个例子中我采取 Vector.forward,局部归一化 (或单位) 矢量即 (0,0,1)。这种情况本身没有多大意义。然而,它显示移动的东西,在 Z 轴上是向前的意图。如果该对象从 0,0) 是旋转 90 度吗?转发可以现在有两个含义。它可以意味着原始绝对 Z 轴 (在世界坐标系中) 或相对于旋转对象,一直指向前为对象的 Z 轴。如果我想要一直前进不管其旋转的对象,我可以简单地翻译之间本地转发到真实世界前向向量的变换。TransformDirection(Vector3.forward * speed) 这个例子所示。
线程处理和协同
统一使用协同系统来管理它的线程。如果你想要什么发生在你认为应该是一个不同的线程,你踢掉协程,而不是创建一个新线程。统一管理这一切的幕后。会发生什么是协同暂停时它击中的产量的方法。在示例中图 7,攻击动画演奏为一个随机长度而暂停,然后再次在攻击中发挥。
图 7 使用暂停行动的协同
void Start()
{
// Kick off a separate routine that acts like a separate thread.
StartCoroutine(Attack());
}
IEnumerator Attack()
{
// Trigger an attack animation.
_animator.SetTrigger("Attack");
// Wait for .5 to 4 seconds before playing attacking animation, repeat.
float randomTime = Random.Range(.5f, 4f);
yield return new WaitForSeconds(randomTime);
}
物理和碰撞检测
在 3D 中的物理现象和碰撞检测功能则与 2D、 几乎相同,除了形状也不同、 碰撞和刚体组件具有几个不同的属性,比如能够做到自由旋转或在 X、 Y 和 Z 轴的运动。在 3D 现在是作为一个碰撞检测区环绕整个形状的一种模型的网格碰撞器。这可能听起来很好,和碰撞是不错,但它不是很好的性能。理想情况下,您想要简化对撞机形状和限制使用它们所需的处理能力。有一只僵尸吗?没问题,使用一个胶囊碰撞器。一个复杂的对象吗?使用多个碰撞器。请尽量避免网格碰撞器。
Unity 提供了大量的方法,要知道当发生碰撞情况下,或触发触发器。下面显示了只是一个基本的例子:
void OnCollisionEnter(Collision collision)
{
// Called when you have a physical collision.
Debug.Log("Collided with " + collision.gameObject.name);
}
void OnTriggerEnter(Collider collider)
{
// Called when another object comes within the trigger zone.
Debug.Log("Triggered by " + collider.gameObject.name);
}
有更多的方法比在此处列出,如 OnTriggerExit 和 OnCollisionExit,他们就几乎等同于 2D 的同行。
对象创建
当你想要在运行时创建新的基于游戏的项目时,您不使用构造函数。相反,您使用实例化。你当然可以使用构造函数,只是不能直接在脚本从 MonoBehavior,碰巧被分配到任何游戏对象的所有顶级脚本继承中上课。这些脚本可以,然而,呼吁他们想要的所有其他对象构造函数:
// Assume this reference has been assigned in the editor.
[SerializeField]
private GameObject _zombie;
void Start()
{
// Create a new instance of that game object. This can be
// a prefab from your project or object already in scene.
Instantiate(zombie, transform.position, Quaternion.identity);
}
粒子效果
如果你想要闪烁的星星、 灰尘、 雪、 爆炸、 火灾、 雾从瀑布,血液影响或许多其他的影响,您使用了粒子效果。还有老的粒子系统在统一和更新,更优化一个叫做手里剑。你可以用手里剑在统一中,包括有支持碰撞你下落微粒的这么多不可思议的事情。因为有很多教程,如在一个 bit.ly/1pZ71it,它们通常在编辑器中使用设计器创建,在这里,我将只显示如何当一个字符说,进入一个硬币收集该触发器区域,他们可以实例化。
若要开始与粒子,只是转到游戏对象 |粒子系统菜单,你会立即看到一个添加到你的场景,如图 8。
图 8 粒子效果
我想从我的粒子系统创建预置 (是我所提及的第二篇文章),所以我可以轻松地重用它们,和我然后可以轻松地实例化它们通过代码通过第一次将脚本指派到一个游戏物体 (假设所有游戏对象脚本组件都在 MonoBehavior,从派生的类),然后在编辑器从我的场景或预置在我的项目上,例如拖动了粒子效果,在公开的 SmokeEffect 属性图 9。
图 9 公开的 SmokeEffect 属性
[SerializeField]
private ParticleSystem _smokeEffect;
void OnTriggerEnter(Collider collider)
{
// Ensure you only show particles if the player comes within your zone.
if (collider.gameObject.tag == "Player")
{
GameController.Score++;
// Create particle system at the game objects position
// with no rotation.
Instantiate(_smokeEffect, transform.position, Quaternion.identity);
// Don’t do: Destroy(this) because "this"
// is a script component on a game object, so use
// this.gameObject, that is, just gameObject.
Destroy(gameObject);
}
}
创建一个用户界面
统一 4.6 添加全新的用户界面系统用于创建平视显示器中使用文本、 面板、 小部件和更多的游戏元素。将文本添加到您的游戏的显示器是只需点击游戏 |UI |文本和设置中的字体和文本。如果你想要控制,后来通过代码也许更新分数,您只需使用:
// Gets the UnityEngine.UI.Text component.
var score = GetComponent<Text>();
score.text = "Score:0";
如果希望图像在 UI 中,只需点击游戏 |UI |图像,并分配一个 2D sprite 图像到此新组件。我可以只是因为与其他任何游戏对象设置这些值。我希望你现在看到一种模式。若要创建一个简单的 GUI,创建通过游戏物体的 UI 对象 |用户界面菜单编辑器中设置的初始值和控制他们后来通过获取对这些 UI 组件的引用和设置的值,或甚至对值进行动画处理。建立了一个基本的 GUI,示图 10,通过创建下一个新的画布组件元素。新的统一 4.6 UI 系统包含大量的基本对象类型如面板、 按钮、 文本、 图像、 滑块、 滚动条和切换,并非常容易锚定它们,缩放,和拖放它们来创建一个用户界面。
图 10 图像和单挑文本的用户界面
在你的游戏的 AI
它不会公平而不应提及 AI,虽然我不会陷入在这里创建 AI,(即使它的构造块是在前面的代码示例为查找移动旋转)。不过,我要提到几个可供您使用的选项。我毫不犹豫地呼吁 AI 人工智能游戏,因为它不是只是一个非常基本的动作如此多的情报。我向您展示了如何向另一个对象的旋转和移动该对象的变换。这是基本的 AI 在很多比赛。统一具有一些内置的寻找路径功能,其 NavMesh 的支持,提前计算对象周围的所有路径。NavMesh 运作相当良好,现已列入统一,免费版虽然很多人选择相反使用 A * 寻路项目 (arongranberg.com/astar),这是一种算法,您可以自己实现,或救救你自己的时间通过购买资产打包为它。截至这写作,2D 的寻路支持内置不统一,只有 3D,虽然 A * 不会有那样的能力。行为从 AngryAnt 2.0 是一个受欢迎的 AI 插件为统一与一些真正强大的功能,而且还有雨,从一个免费的 AI 工具包 rivaltheory.com,也是相当不错并且有内置行为进行跟踪,发现,Mecanim 集成。
总结
3D 世界增添了额外的复杂性层 2D 因为它涉及全网格和增加一个维度。资产存储库是绝对关键的初学者和先进的一样,你可以真的下车一个快速的开始通过使用预先创建的资产。
开始开发游戏,疯狂地将在互联网上找到很多的模型和贴图。有一些伟大的资产市场在那里,但你很快就会发现他们并非全是好的游戏。我一旦下载了 100,000 在其模型中顶点附近的小一块 !寻找的移动优化的资产或签出的多边形顶点/计数,以确保你找到那些能为你的游戏。否则,他们可以大大拖慢你的表现。有您可以使用在模型,其中包括一个用于统一称为彻的优化工具。在下一篇文章中,我将讨论如何接管一个游戏或应用程序从统一到 Windows 平台。签出我的频道 9 博客 (aka.ms/AdamChannel9) 的一些视频和下载内容的链接。
Adam Tuliper 是生活在阳光明媚的加利福尼亚州南部的一位 Microsoft 资深技术传播者。他是独立游戏开发的奥兰治县团结 Meetup co 管理员和 Pluralsight.com 作者。他和他的妻子即将拥有自己的第三个孩子,所以在他尚有闲暇的时间里,您可以通过访问 adamt@microsoft.com 或 Twitter twitter.com/AdamTuliper 来联系到他。
衷心感谢以下技术专家参与本文的审阅:马特 · 纽曼 (潜科学工作室) 和 Tautvydas Žilys (统一)