前言
Unity官方教程 2D Roguelike 英文原版视频教程地址 ——> 传送门
由于官方视频使用的Unity版本较老,部分方法已经不再使用,本文已经采用较新的Unity版本和对应的替代方法。另外为了更方便理解,文章讲述的顺序和官方视频会有少许出入(结果是一致的)。
电脑环境:Windows10
Unity版本:2017.2.0f3
脚本/代码编辑器:MonoDevelop 5.9.6
目标游戏理解
在开始一个项目之前,需要清楚即将要制作的是什么样的一个游戏,这样才能更容易地理解官方代码。简单来说,2D Roguelike 是一个基于回合和瓷砖的地牢探险类游戏,玩家控制大胡子(角色)移动,进行拾取食物、躲避敌人等操作以便更好地通关进入下一个关卡,直到耗尽生命游戏结束。它具备以下内容:
- 每次尝试移动(比如键盘按一次方向键)都会扣除生命。
- 拾取食物可以恢复定量生命。
- 可以攻击打碎障碍墙从而开辟路线。
- 大胡子需要避开怪物,以免被攻击减少生命。
- 越后面的关卡,怪物数量越多。
一个规则比较简单的2D游戏,OK,摩拳擦掌,开始吧!ᕦ(・ㅂ・)ᕤ
本节你将学会什么?
- 如何给GameObject添加动画Animation
- 如何重写动画控制器
- 对Tag/Layer/Sorting Layer有基本的了解
一、新建一个项目Project
如何创建项目不再赘述,有需要的朋友可以查看Unity官方教程 2D UFO(上)的内容了解一下。本次新项目的名称为2D Roguelike(可自定义),记得是选2D!
二、在Asset Store中下载素材
Ctrl+9(或菜单Window->Asset Store)打开Asset Store商店,在最上方搜索栏输入我们想要查找的游戏资源名称。本例是2D Roguelike,回车之后第一个结果就是我们想要的。
点击该资源,在页面找到Download按钮,下载完成之后即可导入Import。(已下载过了就只有Import按钮)
点击Import之后,会弹出导入资源列表选项。在这里一定要确保所有资源都勾选了(默认情况下是全部勾选),保险起见也可以点击All按钮选中全部资源,然后便可以点击Import按钮来进行导入操作了。
导入成功之后,我们可以看到在Project窗口的Assets文件夹下多出了很多资源文件。
Audio:存放音乐、音效文件
Fonts:存放字体文件
Sprites:存放精灵图
删除2个无用的文件夹_Complete-Game、TutorialInfo,新增以下四个资源文件夹。
- Animations 包含AnimationControllers和Animations文件夹,分别存放动画管理器和动画
- Prefabs 存放预制件文件
- Scenes 存放场景文件
- Scripts 存放脚本文件
合理分类资源文件,以便查找和管理,是个非常必要的好习惯。
三、保存场景
在保存场景之前先调整下Unity编辑器的窗口布局,根据游戏界面和自己的工作习惯。编辑器右上角的Layout按钮点击打开进行选择即可改变布局,在这里我们推荐选择Default布局,也是官方推荐使用的。
保存场景的方法在之前Unity官方教程 2D UFO(上)有提到,不再赘述。场景保存在Scenes文件夹,名字依旧是Main。
四、创建角色动画和预制件
大胡子有三种动画状态需要添加:空闲idle、劈砍chop、受击hit。默认情况下大胡子处于idle状态,当大胡子击打障碍墙的时候需要切换到chop状态,被怪物攻击的时候播放hit动画。为了让大胡子动起来,并且和其他对象进行互动比如碰撞,还需要给大胡子添加刚体和碰撞器。
第1步:大胡子添加idle动画(空闲)
通过菜单GameObject->Create Empty(快捷键Ctrl+Shift+N)添加一个空白游戏对象,重置Transform,命名为Player。
创建游戏对象之后,良好的习惯是先重置Transform和改名。
打开Sprites文件夹,点击里面精灵图的右侧小三角打开图片集,同时选中前六张,鼠标左键拖着它们移动到Player再松开。
这时候会弹出存放动画的窗口,默认是Sprites文件夹,我们改一下,把它保存在之前已经创建好的文件夹Animations下的子文件夹Animations内,命名为PlayerIdle。
我们可以看到,Animations文件夹下自动生成了一个动画控制器Player,它控制大胡子当前播放哪个动画。把它拖到AnimationControllers文件夹,这里专门存放动画管理器文件。
与此同时,因为Player添加了第一个动画,所以自动增加了两个组件。Sprite Renderer,精灵渲染器,管理图像的显示;Animator,控制是否播放动画。
第一个动画添加好了,让我们运行游戏测试一下吧!
有点担心,大胡子一副抽搐的样子,给人下一秒就要晕厥倒下的感觉 (;´д`)ゞ。
好吧不开玩笑,其实这是由于动画播放速度较快导致的。打开AnimationControllers文件夹,双击Player,打开Animator窗口。
选中PlayerIdle状态,在右侧的Inspector窗口中修改Speed选项为0.5。这时候再运行游戏,就可以看到大胡子已经平静下来了。不错的开端!
第2步:大胡子添加chop、hit动画
添加chop和hit动画的操作其实是和添加idle动画一样的,只不过选择的图片不同。(不需要改这两个动画的速度٩(๑❛ᴗ❛๑)۶)
chop动画保存命名为PlayerChop,Hit动画保存命名为PlayerHit。
我们会发现,添加后面2个动画的时候,并不会再新增动画控制器,而是在动画控制器内添加了两种动画状态。
在后续的内容会提到如何在这三种动画状态中进行切换。
第3步:大胡子添加刚体和碰撞器组件
刚体能让大胡子受物理引擎控制,实现真实的物理表现效果比如移动。而没有碰撞器组件的话,两个物体之间也无法检测碰撞。
在Player的Inspector窗口点击最下方的Add Component按钮,搜索Rigidbody 2D添加。
继续搜索Box Collider 2D添加碰撞器组件。
为了避免在相邻空间进行碰撞,我们把大胡子的碰撞尺寸缩小成0.9。
第4步:制作Player预制件
关于为什么要制作预制件,之前在Unity官方教程 2D UFO(下)有详细说明,这里就不再细说了。
在制作Prefab之前,我们还需要对Player配置3个地方。
- Tag:选择Player。tag是Unity引擎里面的标签,一般用来对单一的GameObject进行标识。(对,有“Player”这个tag的GameObject就是大胡子没错了!)
- Layer:选择BlockingLayer。Layer就在Tag的右侧,是Unity引擎里面的层,作用和tag很相似也是对GameObject进行标识。但是他们的不同在于Layer常用于一组的GameObject,而Tag是单个。
3.Sorting Layer:选择Units。不知道大家思考过没有,当多个图片资源比如大胡子、食物、墙壁都在同一个坐标的时候,我们会看到什么?是大胡子在最上面覆盖了其他,还是食物,抑或墙壁?是什么控制了它们的渲染层级顺序?这其中的奥义就是Sorting Layer了!越上方的渲染层级越低,比如floor层就相当于背景,Units层级最高,优先看到的是Units层的图片,在此例中,大胡子和怪物都是Units层。
配置完毕,我们先运行游戏确保没问题再制作Prefab吧!
啊咧?!!!!∑(゚Д゚ノ) 大胡子直直地掉下来了!
前面提过,刚体可以让物体受物理引擎作用,所以不难判断出这是大胡子受到重力影响所以掉下来了。由于我们后续是通过变化大胡子的transform的位置来进行移动,并不依赖任何物理作用力,所以我们可以通过修改Body Type为Kinematic来解决这个问题。
Kinematic:开启运动学,开启后不再受力的影响,只能通过Transform属性来操作。
运行游戏,没问题了吧?(๑╹◡╹)ノ"""把Player往下拖到Prefabs文件夹即可生成预制件。
五、创建怪物动画和预制件
创建怪物的动画和预制件的流程和角色是差不多的,只不过资源不一样。怪物只有两种动画:空闲idle、攻击attack。默认情况下怪物处于idle状态,当大胡子在隔壁格子并且轮到怪物回合的时候它会触发攻击操作播放attack动画。当然,怪物也有移动和碰撞,所以同样需要添加刚体和碰撞器。
第1步:怪物1添加idle、attack动画
删除Hierarchy窗口下的Player,通过菜单GameObject->Create Empty(快捷键Ctrl+Shift+N)添加一个空白游戏对象,重置Transform,命名为Enemy1。
找到第一只怪物的idle动画帧图片,拖到Enemy1游戏对象上。
依然是保存在Animations下的子文件夹Animations内,命名为Enemy1Idle。把生成的Enemy1控制器转移到AnimationControllers文件夹。
对资源进行合理分类保存是非常重要的习惯。
运行游戏看看添加是否正常。
动画正常播放,速度也可以。OK,接下来就是找到怪物1的攻击动画帧图片,和前面一样添加到Enemy1身上,保存为Enemy1Attack。
第2步:怪物1添加Rigidbody 2D和Box Collider 2D
怪物和角色一样,都会在关卡内走来走去,并且和其他元素(外墙、障碍墙、角色)均会发生碰撞。而这些都需要刚体和碰撞器的组件支持。添加这两个组件的方法在上面已经介绍,在这里只提一下,Box Collider 2D的Size选项均是1,Rigidbody 2D的Body Type选项为Kinematic。
第3步:制作怪物1的预制件
在制作预制件之前,记得把Inspector窗口下的tag选择Enemy,Sorting Layer选择Units,Layer选择BlockingLayer。
Layer,角色、怪物、外墙、障碍墙都会标识为BlockingLayer,这是因为此例中检测碰撞的方法是使用射线检测(后面脚本会写这方面内容),射线判断前方空间是否存在携带这个标识的碰撞体并且返回结果。(BlockingLayer是之前导入资源的时候预设好的,朋友们也可以自己添加新的Layer,然后在这一层检测碰撞)
最后,左键按住Enemy1不动往下拖到Prefabs文件夹,即可生成Enemy1预制件了。
第4步:怪物2重写动画控制器
通过上面的操作,我们学到了给GameObject添加动画的一种方法,而接下来会接触到另外一个知识点:重写动画控制器。
由于怪物1和怪物2除了样子(图片资源)不同,其他机制均是一样。为了节省时间,我们把Enemy1游戏对象的名字修改成Enemy2。
然后和之前一样,分别制作Enemy2Idle和Enemy2Attack动画。
这时候打开AnimationControllers文件夹,双击Enemy1控制器打开动画状态机Animator,会看到四种动画都已经作为状态添加在里面。
由于还是原来的GameObject,只是修改了名字,所以并没有生成另外的动画控制器。
动画状态机Animator的功能是在不同的动画状态里添加转换逻辑,比如idle和attack之间是怎么转换,什么样的条件下会进行转换,过度耗时是多少秒等等。对于怪物1和怪物2来说,它们都拥有同样的状态和逻辑,区别仅仅是美术资源不同而已。那有什么办法可以节省人工,使用同一个动画状态机和同一个脚本控制,但是又可以播放不同的动画?机智的Unity提供了一个办法:动画重写控制器。让我们来看看是怎么实现的。
首先把Enemy1的Animator里的Enemy2Idle、Enemy2Attack状态删除。选中它们,右键菜单选中Delete确认删除。
在AnimationControllers文件夹内空白处右键,依次选择Create->Animator Override Controller,创建了一个动画重写控制器,我们命名为Enemy2。
可以看到这个重写控制器右侧Inspector窗口内有个Controller选项,可以选择继承哪个动画控制器。在这里,我们可以把Enemy1直接拖入到输入框,或者点击输入框右侧的小圆圈选择Enemy1。
继承Enemy1的动画控制器后,还需要把美术资源也替换掉。Enemy1Attack改写成Enemy2Attack,Enemy1Idle改写成Enemy2Idle,这样重写控制器就配置好了。
这时候,怪物2的Animator组件使用的控制器还是Enemy1。选择Enemy2游戏对象,在Animator组件的Controller选项点击右侧小圆圈,在弹出的窗口中选择Enemy2即可。
这时候运行游戏,可以看到怪物2的动画正常。
通过这样重写控制器,以后在怪物1的动画控制器添加逻辑的时候,怪物2自动继承,不需要再额外给怪物2添加一样的逻辑。我们这个项目才2个小怪,如果怪物类型或者动画类型再多一点,重写控制器就非常有必要。而且修改起来也只需要改一个地方即可,非常方便。其实个人觉得和Prefab预制件有点相似。
由于动画状态的转换逻辑和后面的移动碰撞内容有关联,我们会放到后面讲到角色怪物脚本的时候再添加。
把Enemy2游戏对象往下一拖,怪物2的预制件也做好啦!
六、制作素材的预制件
回到文章最开头看游戏动画,我们可以看到游戏的每个关卡除了大胡子和怪物,还包含6个元素:地板floor、外墙outerWall、障碍墙/内墙wall、食物food、苏打水soda、离开exit。所以我们接下来把这些资源预制件都做出来,便于后面生成关卡的时候使用。
第1步:Floor
柿子先挑软的捏!作为构成关卡地板的floor是最简单的了,又不碰撞又不和其他元素互动。首先我们把上集残留的Enemy2删掉,再新建GameObject,改名为Floor1。(为了每次随机生成关卡的时候显示没那么单调,地砖一共8个样式)
创建新的GameObject的时候,重置transform和改名两个操作可以避免一些异常问题产生。
给Floor1添加组件Sprite Renderer,精灵渲染器,可以控制图像的显示。
组件的Sprite选项,点击小圆圈在弹出的面板内选择文件名末尾数字是32的瓷砖图片。
前面提过渲染层级里面,地板是最底层的,因此我们把Sorting Layer设置为Floor。
把Floor1拖到Prafabs文件夹,第一张地板预制件就做好了。
按照以上流程,我们把其他Floor预制件都做好。Floor2-8(直接改名,不需要创建新的空白对象),选Sprite(33-39),往下一拖即可。
第2步:Exit
Exit的作用是,大胡子移动到这个格子的时候会进入下一个关卡。
把Floor8更名为Exit,Sprite换成20号。
要判断角色是否移动到这里,需要添加一个Box Collider 2D来检测碰撞。但我们并非真的想阻止角色移动到这里,只是想知道角色是否到了这个格子,所以需要把Is Trigger选项选中。
Is Trigger,触发器,选中之后就没有碰撞效果,但可以在脚本代码里使用OnTriggerEnter等方法执行检测到碰撞之后的操作。
为了让大胡子识别碰撞到的是Exit,需要把Tag设置成Exit。Exit的图片渲染层级比地板高,但是又比角色低,所以Sorting Layer选择Items。把Exit拖到Prafabs文件夹,Exit预制件完成。
第3步:Food
角色移动到Food格的时候,Food消失,同时角色增加一定数量的生命。因此Food和Exit一样需要添加Box Collider 2D来检测碰撞,而且也是激活触发器Is Trigger。渲染层级是Items,Tag是Food。
所以很简单,把Exit更名成Food,Sprite Renderer的Sprite更换成19号图,Tag改成Food,往下一拖,Food预制件就做好了。
第4步:Soda
Soda作用同Food,差别在于增加的生命数值可以不同。把Food更名成Soda,Sprite Renderer的Sprite更换成18号图,Tag改成Soda,往下一拖。
第5步:OuterWall
关卡最外围的一圈墙,阻碍玩家和怪物移动到关卡外面。避免单调,一共3张不同的外墙图片。
把Soda改名成OuterWall1,Sprite换成25号(特点是石头多)。
外墙需要碰撞器来阻碍角色和怪物进入到它的空间,所以把Is Trigger取消选中。
Layer选择Blocking Layer,这样角色和怪物在进行射线检测是否存在这个标记的碰撞体的时候才能检测到它。
Sorting Layer选择Floor。(虽然其实选什么都没什么影响,其他资源正常情况下不会和它在同一个位置,但是严谨点好。)
Tag选择Untagged,不需要标签。
把OuterWall1拖到Prefabs文件夹生成预制件。按上述步骤把OuterWall2、OuterWall3的预制件也做了(改名、换Sprite、拖),三个外墙的图片序号是25、26、28哦,不要选错了|ू•ૅω•́)ᵎᵎᵎ。
第6步:Wall
每个关卡随机生成障碍墙在不同位置,能阻碍角色和怪物通行,同时角色可以劈砍这些障碍墙来开辟道路。
把OuterWall3改名为Wall1,Sprite换成21号(特点是像荆棘、灌木丛)。
把Sorting Layer改成Items(层级比floor高,这样才能显示在上面)。
其他维持不变,生成预制件之后把其他7个的预制件也做出来。Sprite序号是21、22、23、24、27、29、30、31。
这些资源都准备好了,接下来可以进入新的环节——制作关卡٩(๑❛ᴗ❛๑)۶