基于Unity引擎制作塔防游戏

发布时间:2023-08-05 11:30:25

基于Unity引擎制作塔防游戏
吕学琴;苏闰;陈彦军
【摘要】塔防游戏让玩家可以在游戏中充分发挥想象,根据地形建设出不同种类的炮塔,以此来和不断涌现的敌人进行多轮的交锋.同样是近年在游戏制作界中颇为流行的Unity游戏引擎是一款可以让玩家轻松创建诸如三维游戏、立体动画等类型的综合型游戏开发工具.本论文就是基于Unity引擎制作一款塔防游戏.【期刊名称】《智能计算机与应用》【年(,期】2018(008005【总页数】5(P95-99
【关键词】Unity引擎;塔防游戏;C#脚本【作者】吕学琴;苏闰;陈彦军
【作者单位】哈尔滨师范大学数学科学学院,哈尔滨150025;哈尔滨师范大学数学科学学院,哈尔滨150025;哈尔滨师范大学数学科学学院,哈尔滨150025【正文语种】【中图分类】TP317
1游戏制作的目标效果
塔防游戏的三大要素:敌人、炮塔、地图。本文中制作的塔防游戏设置4波敌人,每波敌人具备不同的移速和血量,消灭难度递增;设计3种炮塔,每种炮塔各有玄机并且可进阶为威力更强、射程更广的升级版炮塔;地图为15×15的网格形式,

其中包括敌人起点、终点以及敌人行进的道路。
玩家通过金币购买炮塔并安置在地图上,炮塔将自动攻击敌人。敌人将一波一波地轮番出现,每波敌人被完全消灭后才会出现下一波敌人。如果有一个敌人到达终点,游戏将宣告失败。当玩家将4波敌人全都消灭时,则判定为胜利[1]。2敌人
2.1敌人路径管理
在敌人路径上设置一些路径点用于引导敌人移动:让敌人达到固定的路径点,从而实现敌人朝着既定方向移动和转弯。2.2创建并控制敌人的移动
游戏中有若干类敌人,用不同的颜色区分。使用Sphere(球物体)充当敌人。首先创建1类敌人,重命名为Enemy1,并将Enemy1放到Start位置(敌人生成处)。当敌人和路径点很接近时,就要朝着下一个路径点移动,此处设置为:两者相距0.2个单位时,朝着下一个路径点移动。2.3创建敌人孵化器管理敌人的生成
新增另外3类敌人,分别命名为Enemy2Enemy3Enemy4。每类敌人有不同的生命值、移速和生成的数量。敌人将会一波一波地出现,在各波梯队之间应有间隔,例如第二波敌人应该在第一波敌人全部死亡或到达终点后才会出现。敌人生成采用协程[2]的方式实现,定义函数代码如下。1IEnumeratorSpawnEnemy(){2ForeachWavewaveinwaves){3forinti0iwave.counti+){
4GameObject.Instantiatewave.enemyPreFabSTART.positionQuaternion.identity);
5yieldreturnnewWaitForSecondswaveRate);}

6yieldreturnnewWaitForSecondswave.rate);}}
waves数组进行遍历,生成每个wave(一波)中的敌人数目(wave.count)。为此使用for循环,每层循环生成一个敌人。每个敌人生成完毕后,需要暂停极短时间,即第5行。每一波敌人生成完毕后再暂停若干秒时间,即第6行。3炮塔
3.1创建3种炮台的Prefab
游戏中,玩家可以选择部署3种炮塔对敌人进行攻击。3种炮塔有自己的攻击模式,且价格不同。
3.2创建炮塔选择的UI
要实现玩家能够在游戏中创建炮塔,需要有一个UI界面供玩家选择炮台。UI上有3个按钮,代表3个炮塔。创建UI,选择Toggle(单选按钮),然后修改其中的属性。将Toggle中的IsOn取消勾选,这样在最初开始就没有炮台被选中。此时,3个按钮还不能做到单选。在Canves中创建空物体,重命名为TurretSwitch,并将所有的Toggle放在其下。为TurretSwitch添加ToggleGroup组件,再将TurretSwitch挂载到每类Toggle上,这样就实现了3个按钮的单选[2]。3.3监听炮塔选择的事件
创建一个炮塔的管理类,用于保存当前选择的炮塔。
玩家通过点击UI中的炮塔图标做出对炮塔的选择,因此需要监听玩家选中了哪个炮塔。在每个Toggle中添加OnValueChanged的事件。这样就可以为每个Toggle注册OnValueChanged的事件。在此基础上,当玩家点击了一个炮塔图标,就会让该炮塔的IsOn属性变为True,其它2个炮塔的IsOn属性变为False,此时值就发生了变化,事件被成功监听后就会调用相应的函数。3.4检测鼠标点击到哪个MapCube
玩家选择了炮塔后,就要将炮塔放置在Cube上,此时就要检测玩家选定了哪个

MapCube。这里采用射线检测,即从鼠标发出射线,查看射线碰撞到哪一个MapCube
MapCube设置为一层(Layer),这样在射线检测时,就可以指定检测碰撞在MapCube层,而不与其它层进行碰撞检测。当玩家操作转为鼠标点击时,鼠标如果在UI上,此后接续的就是炮塔的选择。这时需要对鼠标是否点击到UI进行判断。当没有在UI上时,就指定和Mapcube层进行射线检测。3.5币值不足的提示效果
当金币不足时,将不能建造炮塔,此时总金额闪烁,提示玩家金币不足。为呈现这一效果,要为总金额的Label添加动画。创建Animation文件夹,并创建子文件Money,用于保存总金额闪烁的动画。为Money标签创建动画,界面效果如1所示。
1动画关键帧Fig.1Animationkeyframe
添加关键帧,改变相邻关键帧的颜色,实现闪烁的效果。只有在金币不足时,才会闪烁,且动画只播放一次。这就需要编辑状态机,界面设计效果如图2所示。创建一个新状态(newstate),重命名为Empty,并设为默认状态,也就是没有闪烁。Empty状态可以转变为Fliker状态,当Flicker状态结束(动画播放了一次)转回Empty状态。将Flicker动画的循环(LoopTime)选项取消。添加新的Trigger(触发器)类型参数,重命名为Flicker。接下来将Empty状态切换到Flicker状态的条件设置为Flicker,为此将特别取消HasExitTime选项。Flicker状态切换到Empty状态则不变。
2动画状态机Fig.2Animationstatemachines3.6利用粒子系统创建营造的特效
为了增加游戏的真实感,每当建造炮塔时四周会扬起泥土。这里使用Unity自带的粒子效果来实现该特效,并在每次创建炮塔时实例化该特效。

首先创建一个空物体并重命名为BuildEffect,然后在其下添加ParticleSystem(粒子系统),对粒子系统进行修改,使之生成像素风格的泥土。
参数设置如图3所示。其中,Renderer用于控制粒子渲染模式,这里将其模式更改为Mesh,再为其添加材质,就能喷射出像素风格的泥土。而后调整粒子的大小、生命周期、喷射速度等参数,使其状似溅射的泥土。3粒子系统的设置Fig.3Settingofparticlesystem4炮塔攻击敌人
4.1添加触发器,检测炮台附近的敌人
为了让炮塔能够发现敌人。需要为炮塔添加碰撞器,当敌人与碰撞器碰撞后,就提示炮塔发现敌人。先为标准型炮塔添加碰撞器。这里使用球形碰撞器,并设置为触发器(将IsTrigger选项选上)。还要添加刚体(Rigidbody),并取消重力(不UseGravity)。
为标准型炮塔添加Turret脚本,用于识别并攻击敌人。考虑到可能同时有多个敌人进入攻击范围,就要创建一个列表用于存放进入的敌人,这样炮塔会优先攻击先进入的敌人,直至敌人死亡后,再攻击下一个。4.2控制炮台发射炮弹
炮塔在检测到敌人后就要开启攻击了。定义变量attckRateTime表示每多少秒发射一次炮弹。另外还需要定义计时器,当计时器timer预设时间到,后才能进行一次攻击。研发代码可详见如下。publicGameObjectbulltePrefab1voidStart(){
2timerattckRateTime;}3voidUpdate(){
4timerTime.deltaTime

5ifenemys.Count0&&timerattackRateTime){6timer07Attack();}}
8voidAttack(){//待补充}
计时器的累加在Update函数中完成。当计时器累计时间达到攻击阈值并且有敌人进入射程就调用Attack函数进行攻击。攻击时,timer也要重置为0。在炮塔创建之初就能对敌人进行攻击,所以在Start方法中,将timer初始化为attckRateTime
在发起攻击前需给出炮弹定义。在标准型炮塔的炮口下,创建一个空物体并重命名FirePosition,用来定位炮弹的位置。如此一来,当有敌人进入炮塔射程,就会在炮口实例化一个炮弹。4.3控制炮弹的飞行
为炮弹添加脚本来控制炮弹的飞行。创建变量分别表示炮弹的伤害、速度以及目标。炮弹的飞行同样在Update函数中设计实现。研究可推得代码如下。1voidUpdate(){
2transform.LookAttarget.position);
3transform.TranslateVector3.forwardspeedTime.deltaTime);}该脚本是绑定在炮弹上的,第2行将炮弹的方向设置为朝着目标的方向,第3为让炮弹朝着设置的方向飞行。炮弹的目标要在Turret脚本中进行设置,当炮弹被实例化后,将第一个进入射程的敌人设置为攻击目标,即列表中的第一项enemys0]。修改Attack函数,研发内容如下。1voidAttack(){
2GameObjectbulletGameObject.InstantiatebulltePrefabfirePosition.positionfirePosition.rotation);

3bullet.GetComponentBullet().SetTargetenemys0]);}综上代码中,第2行将实例化后的炮弹保存下来,在第3行获取绑定该炮弹的Bullet脚本,并调用SetTarget函数对变量Target进行设置。4.4控制炮弹与敌人的碰撞处理
此时,炮弹和敌人碰撞后是未做任何处理的,因此要检测炮弹是否与敌人有接触,这里采用触发检测,勾选BulletSphereCollider组件的IsTrigger选项。并Bullet添加刚体,取消重力选项。
当检测到炮弹与敌人发生了碰撞,敌人就将掉血,炮弹随即销毁,播放爆炸特效。编辑Bullet脚本,具体如下。
1voidOnTriggerEnterCollidercol){2ifcol.tag==Enemy"){
3col.GetComponentEnemy.TakeDamagedamage);4GameObject.InstantiateexplosionEffectPrefabtransform.positiontransform.rotation);
5Destroythis.gameObject);}}
2行判断与炮弹碰撞的是否为敌人。如果是,则调用碰撞的敌人的
TakeDamage函数进行掉血处理(第3行),并在碰撞处播放爆炸特效(第4行),同时一并销毁炮弹(第5行)。5炮塔升级
5.1创建炮塔升级的UI按钮
当鼠标点击到已经创建的炮塔时,可以选择让炮塔升级或是拆毁已有的炮塔。这个UI可以被所有的炮塔所共用(包括升级后的炮塔)。当点击一个炮塔后,在炮塔上方出现UI,其它处的UI消失,同一时刻至多有一座炮塔上方有UI。创建Canvas,重命名为UpgradeCancas,并添加2个按钮。一个为升级,另一个为

拆毁。调整按钮和画布的相对位置和大小。将画布的中心点放在Cube中心点处,并且使2个按钮置于炮塔上方[3]。
此后,就是给按钮添加动画,控制升级面板显示和隐藏方法的创建,用于控制升级面板的显示和隐藏。
5.2处理升级按钮和拆按钮的点击事件
升级炮塔时要明确被升级炮塔的类型。在BuildManager脚本持有的是已选中的炮塔(变量selectedTurretGo),但是升级功能是在MapCube脚本中运行通过的,也就是需要保存当前选中的MapCube,调用MapCube脚本中的UpgradeTurret函数完成升级。研究中,可参考如下设计代码。1publicvoidUpgradeTurret(){2DestoryturretGo);3isUpgradetrue
4turretGoGameObject.InstantiateturretData.turretUpgradedPrefabtransform.positionQuaternion.identity);
5GameObjecttempGameObject.InstantiateBuildEffecttransform.positionQuaternion.identity);6Destroytemp1);}
升级炮塔时先将原先的炮塔摧毁(第2行),并设置为已升级(第3行),实例化升级后的炮塔(第4行)。第56行为特效的添加和销毁,使用的是建造炮塔时的特效。
摧毁炮塔时就直接调用Destroy函数,再调试规整某些变量。设计代码可表述如下。
1publicvoidDestroyTurret(){2DestroyturretGo);

3isUpgradefalse4turretGonull5turretDatanull;}
5.3创建剩余类型的炮台及其升级的预制体
为标准炮塔的升级预制体添加脚本,并挂载相应的成分。为标准炮塔升级预制体同样添加上球型碰撞器和刚体,设置半径(比标准炮塔射程大),取消勾选使用重力。这样标准炮塔升级预制体也可以进行瞄准攻击了。
除此之外,还将涉及激光炮塔的设计研发。激光炮塔的球型碰撞器,刚体组件同样可以从标准型炮塔上完整拷贝。为激光炮塔添加Turret脚本。激光炮塔无需子弹进行攻击,但激光有起始位置,并且炮塔头部需要跟随敌人进行旋转。5.4利用LineRenderer显示激光
为激光炮塔制作激光,这里使用的是LineRenderer组件。在设计上可以实现画线的功能。在激光炮塔下添加空物体,重命名为Laser,为其添加LineRenderer件。再为该组件添加材质,并调整宽度。使得描绘后画出的线像一束光。此时激光炮塔已经可以瞄准敌人并发出射线,但是一旦敌人离开射程,那条射线依然还在。为此,当没有敌人在射程内时,不再使用LineRenderer组件。基于此,可得研发代码如下。
1elseifenemys.Count0){//激光攻击
2iflaserRender.enabledfalse){3laserRender.enabledtrue;}4ifenemys0null){5UpdateEnemys();}6ifenemys.Count0){

7laserRender.SetPositionsnewVector3[]{firePosition.positionenemys0.transform.position});}8else
9laserRender.enablesfalse;}
89行表示当没有敌人时,禁用LineRenderer组件,而当需要选择激光攻击时还要预先启用(第23行)。5.5添加激光的伤害和伤害特效
研究发现激光炮塔攻击方式与炮弹攻击不同,因此要专门添加伤害计算的代码。在Turret脚本的Update函数中可添加代码(在上面的代码中添加)如下:enemys0.GetComponentEnemy.TakeDamagedamageRateTime.deltaTime);
敌人受到激光攻击时也会有受伤的特效,激光攻击对敌人伤害时需要连续地播放特效。应当在敌人被激光击中的位置播放特效,而且播放时还应当朝着炮塔方向。研究可得该部分的功能设计代码如下。1laserEffect.SetActivetrue);
2laserEffect.transform.positionenemys0.transform.position3Vector3postransform.position
4pos.yenemys0.transform.position.y5laserEffect.transform.LookAtpos);
将特效的位置设置在敌人的位置(第2行),并且特效需要朝向炮塔(第5行),当时炮塔的中心是位于炮塔的下方,因而需要修改特效的纵向朝向,在竖直方向特效的朝向保持不动(第34行)。如果不做修改,那么特效将会朝向炮塔下方。6结束语
本款研发游戏中,主要的6个物体、8个脚本,以及相互之间的关系,可用图形进

行展现,即如图4所示。方框中的是物体,椭圆中的是脚本。各个脚本之间又存在着调用关系,箭头上标记了调用其它脚本的对象或函数,箭头起点是调用方,终点是被调用方[3]。本款游戏的成功实现也将为此后的同类研究提供有益的技术参考借鉴。
4关系图Fig.4Relationdiagram参考文献

【相关文献】
1]宣雨松.Unity3D游戏开发[M.北京:人民邮电出版社,2012.
2]商宇浩,李一帆,张吉祥.Unity5.x完全自学手册[M.北京:电子工业出版社,2016.3]李在贤.Unity5权威讲解[M.孔雪玲,译.北京:人民邮电出版社,2016.4UnityTechnologies.Unity5.x从入门到精通[M.北京:中国铁道出版社,2016.5SKEETJ.深入理解C#[M.3.姚琪琳,译.北京:人民邮电出版社,2014.6JACKSONS.UnityUI设计[M.张骞,译.北京:清华大学出版社,2017.

基于Unity引擎制作塔防游戏

相关推荐