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

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

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

管理

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


Unity官方教程《Tanks》学习笔记(五)_第1张图片
SpawnPoint1

选中SpawnPoint2,作以下修改:


Unity官方教程《Tanks》学习笔记(五)_第2张图片
SpawnPoint2

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


Unity官方教程《Tanks》学习笔记(五)_第3张图片
视图2D模式

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


Unity官方教程《Tanks》学习笔记(五)_第4张图片
Text

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


Shadow

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

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

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

Unity官方教程《Tanks》学习笔记(五)_第5张图片
初始化变量

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

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

Unity官方教程《Tanks》学习笔记(五)_第6张图片
游戏逻辑1

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

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

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

Unity官方教程《Tanks》学习笔记(五)_第8张图片
游戏逻辑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();   //获取移动和射击的脚本
        m_Shooting = m_Instance.GetComponent();
        m_CanvasGameObject = m_Instance.GetComponentInChildren().gameObject;

        m_Movement.m_PlayerNumber = m_PlayerNumber;     //设置玩家编号
        m_Shooting.m_PlayerNumber = m_PlayerNumber;

        m_ColoredPlayerText = "PLAYER " + m_PlayerNumber + "";

        MeshRenderer[] renderers = m_Instance.GetComponentsInChildren();  //用特定颜色渲染坦克

        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。双击打开该文件。


Unity官方教程《Tanks》学习笔记(五)_第9张图片
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界面做更改如下:


Unity官方教程《Tanks》学习笔记(五)_第10张图片
image.png

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


Unity官方教程《Tanks》学习笔记(五)_第11张图片
Driving

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


Unity官方教程《Tanks》学习笔记(五)_第12张图片
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》学习笔记(五))