Unity3D制作塔防类游戏

时间:2022-11-21 07:54:25

 

演示

功能简介 

制作细节详解


演示

Unity3D制作塔防类游戏

资源包:链接:https://pan.baidu.com/s/15MMtYeKkNk5xChvCx0EckQ?pwd=d1ub 提取码:d1ub 

对应视频教学:01-开始介绍和创建工程_哔哩哔哩_bilibili  

功能简介 

 分为蓝,紫,粉,红四批敌人,每一批的敌人都比前一批的数量要多,并且速度要快,血量要多,当一批敌人死光了,才会出来第二批敌人,一共有三种炮塔每个金额为70,80,90,初始金额为1000,选择炮塔类型,点击Cube,即可以插放,再次点击时候可以选择升级或拆除,由于地图过大,可以一共上下左右键来控制地图前后左右视角,用鼠标滑轮来控制上下视角,把四批敌人杀光才可以通关成功,否则失败。

制作细节详解

Cube创建基本的地图

创建一个空物体记作"MapCube",把与地图map相关的都放进去,创建一个cube,进行ctrl+d复制,要按住ctrl进行拖拽(一米一米的移动否则将随意移动)。

Unity3D制作塔防类游戏

创建敌人行走的路

定位两个位置,起始点/终点,然后随机连起来选择一些个MapCube删除掉,然后在空的路上边还是用Cube(Road纯黑材质)连出这条路来,该长的长,该短的短。

Unity3D制作塔防类游戏

 把上面的敌人走的路径放到一个新建的"RoadCube"

Unity3D制作塔防类游戏

  然后做两个Cube,命名为Start 和 End,调整好大小,材质,放置在起始点和终点正上方,(注意把这两个的Collider取消掉,就不会和下面创建的敌人做碰撞了)

Unity3D制作塔防类游戏

控制游戏的视野(视野移动和放大缩小)

由于地图还是蛮大的,在这里添加地图上下前后移动的功能,给玩家提供便利,具体情况如下所示:

Unity3D制作塔防类游戏

 这里创建一个"Scripts"文档,把脚本都放进去,首先创建一个"ViewController"脚本来控制视野。

Unity3D制作塔防类游戏

代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ViewController : MonoBehaviour {

    public float speed = 1;
    public float mouseSpeed = 60;
	
	// Update is called once per frame
	void Update () {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float mouse = Input.GetAxis("Mouse ScrollWheel");
        transform.Translate(new Vector3(h*speed, mouse*mouseSpeed, v*speed) *Time.deltaTime ,Space.World);
	}
}

敌人的路径管理

让敌人按照我们设计的路线行走,这里我们直接在拐弯的地方添加一些关键点就可以按照这些点一个个向下移动,把这些路径点都放入到"Waypoints中"

Unity3D制作塔防类游戏

 然后在这里添加一个脚本去管理这些路径点,

Unity3D制作塔防类游戏

代码如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Waypoints : MonoBehaviour {


    public static Transform[] positions;
    //脚本被载入时调用(最早的执行函数)
    void Awake()
    {
//注意这里如果用transform.GetComponent这种方法,会把自身的组件也带上,所以要用下面的方式0
        positions = new Transform[transform.childCount];//先从孩子点位里获得数组大小
        for (int i = 0; i < positions.Length; i++)
        {
            positions[i] = transform.GetChild(i);
        }
    }
}

创建敌人,控制敌人的移动

这里简单的就拿做不同颜色的小球,当作不同的敌人,然后创建一个预制体"Prefab"文件夹,把不同的敌人放进文件夹中,如下所示:

Unity3D制作塔防类游戏

 为了让敌人之间区分,给小球涂上不同的颜色,给每种敌人创建一个材质:

Unity3D制作塔防类游戏

 控制每个敌人的移动,这里创建一个"Enemy"脚本,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Enemy : MonoBehaviour {

    public float speed = 10;
    public float hp = 150;
    private float totalHp;
    public GameObject explosionEffect;
    private Slider hpSlider;
    private Transform[] positions;
    private int index = 0;


	// Use this for initialization
	void Start () {
        positions = Waypoints.positions;
        totalHp = hp;
        hpSlider = GetComponentInChildren<Slider>();
	}
	
	// Update is called once per frame
	void Update () {
        Move();
	}


    void Move()
    {
        if (index > positions.Length - 1) return;
        transform.Translate((positions[index].position - transform.position).normalized * Time.deltaTime * speed);
        if (Vector3.Distance(positions[index].position, transform.position) < 0.2f)
        {
            index++;
        }
        if (index > positions.Length - 1)
        {
            ReachDestination();
        }
    }
    //达到终点
    void ReachDestination()
    {
        GameManager.Instance.Failed();
        GameObject.Destroy(this.gameObject);
    }


    void OnDestroy()
    {
        EnemySpawner.CountEnemyAlive--;
    }

    public void TakeDamage(float damage)
    {
        if (hp <= 0) return;
        hp -= damage;
        hpSlider.value = (float)hp / totalHp;
        if (hp <= 0)
        {
            Die();
        }
    }
    void Die()
    {
        GameObject effect = GameObject.Instantiate(explosionEffect, transform.position, transform.rotation);
        Destroy(effect, 1.5f);
        Destroy(this.gameObject);
    }

}

创建敌人孵化器管理敌人的生成

创建四种敌人,每种敌人用不同的颜色表示,每种颜色的敌人血量和移动速度是不一样的,这就需要我们创建单独的脚本来保存每一波敌人的属性,脚本记为"Wave"。

代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//保存每一波敌人生成所需要的属性
[System.Serializable]
public class Wave  {
    public GameObject enemyPrefab;
    public int count;
    public float rate;
}

 接着我们创建一个生成器,管理敌人一波一波的生成,这里创建一个空物体,设置为“GameManager",创建一个"Enemy Spawner"脚本拖入"GameManager"中,

Unity3D制作塔防类游戏

可以设置每种敌人的数量和速度。

代码如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemySpawner : MonoBehaviour {

    public static int CountEnemyAlive = 0;
    public Wave[] waves;
    public Transform START;
    public float waveRate = 0.2f;
    private Coroutine coroutine;

    void Start()
    {
        coroutine = StartCoroutine(SpawnEnemy());
    }
    public void Stop()
    {
        StopCoroutine(coroutine);
    }
    IEnumerator SpawnEnemy()
    {
        foreach (Wave wave in waves)
        {
            for (int i = 0; i < wave.count; i++)
            {
                GameObject.Instantiate(wave.enemyPrefab, START.position, Quaternion.identity);
                CountEnemyAlive++;
                if(i!=wave.count-1)
                    yield return new WaitForSeconds(wave.rate);
            }
            while (CountEnemyAlive > 0)
            {
                yield return 0;
            }
            yield return new WaitForSeconds(waveRate);
        }
        while (CountEnemyAlive > 0)
        {
            yield return 0;
        }
        GameManager.Instance.Win();
    }

}

创建三种炮台Prffab

首先在Prefab文件中创建三种炮台和三种炮台升级后的炮台:将第一个激光炮塔放置到0点上的Cube位置进行调整,j将资源里的材质啥的往合适的地方塞,弄好看了然后命名并设置成LaserTurret预制体,将升级后的命名为LaserTurretUpgraded,同理弄完MissileTurret、MissileTurretUpgraded、StandardTurret、StandardTurretUpgraded。

Unity3D制作塔防类游戏

创建炮塔选择的UI

要在场景中创建炮台,首先要有UI界面,然后才能对炮台进行选择,创建一个新文档"Canvas"然后鼠标右击UI选项,选择"Toggle"开关按钮,名为LaserToggle,将Is On的去掉勾选。来代表三种炮塔的选择。它下面用Label表示介绍,用Text表示价格。ackground里的Image-Source Image设置成资源里的LaserBeamerIcon,可点击Image-Set Native Size将其图片设置为原生大小后再进行调整。将Checkmark大小和Background改成一致(用那个Alt键充满的方式),将里面的Image-Source Image改成一种被遮罩的图片(这里用的一个圆的Knob),修改颜色中的α值透明度,代表着选中后的效果。复制出两个LaserToggle,更换一下背景图片做出MissileToggle和StandardToggle。在Canvas下创建一个空物体,命名为TurretSwitch,在它上创建一ToggleGroup组件来包含这个三开关,位置摆放在Canvas居右,设置好三个炮台开关的分组,都选中后,在Toggle下的Group将TurretSwitch拉过来,这样就可以单选了。

Unity3D制作塔防类游戏

Unity3D制作塔防类游戏

 创建炮台的数据类

 创建炮台数据类,用来保存炮台相关数据,创建脚本TurretData:

System.Serializable]
public class TurretData
{
    public GameObject turretPrefab;//炮塔的模型
    public int cost;//价格
    public GameObject turretUpgradedPrefab;//升级的模型
    public int costUpgraded;//升级的价格
    public TurretType type;
}

public enum TurretType
{
    LaserTurret,//激光炮台
    MissileTurret,//导弹炮台
    StandardTurret,//标准炮台
}

监听炮塔选择的事件

在GameManager中再创建一个BuildManager脚本:(测试的时候可以将selectedTurredData设成public,方便在Inspector面板选择UI炮台时能看出来是否有数据)

检测鼠标点击到了哪个Cube上

在MapCube预制体上添加一个Layer图层,叫MapCube,然后将其图层选择为MapCube。

控制开始按钮1和退出时按钮的点击事件处理

金钱的管理

下边代码有设定钱了,把它显示在UI界面上,Canvas下新建个Text命名为Money,设置一下字体和居右,在BuildManager代码新加入一个方法ChangeMoney和字段moneyText,在Inspector将Money拖入至该字段。

//放置原理:点击的时候在鼠标的位置发射出来一条射线,看一下射线和哪个Node发生了碰撞,发生碰撞后要去检查下这个Node上是否为空,再做处理
//在MapCube添加一个Layer(图层),叫做MapCube,然后选择为它,这样在利用射线做检测时只检测对MapCube的碰撞。
public class BuildManager : MonoBehaviour
{
    public TurretData laserTurretData;//在Inspector面板将预制体拉入,并填写其它相关数据
    public TurretData missileTurretData;//在Inspector面板将预制体拉入,并填写其它相关数据
    public TurretData standardTurretData;//在Inspector面板将预制体拉入,并填写其它相关数据
    //表示当前选择的炮台(要建造的炮台)
    private TurretData selectedTurredData;//UI上显示和选择的炮台,写三个炮台的选择方法,通过注册三个炮台的Toggle事件来识别哪个被选择了
    private int money = 1000;
    public Text moneyText;
    public Animator moneyAnimator;

    void ChangeMoney(int change=0)
    {
        money += change;
        moneyText.text = "$ " + money;
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            //如果鼠标在UI上面,则不做处理; EventSystem.current返回的是Hierarchy里EventSystem里EventSystem(Script)组件。
            //IsPointerOverGameObject表示鼠标是否按在了UI上
            if (EventSystem.current.IsPointerOverGameObject() == false)
            {
                //开发炮台的建造,首先判断鼠标点击到了哪个MapCube上,就要使用射线检测了,得到一个射线ray
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//把鼠标的点转化成射线
                RaycastHit hit;
                //Physics.Raycast来进行射线检测,(射线,RaycastHit射线检测跟什么东西做了碰撞的结果,maxDistance最大距离,layerMask和哪一层做射线检测如不指定就是和所有的层)
                bool isCollider = Physics.Raycast(ray,out hit, 1000, LayerMask.GetMask("MapCube"));//得到是否碰撞到MapCube上
                if (isCollider)
                {
                    MapCube mapCube = hit.collider.GetComponent<MapCube>();//得到点击的mapCube
                    if(mapCube.turretGo == null && selectedTurredData != null)//可以创建
                    {
                        if (money > selectedTurredData.cost)
                        {
                            money -= selectedTurretData.cost;
                            mapCube.BuildTurret(selectedTurredData);
                        }
                        else//提示钱不够
                        {
                            moneyAnimator.SetTrigger("Flicker");
                        }
                    }
                    else
                    {
                        //TODO 升级处理
                    }
                }
            }
        }
    }


    //在Canvas里的设备里有On Value Changed里添加GameManager,然后选择对应的下面方法,只要是点击设备值发生改变了,也就是is on发生改变了,都会触发
    public void OnLaserSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurredData = laserTurretData;
        }
    }
    public void OnMissileSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurredData = missileTurretData;
        }
    }
    public void OnStandardSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurredData = standardTurretData;
        }
    }
}

控制子弹跟敌人的碰撞处理,让子弹碰到敌人就爆炸

在Bullet预制体上添加一个Rigidbody刚体,取消勾选Use Gravity,在Bullet中处理触发检测OnTriggerEnter方法,定义好爆炸的特效字段explosionEffectPrefab,在Enemy中添加TakeDamage方法表示受到了伤害,Enemy脚本全部代码如下:

public class Enemy : MonoBehaviour
{
    public float speed = 10;//每秒移动10米
    public float hp = 150;
    private float totalHp;
    private Transform[] positions;
    private int index = 0;//默认的位置
    public GameObject explosionEffect;//爆炸特效
    private Slider hpSlider;//血条

    void Start()
    {
        //获取到小球行走的路径点
        positions = Waypoints.positions;
        totalHp = hp;
        hpSlider = GetComponentInChildren<Slider>();//从子物体中寻找Slider物体装配上
    }
    void Update()
    {
        Move();
    }

    void Move()
    {
        //if (index > positions.Length - 1) return;//当到达最后一个位置
        //(目标位置 - 当前位置)得到一个向量.单位化每次移动1,取得单位向量之后再做计算
        transform.Translate((positions[index].position - transform.position).normalized * Time.deltaTime * speed);
        //判断有没有到达目标位置,取得两个点位置是否小于一定距离
        if( Vector3.Distance( positions[index].position,transform.position) < 0.2f)
        {
            index++;
        }
        if (index > positions.Length - 1)//当到达最后一个位置
        {
            ReachDestination();
        }
    }
    //到达目的地,游戏就失败了
    void ReachDestination()
    {
        GameManager.Instance.Faild();
        GameObject.Destroy(this.gameObject);
    }
    //被打掉销毁
    private void OnDestroy()
    {
        EnemySpawner.CountEnemyAlive--;
    }
    //表示受到了伤害
    public void TakeDamage(float damage)
    {
        if (hp <= 0) return;
        hp -= damage;
        hpSlider.value = (float)hp / totalHp;//hpSlider.value是一个0~1的值,所以它以百分比来计算得到
        if(hp <= 0)
        {
            Die();
        }
    }
    void Die()
    {
        GameObject effect = GameObject.Instantiate(explosionEffect, transform.position, transform.rotation);
        Destroy(effect, 1.5f);
        Destroy(this.gameObject);
    }
}

添加爆炸特效

修改检测碰撞方式,创建一个Particle System命名为ExplosionEffect特效,然后将其拉入到Bullet的Bullet字段中,可以将Bullet下Rigidbody下的Collision Detection(碰撞检测)改成Continuous(连续的)或者Continuous Dynamic(动态的)这样对高速移动的物体检测更加准确,把Bullet图层设为Turret。

敌人添加血条显示

创建一个Canvas,把Canvas里的Render Mode修改为World Space,就可以调节画布的大小了,下面创建一个Slider,它不需要交互,所以把Slider(Script)下面Intractable(可交互的)取消勾选,Canvas调整成和Slider差不多大,将Slider下的Handle Slide Area(手柄滑动区)移除,Background取消勾选,Slider是依靠Slider(Script)下面Value来控制条长的,调整Fill Area长度与默认充满,可以把Fill(前置背景)的Image中Color改为绿色,然后把Canvas整体移到各个Enemy预制体下面,调整Canvas的位置和大小。

创建炮塔升级的UI按钮

创建一个Canvas命名为UpgradeCanvas,设置成World Space,下面创建两个Button,命名为ButtonUpgrade和ButtonDestroy,里面Image(Script)去掉,下面Text是升级和拆除,弄好后可拖到在炮塔下面进行编辑,方便定位位置和调整大小,完成后再挪出来。

控制升级面板显示

在BuildManager里进行控制,添加两个引用,public GameObject upgradeCanvas 和 public Button buttonUpgrade,在Inspector面板注册上,脚本里继续添加ShowUpgradeUI 和 HideUpgradeUI 显示/隐藏按钮方法,添加 OnUpgradeButtonDown 和 OnDestroyButtonDown 升级/拆按钮按下方法,然后在Inspector面板把后两个方法注册给两个按钮。

BuildManager脚本全部代码如下:

/放置原理:点击的时候在鼠标的位置发射出来一条射线,看一下射线和哪个Node发生了碰撞,发生碰撞后要去检查下这个Node上是否为空,再做处理
//在MapCube添加一个Layer(图层),叫做MapCube,然后选择为它,这样在利用射线做检测时只检测对MapCube的碰撞。

public class BuildManager : MonoBehaviour
{
    public TurretData laserTurretData;
    public TurretData missileTurretData;
    public TurretData standardTurretData;
    //表示当前选择的炮台(要建造的炮台)
    private TurretData selectedTurredData;//UI上显示和选择的炮台
    public Text moneyText;
    public Animator moneyAnimator;
    private int money = 1000;
    public GameObject upgradeCanvas;//升级UI画板
    public Button buttonUpgrade;
    private MapCube selectedMapCube;//3D场景中选择的炮台
    private Animator upgradeCanvasAnimator;//升级UI显示隐藏的动画转换状态机

    void ChangeMoney(int change=0)
    {
        money += change;
        moneyText.text = "$ " + money;
    }
    private void Start()
    {
        upgradeCanvasAnimator = upgradeCanvas.GetComponent<Animator>();//得到状态机
    }
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            //如果鼠标在UI上面,则不做处理; EventSystem.current得到的是EventSystem模块里EventSystem那个组件。
            if (EventSystem.current.IsPointerOverGameObject() == false)//IsPointerOverGameObject表示鼠标是否按在了UI上
            {
                //开发炮台的建造,首先判断鼠标点击到了哪个MapCube上,就要使用射线检测了,得到一个射线ray
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//把鼠标的点转化成射线
                RaycastHit hit;
                //Physics.Raycast来进行射线检测,(射线,RaycastHit射线检测跟什么东西做了碰撞的结果,maxDistance最大距离,layerMask和哪一层做射线检测如不指定就是和所有的层)
                bool isCollider = Physics.Raycast(ray,out hit, 1000, LayerMask.GetMask("MapCube"));//得到是否碰撞到MapCube上
                if (isCollider)
                {
                    MapCube mapCube = hit.collider.GetComponent<MapCube>();//得到点击的mapCube
                    if(mapCube.turretGo == null && selectedTurredData != null)//可以创建
                    {
                        if (money > selectedTurredData.cost)
                        {
                            ChangeMoney(-selectedTurredData.cost);
                            mapCube.BuildTurret(selectedTurredData);
                        }
                        else//提示钱不够
                        {
                            //TODO
                            moneyAnimator.SetTrigger("Flicker");
                        }
                    }
                    else if(mapCube.turretGo != null)//如果上边有炮台,那么判断是否做升级处理
                    {
                        if(mapCube.turretGo == selectedMapCube && upgradeCanvas.activeInHierarchy)//如果第二次点击此炮台了并且UI的激活属性是true
                        {
                            StartCoroutine("HideUpgradeUI");//将UI隐藏,用协程的方式
                        }
                        else
                        {
                            //否则显示升级/拆除UI面板,第二个参数的bool值与是否有炮台判断相符,所以不再if判断直接传即可
                            ShowUpgradeUI(mapCube.transform.position, mapCube.isUpgraded);
                        }
                        selectedMapCube = mapCube;//把点击的炮台赋给点击的炮台
                    }
                }
            }
        }
    }
    //在Canvas里的设备里有On Value Changed里添加GameManager,然后选择对应的下面方法,只要是点击设备值发生改变了,就会触发
    public void OnLaserSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurredData = laserTurretData;
        }
    }
    public void OnMissileSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurredData = missileTurretData;
        }
    }
    public void OnStandardSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurredData = standardTurretData;
        }
    }
    void ShowUpgradeUI(Vector3 pos, bool isDisableUpgrade=false)
    {
        StopCoroutine("HideUpgradeUI");//搜索下面的HideUpgradeUI协程方法有没有在运行,有的话先给暂停掉,没有也不会影响。
        //设置画布禁用,为的是切换到新的炮台时候,状态机会初始化一下,能有一个激活弹出UI的效果,这里调用状态机里show的时候,可能HideUpgradeUI还在播放,
        //为了防止冲突故在上面加上一个暂停的协程方法。
        upgradeCanvas.SetActive(false);
        upgradeCanvas.SetActive(true);//设置画布显示
        pos.y = pos.y + 4;
        upgradeCanvas.transform.position = pos;//设置画布位置
        buttonUpgrade.interactable = !isDisableUpgrade;//开启或者禁用升级按钮
    }
    IEnumerator HideUpgradeUI()
    {
        upgradeCanvasAnimator.SetTrigger("Hide");
        yield return new WaitForSeconds(0.8f);//消失的效果结束后再去调用下面
        upgradeCanvas.SetActive(false);//隐藏的时候不能直接把画布禁用,不然就无法播放禁用的动画了
    }
    public void OnUpgradeButtonDown()//按下升级触发的方法
    {
        if(money >= selectedMapCube.turretData.costUpgraded)//如果大于升级所需要的钱
        {
            ChangeMoney(-selectedMapCube.turretData.costUpgraded);
            selectedMapCube.UpgradeTurret();
        }
        else
        {
            moneyAnimator.SetTrigger("Flicker");
        }
        StartCoroutine("HideUpgradeUI");//把UI隐藏掉
    }
    public void OnDestroyButtonDown()//按下拆除触发的方法
    {
        selectedMapCube.DestroyTurret();
        StartCoroutine("HideUpgradeUI");
    }
}

给升级面板添加的动画。
让升级和拆按钮”弹”出来,”缩”回去,创建一个Upgrade文件夹,右键UpgradeCanvas创建动画,创建面积从小到大的show动画,和从大到小的hide动画,存在Upgrade里,做完后会自动有一个UpgradeCanvas状态机,设置从Entry指向show,show—>hide,注意取消各自的Loop Time(从Project里点击动画能看到此菜单),show—>hide的连接线取消勾选Has Exit Time ,左上角添加一个Trigger命名为Hide来取触发这个连接。
在BuildManager里需要用状态机来控制,创建字段 private Animator upgradeCanvasAnimator ,从Start方法中由之前定义好的upgradeCanvas物体直接获得此控制器,然后在隐藏方法HideUpgradeUI里用upgradeCanvasAnimator.SetTrigger(“Hide”) 来设置播放隐藏动画,隐藏之后再禁用这个upgradeGcanvas,因为播放需要一下等待时间,所以需将HideUpgradeUI改成协程方法。
为了每一次点击不同炮台都会有show效果,在ShowUpgradeUI方法里先暂停一下HideUpgradeUI协程,再禁用一下upgradeCanvas再开启。
(暂停协程方法使用时是去搜索该方法名,有就暂停,没有也不会受影响)

设计游戏结束时候的UI界面

在主Canvas下创建一个空物体命名为End,让其与Canvas画布大小保持一致(用Alt填充方法),在它下面创建一个Image命名为Bg,修改颜色和透明度,创建一个Text命名为Message居中,创建两个Button命名为ButtonRetry、ButtonMenu,修改这俩下面的Text内容为重玩和菜单,选择End创建动画show,做一个背景慢慢显示,Text和俩Button从外进来的效果,

控制失败界面的显示

在GameManager下创建GameManager脚本,添加Win胜利和Failed失败方法,创建字段public GameObject endUI 和 public Text endMessage ,把End和它下面的Message拖入,创建字段public static GameManager Instance ,做成单例模式方便外界调用,在Enemy脚本就可以调用到GameManager脚本方法了,在Enemy脚本ReachDestination到达终点方法中调用GameManager的Faild方法。
因为EnemySpawner和GameManager在同一物体GameManager上的,所以在GameManager中控制敌人的生成,
GameManager脚本全部代码如下:
public class GameManager : MonoBehaviour
{
    public GameObject endUI;//结束的UI画面
    public Text endMessage;
    public static GameManager Instance;
    private EnemySpawner enemySpawner;

    private void Awake()
    {
        Instance = this;
        enemySpawner = GetComponent<EnemySpawner>();
    }
    public void Win()
    {
        endUI.SetActive(true);//设置为true后,它下面的Animator动画已经勾上了,会自动播放
        endMessage.text = "胜 利";
    }
    public void Faild()
    {
        enemySpawner.Stop();//停止生成敌人。
        endUI.SetActive(true);
        endMessage.text = "失 败";
    }
    public void OnButtonRetry()
    {
        endUI.SetActive(false);
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);//重新加载当前场景
    }
    public void OnButtonMenu()
    {
        SceneManager.LoadScene("MainMenu");
    }
}

添加胜利界面和重玩,菜单按钮的点击

游戏胜利的条件是所有的敌人都生成了并且都死亡了,在EnemySpawner脚本中的SpawnEnemy协程方法中写一个对敌人数量的while循环,如果还有,就截止到此返回,如果没了就往下执行GameManager.Instance.Win() 方法。

GameManager脚本中,添加OnButtonRetry、OnButtonMenu 重玩和菜单方法,并注册到End对应的两个Button下。
可以给两个Button添加一下之前的放大的动画,在其组件下添加Animator,然后Controller处选择ButtonUpgrade动画,注意Button(Script)处的Transition选择Animation。

开发菜单场景

Project里新建一个场景命名为MainMenu,做一个Plane当做地面,Scale面积设置大一些能遮住视野,把StandardTurret拿过来,把里面有用的零件拿出来即可,弄一个空物体命名为Pviot,把炮塔拖到它下面,调整Pviot位置,创建一个Canvas的Button,因为要放在Pviot物体上随着运动所以要使用World Space,调整Button字体、大小等等,放在炮台下面再调整好位置,做成开始游戏,调好一个了再复制一个Canvas出来旋转一下做成退出,可以给俩Button添加之前的button动画(调成Animation和添加Animator设置Controller),可以在Button里添加一个Shadow组件,设置Effect Distance的值。 调整相机位置。

控制开始按钮和退出按钮的点击事件处理

控制开始按钮和退出按钮的点击事件处理。通过控制炮台Y轴旋转做一个旋转动画rotate。
创建一个空物体命名为GameMenu,在它身上添加脚本GameMenu,添加OnStartGame和OnExitGame方法,注册到两个Button下,在Build Setting里把两个场景添加进去。
Unity3D制作塔防类游戏

GameMenu脚本全部代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameMenu : MonoBehaviour {

    public void OnStartGame()
    {
        SceneManager.LoadScene(1);
    }
    public void OnExitGame()
    {
#if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
#else
        Application.Quit();
#endif
    }
}