欢迎来到我的博客小站。  交流请加我微信好友: studyjava。  也欢迎关注同名公众号:Java学习之道

[转]Unity实战之塔防游戏(二)

  |   0 评论   |   0 浏览

欢迎来到“如何在Unity中创建塔防游戏”的。您正在Unity中制作塔防游戏,并且在第1部分结束时,您可以放置​​和升级怪物。您也有一个敌人攻击了cookie。

但是,敌人不知道该面对哪条路!而且,这是进攻的可怜借口。在这一部分中,您将添加敌人的波浪并武装您的怪物,以便它们可以捍卫您的珍贵cookie。

入门

在Unity中,从本教程系列的第一部分中打开完成的项目,或者,如果您现在才加入,请下载启动器项目并打开TowerTefense-Part2-Starter

从“场景”文件夹中打开GameScene。**

旋转敌人

在上一教程的结尾,敌人走了这条路,但似乎不知道该面对哪条路。

在您的IDE中打开MoveEnemy.cs,然后添加以下方法来解决此问题。

private void RotateIntoMoveDirection()
{
  //1
  Vector3 newStartPosition = waypoints [currentWaypoint].transform.position;
  Vector3 newEndPosition = waypoints [currentWaypoint + 1].transform.position;
  Vector3 newDirection = (newEndPosition - newStartPosition);
  //2
  float x = newDirection.x;
  float y = newDirection.y;
  float rotationAngle = Mathf.Atan2 (y, x) * 180 / Mathf.PI;
  //3
  GameObject sprite = gameObject.transform.Find("Sprite").gameObject;
  sprite.transform.rotation = Quaternion.AngleAxis(rotationAngle, Vector3.forward);
}

RotateIntoMoveDirection 旋转敌人,使其始终向前看,如下所示:

  1. 它通过从下一个航路点的位置减去当前航路点的位置来计算错误的当前移动方向。
  2. 假定右边为零,它用于Mathf.Atan2确定newDirection弧度所指向的角度 。通过180 / Mathf.PI将结果相乘将角度转换为度。
  3. 最后,它检索名为Sprite的子级并将其rotationAngle沿z轴旋转角度。请注意,您旋转了孩子而不是父母,因此健康栏(您将很快添加它)保持水平。

在中Update(),将注释替换为// TODO: Rotate into move direction以下调用RotateIntoMoveDirection

RotateIntoMoveDirection();

保存文件并切换到Unity。运行场景;现在您的怪物知道他要去哪里。

您的错误应该随路而去(此处加快了20倍,因此观看起来更有趣)
该错误现在看起来在哪里。

一个敌人?令人印象深刻。让部落来吧。就像在每个塔防游戏中一样,成群结队也将成群结队!

通知玩家

在使团队前进之前,您需要让玩家知道即将来临的猛攻。另外,为什么不在屏幕顶部显示当前波浪的编号呢?

几个GameObjects需要交流的信息,所以你把它添加到GameManagerBehavior的组件游戏管理器

在您的IDE中打开GameManagerBehavior.cs并添加以下两个变量:

public Text waveLabel;
public GameObject[] nextWaveLabels;

waveLabel商店在屏幕的右上角波读出的参考。nextWaveLabels存储两个GameObject,将它们组合在一起,创建一个动画,将在新波浪开始时显示,如下所示:

nextWaveAnimation

保存文件并切换到Unity。在“层次结构”中选择GameManager。单击Wave标签右侧的小圆圈,然后在“选择文本”对话框中,在“场景”选项卡中选择WaveLabel********

现在设置大小下一波标签2。然后分配元素0NextWaveBottomLabel元素1NextWaveTopLabel相同的方式,设置波标签。

这就是您的游戏管理员行为应为的样子
这就是您的游戏管理员行为应为的样子

如果玩家输掉了游戏,则不应看到下一个波动消息。要处理此问题,在IDE中切换回GameManagerBehavior.cs并添加另一个变量:

public bool gameOver = false;

在其中,gameOver您将存储玩家是否输了游戏。

再次,您将使用属性来使游戏的元素与当前wave保持同步。将以下代码添加到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应该是第二天性了。但是同样,二传手有点棘手。

wave使用新的更新value

然后,您检查游戏是否还没有结束。如果是这样,则遍历nextWaveLabels中的所有标签-这些标签具有Animator组件。要在Animator上触发动画,请设置触发条件nextWave

最后,您设定waveLabeltext到的价值wave + 1。为什么+1呢?–正常人不会从零开始计数。很奇怪,我知道:]

在中Start(),设置此属性的值:

Wave = 0;

您在开始计数Wave号码0

保存文件,然后在Unity中运行场景。Wave读数正确开始于1。

在内部,您从0开始计数,但对于播放器,一切都从wave 1开始。
对于玩家而言,一切都始于第一波。

波浪:产生,产生,产生

听起来很明显,但是您需要能够创建更多的敌人来释放部落-现在您不能这样做。此外,一旦当前波被消灭,就不应生成下一波(至少目前如此)。

因此,游戏必须能够识别场景中是否存在敌人,而标签是识别游戏对象的好方法。

设置敌人标签

在项目浏览器中选择敌人预制件。在检查器顶部,单击“*标签”*下拉列表,然后选择“添加标签”

创建标签

创建一个名为Enemy标签。**

创建一个新标签

选择敌人预制。在检查器中,将其标记设置为“敌人”

定义敌人波

现在您需要定义一波敌人。在您的IDE中打开SpawnEnemy.cs,并在下面添加以下类实现SpawnEnemy

[System.Serializable]
public class Wave
{
  public GameObject enemyPrefab;
  public float spawnInterval = 2;
  public int maxEnemies = 20;
}

Wave拥有enemyPrefab实例化该浪中所有敌人的基础(a)spawnInterval,即浪中敌人之间的时间(以秒为单位)和maxEnemies,即该浪中产生的敌人数量。

此类是Serializable,这意味着您可以在Inspector中更改值。

将以下变量添加到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()

// 1
int currentWave = gameManager.Wave;
if (currentWave < waves.Length)
{
  // 2
  float timeInterval = Time.time - lastSpawnTime;
  float spawnInterval = waves[currentWave].spawnInterval;
  if (((enemiesSpawned == 0 && timeInterval > timeBetweenWaves) ||
       timeInterval > spawnInterval) && 
      enemiesSpawned < waves[currentWave].maxEnemies)
  {
    // 3  
    lastSpawnTime = Time.time;
    GameObject newEnemy = (GameObject)
        Instantiate(waves[currentWave].enemyPrefab);
    newEnemy.GetComponent<MoveEnemy>().waypoints = waypoints;
    enemiesSpawned++;
  }
  // 4 
  if (enemiesSpawned == waves[currentWave].maxEnemies &&
      GameObject.FindGameObjectWithTag("Enemy") == null)
  {
    gameManager.Wave++;
    gameManager.Gold = Mathf.RoundToInt(gameManager.Gold * 1.1f);
    enemiesSpawned = 0;
    lastSpawnTime = Time.time;
  }
  // 5 
}
else
{
  gameManager.gameOver = true;
  GameObject gameOverText = GameObject.FindGameObjectWithTag ("GameWon");
  gameOverText.GetComponent<Animator>().SetBool("gameOver", true);
}

逐步执行以下代码:

  1. 获取当前波的索引,并检查它是否为最后一个。
  2. 如果是这样,请计算自上次敌人生成以来经过了多少时间,以及是否该该生成一个敌人了。在这里,您考虑两种情况。如果是波浪中的第一个敌人,则检查是否timeInterval大于timeBetweenWaves。否则,请检查是否timeInterval大于此波的spawnInterval。无论哪种情况,都请确保您没有为此波生成所有敌人。
  3. 如有必要,请实例化的副本以生成敌人enemyPrefab。您还增加了enemiesSpawned数量。
  4. 您检查屏幕上的敌人数量。如果没有任何敌人,并且它是波浪中的最后一个敌人,则产生下一个波浪。您还给了玩家波浪结束时剩下的全部金子的10%。
  5. 击败最后一波后,该游戏便赢得了动画。

设置生成间隔

保存文件并切换到Unity。在“层次结构”中选择“道路” 。在检查中,设置大小4。**********

现在,将所有四个元素都设置为“*敌人预制”为“敌人”。如下设置“生成时间间隔”*和“*最大敌人”*字段:

  • 元素0:生成间隔:2.5,最大敌人:5
  • 元素1:产生间隔:2,最大敌人:10
  • 元素2:产生时间间隔:2,最大敌人:15
  • 元素3:生成时间间隔:1,最大敌人:5

最终设置应类似于以下屏幕截图。

波浪
当然,您可以使用这些设置来增加或减少屠杀。
运行游戏。啊哈!漏洞正在向您的cookie进发!

虫子

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

没有一种只有一种类型的敌人才能完成的塔防游戏。幸运的是,Prefabs文件夹包含另一个选项Enemy2

检查器中选择“ Prefabs \ Enemy2”,然后向其添加MoveEnemy脚本。将其速度设置为3,将其标记设置敌人。现在,您可以使用此快速漏洞使玩家保持警觉!************

更新玩家健康状况–轻柔地杀死我

即使成群的bug涌向cookie,玩家也不会受到伤害。但没有更多。玩家让敌人入侵时应该受到打击。

在IDE中打开GameManagerBehavior.cs,并添加以下两个变量:

public Text healthLabel;
public GameObject[] healthIndicator;

您将healthLabel用来访问玩家的健康状况读数,以及healthIndicator访问五个绿色的曲奇小怪物-它们只是比标准的健康标签更有趣地表示玩家的健康。

管理健康

接下来,在中添加属性以维护玩家的健康GameManagerBehavior

private int health;
public int Health
{
  get
  {
    return health;
  }
  set
  {
    // 1
    if (value < health)
    {
      Camera.main.GetComponent<CameraShake>().Shake();
    }
    // 2
    health = value;
    healthLabel.text = "HEALTH: " + health;
    // 3
    if (health <= 0 && !gameOver)
    {
      gameOver = true;
      GameObject gameOverText = GameObject.FindGameObjectWithTag("GameOver");
      gameOverText.GetComponent<Animator>().SetBool("gameOver", true);
    }
    // 4 
    for (int i = 0; i < healthIndicator.Length; i++)
    {
      if (i < Health)
      {
        healthIndicator[i].SetActive(true);
      }
      else
      {
        healthIndicator[i].SetActive(false);
      }
    }
  }
}

这可以管理玩家的健康。同样,大部分代码都在设置器中:

  1. 如果您要减少播放器的健康状况,请使用该CameraShake组件来创建良好的震动效果。该脚本包含在项目中,此处不介绍。
  2. 更新屏幕左上角的私有变量和运行状况标签。
  3. 如果运行状况下降到0并且尚未结束,请设置gameOvertrue并触发GameOver动画。
  4. 从Cookie中删除其中一个怪物。如果只是禁用了它们,则可以更简单地编写此位,但是它还支持在添加运行状况时重新启用它们。

初始化HealthStart()

Health = 5;

您设置Health5场景开始播放的时间。

有了此属性,您现在可以在错误到达Cookie时更新播放器的运行状况。保存此文件,然后切换到MoveEnemy.cs,仍在您的IDE中。

更新健康

要更新玩家的健康状况,请在Update()其中找到注释,// TODO: deduct health并用以下代码替换:

GameManagerBehavior gameManager =GameObject.Find("GameManager").GetComponent<GameManagerBehavior>();
gameManager.Health -= 1;

这得到GameManagerBehavior并从减去1 Health

保存文件并切换到Unity。

层次结构中选择GameManager并将其运行状况标签设置为HealthLabel。******

层次结构中展开Cookie,并将其五个HealthIndicator子对象拖放到*GameManager的“健康指标”数组中-健康指标是快乐地吃饼干的小绿色怪物。*****

播放场景并等待错误到达cookie。什么都不做,直到输了。

Cookie攻击

怪物大战:怪物的复仇

怪物到位了吗?检查一下 敌人在前进?检查-它们看起来很卑鄙!是时候修剪那些吸盘了!

这需要几件事:

  • 生命线,让玩家知道哪些敌人是强者还是弱者
  • 检测怪物范围内的敌人
  • 决策点-向哪个敌人开火
  • 很多子弹

敌人健康吧

您将使用两张图像来实现生命线,一幅用于深色背景,而一幅较小的绿色线可以缩放以匹配敌人的健康。

Prefabs \ EnemyProject Browser拖到场景中。

然后将Images \ Objects \ HealthBarBackground拖到层次结构中的**Enemy上以将其添加为子级。**

检查器,所设定的位置HealthBarBackground至*(0,1,-4) *。

接下来,在项目浏览器中选择Images \ Objects \ HealthBar并确保其Pivot设置为Left。然后,将其添加为的子敌人层次结构和设置位置到*(-0.63,1,-5) 。将其X比例设置为125*。******************

将一个名为HealthBar的新C#脚本添加到HealthBar游戏对象。稍后,您将对其进行编辑以调整健康栏的长度。****

随着敌人在选定的层次结构,确保它的位置是*(20,0,0) *。

单击检查器顶部的“应用”,将所有更改保存为预制件的一部分。最后,从“层次结构”中删除“敌人” 。******

Healthbar的错误

现在,重复这些步骤以将运行状况栏添加到Prefabs \ Enemy2

调整健康栏长度

在IDE中打开HealthBar.cs,并添加以下变量:

public float maxHealth = 100;
public float currentHealth = 100;
private float originalScale;

maxHealth存储敌人的最大生命值,并 currentHealth跟踪剩余的生命值。最后,originalScale记住健康栏的原始大小。

将对象存储在originalScaleStart()

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中运行游戏。您会在敌人上方看到健康栏。

抵抗是徒劳的! -等一下,什么抵抗?

游戏运行时,在层次结构中展开一个Enemy(Clone)对象,然后选择其HealthBar子级。更改其“当前运行状况”值,然后检查该运行状况栏是否发生更改。******

AdjustHealthbar

跟踪范围内的敌人

现在,怪物需要知道要瞄准哪些敌人。在实施之前,您需要对“怪物”和“敌人”进行一些准备工作。

在项目浏览器中选择“ Prefabs \ Monster”,然后在“检查器”中向其添加Circle Collider 2D组件。**

将对撞机的半径设置为*2.5-*这将设置怪物的射击距离。

选中*“触发”,*以使对象穿过该区域而不是撞到该区域。

最后,在检查器顶部,将“怪物的图层”设置为“忽略Raycast”。单击是,在对话框中更改子级。如果您不忽略射线广播,则对撞机会对点击事件做出反应。这是一个问题,因为怪物阻止了针对其下方的Openspot的事件。

Bildschirmfoto 2015-06-05 um 14.47.15

要允许在触发区域中检测到敌人,您需要为其添加一个碰撞器和刚体,因为Unity仅在其中一个碰撞器具有刚体时才发送触发事件。

项目浏览器中,选择Prefabs \ Enemy。将刚体**类型设置为KinematicRigidbody 2D零部件添加。这意味着身体不应该受到物理的影响。****

添加一个圈撞机2D半径1。对Prefabs \ Enemy 2重复这些步骤**

现在已设置了触发器,因此怪物可以检测到敌人何时在射程内。

您还需要准备一件事:一个脚本,该脚本会在消灭敌人时通知怪物,以便它们不会因继续开火而引起异常。

创建一个名为EnemyDestructionDelegate的新C#脚本,并将其添加到EnemyEnemy2预制中。******

在您的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。

授予怪物杀死许可

现在,怪物可以检测到范围内的敌人了。在Monster预制件上添加一个新的C#脚本,并将其命名为ShootEnemies。****

在您的IDE中打开ShootEnemies.cs,并添加以下using语句以访问Generics

using System.Collections.Generic;

添加一个变量以跟踪范围内的所有敌人:

public List<GameObject> enemiesInRange;

在中enemiesInRange,您将存储范围内的所有敌人。

初始化中的字段Start()

enemiesInRange = new List<GameObject>();

一开始,范围内没有敌人,因此您创建了一个空列表。

填写enemiesInRange清单!将此代码添加到脚本中:

// 1
void OnEnemyDestroy(GameObject enemy)
{
  enemiesInRange.Remove (enemy);
}

void OnTriggerEnter2D (Collider2D other)
{
// 2
  if (other.gameObject.tag.Equals("Enemy"))
  {
    enemiesInRange.Add(other.gameObject);
    EnemyDestructionDelegate del =
        other.gameObject.GetComponent<EnemyDestructionDelegate>();
    del.enemyDelegate += OnEnemyDestroy;
  }
}
// 3
void OnTriggerExit2D (Collider2D other)
{
  if (other.gameObject.tag.Equals("Enemy"))
  {
    enemiesInRange.Remove(other.gameObject);
    EnemyDestructionDelegate del =
        other.gameObject.GetComponent<EnemyDestructionDelegate>();
    del.enemyDelegate -= OnEnemyDestroy;
  }
}
  1. OnEnemyDestroy,你从敌人身上移走enemiesInRange。当敌人在扳机上行走时,OnTriggerEnter2D会呼叫您的怪物。
  2. 然后,您将敌人添加到的列表中,enemiesInRange并添加OnEnemyDestroy到中EnemyDestructionDelegate。这样可以确保OnEnemyDestroy在敌人被摧毁时被调用。您现在不希望怪物在死去的敌人上浪费弹药-是吗?
  3. OnTriggerExit2D您中,从列表中删除敌人并注销您的代表。现在您知道哪些敌人在射程内。
    保存文件,然后在Unity中运行游戏。要测试它是否有效,请放置一个怪物,将其选中,然后enemiesInRangeInspector中查看列表中的更改。

选择一个目标

现在,怪物知道哪个敌人在射程内。但是,当有多个远程敌人时,他们会怎么做?

他们会攻击最接近Cookie的那一个!

在您的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以开始设置项目符号。

给怪物子弹-很多子弹!

从“项目浏览器”中将“ Images / Objects / Bullet1”拖放到场景中。将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是不言自明的。

targetstartPosition以及targetPosition确定子弹的方向。

distancestartTime跟踪子弹的当前位置。gameManager击溃敌人时奖励玩家。

为这些变量分配值Start()

startTime = Time.time;
distance = Vector2.Distance (startPosition, targetPosition);
GameObject gm = GameObject.Find("GameManager");
gameManager = gm.GetComponent<GameManagerBehavior>();

您设置startTime为当前时间,并计算起始位置和目标位置之间的距离。您也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.Find("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);
}
  1. Vector3.Lerp可以在开始位置和结束位置之间进行插值,从而计算新的项目符号位置。
  2. 如果项目符号到达targetPosition,则请确认该项目target仍然存在。
  3. 您检索目标的HealthBar组成部分,并通过项目符号的来减少其运行状况damage
  4. 如果敌人的生命值降至零,则将其消灭,发挥音效,并奖励玩家枪法。

保存文件并返回到Unity。

获取更大的项目符号

如果您的怪物在更高的水平上发射更大的子弹会不会很酷?-是的,会的!幸运的是,这很容易实现。

Bullet1游戏对象从“*层次结构”*拖放到“*项目”*选项卡,以创建子弹的预制件。从场景中删除原始对象-您不再需要它。

复制Bullet1预制两次。将副本命名为Bullet2Bullet3

选择Bullet2。在检查器中,将Sprite Renderer组件的Sprite字段设置为Images / Objects / Bullet2。这使Bullet2看起来比Bullet1大。

重复该过程,将Bullet3预制的精灵设置为Images / Objects / Bullet3

接下来,在“子弹行为”中设置子弹造成的伤害。

项目选项卡中选择Bullet1预制。在检查你可以看到子弹行为(脚本),还有你所设置的损害,以10Bullet115文档bullet2,和20Bullet3 -或任何使你快乐的。********************

注意:我设置这些值是为了在更高级别上,每次损坏的成本更高。这抵消了升级使玩家可以在最佳位置改进怪物的事实。

子弹预制件-尺寸随级别增加
子弹预制件-尺寸随级别增加

平衡子弹

将不同的子弹分配给不同的怪物等级,以便更强大的怪物更快地粉碎敌人。

在您的IDE中打开MonsterData.cs,并将这些变量添加到MonsterLevel

public GameObject bullet;
public float fireRate;

这些将为每个怪物等级设置子弹预制和射速。保存文件并返回Unity以完成怪物的设置。

在项目浏览器中选择Monster预制件。在“检查器”中,展开“怪物数据(脚本)”组件中的关卡” 。将每个元素的防火率设置为1。然后将元素0、1和2的Bullet分别设置为Bullet1Bullet2Bullet3。**************

您的怪物等级应如下配置:

带子弹的MonsterData

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

皮尤皮尤激光器! (来自Gisela Giardino)
皮尤皮尤激光器!(来自Gisela Giardino

开火

在您的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;
  // 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);
}
  1. 获取子弹的起点和目标位置。将z位置设置为bulletPrefab。之前,您设置了子弹预制件的z位置值,以确保子弹出现在发射它的怪物后面,但在敌人前面。
  2. 使用bulletPrefabfor实例化新项目符号MonsterLevel。分配startPositiontargetPosition的项目符号。
  3. 使游戏更加有趣:每当怪物射击时,运行射击动画并播放激光声音。

放在一起

是时候将所有内容连接在一起了。确定目标并让您的怪物观看。

仍在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));
}

逐步执行此代码。

  1. 确定怪物的目标。从中最大可能的距离开始minimalEnemyDistance。迭代范围内的所有敌人,如果到cookie的距离小于当前的最小值,则将其设为新目标。
  2. Shoot如果经过的时间大于怪物的射速,则呼叫并设置lastShotTime为当前时间。
  3. 计算怪物与其目标之间的旋转角度。您可以将怪物的旋转设置为此角度。现在,它始终面向目标。

保存文件并在Unity中玩游戏。您的怪物会大力捍卫您的cookie。您完全,完全完成!

从这往哪儿走

您可以在此处下载完成的项目。

哇,所以您确实在两个教程之间做了很多工作,并且有一个很酷的游戏可以展示。
以下是您所做工作的一些想法:

  • 更多敌人类型和怪物
  • 多条敌人之路
  • 不同级别

所有这些想法都需要进行最小的更改,并且可以使您的游戏上瘾。如果您是根据本教程创建的新游戏,我们很乐意玩-请在评论中分享链接和您的吹牛。

感谢您抽出宝贵的时间来学习这些教程。我期待看到您的出色构想并杀死大量怪物。


标题:[转]Unity实战之塔防游戏(二)
作者:shirln
地址:https://mmzsblog.cn/articles/2020/10/12/1602472063466.html
-----------------------------
如未加特殊说明,此网站文章均为原创。
转载须在文章起始位置标注作者及原文连接,否则保留追究法律责任的权利。

个人微信公众号 ↓↓↓                 

微信搜一搜爱上游戏开发