前言   到目前为止,我们已经能在场景中控制角色进行移动
、攻击怪物
并拾取道具
,但我们还没有做出一个完整的游戏,我们还需要加入游戏的胜负条件
。
游戏的胜负条件
胜利条件:每击杀一个怪物获得100分
,当已获得的分数达到预设的分数
时,游戏胜利
失败条件:当角色死亡时,游戏失败
  知道了游戏胜负条件
之后,我们还需要加入游戏主逻辑管理器
来管理整个游戏的状态
。通常来说,一个游戏场景里面,只有唯一一个游戏主逻辑管理器
。因此,我们可以使用单例模式
来实现游戏主逻辑管理器
。
什么是单例模式   单例模式 是一种常用的软件设计模式
。在应用这个模式时,单例对象的类必须保证只有一个实例存在
,并提供一个访问这个唯一实例
的全局访问点
。
  单例模式
的实现思路是:一个类定义一个静态的实例引用
和一个获得该实例的方法(必须是静态方法)
,当我们调用获取静态实例的方法
时,如果类持有的静态实例引用不为空就返回这个引用
,如果类保持的静态实例引用为空就创建该类的实例,并将新创建的实例引用赋予该类持有的静态实例引用
。同时我们还应该将该类的构造函数定义为私有方法
,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例
。
单例模式
的两种构建方式:
懒汉方式(Lazy initialization)
:指单例类的单例实例在第一次被使用时
构建。
饿汉方式(Eager initialization)
:指单例类的单例实例在类装载时
先主动构建。
  由于Unity采用了组件化编程
的方式,所以在Unity中,除了对象以外一切都是组件(Component)
,所有定义了继承自MonoBehaviour
的类的C#脚本都需要先绑定到游戏对象(GameObject)
上,Unity才会自动实例化该类
,并在游戏运行时调用MonoBehaviour
的各个事件函数
。此外,我们不可以使用new
关键字对继承自MonoBehaviour
的类进行实例化,只能使用GetComponet<>
函数来获取该类的实例对象,这就决定了如果我们使用饿汉方式(Eager initialization)
来实现单例模式
,那么我们只能在Awake
函数中初始化单例对象
。
  需要注意的是,根据Unity关于Event Functions的说明文档 ,Unity会按照随机的顺序
执行所有继承自MonoBehaviour
的类的Awake
函数,因此如果我们使用饿汉方式(Eager initialization)
来实现单例模式
,那么我们需要保证单例类
的Awake
函数在其他继承自MonoBehaviour
的类的Awake
函数之前执行,否则将有可能出现空引用的问题
。考虑到保证脚本之间的执行顺序工作量较大,为了避免出现空引用的问题
,我们采用懒汉方式(Lazy initialization)
来实现单例模式
。
实现游戏主逻辑管理器框架   在了解了单例模式
是什么,以及采用哪种方式实现单例模式
之后,我们开始实现游戏主逻辑管理器
。首先,我们在Assets\Scripts
文件夹下创建一个名为Manager
的文件夹,然后在Assets\Scripts\Manager
文件夹下创建一个名为GameStateManager
的C#脚本。接着,我们编辑GameStateManager.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 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 using System.Collections;using System.Collections.Generic;using UnityEngine;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; 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 () { Debug.Log("Game Init" ); m_CurrentState = GameState.Start; } private void GameStart () { Debug.Log("Game Start" ); if (BackgroundMusic != null ) { m_AudioSource.clip = BackgroundMusic; m_AudioSource.loop = true ; m_AudioSource.Play(); } else { Debug.LogError("请设置BackgroundMusic" ); } m_CurrentState = GameState.Running; } private void GamePause () { Debug.Log("Game Pause" ); m_AudioSource.Pause(); Time.timeScale = 0f ; m_IsPaused = true ; } private void GameContinue () { Debug.Log("Game Continue" ); Time.timeScale = 1f ; m_AudioSource.UnPause(); m_IsPaused = false ; } private void GameRunning () { Debug.Log("Game Running" ); if (Input.GetKeyDown(KeyCode.P)) { if (m_IsPaused) { GameContinue(); } else { GamePause(); } } if (Input.GetKeyDown(KeyCode.E)) { SetGameResult(false ); } if (Input.GetKeyDown(KeyCode.Q)) { SetGameResult(true ); } } private void GameEnd () { Debug.Log("Game End" ); m_AudioSource.Stop(); m_AudioSource.loop = false ; 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); }#endregion #region 外部调用函数 public void SetGameResult (bool result ) { m_GameResult = result; m_CurrentState = GameState.End; }#endregion }
代码说明:
这里,我们自定义了GameInit
、GameStart
、GamePause
、GameContinue
、GameRunning
和GameEnd
这几个游戏状态函数,用于执行对应状态的代码。
其次,我们还使用了协程来实现GameMainLoop
这个函数,用于切换和管理游戏状态
最后,我们使用键盘上的P
来暂停和恢复游戏
,使用Q
来切换至游戏胜利
,使用E
来切换至游戏失败
  编辑完毕之后,我们在场景中新建一个名为GameStateManager
的Empty GameObject
,然后为其添加GameStateManager.cs
,可以看到Unity自动帮我们添加了AudioSource
组件。
GameStateManager
物体上各个组件的属性设置:
Transform
:
GameStateManager.cs
Background Music
: Assets\Audio\Music
文件夹下的MainTheme
GameWinClip
: Assets\Audio\Music
文件夹下的GameWin
GameLoseClip
: Assets\Audio\Music
文件夹下的GameLose
AudioSource
:
Play On Awake
: false
Vloume
: 0.05
  为GameStateManager
添加完组件之后,我们可以听到游戏场景中出现了背景音乐,且Console
窗口输出了对应的游戏状态。此外,当我们按键盘上的P
键时,游戏可以正常暂停和恢复
;当我们按Q
或者E
时,会停止播放背景音乐
,开始播放游戏胜利
或者游戏失败
的音效,并在播放完之后删除场景中的Generator
物体,不再产生新的东西。
  但同时我们发现导弹击中物体时的爆炸音效音量过大,因此我们还需要将Assets\Prefabs\Weapons
文件夹下的MissileExplosion
上AudioSource
组件的Volume
设置为0.2
。
加入游戏胜负条件   有了游戏主逻辑管理器框架
之后,我们开始加入游戏胜负条件。如果我们直接在GameStateManager
加入分数管理的代码
,那么势必会出现GameStateManager
里的代码多且杂
的问题。我们应该让其他Manager
来执行分数管理
的工作,然后让管理整个游戏状态的GameStateManager
来管理其他的Manager
。
  清楚了这一设计思路之后,我们先在Assets\Scripts\Manager
文件夹下创建一个名为ScoreManager
的C#脚本,然后编辑ScoreManager.cs
如下:
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 using System;using System.Collections.Generic;using UnityEngine; [Serializable ]public class ScoreManager { [Tooltip("游戏胜利的目标分数" ) ] public int TargetScore = 5000 ; [Tooltip("保存嘲讽音效" ) ] public AudioClip[] TauntClips; [Tooltip("得分之后播放嘲讽音效的概率" ) ] public float TauntProbaility = 50f ; [Tooltip("嘲讽的间隔" ) ] public float TauntDelay = 1f ; private int m_CurrentScore; private int m_TauntIndex; private float m_LastTauntTime; private bool m_Stop; private Transform m_Player; public void Init (Transform player ) { m_CurrentScore = 0 ; m_TauntIndex = 0 ; m_LastTauntTime = Time.time; m_Stop = false ; m_Player = player; } public void Stop () { m_Stop = true ; } public void AddScore (int score ) { if (m_Stop) { return ; } m_CurrentScore += score; 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; } }
代码说明:
因为我们使用GameStateManager
来管理ScoreManager
,所以ScoreManager
不需要继承MonoBehaviour
因为ScoreManager
没有继承MonoBehaviour
,所以我们需要为ScoreManager
添加Unity提供的Serializable
这一Attribute
,通过自定义序列化的方式 使ScoreManager
能被Unity序列化
因为ScoreManager
没有继承MonoBehaviour
,所以我们不能使用协程
,我们需要使用Time.time
来实现延时执行某段代码
的功能
  编辑完ScoreManager.cs
之后,我们修改GameStateManager.cs
,删除用于测试的代码,并加入管理ScoreManager
的代码:
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 using System.Collections;using System.Collections.Generic;using UnityEngine;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("ScoreManager的实例" ) ] public ScoreManager ScoreManagerInstance = new ScoreManager(); 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(); 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 GamePause () { m_AudioSource.Pause(); Time.timeScale = 0f ; m_IsPaused = true ; } private void GameContinue () { Time.timeScale = 1f ; m_AudioSource.UnPause(); m_IsPaused = false ; } private void GameRunning () { if (Input.GetKeyDown(KeyCode.P)) { if (m_IsPaused) { GameContinue(); } else { GamePause(); } } } private void GameEnd () { m_AudioSource.Stop(); m_AudioSource.loop = false ; 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); }#endregion #region 外部调用函数 public void SetGameResult (bool result ) { m_GameResult = result; m_CurrentState = GameState.End; }#endregion }
  接着,我们在Hierarchy
窗口中选中GameStateManager
物体,可以看到Inspector
窗口多出了一个名为Score Manager Instance
折叠框,且在Score Manager Instance
折叠框下出现了ScoreManager
类里定义的公共字段。
ScoreManager
类定义的公共字段设置:
Target Score
: 5000
Taunt Clips
: Assets\Audio\Player\Taunts
文件夹下的9个音频
Taunt Probaility
: 50
Taunt Delay
: 1
  设置好ScoreManager
类定义的公共字段之后,我们还需要在Remover.cs
的OnTriggerEnter2D
加入设置游戏结果
的代码:
Remover.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System.Collections;using System.Collections.Generic;using UnityEngine; [RequireComponent(typeof(BoxCollider2D)) ]public class Remover : MonoBehaviour { ... private void OnTriggerEnter2D (Collider2D collision ) { if (collision.CompareTag("Player" )) { GameStateManager.Instance.SetGameResult(false ); } Instantiate(SplashPrefab, collision.transform.position, transform.rotation); Destroy(collision.gameObject); } }
  最后,我们还需要修改一下CameraFollow.cs
的LateUpdate
函数:
CameraFollow.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using System.Collections;using System.Collections.Generic;using UnityEngine;public class CameraFollow : MonoBehaviour { ... private void LateUpdate () { if (m_Player != null ) { TrackPlayer(); } } ... }
  修改完成之后,运行游戏,可以看到当我们打死怪物时,Console
窗口会输出我们当前不断增加的分数
,且一定概率会播放嘲讽音效
。此外,当角色死亡或者直接掉进河里时,背景音乐停止播放并播放游戏失败的音效
。
添加击杀怪物得分特效   因为击杀怪物可以得分,所以我们需要添加击杀怪物得分的特效
。
得分特效
的制作步骤:
在场景中新建一个名为Score
的Empty GameObject
,然后将Assets\Sprites\UI
下的numeric-1
和numeric-0
图片拖拽至Score
物体下成为Score
物体的子物体
复制Score
物体下的numeric-0
得到numeric-0 (1)
物体
设置numeric-1
的Position
为(-0.4, 0, 0)
,Sprite Renderer
组件的Sorting Layer
属性为Character
、Order In Layer
属性为10
设置numeric-0
的Position
为(0, 0, 0)
,Sprite Renderer
组件的Sorting Layer
属性为Character
、Order In Layer
属性为10
设置numeric-0 (1)
的Position
为(0.5, 0, 0)
,Sprite Renderer
组件的Sorting Layer
属性为Character
、Order In Layer
属性为10
打开Animation
窗口,选中Hierarchy窗口中的Score
物体,然后点击Animation
窗口中的Create
按钮创建一个名为Score.anim
的动画文件,并将其保存在Assets\Animation\Enemy
文件夹下,最后将Score.controller
文件移至Assets\Animator\Enemy
文件夹下
为Score
物体添加Destrpyer.cs
脚本
点击Animation
窗口的红点按钮
,开始为Score.anim
添加关键帧,添加的关键帧信息如下:
Score.anim
的关键帧:
第一帧
:
frame
: 0
numeric-1:Position
: (-0.4, 0, 0)
numeric-0:Position
: (0, 0, 0)
numeric-0 (1):Position
: (0.5, 0, 0)
第二帧
:
frame
: 5
numeric-1:Position
: (-0.4, 0.25, 0)
第三帧
:
frame
: 10
numeric-0:Position
: (0, 0.31, 0)
第四帧
:
frame
: 30
numeric-1:Position
: (-0.4, 0.78, 0)
numeric-0:Position
: (0, 0.78, 0)
numeric-0 (1):Position
: (0.5, 0.78, 0)
第五帧
:
frame
: 40
numeric-1:Position
: (-0.4, 1.1, 0)
numeric-0:Position
: (0, 1.1, 0)
numeric-0 (1):Position
: (0.5, 1.1, 0)
第六帧
:
frame
: 60
numeric-1:Position
: (-0.4, 1.25, 0)
numeric-0:Position
: (0, 1.25, 0)
numeric-0 (1):Position
: (0.5, 1.25, 0)
在Score.anim
的最后一帧处添加一个Animation Event
,选择调用的函数为DestroyGameObject
将场景中的Score
物体拖拽至Assets\Prefabs\Character
文件夹将其制作为Prefab,然后删除场景中的Score
物体
  至此,我们的得分特效
就制作好了。接下来,我们改写Enemy.cs
,在怪物死亡时生成得分特效:
Enemy.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 using System.Collections;using System.Collections.Generic;using UnityEngine; [RequireComponent(typeof(Wander)) ] [RequireComponent(typeof(Rigidbody2D)) ]public class Enemy : MonoBehaviour { [Tooltip("角色受伤时减少的血量" ) ] public float DamageAmount = 10f ; [Tooltip("角色被怪物伤害时受到的击退力大小" ) ] public float HurtForce = 500f ; [Tooltip("障碍物检测点" ) ] [SerializeField ] private Transform FrontCheck; [Tooltip("怪物的血量" ) ] public float MaxHP = 10f ; [Tooltip("怪物受伤时用来展示的图片" ) ] public Sprite DamagedSprite; [Tooltip("怪物死亡时用来展示的图片" ) ] public Sprite DeadSprite; [Tooltip("怪物死亡时用来展示DeadSprite" ) ] public SpriteRenderer BodySpriteRenderer; [Tooltip("怪物死亡时的音效" ) ] public AudioClip[] DeathClips; [Tooltip("得分特效" ) ] public GameObject ScorePrefab; private Wander m_Wander; private Rigidbody2D m_Rigidbody2D; private LayerMask m_LayerMask; private float m_CurrentHP; private bool m_Hurt; private bool m_Dead; private void Awake () { m_Wander = GetComponent<Wander>(); m_Rigidbody2D = GetComponent<Rigidbody2D>(); } private void Start () { m_LayerMask = LayerMask.GetMask("Obstacle" ); m_CurrentHP = MaxHP; m_Hurt = false ; m_Dead = false ; } private void Update () { if (m_Dead) { return ; } Collider2D[] frontHits = Physics2D.OverlapPointAll(FrontCheck.position, m_LayerMask); if (frontHits.Length > 0 ) { m_Wander.Flip(); } } private void OnCollisionEnter2D (Collision2D collision ) { if (collision.gameObject.CompareTag("Player" )) { collision.gameObject.GetComponent<PlayerHealth>().TakeDamage(this .transform, HurtForce, DamageAmount); } } public void TakeDamage (Transform weapon, float hurtForce, float damage ) { m_CurrentHP -= damage; Vector3 hurtVector = transform.position - weapon.position; m_Rigidbody2D.AddForce(hurtVector.normalized * hurtForce); if (!m_Hurt) { m_Hurt = true ; if (DamagedSprite != null ) { SpriteRenderer[] children = GetComponentsInChildren<SpriteRenderer>(); foreach (SpriteRenderer child in children) { child.enabled = false ; } if (BodySpriteRenderer != null ) { BodySpriteRenderer.enabled = true ; BodySpriteRenderer.sprite = DamagedSprite; } else { Debug.LogError("请设置BodySpriteRenderer" ); } } else { Debug.LogWarning("请设置DamagedSprite" ); } } if (m_CurrentHP <= 0 && !m_Dead) { m_Dead = true ; Death(); } } private void Death () { m_Wander.enabled = false ; if (DeadSprite != null ) { SpriteRenderer[] children = GetComponentsInChildren<SpriteRenderer>(); foreach (SpriteRenderer child in children) { child.enabled = false ; } if (BodySpriteRenderer != null ) { BodySpriteRenderer.enabled = true ; BodySpriteRenderer.sprite = DeadSprite; } else { Debug.LogError("请设置BodySpriteRenderer" ); } } else { Debug.LogWarning("请设置DeadSprite" ); } Collider2D[] cols = GetComponents<Collider2D>(); foreach (Collider2D c in cols) { c.isTrigger = true ; } if (DeathClips != null && DeathClips.Length > 0 ) { int i = Random.Range(0 , DeathClips.Length); AudioSource.PlayClipAtPoint(DeathClips[i], transform.position); } else { Debug.LogWarning("请设置DeathClips" ); } if (ScorePrefab != null ) { Vector3 scorePos = this .transform.position + Vector3.up * 1.5f ; Instantiate(ScorePrefab, scorePos, Quaternion.identity); } else { Debug.LogError("请设置ScorePrefab" ); } GameStateManager.Instance.ScoreManagerInstance.AddScore(100 ); } }
  最后,我们分别选中Assets\Prefabs\Character
文件夹下的AlienShip
和AlienSlug
这两个Prefab,将其Enemy.cs
上的Score Prefab
字段都设置为Assets\Prefabs\Character
文件夹下的Score
物体对应的Prefab。运行游戏,我们可以看到击杀怪物时,怪物上方会出现得分特效。
后言   至此,我们就已经完成了使用单例模式实现游戏游戏主逻辑管理器
的所有功能。最后,本篇文章所做的修改,可以在PotatoGloryTutorial 这个仓库的essay17
分支下看到,读者可以clone这个仓库到本地进行查看。
参考链接
单例模式
Unity-Initialization Events
Unity-Custom serialization