Unity 4.3 2D 教程:新手上路
这篇文章译自 Christopher LaPollo 先生的 Unity 4.3 2D 教程的第一部分 Unity 4.3 2D Tutorial: Getting Started
感谢这套优秀教程的作者@Chris!译者水平有限,翻译不准确的地方请参考原文,文中所有Unity的关键字都没有翻译。
如果你尝试用更早版本的Unity来制作2D游戏,那当然没问题,但你也知道必须先解决一些问题。
可能你通过给quad应用纹理,使用脚本调整纹理参数来实现一些动画。由于它们在3D环境,如果添加物理效果,你需要保证它们在同样的深度才能互相作用,同时还要确保它们没有意外的绕x或y轴旋转。再者,你可能添加Unity的Asset Store中各种各样的元件,例如2D Toolkit或者Orthello 2D Framework,它们都拥有一些很棒的特性,但还是需要你来控制。
现在Unity 4.3本地包提供了一个新的维度,你可以工作在2维环境,同时原先的一切仍然可用。
这是探索Unity2D系类教程的第一篇,通过这一系列教程,你将学会制作一个名叫Zombie Conga的游戏,这是一个2D卷轴游戏,其中有一个逍遥自在只想跳舞的僵尸,一个想在来世变成僵尸的猫,和一个想阻止它们的老处女。
学习怎样在Unity 4.3中使用新的内置2D toolset制作一个2D游戏!
这个Unity 4.3 3D的教程重点放在Unity的新类型——Sprite,所有关于它你需要知道的知识,你都将学到,而且,之后你会学到如何使用Unity的 Animators 控制动画,你也会了解到Unity的新2D物理支持。
还有很长的路要走,你应该起航了。
Note:这个教程假定你至少有一些Unity的经验,你应该知道Unity的基本操作界面,GameObjects 和 Components,而且你也应该知道一些操作,比方说:
“通过从 Project browser 拖一个猫到 Hierarchy 来添加到你的场景”
如果你觉得它听起想胡扯,或者你想让你拖动这只猫的时候有一个更好的心态,你可能要先阅读一下Unity的基本简介,比如这个。
最后,注意这个教程是面向OS X的,如果你在Windows下工作,不用担心,因为Unity中大多数操作都是相同的,可能会有一点点不同(比方使用Windows Explorer来代替Finder),但我相信你能搞定,或者你干脆你和我一样用OS X也行。
推开大门
Unity在4.3版引入了本地2D工具包(免费和专业版都有),所以确保你安装的是最新版本,你能在Unity的官网下载到。
你也需要一些素材来制作2D游戏,幸运的是,Mike Berg制作了一些很酷的图片为Zombie Conga,下载Mike的作品,解压它到某个方便的地方。
Note:你可以使用这个教程提供的素材,音乐和音效在你想做的游戏中,但是必须注明来源在你的游戏中:“Artwork/sounds: from iOS Game by Tutorials book, available at http://www.raywenderlich.com”
创建你的游戏
打开Unity通过选择 File\New Project…. 点击出现的 Project Wizard 对话框中的 Create new Project 选项卡中的 Set… 创建一个新的工程。
命名为ZombieConga,选择一个文件来创建它,点击 Save。
最后,选择名称为 Set up defaults for: 组合框中的2D,就像下面这样,然后点击 Create Project。
上面提到的组合框是第一个与2D有关的特性,它允许改变你项目的默认素材资源输入设置,但是到目前为止,我们还没有它正常工作。好在你可以在工程中轻松改变这个设置。
为了确保这个设置正确,同时让你知道在哪里修改这个设置,选择 Edit\Project Settings\Editor 来在 Inspector 打开 Editor Setting。在 Default Behavior Mode 段,把 Mode 的值选择为2D,就像这样。
这个 Default Behavior Mode 定义了你项目的默认素材资源输入设置,当设置为3D时,Unity假设你想将图像文件导入为 Texture (例如PNG文件),当设置为2D时,Unity假定你想将图像文件导入为 Sprite,贯穿这套教程,你将了解到关于 Sprite 资源和输入设置的更多细节。
2D场景视图
你将面对的下一个2D特性是 2D 开关按钮在 Scene 视图的控制条上。
点击 2D开关按钮 来切换到2D模式,就想这样:
这个按钮控制 Scene 视图 camera 在透视投影和正交投影中切换,它们之间有什么不同?
当使用透视投影时,物体对 camera 呈现近大远小,就像在真实世界用眼睛观察中一样。然而,当使用正交投影时,物体距离 camera 的并距离不影响它的大小。因此在2D模式,一个物体从距离 camera 远处移动到近处,它的大小不会因为位置而。
接下来的图片展示出两种 Sence 视图的不同,它们都是从相同的位置观察相同的立方体,上面这幅是使用2D模式,下面的不是。
这个截图也显示出2D模式隐藏了让你改变 camera 透视投影和正交投影的 Scene Gizmo。使用2D模式,上方是y轴正方向,右方是x轴正方向。
Important:开关的设置不影响你游戏最终运行的结果,只是在 Scene 改变 camera 的呈现方式,但它仍然在我们框选物体时非常有用。当你制作2D游戏的时候你可能在两种模式中来回切换,甚至有时制作3D游戏也需要,但是这个教程的插图全部都是截取自2D模式。
Question:跟着截图一步步做感觉还好吗?你完全可以自己安排Unity的界面布局使你操作更轻松,试试吧。
Sprites,使这一切变得容易
看看下面的动画就能发现,用Unity的新特性添加 sprite 到场景中的过程极其容易。
步骤 1:拖动 cat.png 从你的文件夹窗口到 Scene 视图,就想这个演示一样。
步骤 2:使用你所省下的时间给Unity的开发人员发一份感谢信。
啊!那很棘手!如果你失败了,不要难过,只要重新阅读这段教程,再试一次。
Note:想知道为什么动画下方有两只猫?不要着急,待我慢慢道来。
依赖Uniyt的默认输入设置使这个操作变得十分简单,你不用时常的更正你的图片设置。无论如何,这说明Unity的新特性使在2D环境工作惊人的轻松!在接下来的教程将覆盖所有你在Unity2D模式工作的需要知道的知识。
Sprite 资源
在 Hierarchy 选择 cat,同时目光转向 Inspector,你的 Inspector 中非常有可能坐标和下面这张截图不同,但是不用在意它。你要注意的是,Unity为了在 Scene 显示这只猫,为这个 GameObject 添加了一个 Sprite Render 组件。
同时不易察觉到的是,Unity也创建了一个几何图形为这个对象,在Unity免费版中,每个 Sprite 获得一个简单的矩形,但Unity专业版为每个 Sprite 创建一个简易的 mesh 来适应图片不清晰的像素边界。注意在Unity专业版中僵尸身上蓝色的 mesh。
通过创建一个这样的 mesh 而不是应用你的 Sprites 作为纹理在一个 quad 上,Unity可以改进 Scene 的渲染效率,意味着他将产生更少的像素,并且当使用Unity专业版的 Sprite Packer 时,它能把这些纹理打包的更紧密。你将在教程的尾部了解它。
Note:别被这突然出现的僵尸吓着了,我用僵尸当例子只是因为他的 mesh 比猫的更有趣。
你将学习关于 Sprite Render 的属性贯穿这个教程,但现在,看这个 Sprite 属性。这里显示了分配给Sprite Render 的 Sprite 资源的名字,一次只能分配一个,但在后面你会学到如何通过更新这个属性来实现动画。
在下面这个图片中,正如你所见的那样,这个 cat GameObject 有一个 Sprite 属性,同时有一个名叫 cat 的资源分配给了这个渲染器。
保持 Prioject 可视,然后点击 Inspector 中的 Sprite 属性内部,来定位和高亮这个 Sprite 资源在 Project browser,就像这样:
Note:这个高亮框在几秒后渐渐消失,所以如果你没注意到,就再点一下,当然,我们的的工程中只有这一只猫,你不太可能没看到。
就像刚才的截图中的一样,Unity在 Project browser 中高亮的项目名叫 cat,而且它还有一个子对象也叫 cat。我们有两只猫?没错,可能有点迷惑。我来解释下:
- 父 cat 是一个纹理资源,它与你的源素材文件一致(cat.png),用来创建 Sprite 从这个素材,你可以看到它的缩略图。
- 子 cat 是一个当导入 cat.png 时由Unity创建的 Sprite 资源,在我们的例子中,它只有一个,因为Unity仅仅从这个文件创建了一个 Sprite,但是在后面几章,你将了解到如何从一个图像文件创建多个 Sprite。
Note:我们清楚Unity渲染的是 Sprite 对象,Sprite 类实际上仅仅包含一个需要被访问的 Texture2D 对象的信息,就是我们储存的真正图像数据。如果你想在运行时刻生成 Sprite,你可以动态创建你自己的 Texture2D 对象,但关于这些的讨论将在未来的教程中。
你可以通过从电脑中拖拽素材到你的 Scene 视图(或者Hierarchy,如果你喜欢)来添加 Sprite。但是通常,将素材添加进工程总是在添加进 Scene之前。
把你下载的剩下的图片添加到你的工程中:background.png, enemy.png, zombie.png。
通过拖动 Project browser 中的 enemy 到 Hierarchy 来添加一个敌人到你的 Scene 中。
就想猫一样,Project browser 中有两个名叫 enemy,但不要紧,随便拖哪个都行,因为拖动 Sprite asset(子对象)时,总会使用这个 Sprite,而拖动 Texture asset(父对象)时,会使用第一个子对象,在我们现在的情况,其实都一样。
在 Hierarchy 中选择 enemy,设置它的 Transform 组件中的 Position 为(2,0,0),如图。
在你的场景变乱之前,在 Hierarchy 中选择 cat,设置它的 Postition 为 (0,2,0),如图。
你的场景现在看起来可能是这样的。
最后拖动 background 从 project browser 到 Hierarchy,同时设置它的 Position 为(0,0,0),如图。
我们等会调整底图的质量,所以先不要在意它现在看起来不是很清晰(悄悄告诉你,添加 background.png 是Unity默认设置不正确的地方之一),你的 Scene 视图现在看起来是这样的:
不要被因为你没在 Sence 视图中看到猫和老处女而报警。他们只不过被埋在了沙滩下,你很快就会把他们挖出来。在你挖出他们之前,我们要开始分尸!不对,是僵尸的 Sprite,就在那!
切割 Sprite Sheets
你已经导入了 zombie.png 到你的项目,但是这个文件和别的文件不同,它不是只有一只僵尸,它有好几个僵尸,如图。
这样的文件常常被叫做 Sprite Sheet,你会想让Unity为每个图中的独立个体创建一个单独的 Sprite。
在 Project browser 中展开 zombie,如图。Unity只创建了一个子对象——一个 Sprite 包含了全部的图像,这不是我们想要的结果。
好在,Unity提供了解决的办法,你可以用来对付这种 Sprite Sheet,在 Project browser 中选择顶层的 zombie 打开 Import Setting 在Inspector。
设置 Sprite Mode 为 Multiple(如图),点击 Apply。
选择这个选项会显示一个 Sprite Editor 按钮,同时 Pivot 属性消失。
因为每个独立的 Sprite 将在其他地方定义他们各自的 pivot point(轴心点)。
注意 Project browser 这个 zombie 纹理已经没有了子对象(如下图),它的右边也没有了小箭头。
在这种状态下,zombie 纹理是不可用的,如果你尝试把它拖动到 Hierarchy,会有一个消息告诉它没有 Sprite。这是因为你需要告诉Unity,你将如何分割这个 sprite sheet。
保持 Project browser 里的 zombie 选中,点击 Inspector 里的 Sprite Editor 来打开下面的窗口。
这个 Sprite Editor 视图可以让你把这个图片分割为互相独立的 sprite。点击窗口左上角的 Slice 按钮,开始分割 sprite,就如下图。
Unity可以自动找到你图片中的 sprite,但你仍可以调整它的结果。现在我们使用默认设置,点击 Slice。如图。
Unity使用透明的边框圈定出图片中可能存在的每个 sprite,它发现了四个 spites。
经过我的测试,当每个个体直接有明确的空白的时候,Unity的自动分割效果最好。注意Untiy在下面这个图片中只找到了微笑的表情,但之后的图片中找到了三个 sprite。
Unity没有找到所有的 sprite,因为它不能使用没有交集的边界框出每个元素
Unity找到了所有的 sprite,因为它能框出每个元素
上面的图片说明,你应该恰当的安排你的 sprite sheets,它也说明,为什么Mike要这样画这张图。
点击随便一个Unity圈出的 sprite 来编辑它的详细属性,包括它的名字,坐标,边界,和轴心点。当选择了第二个 sprite 时,会出现下面这张图的样子。
你可以在这个窗口里修改这些设置,比如直接在图片上调整边界,设置轴心点。
通常,在你修改完成后,你要点击 Sprite Editor 视图右上角的 Apply 或 Revert 来保存和恢复它。
虽然Unity自动做的很好,但我们不并不想用它自动的配置。这个 zonbie.png 中的每个元素范围都是等大的矩形,在Unity中有方法可以处理这种情况。
点击 Sprite Editor 视图左上角的 Slice 按钮再次打开分割设置,但是这次,把 Type 设置为 Grid,这时它会变成下图这样。
左上角弹出的小视图中,Pixel size 属性可以让你指定网格的大小,X是每个格子的宽度,Y是高度。Unity 将使用这些数值来等距分割你提供的图片。把X设置为157,Y设置为102。如图。
点击 Slice,Unity找到了四个 sprite。
你仍然可以选择其中的一个就像刚才那样设置它们的详细属性,但目前,这不重要。
点击 Sprite Editor 右上角的 Apply 按钮,应用你的操作。注意 Project browser 视图内 zombie 发什么了什么变化。它现在拥有了四个子对象,分别名叫 zombie_0,zombie_1 等,就像下图这样。
虽然 zombie 纹理被分割成为了多个 sprite,但把他们添加进场景的方法没什么不同,你仍然可以把他们直接拖到 Hierarchy 来创建你的 GameObject。这里告诉你一个新的方法。
通过选择 GameObject\CreateEmpty 创建一个新的敌人 GameObject。重命名为 zombie,设置 Position 为 (-2,0,0),如图。
在 Hierarchy 中保持 zombie 选中。点击 Inspector 中的 Add Component,在出现的菜单中选择 Rendering,然后选择 Sprite Render 来添加一个 Sprite Render 组件,如图。
点击 Sprite Render 的 sprite 属性旁的那个小圆圈图标,打开 Select Sprite 对话框。那个图标是这样的:
这个出现的对话框有两个选项卡,分别是 Assets 和 Scene,分别包括所有你项目中和现在场景中的所有 sprite。
切换到 Assets 分页,选择 zombie_0 分配给这个属性来让 Sprite Render 组件渲染,如图。
在场景视图,你就能看到一个僵尸悠闲的站在沙滩上,此刻,沙滩下的某处埋着老处女和她的猫。哈哈。
所有需要的元素都已经导入场景。是时候来解决一些问题了。
配置你的 Game 视图
Zombie Conga 的所有素材是为了制作iPhone游戏,意味着它在特殊的分辨率下看起来才会更好。为了适应iPhone的环境,把 Game 视图的分辨率大小设置为 1136x640。
修改 Game 视图的宽高比或者分辨率都是使用 Game 视图的控制条的那个下拉菜单,就是图中高亮的地方。
点开这个菜单,里面是几个默认提供的设置,如果里面已经有了 1136x640,选择它就行了。如果没有,点击菜单底部的 + 按钮,如图。
创建一个新的分辨率设置,将 Type 设置为 Fixed Resolution,设置宽度和高度分别为1136和640。如图。
点击 OK,在菜单中选择这个新的设置。
你的 Game 视图现在看起来应该是这样:
Note:可能你的视图看起来和这个截图不是完全一样,因为Unity重新按照你设置的宽高比调整了视图的大小,但你仍可以在视图中看到相同的内容。
显然,它不是很正确,你在这里已经看到了三个不同问题,你将一一更正它。
- 场景摄像机的位置不合适,所以底图没有合适的填满视野。
- 你的元素渲染持续不正确,所以猫和老处女都被埋在了沙滩下。
- 底图的质量不是很好,虽然用现在的观察方法不是很容易察觉到,特别当你不熟悉底图原本的样子的时候。但你要相信我我说的,不是吗?
首先开始修正第一个问题。
修正你的 Camera 的属性
在2D游戏中,你通常想使用正交投影而不是透视投影。你在之前的章节已经知道了这两个 Scene 视图的属性,但你可能没意识到,Unity可能会默认设置你的摄像机为透视投影。
在 Hierarchy 中选择 Main Camera,然后,在 Camera 的组件中,确保 Projection 已经设置到了 Orthographic。
通过设置它的 Transform 组件的 Position 为(0,0,-10),来将摄像机面朝场景中心垂直放置。你的 Inspector 面板现在应该看起来是这样:
同时你的 Game 视图现在看起来应该是这样:
现在看看,这和使用透视投影的设置看起来没什么太大的不同。如果 sprite 不会随着它距离摄像机的距离而改变大小,那要如何放大这个底图让它铺满整个屏幕呢?你可以试试缩放你的 GameObject,但这里有一个更好的解决办法——改变摄像机的 Size 到适当的值。
摄像机的 Size 定义了它视野的基本尺寸。这个数字的大小是从视图中心到顶边的距离。换句话说,就是视图高度的一半。而宽度是在运行时由宽高比计算出来的。如图。
在这个例子中,你想底图完美填满整个屏幕从上到下,同时允许水平滚动。底图的高度是640像素,所以一半就是320像素。这不就是我们要的数值吗?
还没完。
在 Project browser 中选择父 background 来在 Inspector 查看它的 Import Settings。
看这个 Sprite 渲染器的 Pixels to Units 属性。它现在使用的是默认的100,如图。
在Unity中,单位不是很重要,相当于屏幕的像素。反而你更经常使用相对大小。可能假设一个单位长度是一米。对于 Sprites,Unity使用 Pixel to Units 来定义除去比例后的大小。
例如,想象导入了一个500像素宽的 Sprite,下面这个表展示了为 Pixels to Units 使用不同的值时候,你的 GameObject 在x轴上渲染出的宽度。
background.png 的高度是640像素,并且它的 Pixel to Unity 属性的值是100,所以 background 对象在 Hierarchy 将为6.4个单位长度高,然而,这个正交摄像机的 Size 属性是它视野范围高度的一半,所以应该把它设置为 background 对象高度的一半,即为 3.2个单位长度。
在 Hierarchy 中选择 Main Camera,设置 Camera 组件中的 Size 属性为 3.2,如下图。
现在,你的背景图片完美的适应了整个视野,如下图。
这时,你会发现一些图像质量上的问题。下面这两张图指出了一些地方,对比它们应当显示的样子,看看我们的图片。
我们现在的海滩
期望得到的海滩
上面的图片显示的问题是由于底图纹理的压缩设置,你可以通过修改这个文件的 Import Setting 来修正它。
修正你的 Import Setting
选择 Project browser 中的父 background 对象,再次打开它的 Import Setting,但这次,看到 Inspector 底部的 Preview 面板了吗?
Preview 面板显示了导入的纹理,连同纹理的大小,颜色,信息,和内存使用量。你可以看下面这个截图,这个 background 纹理现在的大小是 1024x320 像素,但 background.png 的大小是 2048*640 像素!这说明Unity把原始如图缩小了一半,为了把他放在 1024*1024 的纹理中。
为了修正你的纹理,看到那个 Import Settings 下方的选择夹里的 Max Size 和 Fortmat 属性了吗?如图。
Max Size 定义了纹理的最大大小,是一个正方形,它的默认大小是1024像素,同时,Format 指定了色彩深度,它的默认值是 Compressed。
你可以为每种平台设置不同的值,(例如:IOS,Web,Android),但对于我们这个应用,只用考虑 Default 选项卡就行了。
在 Default 选项卡中,修改 Max Size 为 2048,之后点击 Apply。你的设置看起来应该是这样。
立刻,你会注意到 Scene 和 Game 视图都更棒了,因为 background 压缩减少了。下面这张图显示了 Game 视图的样子。
注意在 Inspector 面板的 Preview 区域显示了这个 background 纹理现在是 0.6MB,之前它是 160KB。
增加纹理的大小使它的内存使用量BMW之前的四倍(大致估计)。
对于有些纹理,你可能想调整它的 Format 值来提高它的色彩质量,但那将进一步增加它的大小。例如,如果你把 background 的 Format 改为 16bit,你会发现纹理的大小增长到了 2.5MB,当改为 Truecolor 时它为 3.8MB。
然而,如果你看下面这两个版本的 background,你会发现压缩过的图片和真彩色的图片几乎一样好。
压缩过的纹理
真彩色纹理
因为压缩过的图片看起来足够好,而且占用内存少,所以允许 Format 的值为 Compressed。当你自己制作游戏的时候,尽量对这些设置尝试不同的组合,选择一个占用内存最少,还能达到你预期效果的设置。
那么,摄像机部署就位,底图看起来清晰。现在是时候从沙滩挖出那个老处女和她的小猫咪了!
控制渲染顺序
你仍然不能看到老处女和猫是因为场景把他们画在底图之后,你可以调整游戏对象的Z轴坐标让它更接近摄像机从而显示在前面。事实上,这种方法可以完美的解决这个问题。但对于渲染顺序的问题,现在Unity里有一个很棒的特性,你应该试试:Sorting Layers。
在 Hierarchy 选择 cat,可以看到的 Sprite Render 的 Sorting Layer 属性被设置为 Default。如图。
点击 Sorting Layer 的下拉框,你会看到你的项目中定义了一个排序层,我们现在选择的正是其中的 Default。
你也会看到一个名叫 Add Sorting Layer… 的选项,点击它。
这会带你来到 Tags&Layer 编辑器,你可以从Unity的很多地方来到这个地方。只展开 Sorting Layer 的分组,如图:
点击 Sorting Layer 分组中的 + 来创建一个新的名叫 Cats 的排序层。同样的方法,再创建两个层分别叫做 Enemies 和 Zombie。你的编辑器现在会看起来像这样:
这些层定义了渲染的顺序,名叫 Defalut 的 Layer0 在最底层,名叫 Cat 的 Layer 1,在它之上,以此类推。
目前,所有你添加的游戏对象都是使用的 Default 的排序层。对于 background 对象这没问题,因为我们就是想它在所有元素后面,但你需要改变其他对象的排序层属性。
在 Hierarchy 中选择 cat,设置它的 Sorting Layer 为 Cats,你立刻会注意到那只猫在 Scene 和 Game 视图都可以看到了。
当猫的排序层被改为 Cat …
…立即就能看到猫了
在 Hierarchy 中选择 enemy,设置它的 Sorting Layer 为 Enemies。这样,老处女和她的猫一起在沙滩上散步,谁知道呢,或许他们在度假?
最后在 Hierarchy 中选择 zombie,设置它的 Sorting Layer 为 Zombie 确保你的游戏对象在顶部,你的 Game 视图现在会看起来像这样:
Note:Sprite Renderer 也有一个 Order in Layer,你可以使用它来为游戏对象指定排序顺序。
但你不会用到它,因为 Zombie Conga 没有任何 Z轴的战斗的问题,看起来Unity是按照被添加进 Scene 中的顺序来渲染对象的,所以最新添加的对象总是在最顶层,对象不会在这帧在另一个对象的前面而下一帧又到它后面,这个特性对我们的游戏很有用。
给 Sprite 使用脚本
你已经把这些 sprite 放在沙滩上了,但他们什么都不能做。通过这章,你将编写两个小脚本:一个是僵尸拥有的动画,另一个可以让玩家控制僵尸移动。你将在这一系列剩下的教程中逐渐完善这个游戏。
Note:你将编写C#(读作see-sharp)脚本的代码,如果你更喜欢 JaveScript,你可以很轻松的转换过去。如果你需要什么帮助,可以在评论区自由发言。(我从没用过Unity支持的Boo脚本语言,这都看你喜好)
让 Sprite 动起来!
首先你要添加一个简单的僵尸动画脚本。选择 Hierarchy 中的 zombie,点击 Inspector 中的 Add Component,在弹出的菜单中选择 New Script,将这个脚本命名为 ZombieAnimator,Language 选择为 CSharp,点击 Create And Add。操作步骤如图。
Note:你在这系列教材的第二部分会使用Unity的 Animator 来代替这个基本的动画脚本。但是这个例子可以让你了解到如何在脚本中访问 SpriteRenderer。
使用 MonoDevelop 打开 ZomebieAnimator.cs。这是Untiy自带的代码编辑器。打开脚本有好几种方法,但无论何时,最简单的是双击 ZombieAnimator,或者保持 zombie 选中,在 Inspector 或 Project browser,如图。
Inspector 中的脚本动画脚本
Project browser 中的脚本动画脚本
在 Zombie Conga 中你的僵尸只是简单愚蠢的走路,就像你想象中的好僵尸。为了实现这个简单的动画,你需要一个 Sprite 数组,和一个循环播放它的速度。为了储存这些信息,给 ZombieAnimator 添加下面这两个共有变量。
public Sprite[] sprites; public float framesPerSecond;
Note:在C#中,你可以把这些代码放置在类定义的花括号之内,并且在函数定义的外部的任何地方,技术上没有问题。但通常良好的做法是放置类定义中第一个函数之前。
Public 变量会暴露在Unity的编辑器中,你可以使用Unity的操作界面直接修改它的值而不用修改代码,甚至运行的时候也是。你等下会知道这是多么容易。
你将通过设置不同的 Sprite 给你的 GameObject 的 SpriteRenderer 组件制作一个动画。为了代替在每次 Update里获取 Component,你将在脚本第一次运行的时候缓存它。
添加这样一个私有变量给你的 ZombieAnimator 脚本:
private SpriteRenderer spriteRenderer;
私有变量不会暴露在Unity的编辑器中,在这个例子中,你要初始化这个变量,添加下面的代码到你的 Start 函数。
spriteRenderer = renderer as SpriteRenderer;
你的脚本继承自 MonoBehaviour,它会让你能够访问一个名叫 renderer 的变量。对于显示的 GamObject,renderer 就是 SpriteRenderer 组件。因此在给变量赋值之前你需要把 renderer 类型转换到 SpriteRenderer。
Note:为了更佳的游戏性能,这些优化是非常有必要的。缓存一些频繁使用的对象是最常用到的优化方法。常见的例子是 GameObject 的 Transform,场景的 Main Camera,或者别的什么,其他的使用 GameObject.Find 来访问。保持一切简单明了,这是教程中你唯一缓存对象的地方。这种情况下,这样做是有意义的。
为了完成这个脚本,添加这几行代码到 Updata:
int index = (int)(Time.timeSinceLevelLoad * framesPerSecond); index = index % sprites.Length; spriteRenderer.sprite = sprites[ index ];
它使用自从本关开始到目前为止的秒数(更多信息请查阅 Time 的类文档)乘上每秒渲染的帧数,如果这些帧储存在一个无限长的数组中,那你会得到现在需要显示的帧的索引。
但没那么美好,因为你知道数组不可能是无限的,你需要在到达末尾的时候回到数组的开始,执行求余操作,得到两个整数相除的余数。
换句话说,你得到的所有数值都在0到比数组长度小1的范围内,那会是一个 Sprite 数组的有效索引(当然假设数组的长度不为0)。
你在 MonoDevelop 中写完后,保存脚本(File\Save)回到Unity中。
Note:Unity会自动编译你的脚本,如果在 Console 中有什么错误,先改正它再继续。
在 Hierarchy 中选择 zombie,留心它的 Zombie Animator(Script) 组件,那个区域展示了你的两个共有变量。谢谢,Unity!
现在,Sprite 数组(Unity使用在词之间添加了空格的变量名)没有项目。你的 Sprite 资源名叫 zombie_0 到 zombie_3。这些是打算按顺序设置的图片,所以需要它们放到 Sprite 数组里来制作一个永远循环的动画。
为了定义这个循环的动画,你需要添加下面这六个元素到 Sprite 数组:zombie_0,zombie_1,zombie_2,zombie_3,zombie_2,zombie_1。有几种不同的方式来实现,看你喜欢哪个。
保持 Hierarchy 中的 zombie 选中,点击 Inspector 右上角的 lock 按钮,锁定这个视图。如图。
即便你选择了其他的对象,都会保持显示现在的 Inspector 信息。在这种情况下,这是个十分有用的功能。
在 Project browser 中展开你的 zombie 纹理,左键点击 zombie_0 来选中它,然后 shift+左键点击 zombie_3,就能选择四个僵尸 Sprite。
现在拖动你选中的对象到 Inspector,悬浮在 Zombie Animator(Script) 组件的 Sprites 那一行,当你鼠标移动到正确位置的时候,你会看到你的光标下方出现了一个绿色的添加图标。如图。
释放鼠标,Unity会自动添加这些对象到你的数组里。
Note:拖放的时候无论 Sprites 是否展开都不要紧,上面的截图仅仅演示了展开状态,因为Unity会自动在拖放时展开你鼠标悬浮位置的项目。如果这个时候截屏的话,就会像我这样。
你的 Zombie Animator(Script) 组件现在看起来是这样。
现在只选择 Project browser 中的 zombie_2,用相同的方法添加到数组里,Unity会自动修改 Sprite 数组的大小来放入你新加入的元素。
再一次添加 zombie_1,你的数组现在就有6个顺序正确的元素了,就像这样:
在你结束这些操作的时候,记得再次点击 Inspector 视图右上角的按钮来解锁 Inspector 视图。
如果你忘记怎么做了,之后你可能会为Unity为什么不自动跟随你选择的对象切换 Inspector 的信息而摔杯子。
最后,把 Frames Per Second 设置为 10,如图这样:
把游戏跑起来,现在对着这个拖着脚走的僵尸惊讶去吧。
Note:在游戏运行的时候,你可以调节 Frames Per Second 属性来找到一个你喜欢的速度,但是Unity会在你停止游戏的时候恢复这些数值。因此,你需要记住你喜欢的数值,然后在游戏停止的状态下修改它。
现在,你已经使这个僵尸动了起来(或者说这是复活术?)。它看起来正要去一个聚会,下一章将带你创建一个简单的脚本,你可以为他指出正确的方向。
控制这只僵尸
在 Hierarchy 选择 zombie,为它添加一个新的名叫 ZombieController 的C#脚本。就像刚才你添加 ZombieAnimator脚本一样。
你是喜欢缓缓移动的僵尸还是奔跑的?都没问题,如果想在游戏运行中调节僵尸的移动速度,就需要定义一个共有变量在这个脚本中。
在 MonoDevelop 中打开 ZombieController.cs,添加这个变量。
public float moveSpeed;
moveSpeed 会储存僵尸每秒移动的长度,不是像素,是Unity中的单位。因为你的 Sprite 大小是1单位/100像素,你可能想把这个数值设置的相当低。
正如你在动画中所见的,你将制作一个僵尸在 Zombie Conga 游戏中直线移动。始终走向鼠标最新点击的位置。(或者是当玩家按住鼠标键时候鼠标最新的位置)
僵尸走向你鼠标点击的方向,然后走过。“这里,僵尸!不,是这里!”
僵尸跟随鼠标就像它被拖着。
“哪只小僵尸想吃一口美味的光标?你!没错,就是你!”
你可能不会在每一帧都有一个鼠标的输入事件,所以你需要在目的地改变的时候保存僵尸前进的方向。为了实现这个,你要计算一个单位向量(一个长度为1的向量)从僵尸指向输入的坐标。
添加下面的变量到 ZombieController:
private Vector3 moveDirection;
你正在制作的2D游戏,但要知道Unity仍然工作在三维坐标系系统中。比如 Transorm 储存的 Vector3 的类型。因为你知道僵尸永远不会在z轴上运动,所以你可以在这里使用 Vector2。但你的僵尸运动方向是 Vector3 类型的,你不得不在这两个类型之间不断转换。这全看个人喜好。
添加下面的代码到 Update,为了在所有受到输入事件的时候更新 moveDirection。
// 1 Vector3 currentPosition = transform.position; // 2 if( Input.GetButton("Fire1") ) { // 3 Vector3 moveToward = Camera.main.ScreenToWorldPoint( Input.mousePosition ); // 4 moveDirection = moveToward - currentPosition; moveDirection.z = 0; moveDirection.Normalize(); }
这些代码主要实现了这几个功能:
- 因为你会多次使用僵尸的坐标,所以先把它复制到一个变量里。
- 然后检查 Fire1 是否被按下,因为对于其他情况我们不想去重新计算移动方向。接下来会讲讲关于 Input 和 Fire1 的信息。
- 使用这个场景的 Main Camera(在这个例子中,这是唯一的摄像机),使用正交投影将鼠标现在的坐标转换到世界坐标,ScreenToWorldPoint 会忽略传入的z值,仅仅改变x,y。所以直接传入鼠标坐标是安全的。
- 通过使用僵尸要去的位置减去现在僵尸所处的位置来得到一个方向向量。因为你不想僵尸在z轴的坐标发生变化,所以把它设为0(意思是在z轴移动0个单位长度)。调用 Normalize 保证 moveDirection 的长度为1(你知道,它叫单位向量)。使用单位向量很方便,你可以把它直接与标量相乘(比如 moveSpeed)来得到一个指向某个方向还有确切长度的向量,之后你会用到它。
Note:你使用 Input 通用类的方法去获取信息。工程默认中定义了很多输入方式,叫做 axes,比如 Horizontal,Vertical,和 Jump.Horizontal 用来监视摇杆的x坐标。同样包括键盘上左右光标键的状态,如果你想知道游戏受到的水平移动信息,只要简单的访问 Horizontal 轴就可以,不用关心它的输入源(键盘/手柄/摇杆)。
默认定义的 Fire1 是虚拟按钮之一,它监视了鼠标或摇杆上的0号按钮以及键盘上的左crtl键。当指定键被按下的时候 Input.Getbutton 返回 true。所以你写的代码在每一帧中在按钮按下的时候更新 moveDirection(不只是最初按下,包括按住),是的,这也说明只要你控制好鼠标,你可以通过按下左ctrl键改变僵尸的方向。
如果你想查看或者定制你工程的输入设置,你可以点击 Edit\Project Setting\Input,在 Inspector 中打开 InputManager 的设置。
现在在 Update 中添加这些代码让僵尸走起来吧!
Vector3 target = moveDirection * moveSpeed + currentPosition;
transform.position = Vector3.Lerp( currentPosition, target, Time.deltaTime );
第一行计算出了使用设定速度离开现在位置的目的位置。目标位置就是从现在的位置开始运动一秒后的位置。
第二行使用了 Vector3.Lerp 来定义僵尸的在现在位置到目标位置之间的新位置。Lerp 是一个在两个三维坐标之间插值的简单方法。Lerp 中的第三个参数是一个0到1的值,是全程的百分比,意思是当这个值为0的时候,会返回 currentPosition,当它为1的时候,会返回 target,而当它为0.5时候会返回中点坐标。
在这个例子中,我们使用 Time.deltaTime 作为第三个参数,因为这是一秒的一小部分,肯定远远小于1。它会给你一些路径上的点,因为 Time.deltaTime 接近1,这样做所以僵尸会永远平稳的走下去。
保持脚本,返回Unity。
运行游戏,随便点击一个地方让僵尸走去。如果僵尸没动,是因为 ZombieController 脚本的 moveSpeed 还没设置,所以僵尸的移动速度是0!
游戏运行的时候,在 Hierarchy 中选中 zombie 在 Inspector 面板中找到 Zonmie Controller(Script) 组件,把 Move Speed 设置为2,点击沙滩,你的僵尸就上路了!
在 Inspector 面板调整到你喜欢的速度,根据你调整的速度,你也要调整一下 Zombie Animator(Script) 脚本组件中的 Frames Per Second,不然僵尸的动作会和速度不匹配。
当你尝试出你希望的值的时候,记住他们。停止游戏,再次重新在 Inspector 中修改好,这样下次运行就会如你所愿了。
当你快乐的牵着它到处游荡的时候,你可能会发现僵尸身上的几个问题。
- 当游戏开始时,它没移动但是它的腿在不停的走路。
- 它幸福的走到了屏幕外面。
- 它没有面朝它走的方向。
你将在这系列的未来篇目修正它走出屏幕的问题,所以现在不要管它。如果它走出了屏幕,简单的再点一下沙滩,把它牵回来就行了。僵尸很听话的。
在 MonoDevelop 中回到 ZombieController.cs。
这个脚本在移动僵尸的时候使用了 moveDirection,但你只在输入事件里改变了它的值。为了控制僵尸的方向,你需要在游戏开始的时候初始化 moveDirection 指向它的右边。
添加下面这一行到 Start:
moveDirection = Vector3.right;
这会指向x轴的正方向,换句话说,会指向屏幕右端。
保存 ZombieController.cs,再次运行你的游戏,现在僵尸直接就开始走了,它的也面朝它走的方向,所以它没绊倒。
返回 ZombieController.cs,添加一个公有变量,以便你可以控制僵尸的转向的速度。
public float turnSpeed;
你会使用 trunSpeed 来控制僵尸转到新的方向的速度。
Unity内部使用 quaternion 来代表旋转,如果你对它数学上的定义感兴趣,可以看看这。在你理清了你的思维后,放轻松,制作2D游戏你不需要知道多少关于 quaternion 的知识。
因为 Quaternion.Euler 方法可以让你从欧拉角创建一个 Quaternion 对象。大多数人都习惯使用的就是欧拉角,包括独立的旋转的x,y,z轴,但它不适合在3D环境下使用,因为它会产生一些问题,比如万向节死锁。欧拉角只适用于你只想沿着z轴旋转的2D游戏,
Note:想学习有关 Quaternion 的知识,可以去OpenGL ES Transformations with Gestures教程。
添加下面的代码到 Updata:
float targetAngle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg; transform.rotation = Quaternion.Slerp( transform.rotation, Quaternion.Euler( 0, 0, targetAngle ), turnSpeed * Time.deltaTime );
首先使用 Mathf.Atan2 来确定x轴与 moveDirection 之间的角度。Mathf.Atan2 返回一个弧度制的角,所以你通过乘以 Mathf.Rad2Deg 来转换它。
使用 Quaternion.Slerp 来平滑的转到目标的角。Quaternion.Slep 可以在你指定的两个角之间进行球面线性插值。这和你刚才看到的 Vector3.Lerp 很像,只是用旋转信息代替了坐标信息。
之前,当你使用 Vector3.Lerp 的时候,你使用了 moveSpeed 来调节僵尸移动的速度,这次,我们使用 trunSpeed 做相似的事情。这个值越大,僵尸转到目标地点越快。
Note:肯定会有一部分读者说,“啊!我想让僵尸在转向的时候使用最短的路线,就算我数学烂的像一坨屎,也可以肯定只用反正切是无法做到的,你在骗我!”
对于这些问题,我建议你先冷静下来做一套广播体操并且在饮食中多吃些蔬菜。我要告诉你 Quarternion.Slerp 很强大,它总是会在两个角之间使用最近的路线来内插值。
现在保存 ZombieController.cs,回到Unity。
在 Hierarchy 中选择 zombie,设置 Turn Speed 为 5,如图。
运行游戏,无论你多么辛苦的在沙滩上点击,僵尸都不会感到眩晕。
这就是你在本节教程中制作的 Zombie Conga。通过点击 File\Save Scene as… 保存你的 Scene,就把它叫做 CongaScene吧,点击保存。
下一章会讲述Unity2D专业版中一些很有用的特性。对于我们制作 Zombie Conga 不是非常重要,但你可能会想了解它——Sprite Packing。
Sprite Packing – For Professionals Only (Sort of)
译者:这章内容是关于打包 Sprite 纹理的一些描述,因为我使用的仍然是无需付费的Unity版本,这段的翻译就放下了。有兴趣的同学请去原文阅读。
至此,何去何从?
我希望你能喜欢这套Unity 4.3 2D 教程,如果你不喜欢,我希望你最少能学到点东西。如果你什么都没学到,可能你想和我们接触一下,或者给我们一些建议。
你可以从这里下载完整的工程。
现在虽然 Zombie Conga 还没完成,事实上你已经充分了解了如何编写一个2D游戏,去第二部分学习如何使用Unity的内建动画系统吧。在这系列的第三部分,你会有很多创建动画剪辑的练习,你也会学到如何回放这些动画,或者给它们之间添加过渡效果。在本系列最后一章,你将完成 Zombie Conga,同时了解Unity2D的物理引擎和更多的脚本。
当你学习Unity2D工具的时候,记得Unity的网站上有丰富的资源供你去探索,包括:
和往常一样,你可以在评论区畅所欲言!