第5章 2D游戏

思考并回答以下问题:

本章涵盖:

本章将介绍如何使用Unity开发2D游戏,包括Sprite的创建、动画的制作、2D物理的使用,以及一个2D捕鱼游戏的示例。

Unity 2D系统简介

Unity的2D游戏制作功能,核心模块包括Sprite(精灵)的创建、动画的制作和2D游戏专用的物理模块。

创建Sprite

2D游戏的图像部分主要是图片的处理,通常称图片为Sprite(精灵)。为了提高效率,2D游戏会将动画帧或不同的小图片拼成一张大图,在游戏运行的时候,再将这张大图的某一部分读出来作为Sprite显示在屏幕上。

使用Unity制作Sprite有两种选择:一种是可以将不同的图片在图像软件中拼接为一张大图,然后导入到Unity中,使用Sprite Editor工具将这张大图分割成若干个Sprite使用;另一种是直接将原始图片导入到Unity中,使用Unity的Sprite Packer工具将这些零散的图片打包,再从中读取Sprite使用。接下来,将分别介绍这两种创建Sprite的方式及动画的制作。

使用SpriteEditor创建Sprite

步骤01 

启动Unity编辑器,在菜单栏中选择【Edit】→【Project Settings】→【Editor】,打开编辑器设置窗口,将【Default Behavior Mode】设为【2D】,Unity编辑器将会进入2D编辑模式,在这种模式下,导入到工程中的图片默认会被自动设为Sprite格式。将【Sprite Packer】设为【Always Enabled】,在任何情况下都能启动Sprite Packer,如图5-1所示。

步骤02 

在资源文件目录“rawdata/2d”提供了示例图片envtile.png,将它导入到Unity工程中。这是一张由若干个小图组成的图片,如图5-2所示。

步骤03 

在Project窗口中选择导入的图片,在Inspector窗口中将Sprite Mode设为Multiple,这种模式可以将一张大图分割为若干个Sprite,如图5-3所示。注意,Pixels Per Unit(单位像素)的值越大,Sprite图像显示的默认尺寸越小。

步骤04 

在Inspector窗口中选择Sprite Editor打开编辑窗口,选择左上角的【Slice】,默认的切割方式为Automatic(自动的),将它改为Grid,然后在Pixel Size中输入每个Sprite的尺寸大小,本例为32×32,默认Sprite的轴点心为Center(中心),单击【Slice】按钮开始分割图片,如图5-4所示。

步骤05 

分割好图片后,会发现有些Sprite的尺寸大于32×32,可以用鼠标直接选择分割出来的Sprite,并将无用的删除,重新设置需要放大尺寸的Sprite,选择Trim可以清除无用的Alpha,最后单击【Apply】按钮完成修改,如图5-5所示。

步骤06 

在Hierarchy窗口中单击鼠标右键,选择【2D Object】→【Sprite】,在场景中创建一个空的Sprite实例,在Sprite选项中引用Sprite图像即可显示2D图像,如图5-6所示。

图5-6 创建Sprite实例

步骤07 

在场景中复制多个Sprite,并指定不同的贴图,示例效果如图5-7所示。

尽管我们在场景中创建了很多Sprite图像,但是如果在Game窗口中选择Stats查看运行信息,会发现Batches(Draw Calls)很少,这是因为这些Sprite图像的原始图片是同一张图片,如图5-8所示。如果使用的Sprite图像都来自不同的图片素材,Batches的数量会明显提升。

图5-8 查看Draw Calls

本节的示例工程文件保存在资源文件目录c05_2dgame的tiled sprite场景中。

使用SpritePacker创建Sprite

手动将琐碎的图片拼凑到一张大图上需要做额外的工作,后期修改也不是很方便。Unity提供的另一个Sprite工具SpritePacker可以动态地将零散的图片自动合成,省去了手动设置的麻烦。下面用一个简单的例子介绍这个工具的使用。

步骤01 

首先要确认在Editor设置中将Sprite Packer设为Always Enabled。在资源文件目录rawdata\2d复制player_n.png等共8张图片到Unity工程目录内。

步骤02 

在Project窗口中右键选择【Create】→【Sprite Atlas】创建一个图集文件,如图5-9所示,将需要打包到图集中的图片拖拽到Objects for Packing中。

步骤03 

最后选择Pack Preview就可以预览打包后的效果了,如图5-10所示。

图5-10 打包图片

前面我们在Editor中设置过Sprite Packer的模式,Always Enabled允许在任何情况下使用Sprite Packer,Enabled For Builds只有在导出游戏时才会使用Sprite Packer,Disabled则取消使用Sprite Packer的功能,如图5-11所示。

图5-11 Sprite packer模式

图层排序

2D游戏中的图片之间没有深度关系,都处在一个平面上,但在实际游戏中仍需要为它们排序,按视觉上的空间先后顺序显示。在Sprite的Sorting Layer中可以为不同的Sprite创建不同的层,每个层有一个名称,层的顺序即是Sprite的显示顺序,值越大越显示在前面。在同一个层中的Sprite可以通过Order in Layer来排列显示顺序,如图5-12所示。

图5-12 图层排序

Sorting Layer和Order in Layer的值在脚本中都可以获取,通过脚本可以动态地改变图层的顺序。下面的代码改变了Sprite的层和排序顺序:

1
2
3
SpriteRenderer r = this.GetComponent<SpriteRenderer>(); 
r.sortingLayerName = "Layer Name";
r.sortingOrder = 100;

Sprite边框和重复显示

默认的Sprite显示方式,一种是Sprite只能显示一张图像,如果需要显示大片重复的图像,就需要复制很多Sprite。

Sprite的另一种显示方式是Tiled(单元格重复)模式,允许重复显示图像,如图5-13所示。使用这种方式,要将Sprite的Mesh Type设为Full Rect。

图5-13 重复显示

在Sprite Editor编辑Sprite时,可以编辑边框区域(绿色的框),将带有边框的Sprite设为Sliced(切片)或Tiled模式,改变Sprite图像大小时不会拉伸边框像素,如图5-14所示。

图5-14 Sliced和Tiled显示模式

动画制作

序列帧动画

传统的2D动画主要是由若干张表现有连续动作的图片组成,在Unity中制作这种类型的2D动画非常简单,可以将动画序列帧直接保存为动画文件,使用起来和普通的3D动画一样。

步骤01 

在Project窗口中按住Shift键选中需要的序列帧图片,将它们拖向Hierarchy窗口,这时Unity会自动弹出一个窗口保存动画文件,为这个动画文件命名,单击【保存】按钮,将动画文件保存在工程内,如图5-15所示。

图5-15 保存序列帧动画文件

步骤02 

上一步在创建动画的同时,也在场景中创建了一个Sprite文件。运行游戏,即可看到2D的序列帧动画效果,如图5-16所示。

图5-16 序列帧动画

步骤03 

保存动画后,在Project窗口中多出了两个文件:一个是动画控制器(Animator Controller);另一个是动画文件。生成动画的时候,在场景中创建的Sprite会被自动添加一个Animator组件。双击动画控制器打开Animator窗口,可以像设置3D动画一样设置2D动画,如图5-17所示。

图5-17 序列帧动画

使用脚本实现序列帧动画

因为2D的序列帧动画只是将图片一张张地按顺序显示出来,我们也可以使用脚本实现序列帧动画,下面是一个简单的示例。

步骤01 

在场景中创建一个Sprite,为它创建一个脚本,这里命名为SpriteAnimator.cs,添加代码如下

步骤02 

在编辑器中设置用于动画的Sprite,如图5-18所示。

图5-18 设置动画帧

运行程序,可以看到与5.3.1节类似的动画效果。

骨骼动画

如果需要表现细腻的序列帧动画效果,就需要很多图片,相当消耗资源,因此游戏中的序列帧动画往往是按一定程度跳帧的,但跳帧后动画流畅度会大幅下降。现在也有很多2D游戏,将2D的角色拆分为若干个Sprite,用骨骼绑定起来,利用制作3D动画的方式制作2D动画,在更加节约资源的同时又加强了动画的流畅性,下面是一个简单的示例。

步骤01 

新建一个工程,复制资源文件目录rawdata/2d/下的warrior.png到当前工程目录内,这是一个角色图片,角色的不同位置是分开的,如图5-19所示。

步骤02 

将角色图片的Sprite Mode设为Multiple,打开Sprite Editor,选择左上角的【Slice】,选择【Automatic】模式,然后选择【Slice】自动切分图片,选择每个切分部分,移动上面的圆点设置轴心位置,如图5-20所示。

图5-19 原始图片资源

图5-20 自动切分图片

步骤03 

在场景中新建一个空的GameObject,将角色不同位置的图片放到它的层级下面作为子物体,拼接成完整的角色形象,注意不同位置的层级关系,如图5-21所示。

图5-21 拼接角色

步骤04 

确定当前选择是角色的最顶级物体,在菜单栏中选择【Window】→【Animation】打开动画编辑窗口,单击左上方的“录制”按钮开始为角色录制动画,滑动时间轴到不同的时间,然后在场景中位移或旋转角色的不同部分,即可完成动画的制作,如图5-22所示。

图5-22 动画帧提示

Ctrl+C组合键和Ctrl+V组合键可以复制、粘贴关键帧。

最后的动画效果如图5-23所示。本节的示例工程文件保存在资源文件目录c05_2dgame的bone animation场景中。

图5-23 动画

2D物理

针对2D系统,Unity提供了专门的2D物理功能。下面是一个示例,移动鼠标滑动一个球体,使它向相反的方向弹出去,在场景中有一些方块,如果球体弹到方块,则会将方块撞飞。

步骤01 

新建一个Unity的2D工程,复制资源文件目录rawdata/2d/中的box.png图片到当前工程中。

步骤02 

将box.png的Sprite Mode设为Multiple,然后自动分割,最后单击【Apply】按钮确定,如图5-24所示。

步骤03 

制作场景的地面。创建一个Sprite并指定贴图,然后在菜单栏中选择【Component】→【Physics 2D】→【Rigidbody 2D】,添加一个2D刚体组件,将Body Type设为Kinematic使其不受物理碰撞或重力影响。在菜单栏中选择【Component】→【Physics 2D】→【Box Collider 2D】,添加一个2D矩形碰撞体组件,最后将Sprite复制多个以组成地面,如图5-25所示。

步骤04 

重复步骤(2)的操作,创建不同的Sprite作为物理碰撞的方块,唯一与步骤(2)不同的是不要将Body Type设为Kinematic,如图5-26所示。

图5-25 设置地面

步骤05 

创建一个Sprite作为球,添加Rigidbody 2D组件,将【Body Type】设为【Kinematic】,我们将在后面使用脚本打开这个选项。在菜单栏中选择【Component】→【Physics 2D】→【Circle Collider 2D】,添加一个2D圆形碰撞体组件,如图5-27所示。

图5-27 设置球

步骤06 

选中球的Sprite,添加脚本Ball.cs,添加代码如下:

1
2


步骤07 

添加IsHit函数,判断当前鼠标位置(在手机上就是手指的位置)是否碰到了球体的Sprite,代码如下:

1
2


步骤08 

在Update函数中添加代码如下,先通过按住鼠标记录鼠标起始位置,再通过释放鼠标记录结束位置,通过两个位置算出矢量方向,给球加一个力,发射出去。

运行程序,选中球向后滑动鼠标,释放鼠标会将球射出,如果碰到方块,会将方块弹飞,如图5-28所示。本节的示例工程文件保存在资源文件目录c05_2dgame的2d physics场景中。

图5-28 物理碰撞

捕鱼游戏

游戏玩法

接下来,我们将使用Unity的2D功能完成一个相对完整的2D游戏实例。游戏的玩法比较简单,在屏幕上有很多不同的鱼来回游动,我们的任务则是操作屏幕下方的一门大炮向鱼群开火,将鱼消灭(或称捕获)。

准备2D资源

步骤01 

新建一个工程,复制资源文件目录rawdata\2d\fishgame下的所有图片到当前工程目录中,这里包括背景、鱼、大炮、特效等图片,如图5-29所示。

图5-29 图片资源

步骤02 

将鱼、大炮和爆炸效果的Sprite设为Multiple,使用Sprite Editor进行切割,注意大炮的Sprite需要将轴心点设置到炮的圆轴中心,如图5-30所示。

图5-30 设置大炮的轴心点

步骤03 

将背景和大炮的Sprite放到场景中,在菜单栏中选择【Edit】→【Project Settings】→【Tags And Layers】,在Sorting Layers创建三个新层,然后选择背景和大炮的Sprite,将背景设到background层,将大炮设到weapon层,如图5-31所示。

步骤04 

同时选择鱼的Sprite,将其拖放到场景中,这时会自动创建序列帧动画。使用相同的方式为所有的鱼和爆炸制作动画,如图5-32所示。

步骤05 

为开火、带有动画的鱼和爆炸Sprite制作Prefab,并存放到Resources文件夹内,如图5-33所示。将鱼Sprite的Sorting Layer设为fish,将开火和爆炸设为weapon。创建完成Prefab后,可以删除场景中的Sprite。

图5-32 创建动画

图5-33 制作Prefab

创建鱼

在准备好美术资源后,我们将使用脚本创建一条会游来游去的鱼。

步骤01 

创建脚本Fish.cs,代码如下:

1
2


提示

在实际项目中,不能在Update中使用Resources.Load:Instantiate和Destroy函数,通常要使用缓存池减少游戏运行中的内存请求和释放的开销。

步骤02 

为了观察一下脚本的效果,可以将Resources下鱼的Prefab拖入场景,指定Fish.cs脚本。运行游戏,则会看到鱼在场景中来回游动。

创建鱼群生成器

步骤01 

创建脚本FishSpawn.cs,用来生成鱼群,代码如下:

1
2


这里的代码主要集中在Update函数中,每隔2秒创建一条鱼,在创建的过程中,使用随机数随机鱼的初始位置、方向和鱼的Prefab。

步骤02 

在菜单栏中选择【GameObject】→【Create Empty】,创建一个空游戏体,指定FishSpawn.cs脚本。运行游戏,会不断地出现新的鱼游来游去,如图5-34所示。

图5-34 鱼群

创建子弹和大炮

现在,游戏中已经有足够的鱼供我们捕猎了,接下来将创建子弹和大炮的功能。

步骤01 

在创建大炮之前,先要完成子弹的脚本。创建脚本Fire.cs,添加代码如下:

1
2


步骤02 

创建脚本Canon.cs实现大炮的逻辑,添加代码如下:

1
2


在UpdateInput函数中,计算出大炮到鼠标位置的角度,旋转大炮并发射子弹。运行游戏,单击鼠标左键发射子弹,如图5-35所示。

物理碰撞

现在虽然大炮可以发射子弹,但还不能消灭鱼。接下来我们将添加碰撞功能,使子弹可以碰撞到鱼。

步骤01 

在Resources文件夹中选择鱼的Prefab,为它们添加Rigidbody 2D和Polygon Collider 2D组件,将【Body Type】设为【Kinematic】,将【Is Trigger】复选框选中,单击【Edit Collider】按钮可以改变多边形碰撞体顶点位置或增加顶点,按住Ctrl键可以删除顶点,如图5-36所示。

步骤02 

重复步骤(1)的操作对子弹Prefab做同样的处理。

图5-36 设置鱼的碰撞

运行游戏,子弹现在可以打到鱼了,最后的效果如图5-37所示。

图5-37 2D的爆炸效果

这个示例到这里就结束了,如果深入做下去,还可以添加不同的大炮、不同的鱼、得分系统等。本节的示例工程文件保存在资源文件目录c05_fish2d。

2D材质

修改Sprite颜色

尽管Sprite多用于2D游戏,但它也有自己的材质和Shader。下面的代码获取了SpriteRenderer的默认material,并修改了默认的颜色,使Sprite的颜色变为红色。

1
2
SpriteRenderer render = this.GetComponent<SpriteRenderer>();
render.material.color = new Color(1, 0, 0, 1);

自定义的黑白效果材质

2D游戏中一种常见的效果就是将图片变为黑白色。为了实现这种效果,需要修改默认的Sprite材质,步骤如下:
步骤01 

在[http://unity3d.com/unity/download/archive/] 站点下载最新的Unity官方shader源代码,在本示例工程中提供的builtin_shaders.zip包括Unity5.5.2版本的Shader源代码。

步骤02 

解压shader压缩包,找到Sprites-Default.shader文件并导入到Unity工程中。

步骤03 

双击Sprites-Default.shader修改它的代码,先修改第一行,将Shader的名字重命名,这里改为Sprite/Gray,然后修改fixed4 frag(v2f IN)函数,添加两行代码,使输出的颜色变成灰度显示,如下所示:

步骤04 

创建一个新的Material指定给Sprite,并使用自定义的黑白效果Shader,即可使图片变为黑白色。也可以通过代码动态地改变shader实现该效果,代码如下:

1
2
SpriteRenderer render = this.GetComponent<SpriteRenderer>();
render.material.shader = Shader.Find("Sprites/Gray");

本节的示例工程文件保存在资源文件目录c05_2dgame的change shader场景中。

小结

本章介绍了Unity在2D游戏中的应用,包括创建Sprite、不同的动画方式、2D物理的应用,最后还通过一个较为完整的实例,介绍了如何创建一个2D捕鱼游戏。尽管我们在这里是开发2D游戏,但因为Unity也是一个3D游戏引擎,我们也可以在3D的模式下使用2D功能,创造出3D、2D混合的图形效果。

0%