欢迎来到如安在 Unity 中创立塔防游戏的第二部分。你正在Unity中制作一个塔防游戏,在榜首部分结束时,你能够放置和升级怪物。你还有一个敌人进犯饼干。
然而,敌人不知道该面临哪条路!此外,这是进犯的一个严峻的失误。在这一部分中,你将增加敌人的生成波次,武装你的怪物,这样他们就能够保护你宝贵的饼干。
开端
在 Unity 中,翻开本教程系列榜首部分中已完结的项目,或者假如您现在刚刚参加,请下载入门项目并翻开 TowerDefense-Part2-Starter。
使敌人转起来
在上一个教程结束时,敌人沿着路途行进,但似乎不知道该面临哪条路。
在 IDE 中翻开 MoveEnemy.cs,然后增加以下办法来处理此问题。
private void RotateIntoMoveDirection()
{
Vector3 newStartPosition = waypoints [currentWaypoint].transform.position;
Vector3 newEndPosition = waypoints [currentWaypoint + 1].transform.position;
Vector3 newDirection = (newEndPosition - newStartPosition);
float x = newDirection.x;
float y = newDirection.y;
float rotationAngle = Mathf.Atan2 (y, x) * 180 / Mathf.PI;
GameObject sprite = gameObject.transform.Find("Sprite").gameObject;
sprite.transform.rotation = Quaternion.AngleAxis(rotationAngle, Vector3.forward);
}
RotateIntoMoveDirection
rotates the enemy so that it always looks forward, like so:
RotateIntoMoveDirection
旋转敌人,使其一直向前看,如下所示:
它通过从下一个航点的方位中减去当时航点的方位来核算 虫子 的当时移动方向。
它运用 `Mathf.Atan2` 来确认 `newDirection` 指向的视点(以弧度为单位),假设零点向右。将结果乘以 `180 / Mathf.PI` 会将视点转换为度数。
终究,它检索名为 Sprite 的子项,并沿 z 轴将其旋转 `rotationAngle` 度。请留意,您旋转的是子项而不是父项,因而生命条(您很快就会增加它)坚持水平状态。
在 Update()
中,将注释 // TODO: Rotate into move direction
替换为对 RotateIntoMoveDirection
的以下调用:
RotateIntoMoveDirection();
保存文件并切换到 Unity。运转场景;现在你的怪物知道他要去哪里了。
现在错误表现在哪里?
一个敌人?几乎不令人形象深刻。让三五成群的人来。就像每个塔防游戏相同,三五成群的人会一波又一波地来!
告诉玩家
在你让部落开端举动之前,你需求让玩家知道行将到来的猛攻。另外,为什么不在屏幕顶部显现当时波次的数字?
多个游戏方针需求生成波次信息,因而您需求将其增加到 GameManager 上的 GameManagerBehavior 组件中。
在 IDE 中.cs翻开 GameManagerBehavior,然后增加以下两个变量:
public Text waveLabel;
public GameObject[] nextWaveLabels;
waveLabel
在屏幕右上角存储对波读数的引用。 nextWaveLabels
存储两个游戏方针,当它们组合在一起时,将创立一个动画,您将在新波开端时显现,如下所示:
保存文件并切换到 Unity。在层次结构中挑选游戏办理器。单击“生成波次标签”右侧的小圆圈,然后在“挑选文本”对话框中,挑选“场景”选项卡中的“生成波次标签”。
现在将下一波标签的巨细设置为 2。然后将元素 0 分配给 NextWaveBottomLabel,将元素 1 分配给 NextWaveTopLabel,办法与设置 Wave Label 的办法相同。
这就是您的游戏办理器行为应该的样子
假如玩家输掉了游戏,他应该不会看到下一波音讯。要处理此问题,请在 IDE 中切换回 GameManagerBehavior.cs并增加另一个变量:
public bool gameOver = false;
在 gameOver
中,您将存储玩家是否输掉了游戏。
相同,您将运用特点来坚持游戏元素与当时波同步。将以下代码增加到 GameManagerBehavior
:
private int wave;
public int Wave
{
get
{
return wave;
}
set
{
wave = value;
if (!gameOver)
{
for (int i = 0; i < nextWaveLabels.Length; i++)
{
nextWaveLabels[i].GetComponent<Animator>().SetTrigger("nextWave");
}
}
waveLabel.text = "WAVE: " + (wave + 1);
}
}
创立私有变量、特点和 getter 现在应该是第二天性。但相同,setter有点棘手。
运用新的 value
更新 wave
。
然后你查看游戏是否没有结束。假如是这样,则遍历 nextWaveLabels 中的一切标签 — 这些标签具有 Animator 组件。要在动画器上触发动画,请设置触发器 nextWave。
终究,您将 waveLabel
的 text
设置为 wave + 1
的值。为什么是 +1
?– 正常人不会从零开端计数。古怪,我知道:]
在 Start()
中,设置此特点的值:
Wave = 0;
您从 Wave
数字 0 开端计数。
保存文件,然后在 Unity 中运转场景。波读数从 1 正确开端。
对于玩家来说,一切都从第 1 波开端。
生成敌人波次
这听起来很明显,但你需求能够发明更多的敌人来释放三五成群的敌人——现在你不能这样做。此外,一旦当时生成波次被抹去,你就不应该发生下一波——至少目前是这样。
因而,游戏有必要能够辨认场景中是否有敌人,而标签是辨认游戏方针的好办法。
设置敌人标签
在项目浏览器中挑选敌人预制件。在查看器的顶部,单击“符号”下拉列表,然后挑选“增加符号”。
创立一个命名为enemy_的标签。
挑选敌人预制件。在查看器中,将其标签设置为敌人。
界说敌人波次信息
现在你需求界说一波敌人。在 IDE 中翻开 SpawnEnemy.cs,并在 SpawnEnemy
之前增加以下类完结:
[System.Serializable]
public class Wave
{
public GameObject enemyPrefab;
public float spawnInterval = 2;
public int maxEnemies = 20;
}
Wave 包括一个 enemyPrefab
,这是实例化该波中一切敌人的根底, spawnInterval
是波中敌人之间的时刻(以秒为单位)和 maxEnemies
,即在该波中生成的敌人数量。
此类是可序列化的,这意味着您能够在查看器中更改值。
将以下变量增加到 SpawnEnemy
类:
public Wave[] waves;
public int timeBetweenWaves = 5;
private GameManagerBehavior gameManager;
private float lastSpawnTime;
private int enemiesSpawned = 0;
这将设置一些用于生成的变量,这些变量与您沿航点移动敌人的办法十分相似。
您将在 waves
中界说游戏的各种波次,并别离在 enemiesSpawned
和 lastSpawnTime
中盯梢生成的敌人数量和生成时刻。
玩家在杀戮后需求休息,因而将 timeBetweenWaves
设置为5秒
将 Start()
的内容替换为以下代码。
lastSpawnTime = Time.time;
gameManager =
GameObject.Find("GameManager").GetComponent<GameManagerBehavior>();
在这里,您将 lastSpawnTime
设置为当时时刻,这将是脚本在场景加载后立即启动的时刻。然后以了解的办法检索 GameManagerBehavior
。
将此增加到 Update()
:
int currentWave = gameManager.Wave;
if (currentWave < waves.Length)
{
float timeInterval = Time.time - lastSpawnTime;
float spawnInterval = waves[currentWave].spawnInterval;
if (((enemiesSpawned == 0 && timeInterval > timeBetweenWaves) ||
timeInterval > spawnInterval) &&
enemiesSpawned < waves[currentWave].maxEnemies)
{
lastSpawnTime = Time.time;
GameObject newEnemy = (GameObject)
Instantiate(waves[currentWave].enemyPrefab);
newEnemy.GetComponent<MoveEnemy>().waypoints = waypoints;
enemiesSpawned++;
}
if (enemiesSpawned == waves[currentWave].maxEnemies &&
GameObject.FindGameObjectWithTag("Enemy") == null)
{
gameManager.Wave++;
gameManager.Gold = Mathf.RoundToInt(gameManager.Gold * 1.1f);
enemiesSpawned = 0;
lastSpawnTime = Time.time;
}
}
else
{
gameManager.gameOver = true;
GameObject gameOverText = GameObject.FindGameObjectWithTag ("GameWon");
gameOverText.GetComponent<Animator>().SetBool("gameOver", true);
}
逐渐完结此代码:
- 获取当时波次的索引,并查看它是否是终究一个波次。
-
假如是这样,请核算自前次敌人生成以来通过了多少时刻,以及是否是时候生成敌人了。在这里,您考虑两种状况。假如是波中的榜首个敌人,请查看
timeInterval
是否大于timeBetweenWaves
。否则,请查看timeInterval
是否大于此波次的spawnInterval
。无论哪种状况,你都要确保你没有为这一波生成一切的敌人。 -
如有必要,通过实例化
enemyPrefab
的副本来生成敌人。您还能够增加enemiesSpawned
计数。 - 你查看屏幕上的敌人数量。假如没有,它是波次中的终究一个敌人,你就会生成下一波。你还会给玩家在波次结束时剩下的一切金币的10%。
- 在打败终究一波运转后,游戏赢得了动画。
设置生成间隔
保存文件并切换到 Unity。在层次结构中挑选路途。在查看器中,将波次的巨细设置为 4。
现在,将一切四个元素的“敌人预制件”设置为“敌人”。设置生成间隔和最大敌人数字段,如下所示:
- Element 0: Spawn Interval: 2.5, Max Enemies: 5
- Element 1: Spawn Interval: 2, Max Enemies: 10
- Element 2: Spawn Interval: 2, Max Enemies: 15
- Element 3: Spawn Interval: 1, Max Enemies: 5
终究设置应如下面的屏幕截图所示。
当然,你能够运用这些设置来增加或减少进犯损伤。
运转游戏。啊哈!虫子正在向你的饼干进军!
可选:增加不同类型的敌人
没有一个塔防游戏只要一种类型的敌人是完好的。走运的是,预制件文件夹包括另一个选项,Enemy2。
在查看器中挑选预制件\Enemy2,然后将MoveEnemy脚本增加到其间。将其速度设置为 3,将其标签设置为 敌人。您现在能够运用这个快速错误来让玩家坚持警惕!
更新玩家生命值
即使三五成群的虫子冲向饼干,玩家也不会遭到任何损伤。但仅此而已。当玩家让敌人侵略时,他应该遭到打击。
在 IDE 中.cs翻开“游戏办理器行为”,然后增加以下两个变量:
public Text healthLabel;
public GameObject[] healthIndicator;
您将运用 healthLabel
访问玩家的生命值读数,并运用 healthIndicator
访问五个绿色饼干嘎吱嘎吱的小怪物——它们仅仅以比规范健康标签更风趣的办法代表玩家健康。
办理生命值
接下来,增加一个特点以在 GameManagerBehavior
中保护玩家的生命值:
private int health;
public int Health
{
get
{
return health;
}
set
{
if (value < health)
{
Camera.main.GetComponent<CameraShake>().Shake();
}
health = value;
healthLabel.text = "HEALTH: " + health;
if (health <= 0 && !gameOver)
{
gameOver = true;
GameObject gameOverText = GameObject.FindGameObjectWithTag("GameOver");
gameOverText.GetComponent<Animator>().SetBool("gameOver", true);
}
for (int i = 0; i < healthIndicator.Length; i++)
{
if (i < Health)
{
healthIndicator[i].SetActive(true);
}
else
{
healthIndicator[i].SetActive(false);
}
}
}
}
这将办理玩家的生命值。再一次,大部分代码都在 setter 中:
-
假如要下降玩家的生命值,请运用
CameraShake
组件创立漂亮的摇晃作用。此脚本包括在项目中,此处未介绍。 - 更新屏幕左上角的私有变量和健康标签。
-
假如生命值降至 0 且游戏没有结束,请将
gameOver
设置为true
并触发GameOver
动画。 - 从饼干中移除其间一个怪物。假如它仅仅禁用了它们,则能够更简单地编写此位,但它也支撑在增加运转状况时从头启用它们。
Initialize Health
in Start()
:
Health = 5;
当场景开端播映时,将 Health
设置为 5
。
设置此特点后,您现在能够在 虫子 到达 Cookie 时更新玩家的运转状况。保存此文件,然后切换到仍在 IDE 中的 MoveEnemy.cs。
更新显现生命值
若要更新玩家的生命值,请在 Update()
中找到 // TODO: deduct health
的注释,并将其替换为以下代码:
GameManagerBehavior gameManager =
GameObject.Find("GameManager").GetComponent<GameManagerBehavior>();
gameManager.Health -= 1;
这将获取 GameManagerBehavior
并从其 Health
中减去一个。
保存文件并切换到 Unity。
在层次结构中挑选“游戏办理器”,并将其“健康标签”设置为“健康标签”。
翻开层次结构中的 Cookie,并将其五个健康指示器子项拖放到 GameManager 的健康指示器数组中 – 健康指示器是快乐地吃饼干的绿色小怪物。
播映场景并等待虫子到达饼干。什么都不做,直到你输了。
怪物战争:怪物的复仇
怪物就位?完毕。敌人在行进?完毕。 – 他们看起来很卑鄙!是时候把那些傻瓜割下来了!
一个生命条,所以玩家知道哪些敌人是强的,哪些是弱的
勘探怪物范围内的敌人
决议计划点——向哪个敌人开火
Enemy Health Bar敌人生命条
你将运用两张图片来完结生命条,一张用于深色布景,另一张用于稍微小一点的绿色条,你将缩放以匹配敌人的生命值。
将 Prefabs\Enemy 从项目浏览器拖到场景中。
然后将“ Images\Objects\HealthBarBackground ”拖到层次结构中的“敌人”上,将其增加为子项。
在查看器中,将 _HealthBarBackground_的方位设置为 (0, 1, -4)。
接下来,在项目浏览器中挑选“ Images\Objects\HealthBar”,并确保其“透视”设置为“左”。然后,将其增加为层次结构中敌人的子项,并将其方位设置为 (-0.63, 1, -5)。将其 X 份额设置为 125。
将一个名为 HealthBar 的新 C# 脚本增加到 HealthBar 游戏方针。稍后,您将对其进行编辑以调整生命条的长度。
在层次结构中挑选敌人后,确保它的方位为 (20, 0, 0)。
单击查看器顶部的“应用”,将一切更改保存为预制件的一部分。终究,从层次结构中删去敌人。
现在,重复这些过程,将生命值栏增加到预制件_Prefabs\Enemy2_。
Adjust Health Bar Length调整生命条长度
在 IDE 中翻开 HealthBar.cs,然后增加以下变量:
public float maxHealth = 100;
public float currentHealth = 100;
private float originalScale;
maxHealth
存储敌人的最大生命值, currentHealth
盯梢剩下的生命值。终究, originalScale
会记住健康条的原始巨细。
将方针的 originalScale
存储在 Start()
中:
originalScale = gameObject.transform.localScale.x;
保存 localScale
的 x
值。
通过将以下内容增加到 Update()
来设置健康条的份额:
Vector3 tmpScale = gameObject.transform.localScale;
tmpScale.x = currentHealth / maxHealth * originalScale;
gameObject.transform.localScale = tmpScale;
将 localScale
仿制到临时变量,由于不能仅调整其 x 值。然后,依据 虫子 的当时运转状况核算新的 x 刻度,并将临时变量设置回 localScale
。
保存文件并在 Unity 中运转游戏。你会在敌人上方看到生命条。
在游戏运转时,翻开层次结构中的一个敌人(克隆)方针,然后挑选其 HealthBar 子方针。更改其“当时运转状况”值,并查看该运转状况栏是否要更改。
Track Enemies in Range盯梢范围内的敌人
现在怪物需求知道要瞄准哪些敌人。在施行之前,你对怪物和敌人有一些准备作业要做。
在项目浏览器中挑选预制件\怪物,然后在查看器中向其增加圆形磕碰体 2D 组件。
将磕碰体的半径设置为 2.5 – 这将设置怪物的射程。
选中“ Is Trigger”,以便方针穿过该区域而不是撞到该区域。
终究,在查看器的顶部,将怪物图层设置为忽略光线投射。在对话框中单击“是,更改子项”。假如不忽略光线投射,磕碰体会对单击事件做出反响。这是一个问题,由于怪物会阻止针对他们下方的开放点的事件。
为了答应在触发区域中检测敌人,您需求向其增加磕碰体和刚体,由于 Unity 仅在其间一个磕碰体附加了刚体时才发送触发事件。
在“项目浏览器”中,挑选“预制件\敌人”。增加主体类型设置为运动学的刚体 2D 零部件。这意味着身体不应该遭到物理学的影响。
增加半径为 1 的 2D 圆形磕碰体。对预制件\敌人 2 重复这些过程
触发器现在已设置,因而怪物会检测敌人何时在射程内。
你需求准备一件事:一个脚本,当敌人被炸毁时告诉怪物,这样他们就不会由于持续射击而引起反常。
创立一个名为 EnemyDestructionDelegate 的新 C# 脚本,并将其增加到 Enemy 和 Enemy2 预制件中。
在 IDE 中翻开 EnemyDestructionDelegate.cs,并增加以下托付声明:
public delegate void EnemyDelegate (GameObject enemy);
public EnemyDelegate enemyDelegate;
在这里,您创立一个 delegate
,它是一个函数的容器,能够像变量相同传递。
注: 当您期望一个游戏方针主动告诉其他游戏方针更改时,请运用托付。有关托付的更多信息,请参阅 Unity 文档 。
增加以下办法:
void OnDestroy()
{
if (enemyDelegate != null)
{
enemyDelegate(gameObject);
}
}
毁掉游戏方针后,Unity 会主动调用此办法,并查看托付是否不是 null
。在这种状况下,您能够运用 gameObject
作为参数调用它。这让一切注册为代表的侦听器都知道敌人已被消灭。
保存文件并回来到 Unity。
Give Monsters a License to Kill
给怪物一个击杀的脚本
现在怪物能够勘探范围内的敌人。将新的 C# 脚本增加到 Monster 预制件中,并将其命名为 ShootEnemies。
在 IDE 中翻开 ShootEnemies.cs,然后增加以下 using
语句以访问 Generics
。
using System.Collections.Generic;
增加一个变量来盯梢范围内的一切敌人:
public List<GameObject> enemiesInRange;
在 enemiesInRange
中,您将存储范围内的一切敌人。
初始化 Start()
中的字段。
enemiesInRange = new List<GameObject>();
一开端,范围内没有敌人,所以你创立一个空列表。
填写 enemiesInRange
列表!将此代码增加到脚本中:
void OnEnemyDestroy(GameObject enemy)
{
enemiesInRange.Remove (enemy);
}
void OnTriggerEnter2D (Collider2D other)
{
if (other.gameObject.tag.Equals("Enemy"))
{
enemiesInRange.Add(other.gameObject);
EnemyDestructionDelegate del =
other.gameObject.GetComponent<EnemyDestructionDelegate>();
del.enemyDelegate += OnEnemyDestroy;
}
}
void OnTriggerExit2D (Collider2D other)
{
if (other.gameObject.tag.Equals("Enemy"))
{
enemiesInRange.Remove(other.gameObject);
EnemyDestructionDelegate del =
other.gameObject.GetComponent<EnemyDestructionDelegate>();
del.enemyDelegate -= OnEnemyDestroy;
}
}
-
在
OnEnemyDestroy
中,您将敌人从enemiesInRange
中移除。当敌人在你的怪物周围扣动扳机时,OnTriggerEnter2D
被召唤。 -
然后将敌人增加到
enemiesInRange
列表中,并将OnEnemyDestroy
增加到EnemyDestructionDelegate
。这样能够确保在敌人被炸毁时调用OnEnemyDestroy
。你现在不想让怪物把弹药浪费在死去的敌人身上——是吗? -
在
OnTriggerExit2D
中,您将敌人从列表中删去并取消注册您的代表。现在你知道哪些敌人在射程内了。 -
保存文件,然后在 Unity 中运转游戏。要测试它是否有效,请放置一个怪物,挑选它并在查看器中观察对
enemiesInRange
列表的更改。
Select a Target挑选方针
现在怪物知道哪个敌人在范围内。可是当有多个射程内的敌人时,他们会怎么做?
当然,他们进犯最接近饼干的人!
在 IDE 中翻开 MoveEnemy.cs,然后增加以下新办法来核算:
public float DistanceToGoal()
{
float distance = 0;
distance += Vector2.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 += Vector2.Distance(startPosition, endPosition);
}
return distance;
}
此代码核算敌人没有行进的路途长度。它运用 Distance
来核算两个 Vector3
实例之间的间隔。
稍后您将运用此办法来确认要进犯的方针。可是,你的怪物手无寸铁,所以先处理这个问题。
保存文件并回来 Unity 以开端设置项目符号。
给怪物子弹 – 许多子弹!
将图像/方针/项目符号 1 从项目浏览器拖放到场景中。将 z 方位设置为 -2 – x 和 y 方位无关紧要,由于每次在运转时实例化新项目符号时都会设置它们。
增加一个名为 BulletBehavior 的新 C# 脚本,并在 IDE 中向其增加以下变量:
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 = Vector2.Distance (startPosition, targetPosition);
GameObject gm = GameObject.Find("GameManager");
gameManager = gm.GetComponent<GameManagerBehavior>();
将 startTime
设置为当时时刻并核算起始方位和方针方位之间的间隔。您也能够像平常相同取得 GameManagerBehavior
。
将以下代码增加到 Update()
以操控项目符号移动:
float timeInterval = Time.time - startTime;
gameObject.transform.position = Vector3.Lerp(startPosition, targetPosition, timeInterval * speed / distance);
if (gameObject.transform.position.Equals(targetPosition))
{
if (target != null)
{
Transform healthBarTransform = target.transform.Find("HealthBar");
HealthBar healthBar =
healthBarTransform.gameObject.GetComponent<HealthBar>();
healthBar.currentHealth -= Mathf.Max(damage, 0);
if (healthBar.currentHealth <= 0)
{
Destroy(target);
AudioSource audioSource = target.GetComponent<AudioSource>();
AudioSource.PlayClipAtPoint(audioSource.clip, transform.position);
gameManager.Gold += 50;
}
}
Destroy(gameObject);
}
您能够运用 `Vector3.Lerp` 核算新的项目符号方位,以在开端方位和结束方位之间进行插值。
假如项目符号到达 `targetPosition` ,则验证 `target` 是否依然存在。
检索方针的 `HealthBar` 组件,并通过项目符号的 `damage` 下降其生命值。
假如敌人的生命值降至零,您能够炸毁它,播映声响作用并奖赏玩家的枪法。
保存文件并回来到 Unity。
Get Bigger Bullets取得更大的子弹
假如你的怪物在更高的水平上射出更大的子弹,那不是很酷吗?- 是的,是的,会的!走运的是,这很简单完结。
将 Bullet1 游戏方针从“层次结构”拖放到“项目”选项卡,以创立项目符号的预制件。从场景中删去原始方针 – 您不再需求它。
仿制 Bullet1 预制件两次。将副本命名为项目符号 2 和项目符号 3。
挑选项目符号 2。在查看器中,将精灵渲染器组件的精灵字段设置为图像/方针/子弹 2。这使得 Bullet2 看起来比 Bullet1 大一点。
重复该过程,将 Bullet3 预制件的子画面设置为图像/方针/项目符号 3。
接下来,在子弹行为中设置子弹造成的损伤。
在“项目”选项卡中挑选 Bullet1 预制件。在查看器中,您能够看到子弹行为(脚本),在那里您将子弹 1 的损伤设置为 10,子弹 2 的损伤设置为 15,子弹 3 设置为 20 – 或者任何让你开心的东西。
留意:我设置的值是为了在更高的等级,每次损伤的本钱更高。这抵消了升级答应玩家在最佳方位改进怪物的事实。
项目符号预制件 – 巨细随等级增加
Leveling the Bullets调平子弹
将不同的子弹分配给不同的怪物等级,以便更健壮的怪物更快地撕碎敌人。
在 IDE 中.cs翻开 MonsterData,并将这些变量增加到 MonsterLevel
:
public GameObject bullet;
public float fireRate;
这些将为每个怪物关卡设置子弹预制件和射速。保存文件并回来 Unity 以完结怪物的设置。
在项目浏览器中挑选怪物预制件。在查看器中,翻开怪物数据(脚本)组件中的关卡。将每个元素的“射速”设置为 1。然后将元素 0、1 和 2 的项目符号别离设置为项目符号 1、项目符号 2 和项目符号 3。
您的怪物等级应按如下所示进行配置:
子弹杀死你的敌人?-查看!开火!
开火
在 IDE 中翻开 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;
Vector3 startPosition = gameObject.transform.position;
Vector3 targetPosition = target.transform.position;
startPosition.z = bulletPrefab.transform.position.z;
targetPosition.z = bulletPrefab.transform.position.z;
GameObject newBullet = (GameObject)Instantiate (bulletPrefab);
newBullet.transform.position = startPosition;
BulletBehavior bulletComp = newBullet.GetComponent<BulletBehavior>();
bulletComp.target = target.gameObject;
bulletComp.startPosition = startPosition;
bulletComp.targetPosition = targetPosition;
Animator animator =
monsterData.CurrentLevel.visualization.GetComponent<Animator>();
animator.SetTrigger("fireShot");
AudioSource audioSource = gameObject.GetComponent<AudioSource>();
audioSource.PlayOneShot(audioSource.clip);
}
-
获取子弹的起始方位和方针方位。将 z 方位设置为
bulletPrefab
的方位。之前,您设置了子弹预制件的 z 方位值,以确保子弹出现在发射它的怪物后面,但在敌人的前面。 -
运用
bulletPrefab
表示MonsterLevel
实例化新项目符号。分配项目符号的startPosition
和targetPosition
。 - 让游戏更具趣味性:运转射击动画,并在怪物射击时播映激光声响。
把一切东西放在一起
是时候将一切内容衔接在一起了。确认方针并让你的怪物看着它。
依然在射击敌人.cs中,将此代码增加到 Update()
:
GameObject target = null;
float minimalEnemyDistance = float.MaxValue;
foreach (GameObject enemy in enemiesInRange)
{
float distanceToGoal = enemy.GetComponent<MoveEnemy>().DistanceToGoal();
if (distanceToGoal < minimalEnemyDistance)
{
target = enemy;
minimalEnemyDistance = distanceToGoal;
}
}
if (target != null)
{
if (Time.time - lastShotTime > monsterData.CurrentLevel.fireRate)
{
Shoot(target.GetComponent<Collider2D>());
lastShotTime = Time.time;
}
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
中的最大可能间隔开端。遍历范围内的一切敌人,假如敌人与 cookie 的间隔小于当时最小值,则将其设为新方针。 -
假如通过的时刻大于怪物的射速,请调用
Shoot
,并将lastShotTime
设置为当时时刻。 - 核算怪物与其方针之间的旋转视点。您将怪物的旋转设置为此视点。现在它总是面临方针。
保存文件并在 Unity 中玩游戏。你的怪物大力保护你的饼干。你彻底,彻底完结了!
结尾
哇,所以你在两个教程之间真的做了许多,你有一个很酷的游戏来展现它。
以下是一些能够在您所做的作业根底上构建的想法:
- 更多敌人类型和怪物
- 多条敌人途径
- 不同的敌人等级
查找重视公众号unity小游戏工坊回复“003”获取本教程的资料。
【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(上) – 掘金 ()