前言   在上篇文章 中,我们已经实现了游戏主逻辑管理器
,并加入了游戏的胜负条件。但在游戏中,玩家并不能清晰地知道当前自己获得了多少分数
、游戏胜利的目标分数是多少
以及当前还能释放多少颗炸弹
,我们需要加入UI进行提示。
加入BombManager   在前面的文章中,因为我们还没有实现游戏主逻辑管理器
,为了方便测试,我们直接在PlayerAttack.cs
里面实现管理炸弹数量
和释放炸弹
的功能。因为PlayerAttack.cs
的职责是释放炸弹和导弹
,因此,我们需要将管理炸弹数量
的代码从PlayerAttack.cs
中抽取出来,并创建一个Manager
来负责管理炸弹数量
的工作。
  首先,我们在Assets\Scripts\Manager
文件夹下创建一个名为BombManager
的C#脚本,然后编辑BombManager.cs
如下:
BombManager.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 using System;using System.Collections;using System.Collections.Generic;using UnityEngine; [Serializable ]public class BombManager { [Tooltip("炸弹的初始数量" ) ] public int InitBombNumber = 4 ; private int m_CurrentBombNumber; public void Init () { m_CurrentBombNumber = InitBombNumber; } public bool ReleaseBomb (int bombNum ) { int temp = m_CurrentBombNumber - bombNum; if (temp >= 0 ) { m_CurrentBombNumber = temp; return true ; } else { return false ; } } public void PickupBomb (int bombNum ) { m_CurrentBombNumber += bombNum; } }
  接着,还需要在GameStateManager.cs
中添加对BombManager.cs
的管理代码:
GameStateManager.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 [RequireComponent(typeof(AudioSource)) ]public class GameStateManager : MonoBehaviour { ... [Tooltip("ScoreManager的实例" ) ] public ScoreManager ScoreManagerInstance = new ScoreManager(); [Tooltip("BombManager的实例" ) ] public BombManager BombManagerInstance = new BombManager(); ... private void GameInit () { ScoreManagerInstance.Init(); BombManagerInstance.Init(); m_CurrentState = GameState.Start; } private void GameEnd () { m_AudioSource.Stop(); m_AudioSource.loop = false ; ScoreManagerInstance.Stop(); BombManagerInstance.Stop(); float delay = 0f ; if (m_GameResult) { if (GameWinClip != null ) { AudioSource.PlayClipAtPoint(GameWinClip, this .transform.position); delay = GameWinClip.length; } else { Debug.LogError("请设置GameWinClip" ); } } else { if (GameLoseClip != null ) { AudioSource.PlayClipAtPoint(GameLoseClip, this .transform.position); delay = GameLoseClip.length; } else { Debug.LogError("请设置GameLoseClip" ); } } Destroy(Generator, delay); } ... }
  修改完GameStateManager.cs
之后,我们还需要删除PlayerAttack.cs
中管理炸弹数量
的代码,并使用BombManager
提供的ReleaseBomb函数
来释放炸弹
:
PlayerAttack.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 using System.Collections;using System.Collections.Generic;using UnityEngine; [RequireComponent(typeof(PlayerController)) ]public class PlayerAttack : MonoBehaviour { [Tooltip("导弹Prefab" ) ] public Missile MissilePrefab; [Tooltip("导弹发射点" ) ] public Transform ShootingPoint; [Tooltip("发射导弹的音效" ) ] public AudioClip ShootEffect; [Tooltip("炸弹Prefab" ) ] public Rigidbody2D BombPrefab; [Tooltip("使用火箭筒抛射炸弹的力" ) ] public float ProjectileBombForce = 1000f ; private PlayerController m_PlayerCtrl; private void Awake () { m_PlayerCtrl = GetComponent<PlayerController>(); if (MissilePrefab == null ) { Debug.LogError("请设置MissilePrefab" ); } if (ShootingPoint == null ) { Debug.LogError("请设置ShootingPoint" ); } if (BombPrefab == null ) { Debug.LogError("请设置BombPrefab" ); } } private void Update () { if (Input.GetButtonDown("Fire1" )) { Fire(); } if (Input.GetButtonDown("Fire2" )) { LayBomb(); } if (Input.GetButtonDown("Fire3" )) { ProjectileBomb(); } } private void Fire () { AudioSource.PlayClipAtPoint(ShootEffect, ShootingPoint.position); Missile instance = Instantiate(MissilePrefab, ShootingPoint.position, Quaternion.identity) as Missile; if (m_PlayerCtrl.FacingRight ^ instance.FacingRight) { instance.Flip(); } } private void LayBomb () { if (GameStateManager.Instance.BombManagerInstance.ReleaseBomb(1 ) == false ) { return ; } Instantiate(BombPrefab, this .transform.position, Quaternion.identity); } private void ProjectileBomb () { if (GameStateManager.Instance.BombManagerInstance.ReleaseBomb(1 ) == false ) { return ; } Rigidbody2D body = Instantiate(BombPrefab, ShootingPoint.position, Quaternion.identity) as Rigidbody2D; if (m_PlayerCtrl.FacingRight) { body.AddForce(Vector2.right * ProjectileBombForce); } else { body.AddForce(Vector2.left * ProjectileBombForce); } } }
  最后,我们还需要在AmmunitionBoxPickup.cs
中使用BombManager
提供的PickupBomb函数
来拾取炸弹
:
AmmunitionBoxPickup.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [RequireComponent(typeof(CircleCollider2D)) ] [RequireComponent(typeof(BoxCollider2D)) ]public class AmmunitionBoxPickup : MonoBehaviour { ... private void OnTriggerEnter2D (Collider2D collision ) { if (collision.tag == "Ground" && !m_Landed) { m_Landed = true ; transform.parent = null ; gameObject.AddComponent<Rigidbody2D>(); m_Animator.SetTrigger("Landing" ); return ; } if (collision.tag == "Player" ) { GameStateManager.Instance.BombManagerInstance.PickupBomb(BombAmount); AudioSource.PlayClipAtPoint(PickupEffect, transform.position); Destroy(transform.root.gameObject); } } }
  运行游戏,可以看到此时我们能正常释放和拾取炸弹
。至此,代码重构完成,后面我们就可以在BombManager
中对和炸弹有关的UI
进行管理了。
创建Canvas   添加完BombManager
之后,我们开始添加UI的工作。在Unity中,Canvas控制UI的绘制和缩放
。只有成为Canvas的子物体,UI才能够被正常绘制,且Canvas会按照从上到下的顺序
来绘制它的子物体,也就是上面的UI会被下面的UI覆盖。此外,根据Canvas的说明文档 ,我们知道Unity的Canvas有三种绘制的方式,它们的区别如下:
Canvas三种绘制的方式:
Screen Space - Overlay
: 这是Canvas默认的绘制方式
。使用Screen Space - Overlay
的绘制方式,Canvas不受场景摄像机的影响,直接在屏幕上进行绘制
,且在不同的屏幕分辨率下,Canvas会自动适配屏幕的分辨率大小
。由于Canvas不受摄像机影响,所以整个过程Canvas都是保持静态的,也就不需要重新计算Canvas的位置和角度
,是一种优化性较好的绘制方式。
Screen Space - Camera
: 使用Screen Space - Camera
的绘制方式,我们需要先选择用于绘制Canvas的摄像机
,然后Canvas会被绘制在选定的摄像机最上方
。因此,我们可以通过调整用于绘制Canvas的摄像机的属性来实现UI界面的三维翻转
和让三维物体显示在UI界面之上
等功能。此外,当用于渲染Canvas的摄像机的视口大小发生变化时,Canvas也会自动进行适配
。
World Space
:使用World Space
的绘制方式,Unity会将Canvas当成普通三维物体
进行处理。
  接着,根据CanvasScaler的说明文档 ,我们知道当Canvas进行缩放时,它在绘制UI时有三种对UI进行缩放
的方式。在介绍这三种缩放方式之前,我们需要先清楚Unity的UI单位和像素的区别
。当我们在Unity里编辑UI的时候,UI的Width
和Height
使用的是Unity的UI单位
,当UI被绘制到屏幕上的时候,UI使用的是像素
。了解了Unity的UI单位和像素的区别
之后,我们接着了解一下Canvas对UI进行缩放的三种方式的区别:
Canvas对UI进行缩放的三种方式:
Constant Pixel Size
:在不同尺寸的屏幕上,UI组件会占用相同数量的像素。当ScaleFactor
的值为x
时,1UI单位等于x个像素。此外,采用这种缩放方式,同样的UI在手机上会比在电脑上小很多,因为手机屏幕的DPI(Dots Per Inch,每英寸点数)
,也就是像素密度
要远远大于电脑。
Scale With Screen Size
:让UI根据屏幕尺寸的大小变化进行缩放,因此我们需要先设置一个用于参考的屏幕尺寸基准值
Constant Physical Size
:无论屏幕尺寸多大,都让UI保持一样的物理尺寸大小(像素值会变化),如果我们希望在任意的设备上,自己手掌地方图片都可以和你的手掌完全重合,可以采用这种模式
  在了解了Canvas的绘制方式和对UI的缩放方式之后,我们开始为我们的游戏创建一个用于绘制UI的Canvas:
创建Canvas的步骤如下:
在Hierarchy
窗口中右击鼠标,选择UI->Canvas
在场景中新建一个Canvas
,然后将其重命名为UICanvas
接着我们对UICanvas
下Canvas Scaler
组件的设置进行修改
选择UI Scale Mode
为Scale With Screen Size
,让我们的UI根据屏幕尺寸的变化进行缩放,保证我们的UI在不同尺寸的手机上看起来都差不多
因为我们预设的屏幕分辨率为1920 X 1080
,所以我们设置Reference Resolution
为X(1920), Y(1080)
设置Screen Match Mode
为Match Width or Height
,让Unity根据分辨率的宽和高
的权重
来缩放Canvas
我们希望Canvas能同时考虑分辨率的宽和高的变化
来缩放UI,因此我们设置Match
为0.5
(0表示只考虑宽,1表示只考虑高
)
添加提示UI   添加完BombManager
之后,我们在场景里面添加提示UI
,添加提示UI
的步骤如下:
添加提示UI
的步骤:
在UICanvas
物体下新建一个Image
,然后将其重名为BombUI
BombUI
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择top-left
PosX
: 160
PosY
: -100
Width
: 150
Height
: 150
Image
:
Source Image
: Assets\Sprites\Props
文件夹下的prop_crate_ammo
图片
Color
: (255, 255, 255, 200)
在BombUI
物体下新建一个Text
,然后将其重命名为BombNumberText
BombNumberText
物体需要修改的组件属性:
Rect Transform
:
PosX
: -3.5
PosY
: -10
Width
: 200
Height
: 160
Text
:
Text
: 0
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 80
Alignment
: 水平居中,垂直居中
在UICanvas
物体下新建一个Text
,然后将其重名为TargetScoreLabel
TargetScoreLabel
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择top-center
PosX
: -460
PosY
: -35
Width
: 320
Height
: 80
Text
:
Text
: Target Score:
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 60
Alignment
: 水平居中,垂直居中
Color
: (119, 119, 119, 180)
在TargetScoreLabel
物体下新建一个Text
,然后将其重命名为TargetScoreText
TargetScoreText
物体需要修改的组件属性:
Rect Transform
:
PosX
: 0
PosY
: -50
Width
: 320
Height
: 80
Text
:
Text
: 50000
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 60
Alignment
: 水平居中,垂直居中
Color
: (119, 119, 119, 180)
在UICanvas
物体下新建一个Text
,然后将其重名为ScoreLabel
ScoreLabel
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择top-center
PosX
: -80
PosY
: -70
Width
: 240
Height
: 120
Text
:
Text
: Score:
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 100
Alignment
: 水平居中,垂直居中
Color
: (0, 0, 0, 255)
在ScoreLabel
物体下新建一个Text
,然后将其重名为ScoreLabelForground
ScoreLabelForground
物体需要修改的组件属性:
Rect Transform
:
PosX
: 0
PosY
: -49
Width
: 240
Height
: 120
Text
:
Text
: Score:
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 100
Alignment
: 水平居中,垂直居中
在UICanvas
物体下新建一个Text
,然后将其重名为ScoreText
ScoreText
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择top-center
PosX
: 225
PosY
: -70
Width
: 320
Height
: 120
Text
:
Text
: 50000
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 80
Alignment
: 向左对齐,垂直居中
在UICanvas
物体下新建一个Button
,然后将其重名为BackButton
,并删除BackButton
的Text
子物体
BackButton
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择top-right
PosX
: -140
PosY
: -75
Width
: 115
Height
: 100
Image
:
Source Image
: Assets\Sprites\UI
文件夹下的BackButton
图片
Color
: (119, 119, 119, 255)
在UICanvas
物体下新建一个Button
,然后将其重名为BackButton
,并删除BackButton
的Text
子物体
BackButton
物体需要修改的组件属性:
Rect Transform
:
Anchors
: 点击Rect Transform
组件左上角的方框,然后选择top-right
PosX
: -140
PosY
: -75
Width
: 115
Height
: 100
Image
:
Source Image
: Assets\Sprites\UI
文件夹下的BackButton
图片
Color
: (119, 119, 119, 255)
  添加完提示UI
之后,我们Game
窗口中选择其它尺寸的视口
,可以看到,游戏场景中的UI会随着视口尺寸的改变而缩放
。
更新提示UI的内容   添加完提示UI
之后,我们还需要选择使用代码在游戏运行时更新提示UI
的内容。首先,我们为BombManager.cs
加入更新提示UI
的代码:
BombManager.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 using System;using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI; [Serializable ]public class BombManager { [Tooltip("炸弹的初始数量" ) ] public int InitBombNumber = 4 ; [Tooltip("炸弹UI" ) ] public Image BombUI; [Tooltip("显示炸弹的数量" ) ] public Text BombNumberText; private int m_CurrentBombNumber; private bool m_Stop; public void Init () { m_CurrentBombNumber = InitBombNumber; m_Stop = false ; UpdateUI(); } public void Stop () { m_Stop = true ; } public bool ReleaseBomb (int bombNum ) { if (m_Stop) { return false ; } int temp = m_CurrentBombNumber - bombNum; if (temp >= 0 ) { m_CurrentBombNumber = temp; UpdateUI(); return true ; } else { return false ; } } public void PickupBomb (int bombNum ) { if (m_Stop) { return ; } m_CurrentBombNumber += bombNum; UpdateUI(); } private void UpdateUI () { BombNumberText.text = "" + m_CurrentBombNumber; if (m_CurrentBombNumber <= 0 ) { BombUI.color = new Color(255 , 0 , 0 , BombUI.color.a / 2 ); } else { BombUI.color = new Color(255 , 255 , 255 , BombUI.color.a); } } }
代码说明:   因为我们使用了Text
类和Image
类来操作UI,所以我们需要加上using UnityEngine.UI;
  修改完BombManager.cs
之后,我们继续为ScoreManager.cs
加入更新提示UI
的代码:
ScoreManager.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI; [Serializable ]public class ScoreManager { [Tooltip("游戏胜利的目标分数" ) ] public int TargetScore = 5000 ; [Tooltip("保存嘲讽音效" ) ] public AudioClip[] TauntClips; [Tooltip("得分之后播放嘲讽音效的概率" ) ] public float TauntProbaility = 50f ; [Tooltip("嘲讽的间隔" ) ] public float TauntDelay = 1f ; [Tooltip("显示目标分数" ) ] public Text TargetScoreText; [Tooltip("显示当前的分数" ) ] public Text ScoreText; private int m_CurrentScore; private int m_TauntIndex; private float m_LastTauntTime; private bool m_Stop; private Transform m_Player; public void Init () { m_CurrentScore = 0 ; m_TauntIndex = 0 ; m_LastTauntTime = Time.time; m_Stop = false ; TargetScoreText.text = "" + TargetScore; ScoreText.text = "" + m_CurrentScore; m_Player = GameObject.FindGameObjectWithTag("Player" ).transform;; } public void Stop () { m_Stop = true ; } public void AddScore (int score ) { if (m_Stop) { return ; } m_CurrentScore += score; ScoreText.text = "" + m_CurrentScore; if (m_CurrentScore >= TargetScore) { GameStateManager.Instance.SetGameResult(true ); } if (m_LastTauntTime <= Time.time + TauntDelay) { float tauntChance = UnityEngine.Random.Range(0f , 100f ); if (tauntChance > TauntProbaility) { m_TauntIndex = TauntRandom(); AudioSource.PlayClipAtPoint(TauntClips[m_TauntIndex], m_Player.position); } } } private int TauntRandom () { int i = UnityEngine.Random.Range(0 , TauntClips.Length); if (i == m_TauntIndex) return TauntRandom(); else return i; } }
代码说明:   因为我们使用了Text
类来操作UI,所以我们需要加上using UnityEngine.UI;
  脚本编辑完成之后,我们选择Hierarchy
窗口中的GameStateManager
,然后将UICanvas
物体下的TargetScoreText
和ScoreText
子物体分别拖拽到Score Manager Instance
折叠框下的Target Score Text
和Score Text
属性赋值框,接着将UICanvas
物体下的BombUI
和BombNumberText
子物体分别拖拽到Bomb Manager Instance
折叠框下的Bomb UI
和Bomb Number Text
属性赋值框。最后,运行游戏,可以看到此时提示UI
能正确显示目标分数
、当前的分数
以及当前可释放的炸弹数
。
添加游戏暂停UI和游戏结束UI   添加完提示UI
之后,我们继续在场景里面添加游戏暂停UI
和游戏结束UI
。首先,我们先为场景添加游戏暂停UI
:
添加游戏暂停UI
的步骤:
在UICanvas
物体下新建一个Panel
,然后将其重名为PausedPanel
PausedPanel
物体需要修改的组件属性:
Image
:
Color
: (119, 119, 119, 100)
在PausedPanel
物体下新建一个Image
,然后将其重命名为Background
Background
物体需要修改的组件属性:
Rect Transform
:
Image
:
Source Image
: Assets\Sprites\UI
文件夹下的SF Window
图片
Color
: (119, 119, 119, 100)
在Background
物体下新建一个Text
,然后将其重名为PausedText
PausedText
物体需要修改的组件属性:
Rect Transform
:
PosX
: 0
PosY
: 100
Width
: 560
Height
: 480
Text
:
Text
: Whether to quit the game?
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 80
Alignment
: 水平居中,垂直居中
在PausedPanel
物体下新建一个Button
,然后将其重名为ConfirmButton
ConfirmButton
物体需要修改的组件属性:
Rect Transform
:
PosX
: -220
PosY
: -175
Width
: 300
Height
: 120
Image
:
Source Image
: Assets\Sprites\UI
文件夹下的SF Button
图片
修改ConfirmButton
物体下的子物体Text
Text
物体需要修改的组件属性:
Text
:
Text
: Yes
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 80
Alignment
: 水平居中,垂直居中
在PausedPanel
物体下新建一个Button
,然后将其重名为CancelButton
CancelButton
物体需要修改的组件属性:
Rect Transform
:
PosX
: 220
PosY
: -175
Width
: 300
Height
: 120
Image
:
Source Image
: Assets\Sprites\UI
文件夹下的SF Button
图片
修改CancelButton
物体下的子物体Text
Text
物体需要修改的组件属性:
Text
:
Text
: No
Font
: Assets\Fonts
下的BradBunR
字体文件
Font Size
: 80
Alignment
: 水平居中,垂直居中
  添加完游戏暂停UI
之后,我们继续添加游戏结束UI
:
添加游戏结束UI
的步骤:
复制PausedPanel
得到PausedPanel (1)
,并将PausedPanel (1)
物体重命名为GameResultPanel
将PausedPanel
物体设置为在游戏场景中不可见
将PausedText
物体重名为GameResultText
GameResultText
物体需要修改的组件属性:
Text
:
Width
: 600
Height
: 480
Text
: You Win!!!
Font Size
: 150
Color
: (103, 103, 103, 255)
将ConfirmButton
物体重命名为RestartButton
修改RestartButton
物体下的子物体Text
Text
物体需要修改的组件属性:
将CancelButton
重命名为QuitButton
修改QuitButton
物体下的子物体Text
Text
物体需要修改的组件属性:
  添加完游戏结束UI
之后,我们将GameResultPanel
设置为在游戏场景中不可见。
管理游戏暂停UI和游戏结束UI   添加完游戏暂停UI
和游戏结束UI
之后,我们还需要在GameStateManager.cs
中加入管理游戏暂停UI和游戏结束UI的代码:
GameStateManager.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;using UnityEngine.SceneManagement;public enum GameState { Init, Start, Running, End } [RequireComponent(typeof(AudioSource)) ]public class GameStateManager : MonoBehaviour { private static GameStateManager m_Instance = null ; public static GameStateManager Instance { get { if (m_Instance == null ) { m_Instance = FindObjectOfType(typeof (GameStateManager)) as GameStateManager; if (m_Instance == null ) { GameObject obj = new GameObject("GameStateManager" ); m_Instance = obj.AddComponent<GameStateManager>(); } } return m_Instance; } } [Tooltip("游戏运行时的背景音乐" ) ] public AudioClip BackgroundMusic; [Tooltip("游戏胜利时的音效" ) ] public AudioClip GameWinClip; [Tooltip("游戏失败时的音效" ) ] public AudioClip GameLoseClip; [Tooltip("场景中所有Generator的父物体" ) ] public GameObject Generator; [Tooltip("ScoreManager的实例" ) ] public ScoreManager ScoreManagerInstance = new ScoreManager(); [Tooltip("BombManager的实例" ) ] public BombManager BombManagerInstance = new BombManager(); [Tooltip("游戏暂停界面" ) ] public GameObject PausedPanel; [Tooltip("游戏结束界面" ) ] public GameObject GameResultPanel; [Tooltip("游戏结果" ) ] public Text GameResultText; private GameState m_CurrentState; private bool m_IsPaused; private bool m_GameResult; private AudioSource m_AudioSource;#region MonoBehaviour的事件函数 private void Awake () { m_AudioSource = GetComponent<AudioSource>(); m_AudioSource.playOnAwake = false ; } private void Start () { m_IsPaused = false ; m_CurrentState = GameState.Init; StartCoroutine(GameMainLoop()); }#endregion #region 自定义游戏状态函数 private IEnumerator GameMainLoop () { GameInit(); while (m_CurrentState == GameState.Init) { yield return null ; } GameStart(); while (m_CurrentState == GameState.Running) { GameRunning(); yield return null ; } GameEnd(); } private void GameInit () { ScoreManagerInstance.Init(); BombManagerInstance.Init(); PausedPanel.SetActive(false ); GameResultPanel.SetActive(false ); m_CurrentState = GameState.Start; } private void GameStart () { if (BackgroundMusic != null ) { m_AudioSource.clip = BackgroundMusic; m_AudioSource.loop = true ; m_AudioSource.Play(); } else { Debug.LogError("请设置BackgroundMusic" ); } m_CurrentState = GameState.Running; } private void GameRunning () {#if UNITY_STANDALONE || UNITY_EDITOR if (Input.GetKeyDown(KeyCode.P)) { if (m_IsPaused) { GameContinue(); } else { GamePause(); } }#endif } private void GameEnd () { m_AudioSource.Stop(); m_AudioSource.loop = false ; ScoreManagerInstance.Stop(); BombManagerInstance.Stop(); float delay = 0f ; if (m_GameResult) { if (GameWinClip != null ) { AudioSource.PlayClipAtPoint(GameWinClip, this .transform.position); delay = GameWinClip.length; } else { Debug.LogError("请设置GameWinClip" ); } GameResultText.text = "You Win!!!" ; } else { if (GameLoseClip != null ) { AudioSource.PlayClipAtPoint(GameLoseClip, this .transform.position); delay = GameLoseClip.length; } else { Debug.LogError("请设置GameLoseClip" ); } GameResultText.text = "You Lose!!!" ; } GameResultPanel.SetActive(true ); Destroy(Generator, delay); }#endregion #region 外部调用函数 public void SetGameResult (bool result ) { m_GameResult = result; m_CurrentState = GameState.End; } public void GamePause () { m_AudioSource.Pause(); Time.timeScale = 0f ; m_IsPaused = true ; PausedPanel.SetActive(true ); } public void GameContinue () { Time.timeScale = 1f ; m_AudioSource.UnPause(); m_IsPaused = false ; PausedPanel.SetActive(false ); } public void Restart () { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); } public void Back () { }#endregion }
代码说明:
因为我们使用了Text
来操作UI,所以我们需要加上using UnityEngine.UI;
因为我们使用了SceneManager
来加载场景,所以我们需要加上using UnityEngine.SceneManagement;
为了方便测试,我们在GameRunning
方法中使用了Unity提供的平台宏定义 来进行平台隔离
为了将GamePause函数
和GameContinue函数
设置为按钮点击事件的回调函数
,我们将GamePause函数
和GameContinue函数
的可见性设置为public
  编辑完GameStateManager.cs
之后,我们在Hierarchy
窗口选中GameStateManager
物体,然后将GameStateManager
物体上GameStateManager.cs
组件的Paused Panel
和Game Result Panel
字段分别设置UICanvas
下的子物体PausedPanel
和子物体GameResultPanel
,将Game Result Text
字段设置为GameResultPanel
物体下的子物体Game Result Text
。
  设置完游戏暂停UI
和游戏结束UI
之后,我们还需要设置按钮的点击事件
,我们先来设置BackButton
的点击事件。
BackButton
点击事件的设置步骤:
选中BackButton
物体,然后点击其Button
组件上On Click()
下的+
号增加一个空点击事件
将场景中的GameStateManager
拖拽至On Click()
下的GameObject
赋值框处
点击No Function
下拉菜单,选择GameStateManager
下的GamePause函数
  最后,我们按照相同的步骤,将PausedPanel
物体的子物体ConfirmButton
的点击事件设置为GameStateManager
下的Back函数
,将PausedPanel
物体的子物体CancelButton
的点击事件设置为GameStateManager
下的GameContinue函数
,将GameResultPanel
物体的子物体RestartButton
的点击事件设置为GameStateManager
下的Restart函数
,将GameResultPanel
物体的子物体QuitButton
的点击事件设置为GameStateManager
下的Back函数
。
  运行游戏,可以看到当游戏胜利或者失败时,会弹出游戏结束界面,此时若点击Restart
按钮,游戏将重新开始。此外,当我们点击BackButton
时,游戏会暂停并弹出游戏暂停界面,若我们点击游戏暂停界面的No
按钮时,游戏恢复且游戏暂停界面消失。
后言   需要注意的是,目前我们还没有实现菜单场景,因此我们的Back函数
是一个空函数。最后,本篇文章所做的修改,可以在PotatoGloryTutorial 这个仓库的essay18
分支下看到,读者可以clone这个仓库到本地进行查看。
参考链接
Unity-Canvas
Unity-Canvas Scaler
Unity-Designing UI for Multiple Resolutions
Unity-Platform dependent compilation