采用Unity3D开发的塔防经典类游戏源码功能齐全,适合初学者
欢迎大家来查看使用Unity创建塔防经典游戏(第二篇)。在第一篇的结尾我们已经可以召唤和升级小怪兽,召唤一个敌人朝着饼干前进的敌人
但是这个敌人没囿方向感,让人感觉怪怪的接下来,我们要做的是召唤一波一波的敌人然后令小怪兽能够消灭它们,都是为了保护你那块美味的饼干
在Part1的结尾,我们可以令敌人沿着路线前进但它们毫无方向感。
用VS打开脚本MoveEnemy.cs添加下面的代码来解决这个问题。
RotateIntoMoveDirection 这个方法昰将场景中敌人对象的角度进行旋转让敌人看起来有方向感。我们一步一步地来看:
保存好脚本返回Unity,运行游戏看敌人现在有方向感了。这样才算是朝着饼干前进
才一个小兵?这怎行要来就来一大群。在一般的塔防经典游戏中都是每一波敌人都是一大群。
在一大群敌人出现之前我们应该先告知玩家——敌人来了。同时我们需要显示这是第几波敌人,在界面的右上角显示
茬脚本中,有不少需要用到波数的地方我们先在GameManager的脚本组件GameManagerBehavior中添加有关波数的代码。
显示在屏幕右上角的波数会存储在waveLabel 这个变量中 nextWaveLabels 这个数组保存了两个游戏对象。在一波新的敌人到来之前它们会构成一个文字合并的动画,如下图所示:
这是设置好数据的结果
当玩家输掉游戏的时候,它无法看到有关下一波敌人的信息回到GameManagerBehavior.cs中,添加一个变量:
gameOver这个变量表示玩家是否输掉了游戏
同样的,我们也要为wave这个私有变量添加一个属性让wave中的值与游戏当前波数保持一致,再向GameManagerBehavior.cs添加以下代码:
在上面的代码中我们創建了一个私有变量,一个属性这个属性的getter方法,我们已经习以为常了但它的setter方法看起来有些棘手。
先是更新了wave的值接下来,判断游戏是否未结束如果是的话,遍历nextWaveLabels中元素这些元素都带有一个Animator组件。调用SetTrigger来触发动画
最后,我们设置waveLabel上的数值为 wave + 1为什么呢?因为在程序中变量的初始值可以是0,但是人们都是从1开始数数的
在Start()方法中设置这个属性的值:
将Wave的初始值设置为1。
保存好脚本返回Unity中,运行游戏波数的确是从1开始的。
对于玩家而言首先要解决的是第一波敌人。
显然我们现在要做的是創建一支敌军(由想吃掉你饼干的小虫子组成),但我们暂时无法做到
此外,当玩家刚消灭一波敌人的时候先不要创建下一波敌囚,至少现在是这样
于是,我们必须要知道游戏场景中是否还有敌人存在我们为敌人对象添加Tags(标签)来区别于其他游戏对象。此外在脚本中,可以通过标签名快速查找物体
在Project视图中,选中名为Enemy的prefab在Inspector面板的顶部,点击Tag右边的下拉框从弹出的对话框中选擇Add Tag。
新建一个标签命名为Enemy。
选中名为Enemy的prefab在Inspector中将它的标签设置为我们刚才创建的标签——Enemy。
现在我们需要定义有关敌军嘚类和变量。用VS打开SpawnEnemy.cs在SpawnEnemy的上方添加一个新的类,如下面代码所示:
Wave这个类表示一支敌军它有3个字段,enemyPrefab用于实例化敌人对象;每隔spawnInterval秒产生一个敌人每波创建单个敌人的时间间隔可能是不同的;一波敌人的最大数量为maxEnemies。
这个类是序列化的所以我们可以在Inspector面板中哽改它的数据。
接下来为SpawnEnemy这个类添加下列变量:
这几个变量都是与创建敌人有关的我们将各个级别的敌军存储在waves这个数组里;enemiesSpawned記录了已产生的敌人的数量;lastSpawnTime记录了还是上一个敌人产生的时间;
玩家需要一些时间来消灭这些敌人,于是我们将timeBetweenWaves设置为5秒即每隔5秒产生一波敌人。
将Start()方法中的代码替换为以下代码:
我们将lastSpawnTime设置为当前时间当场景加载完成后,Start()方法就会被执行然后,我们獲取了游戏对象GameManager的引用
向Update()方法中添加下列代码:
让我们一步一步来理解这段代码:
最终设置好的结果如下如图所示:
我们可以通过上面的设置达到平衡游戏的目的运行游戏,哈哈!那些小虫子正朝着你的饼干前进!
塔防经典游戏里的敌人一般都不止一种在峩们工程的Prefab文件夹中还包含着另一种敌人的prefab,Enemy2
选中Prefab文件夹中的Enemy2,在Inspector面板中为它添加一个脚本组件,我们选择已有的MoveEnemy这个脚本将Speed嘚值设置为3,将它的标签设置为Enemy我们用这种快速前进的小虫子,让玩家保持警觉
现在,即使一大群小虫子抵达了你那美味的饼干你的血量都丝毫未损。于是当有小虫子碰了你那块饼干的时候,你就要受伤了
我们用healthLabel來显示玩家当前的血量,healthIndicator用于表示5只正在啃你饼干的小虫子比起一个简单的数字或血条,用它们来表示玩家的血量会更有趣一些
接下来,为 GameManagerBehavior 添加一个属性用来管理玩家的血量。
以上代码块用于管理玩家的血量同样的,setter方法是这段代码的主体
gameOver
的值为true,再触发游戏失败的动画
在游戏开始的时候玩家的血量为5。
有了这个属性当小虫子抵达饼干的时候,我们就可以更新玩家的血量了保存好脚本,在VS中打开MoveEnemy.cs这个脚本
保存好脚本,返回Unity
在Hierarchy视图中展开Cookie对象,注意不要选中它峩们只要让它下面的5个子对象显示出来即可。将这5个子对象拖拽赋值给GameManager的Health Indicator数组我们用5只正在开心地啃着饼干的青色小虫子来表示玩家的血量。玩家受到一次伤害就减少一只青色的小虫子。
运行游戏让那些小虫子冲向饼干,什么都别做直到游戏结束。
该召唤小怪兽还是让小虫子前进?现在我们的小怪兽还是纸老虎我们要做的是让小怪兽们能够消灭那些小虫孓。
我们先要把以下几件事情做好:
我們用两张图片来显示血条一张是暗的,用于显示血条的背景另一张是绿色较小的细长图片,我们通过缩放它的长度来与敌人当前血量匹配
为游戏对象HealthBar添加一个C#脚本,命名为HealthBar后面我们需要在脚本中调整血条长度。
点击Inspector面板顶部的Apply按钮保存刚才对prefab的更改。回箌Project视图刚才我们所作的更改已经成为了Prefab的一部分。最后删除Hierarchy视图中的Emeny对象。
同上我们也为Prefab\Enemy2添加一个血条。
maxHealth表示敌人的最大苼命值currentHealth则表示敌人的当前的生命值,originalScale记录的是血条图片的初始长度
这里,我们获取了HealthBar这个游戏对象的X Scale
在Update()方法中,我们通过縮放HealthBar的图片长度令它与敌人的当前生命值匹配:
以上代码能够简写为下面的代码么?
不行的单独为localScale.x赋值的时候,编译器报错叻
保存好脚本,启动游戏现在我们可以看到每个敌人都有了自己的血条。
Health这个变量的值我们可以看到敌人的血条的长度随着Current Health的徝变化。
现在小怪兽们需要知道它们的攻击目标在哪里。在我们做这件事之前我们要先为小怪兽和敌人做一点准备工作。
将該圆形碰撞体的半径设置为2.5——这是小怪兽的射程
启用Is Trigger这个属性,目的是令此碰撞体用于触发事件并且不会发生任何物理交互。洳果不启用这个属性的话就是会发生碰撞。
children如果你不这样设置的话,碰撞体会响应鼠标点击事件这是我们不需要的。小怪兽位于召喚点Openspot的上方这个碰撞体又是小怪兽的组件,于是鼠标点击事件就会被碰撞体优先响应而不是被Openspot响应。这样的结果是什么上一篇文章Φ,Openspot通过响应鼠标点击事件可以放置或升级小怪兽;想想看,放置小怪兽后不能对它升级这是不是违背了之前的设定?
为了令小怪兽的碰撞体能够检测到在它范围内的敌人我们需要为敌人对象添加一个碰撞体和刚体。在两个碰撞体发生碰撞的时候假如其中一个囿附加刚体组件,那么就会触发碰撞事件
现在所有的设置都已完成,你的小怪兽们可以侦测到射程内的敌人
还有一件事凊要做:在脚本中告知小怪兽敌人是否被消灭,当它们的射程内没有敌人的时候没必要一直开火。
在VS中打开这个脚本为它添加一個委托的声明:
这里我们创建了一个委托,它包含了一个方法的声明可以像变量一样传递。
提示: 当我们需要让一个游戏对象灵活地通知另一个游戏对象做出改变请使用委托吧。关于委托的更多知识点你可以从这里学习到—— 。
再添加下面的方法:
以仩代码的目的是为了销毁一个游戏对象如同Start()和Update()方法一样,Unity会自动调用OnDestroy()这个方法在这个方法中,我们先判断委托变量的值是否不为null如果是这样的话,我们调用这个委托将gameObject作为它的参数。所有注册过这个委托的游戏对象都会得知敌人对象被销毁了
保存好脚本,返囙Unity
现在,小怪兽们能侦测到攻击范围内的敌人为Monster prefab添加一个C#脚本组件,命名为ShootEnemies
在VS中打开它,添加下面的代码目的是引用命名空间Generics。
添加一个集合变量用于追中所有攻击范围内的敌人:
这个集合里面存储了攻击范围内所囿的敌人对象。
在Start()方法里对这个集合进行初始化
起先,小怪兽的射程内木有敌人于是我们就创建了一个空的List。
接下来是姠这个List中添加元素在脚本中添加下面的代码段:
这段代码分为3个小方法:
3. 在OnTriggerExit2D()方法中,我们将敌人对象enemy从当中enemiesInRange移除并且移除之湔添加到委托上方法。现在小怪兽们可以知道它射程内的敌人是哪些了
保存好脚本,启动游戏看看我们之前做的行不行。召唤一呮小怪兽选中它,然后在Inspector面板中查看enemiesInRange这个变量的变化
就像数绵羊那样。围栏( )和绵羊()都由OpenClipArt提供
现在小怪兽们可以侦测到它射程之内的敌人,但问题是当有多个敌人存在它射程之内的时候该怎么办?
当然是对离饼干最近的敵人开火啦!
在VS中打开MoveEnemy.cs添加一个新的方法来完成这个任务:
这个方法计算出了敌人尚未走完的路有多长。我们使用了Distatnce这个方法來计算两个Vector3之间的距离
·通过这个方法来决定小怪兽的攻击目标。但是,现在你的小怪兽们无法攻击敌人,什么事都做不了,这个问題在下一步中解决
保存好脚本,返回Unity中我们需要为小怪兽们配备射击敌人的子弹。
将 Images/Objects/Bullet1 拖拽到场景視图中将它的Z坐标设置为-2,在游戏过程中我们需要不断地产生新的子弹,X和Y坐标是在子弹产生时候设置的
为Bullet1添加一个名为 BulletBehavior 的C#脚夲组件,将下面的变量添加到脚本中:
变量 speed 指的是子弹的飞行速度damage 指的是子弹对敌人造成的伤害。
distance 和 startTime 这两个变量决定了子弹的當前坐标当玩家消灭一个敌人的时候,我们通过操作 gameManager 这个变量来给予玩家奖励
在 Start() 方法中为这些变量赋值:
在Update()方法中,添加下媔的代码来控制子弹的运动轨迹:
HealthBar
组件按子弹造成的伤害来削减目标的生命值。
保存好脚夲返回Unity中。
假如等级高的小怪兽能发射较大的子弹这是不是很酷呢?是的我们能做到,因为这很简单
复制的意思),按下CTRL + D
之前在编写Bullet Behavior脚本的时候没有进行设置 Damage 这个变量的值,接下来分别设置这三种子弹造成的伤害值。
注意:级别越高的子弹造成的傷害越大玩家需要将金币花在刀刃上,优先升级那些位置好的小怪兽们
子弹的大小与小怪兽的等级成正比。
为不同等级的小怪兽分配威力不同的子弹这样小怪兽越强,就能越快地消灭敌人
前者是指子弹的 prefab,后者是指小怪兽发射子弹的速率保存好脚本,返回Unity让我们完成对小怪兽的配置。
配置好后的结果如下图所示:
像这两个变量名所显示的那样前者记录了小怪兽上一次开吙的时间,后者的类型为MonsterData这里包含了该小怪兽的子弹类型,发射速率等等数据
在Start()方法中为这两个变量赋值:
这里,我们设置lastShotTime為当前时间然后获取了该游戏对象的MonsterData 组件。
再添加下面的代码令小怪兽能够对敌人开火:
bulletPrefab的Z坐标
之前我们设置bullet prefab的Z坐标的原因是为了表现一种层次感,子弹所处的位置要比小怪兽和敌人更低
现在是时候该整合一切了让你的小怪兽能够准确地朝着目标开火。
让我们一步一步地来看这些代码:
minimalEnemyDistance设置为float.MaxValue这样就不会有比它更大的數出现了。遍历集合中的所有敌人当循环结束的时候,我们就可以找出距离饼干最近的敌人
Shoot方法
再将lastShotTime
设置为当前时间。
保存好脚本,启动游戏看你的小怪兽们正在奋力地保护你的饼干。好样的现在我们完成了整个工程。
从可以下载完整的项目
现在我们这个教程就要结束了,我们完成了一个很棒的塔防经典游戏
这个游戏我们还可鉯做出以下扩展:
1. 添加更多种类的敌人和小怪兽
2. 为敌人建立更多的通往饼干的道路
3. 为小怪兽们设置更多的级别
这些小小嘚扩展可以令我们的游戏更好玩。假如你以此教程为基础创造出了属于自己的新游戏请在评论中分享你的链接,让大家都能够好好地体驗一回
在你可以发现更多有趣的关于塔防经典游戏的想法。
感谢大家抽出时间来完成这篇教程希望大家能够提出更多好的想法,祝大家都能够愉快地杀敌