本章将介绍一个2D的样例,帮助初学者掌握2D游戏的基本开发方法。
本文仅供个人记录和复习,不用于其他用途
创建2D工程
Unity 提供了 3D 和 2D 两种开发模式,在创建工程的页面选择 2D,得到如下工程:
2D 工程没有天空盒子,它只提供了一个主摄像机,且默认空白处显示蓝色。接下来创建四个文件夹,用于分类各项资源:
这里额外说一句,我们在写代码时最好养成资源分类的习惯,这对于我们今后再来查看项目时会有很大的帮助。
导入资源
现在我们要用到 Ball、BG、Player 这三张图片。把图片拖进编辑器,查看它们的属性:
看到 Texture Type 一栏,由于我们的工程是 2D,所以图片在导入时默认是 Sprite(精灵),如果不是的话记得修改一下。
顺带一提,假如图片的属性面板上提示了黄色的警告信息,例如:
错误信息翻译一下就是:“纹理的长宽为 4 的倍数时才能被压成 DXT5 格式”。什么是 DXT5 格式呢?这其实是 DirectX 的一种压缩形式,主要用于节约内存,具体的原理请感兴趣的各位自行百度。总而言之,如果纹理的长宽不是 4 的倍数,那么也会被压缩成 4 的倍数形式,比较浪费空间(这让我想起了 TexturePacker,图片尺寸要为 2 的倍数)。
摄像机尺寸
接下来要添加背景。BG 图片下有一个矩形,把它拖到上面的窗口,游戏背景就成了黑色。
在上图中,黑色的矩形是背景,而白色的矩形框则是摄像机的尺寸。要想调节尺寸的话,可以选用矩形工具(T),也可以直接修改 Size 属性。不过,当你修改了尺寸之后会发现,摄像机的比例是固定的,你不能够任意地调整某一边。为了修改这一比例,最简单的方法就是直接修改场景视图的宽度,这样摄像机的宽度也会跟着缩小,且高度不会发生变化。
摄像机的尺寸其实就是游戏窗口的尺寸,面对不同型号的手机,游戏需要做到动态的适应屏幕分辨率。在我们刚刚调整尺寸的过程中,不管我们怎么修改,摄像机的高度永远是不变的(可以一边调整一边观察场景视图),这一点对于我们做屏幕自适应会有不少帮助。
回到正题,为了显示方便,我把 U3D 布局改成了 Tall(右上角),并将摄像机的尺寸改成了 5,留出了部分背景。
当然,哪怕你已经意识到了屏幕适配的问题,但背景总共就这么大,还是无法完全解决这一问题。退而求其次,我们将摄像机的默认背景色改为黑色:
这样一来,哪怕真出现了屏幕尺寸大于游戏窗口的情况,摄像机也会将空白区域渲染成黑色,不会显得突兀。
锁定背景
背景导入完成了,但接下来又有一个问题:如何才能把背景锁定住,避免误操作的情况?很简单,我们可以为背景起一个别名。
选择背景 BG,找到属性栏中的 Layer 选项,选择 Add Layer。我们在 User Layer 8 中输入新的图层名 Background:
然后再把 BG 的 Layer 一栏改为 Background:
找到编辑器右上角的 Layer 选项,在下拉菜单中将 Background 图层设置为锁定:
这样一来,除非你直接在 Hierarchy 中选择了背景,否则是不可能在场景视图中对背景进行操作的。
调整图层顺序
如果你之前接触过 2D 游戏,对于游戏的渲染有一定的了解,那么你应该知道 2D 游戏是根据图层顺序由下往上渲染的。Untiy 提供了两种调整顺序的方式,一个是 Sorting Layer,还有一个是 Order in Layer。
为了把背景图层设置为最底层,我们可以先在属性栏中找到 Sorting Layer 一项,选择 Add Sorting Layer。在打开的页面中,我们新添加一个图层 Background,并且将它拖动到 Default 图层的上方。这样,Background 就是目前最底层的图层了。
回到属性栏,将背景的 Sorting Layer 设置为 Background:
请注意,Sorting Layer 只是将图层大致地分为几类,比如背景层、角色层等等,每个层中还会包含其他的元素。打个比方,角色层中会有主角、怪物等元素,我们不可能把每一个元素都划分出一个图层,因为那样就达到不到分类的效果了。为了解决同一层之间渲染顺序,我们可以使用 Order in Layer,它代表了精灵在当前分类层中的渲染顺序,数字越小越先渲染。
创建球拍
说是球拍,其实就是个挡板。把图片 Player 的矩形拖上去,由于球拍要进行碰撞检测,这里给它添加一个碰撞器:
由于碰撞器的尺寸与 Player 的尺寸不一致,所以得稍微调整一下碰撞器。我这里的 Size 是(0.24, 2.85),各位可以自己缩放一下,让绿框与白色矩形的边框重合。
之后再为其添加刚体。由于球拍不需要重力,所以 Gravity Scale 设置为 0。另外,球拍只需要上下移动,所以要把 Freeze Position 和 Freeze Rotation 中的 X、Z打上勾,不让球拍进行水平移动和旋转。
新建一个脚本,为 Player 编写移动逻辑:
|
|
rigidbody2D 是用来获取 Player 的刚体;GetKey() 会在读取到对应按键时一直调用方法,而 GetUp() 和 GetDown() 则分别是在读取到按键按下/松开时调用一次方法。显然,这里要用 GetKey(),因为球拍需要一直移动;velocity 属性代表刚体的速度,读取的是二维向量,即按照某一向量的方向进行运动,速度大小为向量的模。
写完脚本,查看 Player 的属性,将 UpKey 和 DownKey 分别设为 W、S:
运行游戏,球拍能够上下移动:
这里 Unity3D 弹出了一个警告信息:
警告信息不代表出错,这段话的意思是脚本中的变量 rigidbody2D 与 UnityEngine 中的变量名冲突了,各位可以修改一下。
最后,把 Player 制作成预制体:
创建围墙
为了不让小球飞出去,我们得创建四面空气墙把它围在里面。创建的方式没啥好说的,无非就是添加碰撞器,然后再修改一下位置和尺寸就好了。不过为了让围墙始终围绕着游戏窗口,我们得根据摄像机的尺寸对围墙参数进行修改。
为 Walls 创建一个脚本,用于管理围墙,并且之后的分数处理也会写在这里。
|
|
没什么很多好说的,这段代码就是用于初始化四面围墙和两个球拍的位置。我只解释一下 ScreenToWorldPoint() 这个函数,它主要用于将屏幕坐标系转成世界坐标系。屏幕坐标系以左下角为原点,也就是摄像机视野的左下角;世界坐标系的原点是中心点,也就是两条白线相交的点。
上图中的围墙是通过 tempPosition 确定的位置,需要上移 0.5。
这样围墙就弄好了。
顺便要记得用 Player1 和 Player2 给脚本的公共变量赋值。
如上图,哪怕宽度再宽,游戏也能够自适应。
创建小球
导入小球的资源,给它创建球形碰撞器,你可以根据小球的大小修改一下碰撞器的半径。
为了让小球有弹性,我们要新建一个材质,选择 Physics Material 2D。我们不需要摩擦力,所以把摩擦系数改为 0,弹性系数改为 1。
接下来加入刚体组件,把质量设置为 0.2,让其反弹速度加快,然后再把阻力改成 0。
给碰撞器组件添加弹性材质:
运行游戏,小球受重力落下,然后不断地从地面上弹起。达成这一效果后,我们把重力设置成 0,顺便把 Freeze Rotation 选上:
球拍击球
如果你之前把球拍做成预制体了,那在这里先说一个小技巧。预制体的属性栏中有一个 Prefab 选项卡,如果你对预制体做了什么修改(大小、添加新组件),可以点击 Apply 让修改应用到每一个预制体中。
左边的球拍 Player1 我们设置的是 WS 键位,那么 Player2 我们就设置方向键上下吧。
做好了这一步,我们就要为小球编写逻辑,为它赋予一个初速度,并且根据球拍的上下移动来改变自身的速度。
|
|
稍微讲解一下,开局时小球会随机向左或向右移动;移动球拍击球时,小球应该附加上球拍给它的垂直速度,否则小球就只会水平地弹来弹去;附加的速度应该减半再相加,这样才能保持速度稳定,而不是越打越快。另外,之所以要时刻保持小球的水平速度,是因为我在测试的过程中,小球总会因为某种形式的碰撞而减慢速度,所以要每帧都进行判断。至于具体原因,我目前还没有搞清楚。
创建分数统计
创建两个 Text,把锚点(Anchor Point)设置为如图所示:
两个 Text 要对称摆放:
设置锚点可以让 UI 在不同窗口下都始终居于上方:
方便起见,修改分数的代码就直接写在 Walls 脚本中:
|
|
changeScore() 是在小球与左右墙面发生碰撞时调用,因此要在脚本 Ball 的 OnCollisionEnter2D() 方法中加上一个判断:
|
|
当小球发生碰撞时,如果碰撞物体是左右的两面墙,那么就调用 changeScore() 方法。不过问题来了,如何调用其他脚本中的方法呢?很简单,把被调用的脚本声明为代理:
|
|
其他脚本可以用 Walls.Instance.方法名
来调用 Walls 下的方法,但不可以对其修改。
添加音效
导入声音文件。这里一共有三个文件,分别是球拍的撞击声、撞到墙壁的声音、背景音乐。为球拍添加 Audio Source 组件,把声音文件赋给 AudioClip,并把 Play On Wake 取消掉。
为 PlayerController 脚本添加代码:
|
|
当球拍发生撞击时,会一次播放音效。pitch 是指音效的播放速度,为了不让声音显得单调,我用随机数对播放速度进行了些许调整。回到编辑器,点一下预制体的 Apply,让另外一个球拍附上音效。
接下来是为左右两面墙添加音效:
由于墙面没有脚本,这里就随便为 rightWall 创建了一个:
|
|
由于墙面并不是预制体,所以我们要手动为 leftWall 添加音效组件和脚本(直接把 rightWall 的复制过去)。
墙面和球拍的音效都做完了,剩下的背景音乐就更简单了。为 Walls 添加音效组件:
由于背景音是一直播放的,所以把 Play On Wake 和 Loop 勾上。
重置游戏
创建一个 Button,调整一下它的位置和大小。当然,你可以修改它的样式,也就是普通状态、按住状态、高亮状态,不过先得把 Transition 改成 Sprite Swap:
在 Walls 中添加 Reset() 方法:
|
|
Find() 方法能够寻找名为 Ball 的对象,然后发送消息给它,调用 Reset() 方法。所以在 Ball 脚本中也要写一个 Reset():
|
|
initBall() 是把 Start() 中的初始化步骤拿出来了,为的是方便调用:
|
|
编写完代码后,要为按钮添加响应事件:
运行游戏,点击按钮后小球的位置将被重置。