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

塔防游戏非常受欢迎,没有什么比看着你的防御消灭邪恶的入侵者更令人满意的了!在这个由两部分组成的教程中,您将使用 Unity 构建一个塔防游戏!

您将学习如何…

  • 制造一波又一波的敌人
  • 让他们遵循路点移动
  • 建造和升级塔,让它们将你的敌人减少直至消失

最后,您将拥有该类型的框架,可以对其进行扩展!

注意:您需要了解 Unity 基础知识,例如如何添加游戏资源和组件、了解预制件并了解一些基本的 C#

塔防的全貌

在本教程中,您将构建一个塔防游戏,其中敌人(小虫子)爬向属于您和您的爪牙的饼干,这些爪牙当然是怪物!您可以在战略要点放置和升级怪物,以获得一点金币。

玩家必须在虫子吃你的饼干之前杀死它们。每一波敌人都更难被击败。当你在所有浪潮中幸存下来(胜利!)或五个敌人到达饼干时,游戏结束。(失败!)

以下是已完成游戏的屏幕截图:

Monsters Unite! Protect your cookie!

怪物联合起来!保护饼干!

开始

在 Unity 中打开 TowerDefense-Part1-Starter 项目。

入门项目包括美术和声音资源,以及预构建的动画和一些有用的脚本。这些脚本与塔防游戏没有直接关系,因此此处不再解释。但是,如果您想了解有关创建 Unity 2D 动画的更多信息,请查看此 Unity 2D 教程

扫描二维码关注公众号,回复: 16915414 查看本文章

该项目还包含预制件,稍后您将对其进行扩展以创建角色。最后,该项目包括一个场景及其背景和用户界面设置。

打开_“Scenes”_文件夹中的 GameScene,并将游戏视图的纵横比设置为 4:3,以确保标签与背景正确对齐。您应该在“游戏”视图中看到以下内容:

Starter Project Screenshot

学分:

  • 该项目的艺术来自Vicki Wenderlich的免费艺术包!你可以在gameartguppy上找到更多很棒的图形。
  • 很酷的音乐来自BenSound,他有一些很棒的配乐!
  • 感谢Michael Jasper的震撼

入门项目 – 检查!
资源 – 检查!
迈向统治世界的第一步…嗯,我是说你的塔防游戏…大功告成!

X 标记点:放置

怪物只能在标有_x_的地方张贴。

要将它们添加到场景中,请将“Images\Objects\Openspot从“项目浏览器”拖放到“场景_”视图中。目前,职位并不重要。

在“层次结构”中选择_“Openspot_”后,单击__Inspector_中的Add Component”,然后选择“Box Collider 2D”。Unity 在场景视图中显示带有绿线的框碰撞体。您将使用此碰撞体来检测该位置上的鼠标单击。

Unity automatically detects the proper size for the collider. How cool is that?

Unity 会自动检测碰撞体的合适尺寸。这有多酷?

按照相同的步骤,将Audio\Audio Source组件添加到Openspot。将音频源的_音频剪辑_设置为 tower_place,您可以在“Audio”文件夹中找到该文件夹,然后停用_“Play On Awake_”。

您需要再创建 11 个点。虽然重复所有这些步骤很诱人,但 Unity 有一个很好的解决方案:预制件

Openspot 从“层次结构”拖放到“项目浏览器”的_“预制件_”文件夹中。然后,它的名称在层次结构中变为蓝色,以显示它已连接到预制件。像这样:

prefab

现在您已经有了预制件,您可以根据需要创建任意数量的副本。只需将 Openspot 从_项目浏览器中_的_预制件_文件夹拖放到_场景_视图中即可。执行此操作 11 次,即可在场景中总共创建 12 个 Openspot 对象。

现在使用_Inspector_将这 12 个 Openspot 对象的位置设置为以下坐标:

  • (X:-5.2, Y:3.5, Z:0)
  • (X:-2.2, Y:3.5, Z:0)
  • (X:0.8, Y:3.5, Z:0)
  • (X:3.8, Y:3.5, Z:0)
  • (X:-3.8, Y:0.4, Z:0)
  • (X:-0.8, Y:0.4, Z:0)
  • (X:2.2, Y:0.4, Z:0)
  • (X:5.2, Y:0.4, Z:0)
  • (X:-5.2, Y:-3.0, Z:0)
  • (X:-2.2, Y:-3.0, Z:0)
  • (X:0.8, Y:-3.0, Z:0)
  • (X:3.8, Y:-3.0, Z:0)

完成后,场景应如下所示。

Spot positions for the tower defense game

放置怪物

为了便于放置,项目的预制件文件夹包含一个_怪物__预制_件。

Monster prefab - Ready for use

怪物预制件 – 随时可用

此时,它由一个空的游戏对象组成,其中包含三个不同的精灵及其子元素的射击动画。

每个精灵代表不同力量水平的怪物。预制件还包含一个_音频源_组件,每当怪物发射激光时,您都会触发该组件来播放声音。

您现在将创建一个可以将Monster放置在Openspot上的脚本

在“项目浏览器”中,选择“预制件”文件夹中的_“Openspot_”。在_检查器_中,点按“添加组件”,然后选取_“新建脚本_”并将其命名_为 PlaceMonster_。选择C ​​Sharp 作为_语言_,然后单击_创建和添加_。由于您已将脚本添加到 Openspot _预制件_中,因此场景中的所有 Openspot 现在也附加了该脚本。整洁!

双击脚本以在 IDE 中将其打开。然后添加这两个变量:

public GameObject monsterPrefab;
private GameObject monster;

您将实例化存储在其中的对象的副本monsterPrefab以创建怪物,并将其存储在其中monster以便您可以在游戏过程中对其进行操作。

每个位置一个怪物

添加以下方法,每个位置只允许一个怪物:

private bool CanPlaceMonster()
{
  return monster == null;
}

CanPlaceMonster()你检查monster变量是否仍然null. 如果是的话,说明这里目前没有怪物,放一个也可以。

现在添加以下代码,以便在玩家单击此游戏对象时实际放置怪物:

void OnMouseUp()
{
  
  if (CanPlaceMonster())
  {
    
    monster = (GameObject) 
      Instantiate(monsterPrefab, transform.position, Quaternion.identity);
    
    AudioSource audioSource = gameObject.GetComponent<AudioSource>();
    audioSource.PlayOneShot(audioSource.clip);

    
  }
}

此代码在鼠标单击或点击时放置一个怪物。那么这是如何实现的呢?

  1. OnMouseUp当玩家点击游戏对象的物理碰撞器时,Unity 会自动调用。
  2. CanPlaceMonster()调用时,如果返回,此方法会放置一个新怪物true
  3. 您使用 来创建怪物Instantiate,该方法创建具有指定位置和旋转的给定预制件的实例。在这种情况下,您复制monsterPrefab,为其提供当前游戏对象的位置且不进行旋转,将结果转换为 aGameObject并将其存储在monster.
  4. 最后,您调用PlayOneShot播放附加到对象AudioSource组件的声音效果。

现在你的PlaceMonster脚本可以放置一个新的怪物,但你仍然必须指定预制件。

使用正确的预制件

保存文件并切换回 Unity。

要分配 monsterPrefab 变量,请先在项目浏览器的_预制件_文件夹中选择 Openspot

在__检查器__中,单击 PlaceMonster(脚本)组件的“怪物_预制件”字段右侧的圆圈,然后从出现的对话框中选择Monster”。

Assign Prefab

就是这样。运行场景并通过单击或点击在各种x点上构建怪物。

Success! You can build monsters. However they look like a weird mush because all child sprites of your monster are drawn. You’ll fix this next.

成功!你可以建造怪物。然而,它们看起来像一个奇怪的糊状物,因为你的怪物的所有子精灵都被绘制出来了。接下来,您将解决此问题。

升级那些怪物

在下图中,你会看到你的怪物在更高层次上看起来越来越可怕。

It's so fluffy! But if you try to steal its cookie this monster can turn into a real killer.

太蓬松了!但是如果你试图偷它的饼干,这个怪物可能会变成杀手。

脚本充当为怪物实现升级系统的基础。它跟踪怪物在每个级别上应该有多强大,当然还有怪物的当前级别。

现在添加此脚本。

在_项目浏览器中_选择_预制件/怪物_。添加一个名为 MonsterData 的新 C# 脚本。在 IDE 中打开脚本,并在类_上方_添加以下代码。MonsterData

[System.Serializable]
public class MonsterLevel
{
  public int cost;
  public GameObject visualization;
}

这将创建 MonsterLevel.它对成本(以金币为单位,稍后会支持)和特定怪物关卡的视觉表示形式进行分组。

[System.Serializable]您可以在顶部添加以使类的实例可从检查器进行编辑。这使您可以快速更改 Level 类中的所有值,即使在游戏运行时也是如此。它对于平衡你的游戏非常有用。

定义怪物等级

在本例中,您将预定义的怪物等级存储在列表<T>中。
为什么不简单地使用MonsterLevel[]MonsterLevel那么,您将多次需要特定对象的索引。虽然为此编写代码并不困难,但您将使用IndexOf(),它实现了 的功能Lists。这次无需重复写一个方法。:]

MonsterData.cs 的顶部,添加以下语句:using

using System.Collections.Generic;

这使您可以访问通用数据结构,因此可以在脚本中使用该类。List<T>

注意:泛型是 C# 的重要组成部分。它们允许您定义类型安全的数据结构,而无需提交类型。这对于列表和集合等容器类非常实用。若要了解有关泛型的详细信息,请查看 C# 泛型简介

现在将以下变量添加到 MonsterData 中以存储 MonsterLevel 列表:

public List<MonsterLevel> levels;

使用泛型,您可以确保只能包含对象。levels``List``MonsterLevel

保存文件并切换到 Unity 以配置每个阶段。

在_项目浏览器中_选择_预制件/怪物_。在__检查器__中,您现在可以在 MonsterData(脚本)组件中看到“级别”字段。将其_大小_设置为 3

Screen Shot 2015-07-24 at 11.26.28 AM

接下来,将每个级别的_成本_设置为以下值:

  • 元素 0200
  • 元素 1110
  • 元素 2120

现在分配可视化字段值。

在项目浏览器中展开预_制件/怪物_,以便可以看到其子项。将子 Monster0 拖放到_元素 0_ 的_可视化_字段中。

重复此操作,将怪物 1 分配给_元素 1_,将_怪物_ 2 分配给_元素 2_。请参阅以下演示此过程的 GIF:

assign-monsters2

选择预制件_/怪物时,预制件_应如下所示:

Definition of the monsters’ levels in the inspector.

在检查器中定义怪物的等级。

定义当前等级

在 IDE 中_切换_回 MonsterData.cs并将另一个变量添加到 MonsterData

private MonsterLevel currentLevel;

在私有变量中,currentLevel您将存储……等等……怪物的当前等级。]

现在设置currentLevel并使其可供其他脚本访问。将以下内容添加到MonsterData, 以及实例变量声明:

public MonsterLevel CurrentLevel
{
  
  get 
  {
    return currentLevel;
  }
  
  set
  {
    currentLevel = value;
    int currentLevelIndex = levels.IndexOf(currentLevel);

    GameObject levelVisualization = levels[currentLevelIndex].visualization;
    for (int i = 0; i < levels.Count; i++)
    {
      if (levelVisualization != null) 
      {
        if (i == currentLevelIndex) 
        {
          levels[i].visualization.SetActive(true);
        }
        else
        {
          levels[i].visualization.SetActive(false);
        }
      }
    }
  }
}

那里有相当多的 C#脚本,嗯?把所有都看一遍:

  1. 定义私有_变量的属性currentLevel。_定义属性后,您可以像调用任何其他变量一样调用:asCurrentLevel(从类内部)或 as monster.CurrentLevel(从类外部)。您可以在属性的 getter 或 setter 方法中定义自定义行为,并且通过仅提供 getter、setter 或两者,您可以控制属性是只读、只写还是读/写。
  2. 在 getter 中,返回currentLevel的值。
  3. 在资源库中,将新值分配给currentLevel 。接下来,您将获得当前级别的索引。最后,循环访问所有_级别_,并将可视化设置为活动或非活动,具体取决于currentLevelIndex .这很棒,因为这意味着每当有人设置currentLevel 时,子画面都会自动更新。属性肯定会派上用场!

添加以下 实现OnEnable

void OnEnable()
{
  CurrentLevel = levels[0];
}

这CurrentLevel将在放置时设置,确保它只显示正确的精灵。

注意:在 OnEnable中初始化属性而不是OnStart 非常重要,因为您可以在实例化预制件时调用 order 方法。

创建预制件时,将立即调用 OnEnable(如果预制件以启用状态保存),但直到对象开始作为场景的一部分运行后才会调用 OnStart

在放置怪物之前,您需要检查此数据,因此您可以在 OnEnable 中对其进行初始化。

保存文件并切换到 Unity。运行项目并放置怪物;现在,它们显示正确和最低级别的精灵。

No more mushyness

升级那些怪物

切换回您的 IDE,并将以下方法添加到 MonsterData

public MonsterLevel GetNextLevel()
{
  int currentLevelIndex = levels.IndexOf (currentLevel);
  int maxLevelIndex = levels.Count - 1;
  if (currentLevelIndex < maxLevelIndex)
  {
    return levels[currentLevelIndex+1];
  } 
  else
  {
    return null;
  }
}

GetNextLevel中,您可以获得当前级别的索引和最高级别的索引,前提是怪物没有达到最大级别以返回下一个级别。否则,返回 null

您可以使用此方法来确定是否可以升级怪物。

添加以下方法以提高怪物的等级:

public void IncreaseLevel()
{
  int currentLevelIndex = levels.IndexOf(currentLevel);
  if (currentLevelIndex < levels.Count - 1)
  {
    CurrentLevel = levels[currentLevelIndex + 1];
  }
}

在这里,您可以获得当前级别的索引,然后通过检查它是否小于 来确保它不是最大级别。如果是这样,请设置为下一个级别。levels.Count - 1``CurrentLevel

测试升级能力

保存该文件,然后在 IDE 中切换到 PlaceMonster.cs 并添加以下新方法:

private bool CanUpgradeMonster()
{
  if (monster != null)
  {
    MonsterData monsterData = monster.GetComponent<MonsterData>();
    MonsterLevel nextLevel = monsterData.GetNextLevel();
    if (nextLevel != null)
    {
      return true;
    }
  }
  return false;
}

首先检查是否有可以升级的怪物,方法是检查怪物变量是否为 null。如果是这种情况,您可以从怪物数据中获取怪物的当前等级。

然后测试是否有更高级别可用,即 GetNextLevel() 不返回 null 时。如果可以升级,则返回 true,否则返回 false

启用金牌升级

要启用升级选项,请将 else if 分支添加到 OnMouseUp

if (CanPlaceMonster())
{
  
}
else if (CanUpgradeMonster())
{
  monster.GetComponent<MonsterData>().IncreaseLevel();
  AudioSource audioSource = gameObject.GetComponent<AudioSource>();
  audioSource.PlayOneShot(audioSource.clip);
  
}

检查是否可以使用 CanUpgradeMonster() 进行升级。如果是,您可以使用 GetComponent() 访问 MonsterData 组件并调用 IncreaseLevel(),这将增加怪物的级别。最后,你触发怪物的_音频源_。

保存文件并切换回 Unity。运行游戏,放置和升级_任意数量的_怪物…目前。

Upgrade all the monsters

所有怪物升级

支付金币 - 游戏管理器

现在,可以立即建造和升级所有怪物,但这其中的挑战在哪里?

让我们深入了解黄金问题。跟踪它的问题在于您需要在不同的游戏对象之间共享信息。

下图显示了所有要参与其中的对象。

The highlighted game objects all need to know, how much gold the player owns.

突出显示的游戏对象都需要知道,玩家拥有多少金币。

您将使用其他对象可访问的共享对象来存储此数据。

在_层次结构_中单击鼠标右键,然后选择“创建空”。将新游戏对象命名_为游戏管理器_。

将一个名为 GameManagerBehavior 的 C# 脚本添加到 GameManager,然后在 IDE 中打开新脚本。您将在标签中显示玩家的总金币,因此请在文件顶部添加以下行:

using UnityEngine.UI;

这使您可以访问特定于 UI 的类,例如 ,项目将其用于标签。现在将以下变量添加到类中:Text

public Text goldLabel;

Text这将存储对用于显示玩家拥有多少金币的组件的引用。

既然GameManager知道了标签,您如何确保变量中存储的黄金数量与标签上显示的数量同步?您将创建一个属性。

将以下代码添加到GameManagerBehavior

private int gold;
public int Gold {
  get
  { 
    return gold;
  }
  set
  {
    gold = value;
    goldLabel.GetComponent<Text>().text = "GOLD: " + gold;
  }
}

似乎很熟悉?它类似于您在CurrentLevel 中定义的Monster。首先,创建一个私有变量gold ,来存储当前的黄金总量。然后你定义一个名为Gold – creative的属性,对吧?-- – 并实现一个 getter 和 setter。

getter 只需返回 gold的值。setter更有趣。除了设置变量的值外,它还将字段text设置为“打开”以goldLabel显示新的黄金数量。

你觉得有多慷慨?添加以下行以Start()给玩家 1000 金币,如果您感到吝啬,则分配更少:

Gold = 1000;

将标签对象分配给脚本

保存文件并切换到 Unity。

Hierarchy中,选择GameManager。在Inspector 中,单击Gold Label右侧的圆圈。在Select Text对话框中,选择Scene选项卡并选择GoldLabel

Assign goldLabel

运行场景,标签显示_Gold:1000_。

1000 gold

检查玩家的“钱包”

在 IDE 中_.cs打开 PlaceMonster_,然后添加以下实例变量:

private GameManagerBehavior gameManager;

您将用于gameManager访问GameManagerBehavior场景的GameManager的组件。要分配它,请将以下内容添加到Start()

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

您可以使用 获取名为 GameManager 的游戏对象,该游戏对象返回它找到的第一个具有给定名称的游戏对象。然后,检索其组件并将其存储以供以后使用。GameObject.Find()``GameManagerBehavior

注意:您可以通过在 Unity 编辑器中设置字段或向 GameManager 添加静态方法来实现此目的,该方法返回一个单一实例,您可以从中获取游戏管理器行为

但是,上面的块中有一个黑马方法:,它在运行时速度较慢,但方便且可以谨慎使用。Find

拿钱!

你还没有扣除金币,所以在 OnMouseUp() 中添加_了两次_这一行,替换每个注释 // TODO: 扣除金币:

gameManager.Gold -= monster.GetComponent<MonsterData>().CurrentLevel.cost;

保存文件并切换到 Unity,升级一些怪物并观看黄金读数更新。现在你扣除金币,但玩家只要有空间就可以建造怪物。

Infinite credit

无限能力?棒!但你不能允许这种情况发生。只有当玩家有足够的金币时,才应该放置怪物。

怪物需要金币

在 IDE 中切换到 PlaceMonster.cs,并将 CanPlaceMonster()的内容替换为以下内容:

int cost = monsterPrefab.GetComponent<MonsterData>().levels[0].cost;
return monster == null && gameManager.Gold >= cost;

levels从其 中检索放置怪物的成本MonsterData。然后你检查它monster不是null并且它gameManager.Gold大于成本。

CanUpgradeMonster()挑战:添加自己检查玩家是否有足够的金币。

替换此行:

return true; 

有了这个:

return gameManager.Gold >= nextLevel.cost;

这将检查玩家拥有的_金币_是否超过升级成本。

在 Unity 中保存并运行场景。来吧,试着放置无限的怪物!

Limited gold.

现在你只能建造有限数量的怪物。

塔政治:敌人、波浪和航点

是时候为你的敌人“铺路”了。敌人出现在第一个航点,向下一个航点移动并重复,直到他们到达你的饼干。

你会让敌人行进:

  1. 为敌人确定一条道路
  2. 沿路移动敌人
  3. 旋转敌人使其向前看

创建带有航点的道路

在_层次结构_中单击鼠标右键,然后选择创建空以创建新的_空_游戏对象。将其命名为 Road,并确保它位于位置 (0, 0, 0)。

现在,右键单击_层次结构_中的 Road,并创建另一个空游戏对象作为 Road 的子对象。将其命名为_Waypoint0_并将其位置设置为_(-12,2,0)_ - 这是敌人开始攻击的地方。

Road - waypoint hierarchy

使用以下名称和位置以相同的方式再创建五个航点:

  • 航点 1: (X:7, Y:2, Z:0)
  • 航点 2: (X:7, Y:-1, Z:0)
  • 航点3: (X:-7.3, Y:-1, Z:0)
  • 航点4: (X:-7.3, Y:-4.5, Z:0)
  • 航点5: (X:7, Y:-4.5, Z:0)

以下屏幕截图突出显示了航点位置和生成的路径。

Screen Shot 2015-07-24 at 12.09.11 PM

生成敌人

现在要制造一些敌人来跟路。_预制件_文件夹包含_敌人_预制件。它的位置是 _(-20, 0, 0),_因此新实例将在屏幕外生成。

否则,它的设置很像Monster预制件,有一个AudioSource和一个 child Sprite,而且它是一个精灵,所以你可以稍后旋转它,而无需旋转即将需要的血量条。

### 把怪物移到路上

将一个名为 MoveEnemy 的新 C# 脚本添加到预制件_\Enemy 预制件_。在 IDE 中打开脚本,然后添加以下变量:

[HideInInspector]
public GameObject[] waypoints;
private int currentWaypoint = 0;
private float lastWaypointSwitchTime;
public float speed = 1.0f;

waypoints将航点的副本存储在一个数组中,而上述内容可确保您不会意外更改__检查器__中的字段,但您仍然可以从其他脚本访问它。[HideIn_inspector_]``waypoints

currentWaypoint跟踪敌人当前正在离开的路点,lastWaypointSwitchTime存储敌人经过它的时间。最后,存储敌人的speed

Start() 中添加此行:

lastWaypointSwitchTime = Time.time;

这将初始化lastWaypointSwitchTime为当前时间。

要使敌人沿着路径移动,请将以下代码添加到 Update() 中:

Vector3 startPosition = waypoints [currentWaypoint].transform.position;
Vector3 endPosition = waypoints [currentWaypoint + 1].transform.position;

float pathLength = Vector3.Distance (startPosition, endPosition);
float totalTimeForPath = pathLength / speed;
float currentTimeOnPath = Time.time - lastWaypointSwitchTime;
gameObject.transform.position = Vector2.Lerp (startPosition, endPosition, currentTimeOnPath / totalTimeForPath);

if (gameObject.transform.position.Equals(endPosition)) 
{
  if (currentWaypoint < waypoints.Length - 2)
  {
    
    currentWaypoint++;
    lastWaypointSwitchTime = Time.time;
    
  }
  else
  {
    
    Destroy(gameObject);

    AudioSource audioSource = gameObject.GetComponent<AudioSource>();
    AudioSource.PlayClipAtPoint(audioSource.clip, transform.position);
    
  }
}

循序渐进:

  1. 从路点数组中,您可以检索当前路径段的开始和结束位置。
  2. 用公式时间=距离/速度计算全程所需时间,然后确定路径上的当前时间。使用Vector2.Lerp,您可以在段的开始位置和结束位置之间插入敌人的当前位置。
  3. 检查敌人是否已经到达endPosition。如果是,请处理这两种可能的情况:
    1. 敌人还没有在最后一个航路点,所以增加currentWaypoint和更新lastWaypointSwitchTime。稍后,您将添加代码来旋转敌人,使其也指向它移动的方向。
    2. 敌人到达了最后一个路点,所以这会摧毁它并触发声音效果。稍后您还将添加代码来减少玩家的health

保存文件并切换到 Unity。

给敌人一个方向感

在目前的状态下,敌人不知道路标的顺序。

Hierarchy中选择Road ,并添加一个名为 SpawnEnemy的新C# 脚本。然后在您的 IDE 中打开它,并添加以下变量:

public GameObject[] waypoints;

您将使用它waypoints以正确的顺序存储场景中的路点。

保存文件并切换到 Unity。在_层次结构_中选择_道路_,并将_航点_数组_的大小_设置为 6

将 Road 的每个子项拖到字段中,将 Waypoint0 放入元素 0,将 Waypoint1 放入_元素 1_,依此类推。

waypoints2

现在你有一个数组,其中包含整齐有序的航点,所以有一条路径——请注意,它们永远不会撤退;他们会死在试图得到糖的路上。

certaindeath

检查一切是否正常

前往 IDE 中的 SpawnEnemy,并添加以下变量:

public GameObject testEnemyPrefab;

这会在 testEnemyPrefab 中保留对 Enemy 预制件的引用。

要在脚本启动时创建敌人,请将以下代码添加到 Start():

Instantiate(testEnemyPrefab).GetComponent<MoveEnemy>().waypoints = waypoints;

这将实例化存储在 testEnemy中的预制件的新副本,并为其分配要遵循的航点。

保存文件并切换到 Unity。在Hierarchy中选择Road并将其Test Enemy设置为Enemy预制件。

运行项目,看到敌人沿着这条路走。

运行项目以查看敌人沿着道路行驶。

BugFollowsRoadWithoutRotating

你有没有注意到他们并不总是在寻找他们要去的地方?有趣!如果你想在这个项目上更进一步,继续第二部分并完善这个项目,学习如何令他们表现得更出色。

后续

你已经做了很多工作,并且正在拥有自己的塔防游戏。

玩家可以建造怪物,但不是无限数量,并且有一个敌人跑向你的饼干。玩家拥有金币,还可以升级怪物。

在第二部分中,你将介绍生成大量敌人并将它们炸飞。第二部分见

博主属于自学型选手,如果你也是Unity初学者,欢迎加入我的群聊进行互助交流:618012892

猜你喜欢

转载自blog.csdn.net/weixin_72715182/article/details/130629887