欢迎来到如安在 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。运转场景;现在你的怪物知道他要去哪里了。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

现在错误表现在哪里?

一个敌人?几乎不令人形象深刻。让三五成群的人来。就像每个塔防游戏相同,三五成群的人会一波又一波地来!

告诉玩家

在你让部落开端举动之前,你需求让玩家知道行将到来的猛攻。另外,为什么不在屏幕顶部显现当时波次的数字?

多个游戏方针需求生成波次信息,因而您需求将其增加到 GameManager 上的 GameManagerBehavior 组件中。

在 IDE 中.cs翻开 GameManagerBehavior,然后增加以下两个变量

public Text waveLabel;
public GameObject[] nextWaveLabels;

waveLabel 在屏幕右上角存储对波读数的引用。 nextWaveLabels 存储两个游戏方针,当它们组合在一起时,将创立一个动画,您将在新波开端时显现,如下所示:

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

保存文件并切换到 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。

终究,您将 waveLabeltext 设置为 wave + 1 的值。为什么是 +1 ?– 正常人不会从零开端计数。古怪,我知道:]

Start() 中,设置此特点的值:

Wave = 0;

您从 Wave 数字 0 开端计数。

保存文件,然后在 Unity 中运转场景。波读数从 1 正确开端。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

对于玩家来说,一切都从第 1 波开端。

生成敌人波次

这听起来很明显,但你需求能够发明更多的敌人来释放三五成群的敌人——现在你不能这样做。此外,一旦当时生成波次被抹去,你就不应该发生下一波——至少目前是这样。

因而,游戏有必要能够辨认场景中是否有敌人,而标签是辨认游戏方针的好办法。

设置敌人标签

在项目浏览器中挑选敌人预制件。在查看器的顶部,单击“符号”下拉列表,然后挑选“增加符号”。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

创立一个命名为enemy_的标签。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

挑选敌人预制件。在查看器中,将其标签设置为敌人。

界说敌人波次信息

现在你需求界说一波敌人。在 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 中界说游戏的各种波次,并别离在 enemiesSpawnedlastSpawnTime 中盯梢生成的敌人数量和生成时刻。

玩家在杀戮后需求休息,因而将 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

终究设置应如下面的屏幕截图所示。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

当然,你能够运用这些设置来增加或减少进犯损伤。
运转游戏。啊哈!虫子正在向你的饼干进军!

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

可选:增加不同类型的敌人

没有一个塔防游戏只要一种类型的敌人是完好的。走运的是,预制件文件夹包括另一个选项,Enemy2。

在查看器中挑选预制件\Enemy2,然后将MoveEnemy脚本增加到其间。将其速度设置为 3,将其标签设置为 敌人。您现在能够运用这个快速错误来让玩家坚持警惕!

更新玩家生命值

即使三五成群的虫子冲向饼干,玩家也不会遭到任何损伤。但仅此而已。当玩家让敌人侵略时,他应该遭到打击。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

在 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 的健康指示器数组中 – 健康指示器是快乐地吃饼干的绿色小怪物。

播映场景并等待虫子到达饼干。什么都不做,直到你输了。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

怪物战争:怪物的复仇

怪物就位?完毕。敌人在行进?完毕。 – 他们看起来很卑鄙!是时候把那些傻瓜割下来了!

一个生命条,所以玩家知道哪些敌人是强的,哪些是弱的
勘探怪物范围内的敌人
决议计划点——向哪个敌人开火

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)。

单击查看器顶部的“应用”,将一切更改保存为预制件的一部分。终究,从层次结构中删去敌人。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

现在,重复这些过程,将生命值栏增加到预制件_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;

保存 localScalex 值。

通过将以下内容增加到 Update() 来设置健康条的份额:

Vector3 tmpScale = gameObject.transform.localScale;
tmpScale.x = currentHealth / maxHealth * originalScale;
gameObject.transform.localScale = tmpScale;

localScale 仿制到临时变量,由于不能仅调整其 x 值。然后,依据 虫子 的当时运转状况核算新的 x 刻度,并将临时变量设置回 localScale

保存文件并在 Unity 中运转游戏。你会在敌人上方看到生命条。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

在游戏运转时,翻开层次结构中的一个敌人(克隆)方针,然后挑选其 HealthBar 子方针。更改其“当时运转状况”值,并查看该运转状况栏是否要更改。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

Track Enemies in Range盯梢范围内的敌人

现在怪物需求知道要瞄准哪些敌人。在施行之前,你对怪物和敌人有一些准备作业要做。

在项目浏览器中挑选预制件\怪物,然后在查看器中向其增加圆形磕碰体 2D 组件。

将磕碰体的半径设置为 2.5 – 这将设置怪物的射程。

选中“ Is Trigger”,以便方针穿过该区域而不是撞到该区域。

终究,在查看器的顶部,将怪物图层设置为忽略光线投射。在对话框中单击“是,更改子项”。假如不忽略光线投射,磕碰体会对单击事件做出反响。这是一个问题,由于怪物会阻止针对他们下方的开放点的事件。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

为了答应在触发区域中检测敌人,您需求向其增加磕碰体和刚体,由于 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小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

保存文件并回来 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 是不言自明的。

targetstartPositiontargetPosition 确认项目符号的方向。

distancestartTime 盯梢项目符号的当时方位。 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 – 或者任何让你开心的东西。

留意:我设置的值是为了在更高的等级,每次损伤的本钱更高。这抵消了升级答应玩家在最佳方位改进怪物的事实。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

项目符号预制件 – 巨细随等级增加

Leveling the Bullets调平子弹

将不同的子弹分配给不同的怪物等级,以便更健壮的怪物更快地撕碎敌人。

在 IDE 中.cs翻开 MonsterData,并将这些变量增加到 MonsterLevel

public GameObject bullet;
public float fireRate;

这些将为每个怪物关卡设置子弹预制件和射速。保存文件并回来 Unity 以完结怪物的设置。

在项目浏览器中挑选怪物预制件。在查看器中,翻开怪物数据(脚本)组件中的关卡。将每个元素的“射速”设置为 1。然后将元素 0、1 和 2 的项目符号别离设置为项目符号 1、项目符号 2 和项目符号 3。

您的怪物等级应按如下所示进行配置:

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

子弹杀死你的敌人?-查看!开火!

开火

在 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 实例化新项目符号。分配项目符号的 startPositiontargetPosition
  • 让游戏更具趣味性:运转射击动画,并在怪物射击时播映激光声响。

把一切东西放在一起

是时候将一切内容衔接在一起了。确认方针并让你的怪物看着它。

【Unity小游戏】游戏开发案例,轻松打造一款塔防游戏!(下)

依然在射击敌人.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小游戏】游戏开发案例,轻松打造一款塔防游戏!(上) – 掘金 ()