美文网首页
Unity官方教程《Tanks》学习笔记(五)

Unity官方教程《Tanks》学习笔记(五)

作者: 丶蓝天白云梦 | 来源:发表于2018-02-09 16:16 被阅读996次

    本系列文章是根据官方视频教程而写下的学习笔记,原官方视频教程网址:https://unity3d.com/cn/learn/tutorials/s/tanks-tutorial

    系列其他笔记传送门
    Unity官方教程《Tanks》学习笔记(一)
    Unity官方教程《Tanks》学习笔记(二)
    Unity官方教程《Tanks》学习笔记(三)
    Unity官方教程《Tanks》学习笔记(四)

    管理

    本小节的目标是创建一个管理脚本,同一管理该游戏场景中的两辆坦克,并且添加输赢的游戏逻辑,让游戏有始有终。
    在上一节中,我们把根目录下的Tank删除了,我们需要在游戏的过程中动态生成两个Tank,而不是一开始就设置好。因此我们需要两个Tank的出生点。在Hierarchy下新建两个空对象,分别命名为SpawnPoint1和SpawnPoion2。
    选中SpawnPoint1,作以下修改:


    SpawnPoint1

    选中SpawnPoint2,作以下修改:


    SpawnPoint2

    接着,在Hierarchy层级下,新建一个Canvas(GameObject——>UI——>Canvas),重命名为MessageCanvas。接着,在Scene View中点击2D模式,如下图所示:


    视图2D模式

    选中MessageCanvas,右键新建一个Text,让其成为MessageCanvas的子对象,选中Text对象,我们来修改它的数据如下:


    Text

    下一步,在Text内,新建一个组件:Shadow,为Text添加阴影效果:


    Shadow
    接着,取消刚才设置的2D视图模式。

    选中CameraRig,点击Edit——>Frame Selected,在CameraRig的脚本组件那里,我们之前设置了m_Targets为已经被删除的Tank,所以我们要把该数组的长度设置为0,并按回车确认。再打开CameraControl脚本来编辑:这里只需要把之前提及的[HideInInspector]的注释去掉即可,也就是说隐藏掉该公共变量。

    下面就来创建我们的游戏管理者,在Hierarchy层级创建一个空对象,命名为GameManager,在/Scripts/Managers文件夹内找到GameManager脚本,把它拖拽到GameManager对象内。我们先初始化它的几个公共变量:

    初始化变量

    接下来先整理一下我们的游戏逻辑。

    1、首先,我们先从游戏的整个流程来梳理

    游戏逻辑1
    从官方的教程中,我们可以知道,Game Manager充当一个管理全局的角色,首先它初始化的过程中,会在出生点生成两个坦克供玩家控制,并且把摄像机的目标设置为该两辆坦克,那么这样就完成了初始化。接着就是正常的游戏流程,那么这里就涉及到了游戏的输赢判定,这里使用的是分回合的形式,每一回合获胜则获得一分,经过若干回合后,总分最高者获胜。每一回合结束之后,会回到初始化过程,重新生成坦克。具体到每一个回合上,坦克的控制就交给Tank Manager来控制。
    游戏逻辑2

    从上图可以看出,Tank Manager控制了坦克的移动和射击的脚本以及UI的展示。

    2、我们从游戏者的角度来梳理:

    游戏逻辑3

    GameManager可以分为若干个Tank Manager,Game Manager负责管理每个Tank Manager,而具体的游戏坦克的行为则交给每一个Tank Manager负责。这里就实现了解耦的作用,假如以后需要拓展游戏功能,比如增加多个玩家,那么我们只需要修改Game Manager就可以了。

    接着,我们打开GameManager脚本,对它进行完善与编辑:

    using UnityEngine;
    using System.Collections;
    using UnityEngine.SceneManagement;
    using UnityEngine.UI;
    
    public class GameManager : MonoBehaviour
    {
        public int m_NumRoundsToWin = 5;        //5回合获胜则游戏获胜
        public float m_StartDelay = 3f;         //每回合开始的等待时间
        public float m_EndDelay = 3f;           //每回合结束之后的等待时间
        public CameraControl m_CameraControl;   
        public Text m_MessageText;              
        public GameObject m_TankPrefab;         
        public TankManager[] m_Tanks;           //两个坦克管理者
    
    
        private int m_RoundNumber;              
        private WaitForSeconds m_StartWait;     
        private WaitForSeconds m_EndWait;       
        private TankManager m_RoundWinner;
        private TankManager m_GameWinner;       
    
    
        private void Start()
        {
            m_StartWait = new WaitForSeconds(m_StartDelay); //用来协同yield指令,等待若干秒
            m_EndWait = new WaitForSeconds(m_EndDelay);
    
            SpawnAllTanks();             //生成坦克       
            SetCameraTargets();          //设置摄像机
    
            StartCoroutine(GameLoop());  //
        }
    
        /**
         * 在出生点生成坦克
         */
        private void SpawnAllTanks()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].m_Instance =
                    Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject;
                m_Tanks[i].m_PlayerNumber = i + 1;  //为坦克标号
                m_Tanks[i].Setup();                 //调用TankManager的setup方法
            }
        }
    
        /**
         * 设置摄像头的初始位置
         */
        private void SetCameraTargets()
        {
            Transform[] targets = new Transform[m_Tanks.Length];
    
            for (int i = 0; i < targets.Length; i++)
            {
                targets[i] = m_Tanks[i].m_Instance.transform;
            }
    
            m_CameraControl.m_Targets = targets;
        }
    
        //游戏循环
        private IEnumerator GameLoop()
        {
            yield return StartCoroutine(RoundStarting());   //等待一段时间后执行
            yield return StartCoroutine(RoundPlaying());
            yield return StartCoroutine(RoundEnding());
    
            //如果有胜者,则重新加载游戏场景
            if (m_GameWinner != null)
            {
                SceneManager.LoadScene(SceneManager.GetActiveScene().name);
            }
            else
            {
                StartCoroutine(GameLoop());     //如果没有胜者,则继续循环
            }
        }
    
        /**
         * 每一回合的开始
         */
        private IEnumerator RoundStarting()
        {
            ResetAllTanks();                   //重置坦克位置
            DisableTankControl();              //取消对坦克的控制
            m_CameraControl.SetStartPositionAndSize();  //摄像机聚焦位置重置
    
            m_RoundNumber++;                   //回合数增加
            m_MessageText.text = "ROUND" + m_RoundNumber; //更改UI的显示
            yield return m_StartWait;
        }
    
    
        /**
         * 每一回合的游戏过程
         */
        private IEnumerator RoundPlaying()
        {
            EnableTankControl();    //激活对坦克的控制
    
            m_MessageText.text = string.Empty; //UI不显示
    
            //如果只剩下一个玩家,则跳出循环
            while(!OneTankLeft()){
                yield return null;
            }
            
        }
    
    
        /**
         * 每一回合的结束
         */
        private IEnumerator RoundEnding()
        {
            //取消对坦克的控制
            DisableTankControl();
    
            m_RoundWinner = null;
    
            //判断当前回合获胜的玩家
            m_RoundWinner = GetRoundWinner();
    
            //累积胜利次数
            if(m_RoundWinner != null){
                m_RoundWinner.m_Wins++;
            }
    
            //判断是否有玩家达到了游戏胜利的条件
            m_GameWinner = GetGameWinner();
    
            string message = EndMessage();
            m_MessageText.text = message;
    
            yield return m_EndWait;
        }
    
        /**
         * 该方法用于判断是否只剩下一个玩家在场景中
         */
        private bool OneTankLeft()
        {
            int numTanksLeft = 0;
    
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                if (m_Tanks[i].m_Instance.activeSelf)
                    numTanksLeft++;
            }
    
            return numTanksLeft <= 1;
        }
    
        /**
         * 该方法用于判断回合胜者
         */
        private TankManager GetRoundWinner()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                if (m_Tanks[i].m_Instance.activeSelf)
                    return m_Tanks[i];
            }
    
            return null;
        }
    
        /**
         * 该方法用于判断游戏获胜者
         */
        private TankManager GetGameWinner()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                if (m_Tanks[i].m_Wins == m_NumRoundsToWin)
                    return m_Tanks[i];
            }
    
            return null;
        }
    
    
        private string EndMessage()
        {
            string message = "DRAW!";
    
            if (m_RoundWinner != null)
                message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!";
    
            message += "\n\n\n\n";
    
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS\n";
            }
    
            if (m_GameWinner != null)
                message = m_GameWinner.m_ColoredPlayerText + " WINS THE GAME!";
    
            return message;
        }
    
    
        private void ResetAllTanks()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].Reset();     //调用TankManager的Reset()方法
            }
        }
    
    
        private void EnableTankControl()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].EnableControl();   //调用TankManager的EnableControl()方法
            }
        }
    
    
        private void DisableTankControl()
        {
            for (int i = 0; i < m_Tanks.Length; i++)
            {
                m_Tanks[i].DisableControl();   //调用TankManager的DisableControl()方法
            }
        }
    }
    

    编辑完毕之后,我们再来看看TankManager这个脚本,该文件也在Manager文件夹内,但是我们不需要把它拖拽到任何游戏对象上。因为它由GameManager来管理:

    using System;
    using UnityEngine;
    
    [Serializable]              //为了在Inspector显示公共变量,需要使用序列化标识符
    public class TankManager
    {
        public Color m_PlayerColor;            //下面两个变量在GameManager(Script)Inspector初始化
        public Transform m_SpawnPoint;         
        [HideInInspector] public int m_PlayerNumber;             
        [HideInInspector] public string m_ColoredPlayerText;
        [HideInInspector] public GameObject m_Instance;          
        [HideInInspector] public int m_Wins;                     
    
    
        private TankMovement m_Movement;       
        private TankShooting m_Shooting;
        private GameObject m_CanvasGameObject;
    
    
        public void Setup()
        {
            m_Movement = m_Instance.GetComponent<TankMovement>();   //获取移动和射击的脚本
            m_Shooting = m_Instance.GetComponent<TankShooting>();
            m_CanvasGameObject = m_Instance.GetComponentInChildren<Canvas>().gameObject;
    
            m_Movement.m_PlayerNumber = m_PlayerNumber;     //设置玩家编号
            m_Shooting.m_PlayerNumber = m_PlayerNumber;
    
            m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER " + m_PlayerNumber + "</color>";
    
            MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer>();  //用特定颜色渲染坦克
    
            for (int i = 0; i < renderers.Length; i++)
            {
                renderers[i].material.color = m_PlayerColor;
            }
        }
    
    
        public void DisableControl()
        {
            m_Movement.enabled = false;
            m_Shooting.enabled = false;
    
            m_CanvasGameObject.SetActive(false);
        }
    
    
        public void EnableControl()
        {
            m_Movement.enabled = true;
            m_Shooting.enabled = true;
    
            m_CanvasGameObject.SetActive(true);
        }
    
    
        public void Reset()
        {
            m_Instance.transform.position = m_SpawnPoint.position;
            m_Instance.transform.rotation = m_SpawnPoint.rotation;
    
            m_Instance.SetActive(false);
            m_Instance.SetActive(true);
        }
    }
    

    到这一步之后,就可以保存场景,并测试一下了。

    音效

    经过上一小节的测试后,游戏已经算是高度完成了,最后这一小节还需要完善一下音效效果。

    首先,右键单击AudioMixer文件夹,新建一个Audio Mixer,命名为MainMix。双击打开该文件。


    MainMix

    确保左上角选中的是MainMix,然后在Groups选项下点击“+”来创建三个子对象,并分别命名为Music、SFX、Driving。(如果无法重命名,则点击开始游戏再结束游戏)。接着对三个子对象的属性进行更改:

    1、选中Music,把Attenuation选择为-12,并且通过“Add..”按钮新建一个Duck Volume
    2、选中SFX,新建一个Send,设置Receive为Music\Duck Volume
    3、选中Driving,把Attenuation选择为-25。
    4、重新选择Music,在Inspectior界面做更改如下:


    image.png

    然后,在Prefabs文件夹内找到Tank,展开第一个Audio Source把Output选择为Driving。


    Driving

    展开第二个Audio Source,把Output选择为SFX


    SFX
    在Prefabs文件夹内找到Shell,展开,选中ShellExplosion,把Audio Source的Output选择为SFX。
    在Prefabs文件夹找到TankExplosion,把Audio Source的Output选择为SFX。

    在Hierarchy选择GameManager,新建Audio Source组件,音效选择为BackgroundMusic,Output选择为Music。勾选Loop。

    最后,保存场景,运行游戏。整个Tanks游戏的开发流程到此完毕。

    相关文章

      网友评论

          本文标题:Unity官方教程《Tanks》学习笔记(五)

          本文链接:https://www.haomeiwen.com/subject/gkcttftx.html