首页 > Python基础教程 >
-
C#教程之使用Unity创建塔防游戏(Part2)(2)
public float distanceToGoal()
{
float distance = 0;
distance += Vector3.Distance(
gameObject.transform.position,
waypoints[currentWaypoint + 1].transform.position);
for (int i = currentWaypoint + 1; i < waypoints.Length - 1; i++){
Vector3 startPosition = waypoints[i].transform.position;
Vector3 endPosition = waypoints[i + 1].transform.position;
distance += Vector3.Distance(startPosition, endPosition);
}
return distance;
}
这个方法计算出了敌人尚未走完的路有多长。我们使用了Distatnce这个方法来计算两个Vector3之间的距离。
·通过这个方法来决定小怪兽的攻击目标。但是,现在你的小怪兽们无法攻击敌人,什么事都做不了,这个问题在下一步中解决。

保存好脚本,返回Unity中,我们需要为小怪兽们配备射击敌人的子弹。
为小怪兽们配备无尽的子弹
将 Images/Objects/Bullet1 拖拽到场景视图中。将它的Z坐标设置为-2,在游戏过程中,我们需要不断地产生新的子弹,X和Y坐标是在子弹产生时候设置的。
为Bullet1添加一个名为 BulletBehavior 的C#脚本组件,将下面的变量添加到脚本中:
public float speed = 10;
public int damage;
public GameObject target;
public Vector3 startPosition;
public Vector3 targetPosition;
private float distance;
private float startTime;
private GameManagerBehavior gameManager;
变量 speed 指的是子弹的飞行速度,damage 指的是子弹对敌人造成的伤害。
Target、startPosition、 targetPosition 分别指的是:子弹的目标、初始坐标、目标的坐标。
distance 和 startTime 这两个变量决定了子弹的当前坐标。当玩家消灭一个敌人的时候,我们通过操作 gameManager 这个变量来给予玩家奖励。
在 Start() 方法中为这些变量赋值:
startTime = Time.time;
distance = Vector3.Distance(startPosition, targetPosition);
GameObject gm = GameObject.Find("GameManager");
gameManager = gm.GetComponent<GameManagerBehavior>();
我们将 startTime 设置为当前时间;distance变量的值为 startPosition 和 targetPosition 之间的距离;最后,我们获取了GameManagerBehavior的实例。
在Update()方法中,添加下面的代码来控制子弹的运动轨迹:
// 1
float timeInterval = Time.time - startTime;
gameObject.transform.position = Vector3.Lerp(startPosition, targetPosition, timeInterval * speed / distance);
// 2
if (gameObject.transform.position.Equals(targetPosition))
{
if (target != null){
// 3
Transform healthBarTransform = target.transform.FindChild("HealthBar");
HealthBar healthBar = healthBarTransform.gameObject.GetComponent<HealthBar>();
healthBar.currentHealth -= Mathf.Max(damage, 0);
// 4
if (healthBar.currentHealth <= 0)
{
Destroy(target);
AudioSource audioSource = target.GetComponent<AudioSource>();
AudioSource.PlayClipAtPoint(audioSource.clip, transform.position);
gameManager.Gold += 50;
}
}
Destroy(gameObject);
}
-
计算出子弹的当前位置,这里我们还是使用
Vector3.Lerp这个方法。 - 当子弹击中目标的时候,我们会先验证目标是否还存在。
-
获取了目标的
HealthBar组件,按子弹造成的伤害来削减目标的生命值。 - 当一个敌人的生命值减到零的时候,需要销毁这个敌人对象,然后播放一个音效,最后给予玩家金币奖励。
保存好脚本,返回Unity中。
来些更大的子弹
假如等级高的小怪兽能发射较大的子弹,这是不是很酷呢?是的,我们能做到,因为这很简单。
将 Hierarchy 视图中的 Bullet1 拖拽到Project 视图中的Prefab文件夹下,创造出一个子弹的prefab。删除场景中的子弹对象,我们已经不再需要它。
利用 Bullet1 prefab再创建两个prefab,分别命名为 Bullet2 和 Bullet3 。传统的CTRL + C,CTRL + V命令在这里行不通。选中Bullet1后,按下快捷键CTRL + D,(duplicate 复制的意思),按下CTRL + D 两次后,创建 Bullet2 和 Bullet3。因为Bullet2 和 Bullet3都是比较大的子弹,接下来,我们要为这两个prefab设置新的子弹图片。
选中Bullet2 ,在Inspector面板中,设置 Sprite Renderer 组件的Sprite为 Images/Objects/Bullet2。这样,Bullet2的样子会比Bullet1更大一些。
同上,将Bullet3 prefab的sprite设置为 Images/Objects/Bullet3。
之前在编写Bullet Behavior脚本的时候,没有进行设置 Damage 这个变量的值,接下来,分别设置这三种子弹造成的伤害值。
在Inspector面板中,对Bullet1 、Bullet2 、Bullet3 的Damage进行赋值,分别为10、15、20,或者随你的便。
注意:级别越高的子弹造成的伤害越大。玩家需要将金币花在刀刃上,优先升级那些位置好的小怪兽们。

子弹的大小与小怪兽的等级成正比。
提升子弹的威力
为不同等级的小怪兽分配威力不同的子弹,这样小怪兽越强,就能越快地消灭敌人。
打开脚本 MonsterData.cs ,为 MonsterLevel 添加下面的变量:
public GameObject bullet;
public float fireRate;
前者是指子弹的 prefab,后者是指小怪兽发射子弹的速率。保存好脚本,返回Unity,让我们完成对小怪兽的配置。
在Project视图中选中Monster prefab。在Inspector面板中,展开Monster Data脚本组件中的Levels数组,将所有元素的Fire Rate都设置为1,分别设置Elements0、Elements1、Elements2的Bullet为Bullet1、Bullet2、Bullet3。
配置好后的结果如下图所示:

开火
打开脚本ShootEnemies.cs,添加下面的变量:
private float lastShotTime;
private MonsterData monsterData;
像这两个变量名所显示的那样,前者记录了小怪兽上一次开火的时间,后者的类型为MonsterData,这里包含了该小怪兽的子弹类型,发射速率等等数据。
在Start()方法中为这两个变量赋值:
lastShotTime = Time.time;
monsterData = gameObject.GetComponentInChildren<MonsterData>();
这里,我们设置lastShotTime为当前时间,然后获取了该游戏对象的MonsterData 组件。
再添加下面的代码,令小怪兽能够对敌人开火:
void Shoot(Collider2D target)
{
GameObject bulletPrefab = monsterData.CurrentLevel.bullet;
// 1
Vector3 startPosition = gameObject.transform.position;
Vector3 targetPosition = target.transform.position;
startPosition.z = bulletPrefab.transform.position.z;
targetPosition.z = bulletPrefab.transform.position.z;
// 2
GameObject newBullet = (GameObject)Instantiate(bulletPrefab);
newBullet.transform.position = startPosition;
BulletBehavior bulletComp = newBullet.GetComponent<BulletBehavior>();
bulletComp.target = target.gameObject;
bulletComp.startPosition = startPosition;
bulletComp.targetPosition = targetPosition;
// 3
Animator animator = monsterData.CurrentLevel.visualization.GetComponent<Animator>();
animator.SetTrigger("fireShot");
AudioSource audioSource = gameObject.GetComponent<AudioSource>();
audioSource.PlayOneShot(audioSource.clip);
}
-
获取了子弹的初始坐标和目标所在坐标,将这两个坐标的Z坐标设置为
bulletPrefab的Z坐标。之前我们设置bullet prefab的Z坐标的原因是为了表现一种层次感,子弹所处的位置要比小怪兽和敌人更低。 -
方法开头从MonsterData中获取了bulletPrefab,再以bulletPrefab创建出一个子弹对象。将startPosition和targetPosition赋值给我们创建出来的子弹对象。 - 让游戏更生动:当小怪兽开火的时候播放一个射击的动画和音效。
整合所有的模块
现在是时候该整合一切了,让你的小怪兽能够准确地朝着目标开火。
往ShootEnemies.cs脚本的Update()方法中添加下面的代码:
GameObject target = null;
// 1
float minimalEnemyDistance = float.MaxValue;
foreach (GameObject enemy in enemiesInRange)
{
float distanceToGoal = enemy.GetComponent<MoveEnemy>().distanceToGoal();
if (distanceToGoal < minimalEnemyDistance)
{
target = enemy;
minimalEnemyDistance = distanceToGoal;
}
}
// 2
if (target != null)
{
if (Time.time - lastShotTime > monsterData.CurrentLevel.fireRate){
Shoot(target.GetComponent<Collider2D>());
lastShotTime = Time.time;
}
// 3
Vector3 direction = gameObject.transform.position - target.transform.position;
gameObject.transform.rotation = Quaternion.AngleAxis(
Mathf.Atan2(direction.y, direction.x) * 180 / Mathf.PI,
new Vector3(0, 0, 1));
}
让我们一步一步地来看这些代码:
-
决定小怪兽开火的目标,这里我们采用了寻找最小数的算法。先将
minimalEnemyDistance设置为float.MaxValue,这样就不会有比它更大的数出现了。遍历集合中的所有敌人,当循环结束的时候,我们就可以找出距离饼干最近的敌人。 -
当前时间与小怪兽上次开火的时间间隔大于射击速率的时候,调用
Shoot方法,再将lastShotTime设置为当前时间。 - 计算出小怪兽和目标之间的当前角度,然后旋转小怪兽,让小怪兽能够一直面对着目标。
保存好脚本,启动游戏。看你的小怪兽们正在奋力地保护你的饼干。好样的,现在我们完成了整个工程。

从这个项目中我们学到了什么
从这里可以下载完整的项目。
现在我们这个教程就要结束了,我们完成了一个很棒的塔防游戏。
这个游戏我们还可以做出以下扩展:
1. 添加更多种类的敌人和小怪兽
2. 为敌人建立更多的通往饼干的道路
3. 为小怪兽们设置更多的级别
这些小小的扩展可以令我们的游戏更好玩。假如你以此教程为基础创造出了属于自己的新游戏,请在评论中分享你的链接,让大家都能够好好地体验一回。
在这里你可以发现更多有趣的关于塔防游戏的想法。
感谢大家抽出时间来完成这篇教程。希望大家能够提出更多好的想法,祝大家都能够愉快地杀敌。











