Unity3D制作塔防类游戏

简介: Unity3D制作塔防类游戏

演示


image.gif

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

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

功能简介


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

制作细节详解


Cube创建基本的地图

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

image.png

创建敌人行走的路

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

image.png

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

image.png

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

image.png

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

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

image.gif

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

image.png

代码如下:

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中"

image.png

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

image.png

代码如下

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"文件夹,把不同的敌人放进文件夹中,如下所示:

image.png

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

image.png

控制每个敌人的移动,这里创建一个"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"中,

4d6715f893d24c2c99a73db1c44dc233.png

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

代码如下

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。

image.png

创建炮塔选择的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拉过来,这样就可以单选了。

image.png

image.png

创建炮台的数据类

创建炮台数据类,用来保存炮台相关数据,创建脚本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里把两个场景添加进去。

21c54b2859dc437084743621cfb81bf4.png

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
    }
}


相关文章
|
3月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
176 6
|
2月前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
105 4
|
3月前
|
图形学 缓存 算法
掌握这五大绝招,让您的Unity游戏瞬间加载完毕,从此告别漫长等待,大幅提升玩家首次体验的满意度与留存率!
【8月更文挑战第31天】游戏的加载时间是影响玩家初次体验的关键因素,特别是在移动设备上。本文介绍了几种常见的Unity游戏加载优化方法,包括资源的预加载与异步加载、使用AssetBundles管理动态资源、纹理和模型优化、合理利用缓存系统以及脚本优化。通过具体示例代码展示了如何实现异步加载场景,并提出了针对不同资源的优化策略。综合运用这些技术可以显著缩短加载时间,提升玩家满意度。
154 5
|
2月前
|
前端开发 图形学 开发者
【独家揭秘】那些让你的游戏瞬间鲜活起来的Unity UI动画技巧:从零开始打造动态按钮,提升玩家交互体验的绝招大公开!
【9月更文挑战第1天】在游戏开发领域,Unity 是最受欢迎的游戏引擎之一,其强大的跨平台发布能力和丰富的功能集让开发者能够迅速打造出高质量的游戏。优秀的 UI 设计对于游戏至关重要,尤其是在手游市场,出色的 UI 能给玩家留下深刻的第一印象。Unity 的 UGUI 系统提供了一整套解决方案,包括 Canvas、Image 和 Button 等组件,支持添加各种动画效果。
136 3
|
2月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
185 3
|
3月前
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
152 3
|
2月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
213 0
|
2月前
|
vr&ar 图形学 API
Unity与VR控制器交互全解:从基础配置到力反馈应用,多角度提升虚拟现实游戏的真实感与沉浸体验大揭秘
【8月更文挑战第31天】虚拟现实(VR)技术迅猛发展,Unity作为主流游戏开发引擎,支持多种VR硬件并提供丰富的API,尤其在VR控制器交互设计上具备高度灵活性。本文详细介绍了如何在Unity中配置VR支持、设置控制器、实现按钮交互及力反馈,结合碰撞检测和物理引擎提升真实感,助力开发者创造沉浸式体验。
155 0
|
2月前
|
图形学 开发者
【独家揭秘】Unity游戏开发秘籍:从基础到进阶,掌握材质与纹理的艺术,打造超现实游戏视效的全过程剖析——案例教你如何让每一面墙都会“说话”
【8月更文挑战第31天】Unity 是全球领先的跨平台游戏开发引擎,以其高效性能和丰富的工具集著称,尤其在提升游戏视觉效果方面表现突出。本文通过具体案例分析,介绍如何利用 Unity 中的材质与纹理技术打造逼真且具艺术感的游戏世界。材质定义物体表面属性,如颜色、光滑度等;纹理则用于模拟真实细节。结合使用两者可显著增强场景真实感。以 FPS 游戏为例,通过调整材质参数和编写脚本动态改变属性,可实现自然视觉效果。此外,Unity 还提供了多种高级技术和优化方法供开发者探索。
54 0
|
3月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
128 0
下一篇
无影云桌面