一、框架視圖

二、關(guān)鍵代碼
Model
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 模型 業(yè)務(wù)邏輯
/// </summary>
public class Model : MonoBehaviour {
//定義常量
public const int NORMAl_ROWS = 20; //正常的行數(shù)
public const int MAX_ROWS = 23; //判斷最上面的行數(shù) 多出了一個(gè)圖形
public const int MAX_COLUMNS = 10; //列數(shù)
//定義二維數(shù)組 保存地圖 或者格子 方便檢測下落的方塊是否超出格子之外,否則要暫停
private Transform[,] map = new Transform[MAX_COLUMNS,MAX_ROWS];
private int score = 0; //分?jǐn)?shù)
private int highScore = 0; //最高分?jǐn)?shù)
private int numberGame = 0; //游戲次數(shù)
//設(shè)置相關(guān)屬性值 分?jǐn)?shù) 最高分 游戲次數(shù)
public int Score { get { return score; } }
public int HighScore { get { return highScore; } }
public int NumberGame { get { return numberGame; } }
public bool isDataUpdate = false; //是否更新分?jǐn)?shù) 以及最高分
void Awake() {
//把分?jǐn)?shù)加載進(jìn)來
LoadData();
}
/// <summary>
/// 子物體是否在有效位置
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool IsVaildMapPosition(Transform t) {
foreach (Transform child in t)
{
if (child.tag != "Block") continue;
Vector2 pos = child.position.Round();
if (IsInsideMap(pos) == false) return false;
if (map[(int)pos.x, (int)pos.y] != null) return false;
}
return true;
}
/// <summary>
/// 游戲是否結(jié)束
/// </summary>
/// <returns></returns>
public bool IsGameOver() {
for (int i = NORMAl_ROWS; i < MAX_ROWS; i++) //判斷游戲是否結(jié)束 從20行開始判斷 到23行 是否填滿了 就結(jié)束
{
for (int j = 0; j < MAX_COLUMNS; j++)
{
if (map[j,i]!=null)
{
numberGame++;
SaveData();
return true;
}
}
}
return false;
}
/// <summary>
/// 是否在地圖有效位置內(nèi)
/// </summary>
/// <returns></returns>
private bool IsInsideMap(Vector2 pos) {
return pos.x >= 0 && pos.x < MAX_COLUMNS && pos.y >= 0;
}
/// <summary>
/// 方塊位置判斷 擺放圖形
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool PlaceShape(Transform t) {
foreach (Transform child in t)
{
if (child.tag != "Block") continue;
Vector2 pos = child.position.Round() ;
map[(int)pos.x, (int)pos.y] = child;
}
return CheckMap();
}
/// <summary>
/// 檢查地圖要不要消除行
/// </summary>
/// <returns></returns>
public bool CheckMap() {
int count = 0;
for (int i = 0; i < MAX_ROWS; i++)
{
bool isFull = CheckIsRowFull(i);
if (isFull) //如果返回 true 表示滿 應(yīng)該消除
{
count++; //用來計(jì)算分?jǐn)?shù)的
DeleteRow(i); //滿行執(zhí)行銷毀的函數(shù)
MoveDownRowAbove(i+1); //剩下的方塊往下移
i--; //減1 重新開始判斷是否有滿行 再次判斷
}
}
if (count>0)
{
score += (count*100); //消除行之后增加分?jǐn)?shù)
if (score>highScore) //更新最高分?jǐn)?shù)
{
highScore = score;
}
isDataUpdate = true;
return true;
}
else return false;
}
/// <summary>
/// 檢查是否滿行
/// </summary>
/// <returns></returns>
private bool CheckIsRowFull(int row) {
for (int i = 0; i < MAX_COLUMNS; i++)
{
if (map[i, row] == null) return false; //row行數(shù) 表示不變 判斷列數(shù)是否滿 如果是空表示不滿 返回
}
return true;
}
/// <summary>
/// 刪除行數(shù)
/// </summary>
/// <param name="row"></param>
private void DeleteRow(int row) {
for (int i = 0; i < MAX_COLUMNS; i++)
{
Destroy(map[i,row].gameObject); //銷毀物體
map[i, row] = null; //把這一行置空
}
}
/// <summary>
/// 往下移動方塊 把上面方塊都往下移一行 整體移動
/// </summary>
private void MoveDownRowAbove(int row) {
//遍歷 把所有的方塊都往下移動
for (int i =row ; i < MAX_ROWS; i++) //行數(shù)
{
MoveDownRow(i);//調(diào)用往下移動的函數(shù)
}
}
/// <summary>
/// 把這一行往下移
/// </summary>
/// <param name="row">通過行數(shù)來判斷</param>
private void MoveDownRow(int row) {
for (int i = 0; i < MAX_COLUMNS; i++) //列數(shù)
{
if (map[i,row]!=null)
{
map[i, row - 1] = map[i, row]; //把這一行賦值給下一個(gè)位置
map[i, row] = null;//把這一行的置空
map[i, row - 1].position += new Vector3(0,-1,0); //位置信息往下 把y位置減1
}
}
}
/// <summary>
/// 加載分?jǐn)?shù)
/// </summary>
private void LoadData() {
highScore = PlayerPrefs.GetInt("HighScore",0);
numberGame = PlayerPrefs.GetInt("NumberGame",0);
}
/// <summary>
/// 儲存數(shù)據(jù)
/// </summary>
private void SaveData() {
PlayerPrefs.SetInt("HighScore",highScore);
PlayerPrefs.SetInt("NumberGame",numberGame);
}
/// <summary>
/// 重新開始的 方法 游戲結(jié)束點(diǎn)擊重新開始按鈕執(zhí)行的方法
/// </summary>
public void Restart() {
//清空地圖
for (int i = 0; i < MAX_COLUMNS; i++) //列數(shù)
{
for (int j = 0; j < MAX_ROWS; j++) //行數(shù)
{
if (map[i,j]!=null)
{
Destroy(map[i,j].gameObject);
map[i, j] = null;
}
}
}
score = 0; //分?jǐn)?shù)重置
}
/// <summary>
/// 清除數(shù)據(jù)
/// </summary>
public void ClearData() {
score = 0;
highScore = 0;
numberGame = 0;
SaveData(); //清除完之后把之前預(yù)先儲存的也根據(jù)清空后的數(shù)據(jù)進(jìn)行保存
}
}
View
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class View : MonoBehaviour {
private Ctrl ctrl;
//定義類
private RectTransform logoName;
private RectTransform menuUI;
private RectTransform gameUI;
//游戲?qū)ο? private GameObject restartButton;
private GameObject gameOverUI;
private GameObject settingUI;
private GameObject rankUI;
private GameObject mute;
//文本
private Text score;
private Text highScore;
private Text gameOverScore;
private Text rankScore;
private Text rankHighScore;
private Text rankNumberGame;
void Awake() {
ctrl = GameObject.FindGameObjectWithTag("Ctrl").GetComponent<Ctrl>();
//通過尋找 賦值 注意界面中不要存在空格
logoName = transform.Find("Canvas/LogoName") as RectTransform;
menuUI = transform.Find("Canvas/MenuUI") as RectTransform;
gameUI = transform.Find("Canvas/GameUI") as RectTransform;
restartButton = transform.Find("Canvas/MenuUI/RestartButton").gameObject;
gameOverUI = transform.Find("Canvas/GameOverUI").gameObject;
settingUI = transform.Find("Canvas/SettingUI").gameObject;
rankUI = transform.Find("Canvas/RankUI").gameObject;
mute = transform.Find("Canvas/SettingUI/AudioButton/Mute").gameObject;
score = transform.Find("Canvas/GameUI/ScoreLabel/Text").GetComponent<Text>();
highScore = transform.Find("Canvas/GameUI/HighScoreLabel/Text").GetComponent<Text>();
gameOverScore = transform.Find("Canvas/GameOverUI/Text").GetComponent<Text>();
rankScore = transform.Find("Canvas/RankUI/ScoreLabel/Text").GetComponent<Text>();
rankHighScore = transform.Find("Canvas/RankUI/HighScoreLabel/Text").GetComponent<Text>();
rankNumberGame = transform.Find("Canvas/RankUI/NumberGameLabel/Text").GetComponent<Text>();
Debug.Log("打印了 view 視圖 腳本?。?);
}
/// <summary>
/// 顯示菜單欄
/// </summary>
public void ShowMenu() {
logoName.gameObject.SetActive(true);
logoName.DOAnchorPosY(-198f,0.5f);
menuUI.gameObject.SetActive(true);
menuUI.DOAnchorPosY(60f,0.5f);
}
/// <summary>
/// 隱藏菜單欄
/// </summary>
public void HideMenu() {
logoName.DOAnchorPosY(198f, 0.5f).OnComplete(delegate { logoName.gameObject.SetActive(false); });
menuUI.DOAnchorPosY(-60f, 0.5f).OnComplete(delegate { menuUI.gameObject.SetActive(false); });
}
/// <summary>
/// 更新UI分?jǐn)?shù)
/// </summary>
/// <param name="score"></param>
/// <param name="highScore"></param>
public void UpdateGameUI(int score,int highScore) {
this.score.text = score.ToString();
this.highScore.text = highScore.ToString();
}
/// <summary>
/// 顯示游戲分?jǐn)?shù)的界面
/// </summary>
public void ShowGameUI(int score=0,int highScore=0) {
this.score.text = score.ToString(); //更新分?jǐn)?shù)
this.highScore.text = highScore.ToString();//更新最高分?jǐn)?shù)
gameUI.gameObject.SetActive(true);
gameUI.DOAnchorPosY(-168.8f,0.5f);
}
/// <summary>
/// 隱藏游戲分?jǐn)?shù)的界面
/// </summary>
public void HideGameUI() {
gameUI.DOAnchorPosY(168.8f,0.5f).OnComplete(delegate { gameUI.gameObject.SetActive(false); });
}
/// <summary>
/// 顯示重新開始的按鈕
/// </summary>
public void ShowRestartButton() {
restartButton.SetActive(true);
}
/// <summary>
/// 顯示游戲結(jié)束的分?jǐn)?shù)
/// </summary>
/// <param name="score">把分?jǐn)?shù)傳遞過來賦值</param>
public void ShowGameOverUI(int score=0) {
gameOverUI.SetActive(true);
gameOverScore.text = score.ToString();
}
/// <summary>
/// 隱藏游戲結(jié)束的界面 點(diǎn)擊重新開始隱藏的UI
/// </summary>
public void HideGameOverUI() {
gameOverUI.SetActive(false);
}
/// <summary>
/// 點(diǎn)擊主頁按鈕
/// </summary>
public void OnHomeButtonClick() {
ctrl.audioManager.PlayCursor();
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); //加載當(dāng)前場景
}
/// <summary>
/// 點(diǎn)擊設(shè)置按鈕
/// </summary>
public void OnSettingButtonClick() {
ctrl.audioManager.PlayCursor();
settingUI.SetActive(true);
}
/// <summary>
/// 設(shè)置是否可以靜音
/// </summary>
/// <param name="isActive"></param>
public void SetMuteAtcive(bool isActive) {
mute.SetActive(isActive);
}
/// <summary>
/// 點(diǎn)擊設(shè)置UI的界面其他地方時(shí)候 把UI界面隱藏掉
/// </summary>
public void OnSettingUIClick() {
ctrl.audioManager.PlayCursor();
settingUI.SetActive(false);
}
/// <summary>
/// 顯示排行榜的分?jǐn)?shù)界面
/// </summary>
public void ShowRankUI(int score,int highScore,int numberGame) {
this.rankScore.text = score.ToString();
this.rankHighScore.text = highScore.ToString();
this.rankNumberGame.text = numberGame.ToString();
rankUI.SetActive(true);
}
/// <summary>
/// 點(diǎn)擊排行榜其他地方 隱藏排行榜
/// </summary>
public void OnRankUIClick() {
rankUI.SetActive(false);
}
}
Ctrl
1、 Ctrl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 管理類
/// </summary>
public class Ctrl : MonoBehaviour {
//持有model view的引用 定義 類的 類型
[HideInInspector] //在面板上隱藏
public Model model;
[HideInInspector]
public View view;
[HideInInspector]
public CameraManager cameraManager;
[HideInInspector]
public GameManager gameManager;
[HideInInspector]
public AudioManager audioManager;
//有限狀態(tài)機(jī)
private FSMSystem fsm;
/// <summary>
/// 持有model層和視覺層引用
/// </summary>
void Awake()
{
//通過便簽去尋找對應(yīng)的游戲?qū)ο髵煸诘哪_本
model = GameObject.FindGameObjectWithTag("Model").transform.GetComponent<Model>(); //業(yè)務(wù)邏輯層
view = GameObject.FindGameObjectWithTag("View").transform.GetComponent<View>(); //視覺層
cameraManager = GetComponent<CameraManager>(); //持有攝像機(jī)的管理引用
audioManager = GetComponent<AudioManager>(); //持有音效類管理的引用
}
void Start () {
MakeFSM(); //構(gòu)造狀態(tài)機(jī) 放在awake可能會空指針
}
void Update () {
}
/// <summary>
/// 構(gòu)造有限狀態(tài)機(jī)
/// </summary>
void MakeFSM() {
fsm = new FSMSystem(); //實(shí)例化狀態(tài)機(jī)
FSMState[] states = GetComponentsInChildren<FSMState>(); //獲取子物體下所有狀態(tài)機(jī)的狀態(tài)
foreach (FSMState state in states)
{
fsm.AddState(state,this);
}
//設(shè)置當(dāng)前狀態(tài)是菜單狀態(tài)
MenuState s = GetComponentInChildren<MenuState>();
fsm.SetCurrentState(s);
}
}
2、AudioManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour {
private Ctrl ctrl; //定義控制類
//聲音片段
public AudioClip cursor;
public AudioClip drop;
public AudioClip control;
public AudioClip lineclear;
private AudioSource audioSource;
private bool isMute = false; //是否靜音
void Awake() {
audioSource = GetComponent<AudioSource>();
ctrl = GetComponent<Ctrl>();
}
/// <summary>
/// 播放光標(biāo)的音效
/// </summary>
public void PlayCursor() {
PlayAudio(cursor);
}
/// <summary>
/// 播放掉落的聲效
/// </summary>
public void PlayDrop() {
PlayAudio(drop);
}
/// <summary>
/// 播放控制的聲效
/// </summary>
public void PlayControl() {
PlayAudio(control);
}
/// <summary>
/// 清除聲效
/// </summary>
public void PlayLineClear() {
PlayAudio(lineclear);
}
/// <summary>
/// 播放音效的函數(shù)
/// </summary>
/// <param name="clip"></param>
private void PlayAudio(AudioClip clip) {
if (isMute) return;
audioSource.clip = clip;
audioSource.Play();
}
/// <summary>
/// 點(diǎn)擊靜音按鈕
/// </summary>
public void OnAudioButtonClick() {
isMute = !isMute; //取反
ctrl.view.SetMuteAtcive(isMute); //調(diào)用是否顯示斜線的狀態(tài)
if (isMute==false) //是否播放聲效 取消的時(shí)候播放聲效
{
PlayCursor();
}
}
}
3、CameraManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class CameraManager : MonoBehaviour {
private Camera mainCamera;
void Awake()
{
mainCamera = Camera.main;
//mainCamera = transform.Find("MainCamera").GetComponent<Camera>();
}
/// <summary>
/// 放大
/// </summary>
public void ZoomIn() {
mainCamera.DOOrthoSize(13.8f,0.5f);
}
/// <summary>
/// 縮小
/// </summary>
public void ZoomOut() {
mainCamera.DOOrthoSize(8.8f,0.5f);
Debug.Log("相機(jī)縮小了");
}
}
4、GameManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 游戲控制器
/// </summary>
public class GameManager : MonoBehaviour {
private bool isPause = true; //游戲是否暫停
private Shape currentShape = null; //設(shè)置當(dāng)前的形狀為空
private Ctrl ctrl; //申明控制類
private Transform blockHolder; //方塊的處理
public Shape[] shapes; //方塊數(shù)組
public Color[] colors; //顏色的數(shù)組
private void Awake() {
ctrl = GetComponent<Ctrl>();
blockHolder = transform.Find("BlockHolder");
}
void Update() {
if (isPause) return;
if (currentShape == null)
{
SpawnShape(); //執(zhí)行生成方塊的方法
}
}
/// <summary>
/// 清除方塊 就是重新開始之后清空方塊的方法
/// </summary>
public void CleraShape() {
if (currentShape != null)
{
Destroy(currentShape.gameObject); //把當(dāng)前的圖形清空
currentShape = null;
}
}
/// <summary>
/// 開始游戲
/// </summary>
public void StartGame() {
isPause = false;
if (currentShape != null)
currentShape.Resume();
}
/// <summary>
/// 暫停游戲
/// </summary>
public void PauseGame() {
isPause = false;
if (currentShape != null)
currentShape.Pause();
}
/// <summary>
/// 生成方塊
/// </summary>
void SpawnShape() {
int index = Random.Range(0,shapes.Length);
int indexColor = Random.Range(0,colors.Length);
currentShape = GameObject.Instantiate(shapes[index]);
currentShape.transform.parent = blockHolder;
currentShape.Init(colors[indexColor],ctrl,this);
}
/// <summary>
/// 方塊掉落
/// </summary>
public void FallDown() {
currentShape = null;
//下落過程中 判斷是否更新分?jǐn)?shù)
if (ctrl.model.isDataUpdate)
{
ctrl.view.UpdateGameUI(ctrl.model.Score,ctrl.model.HighScore);
}
//消除子物體之后 把相關(guān)的父類也銷毀 相當(dāng)于垃圾清理回收
foreach (Transform t in blockHolder)
{
if (t.childCount<=1) //判斷底下只有一個(gè)節(jié)點(diǎn)的話 就銷毀掉
{
Destroy(t.gameObject);
}
}
if (ctrl.model.IsGameOver())
{
PauseGame();
ctrl.view.ShowGameOverUI(ctrl.model.Score);
}
}
}
5、Shape
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shape : MonoBehaviour {
private Transform pivot; //設(shè)置圖形的錨點(diǎn)
private Ctrl ctrl; //聲明控制類
private GameManager gameManager; //游戲管理類
private bool isPause = false; //是否暫停游戲
private bool isSpeedup = false; //上升速度
private float timer = 0; //設(shè)置時(shí)間間隔
private float stepTime =0.8f; //每一步的所需要的時(shí)間
private int mutiple = 15; //往下掉落的加速倍數(shù)
void Start () {
pivot = transform.Find("Pivot"); //獲取錨點(diǎn) 用來控制旋轉(zhuǎn)的
}
void Update () {
if (isPause) return;
timer += Time.deltaTime; //暫停的話直接返回
if (timer>stepTime) //計(jì)時(shí)器大于每一格設(shè)定的時(shí)間外 執(zhí)行下落的函數(shù)
{
timer = 0; //計(jì)時(shí)器歸零
Fall(); //下落的函數(shù)
}
InputControl(); //監(jiān)聽輸入控制的方法
}
/// <summary>
/// 實(shí)例化方塊顏色
/// </summary>
public void Init(Color color,Ctrl ctrl,GameManager gameManager) {
foreach (Transform t in transform)
{
if (t.tag=="Block")
{
t.GetComponent<SpriteRenderer>().color = color;
}
}
this.ctrl = ctrl;
this.gameManager = gameManager;
}
/// <summary>
/// 方塊下落
/// </summary>
private void Fall() {
Vector3 pos = transform.position;
pos.y = -1;
transform.position = pos;
//判斷位置是否可用
if (ctrl.model.IsVaildMapPosition(this.transform)==false) //超出位置或者位置為占用
{
pos.y += 1;
transform.position = pos;
isPause = true;
bool isLineClear = ctrl.model.PlaceShape(this.transform);
if (isLineClear) ctrl.audioManager.PlayLineClear(); //播放銷毀的聲音
gameManager.FallDown();
return;
}
ctrl.audioManager.PlayDrop();//播放方塊掉落的聲效
}
/// <summary>
/// 輸入控制
/// </summary>
private void InputControl() {
float h = 0;
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
h = -1;
}
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
h = 1;
}
if (h!=0)
{
//移動
Vector3 pos = transform.position;
pos.x += h;
transform.position = pos;
//是否碰到別的方塊
if (ctrl.model.IsVaildMapPosition(this.transform)==false)
{
//如果不能移動 把位置移回去
pos.x -= h;
transform.position = pos;
}
else
{
//播放 控制的聲效
ctrl.audioManager.PlayControl();
}
}
//控制旋轉(zhuǎn)
if (Input.GetKeyDown(KeyCode.UpArrow))
{
transform.RotateAround(pivot.position,Vector3.forward,-90); //可旋轉(zhuǎn) 參數(shù)1:圍繞那個(gè)點(diǎn)旋轉(zhuǎn) 參數(shù)2:圍繞Z軸旋轉(zhuǎn) 參數(shù)3:旋轉(zhuǎn)角度
if (ctrl.model.IsVaildMapPosition(this.transform)==false) //不可旋轉(zhuǎn) 把角度調(diào)回去
{
transform.RotateAround(pivot.position,Vector3.forward,90);
}
else
{
ctrl.audioManager.PlayControl();//播放控制的聲音
}
//下降加速
if (Input.GetKeyDown(KeyCode.DownArrow))
{
isSpeedup = true;
stepTime /= mutiple; //下降時(shí)間加速
}
}
}
/// <summary>
/// 暫停
/// </summary>
public void Pause() {
isPause = true;
}
/// <summary>
/// 恢復(fù)
/// </summary>
public void Resume() {
isPause = false;
}
}
Tools
Vector3Extension
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 工具類
/// </summary>
public static class Vector3Extension {
public static Vector2 Round(this Vector3 v) {
int x = Mathf.RoundToInt(v.x);
int y = Mathf.RoundToInt(v.y);
return new Vector2(x,y);
}
}
FSM 有限狀態(tài)機(jī)
1、FSMSystem
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/**
A Finite State Machine System based on Chapter 3.1 of Game Programming Gems 1 by Eric Dybsand
Written by Roberto Cezar Bianchini, July 2010
How to use:
1. Place the labels for the transitions and the states of the Finite State System
in the corresponding enums.
2. Write new class(es) inheriting from FSMState and fill each one with pairs (transition-state).
These pairs represent the state S2 the FSMSystem should be if while being on state S1, a
transition T is fired and state S1 has a transition from it to S2. Remember this is a Deterministic FSM.
You can't have one transition leading to two different states.
Method Reason is used to determine which transition should be fired.
You can write the code to fire transitions in another place, and leave this method empty if you
feel it's more appropriate to your project.
Method Act has the code to perform the actions the NPC is supposed do if it's on this state.
You can write the code for the actions in another place, and leave this method empty if you
feel it's more appropriate to your project.
3. Create an instance of FSMSystem class and add the states to it.
4. Call Reason and Act (or whichever methods you have for firing transitions and making the NPCs
behave in your game) from your Update or FixedUpdate methods.
Asynchronous transitions from Unity Engine, like OnTriggerEnter, SendMessage, can also be used,
just call the Method PerformTransition from your FSMSystem instance with the correct Transition
when the event occurs.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/// <summary>
/// Place the labels for the Transitions in this enum.
/// Don't change the first label, NullTransition as FSMSystem class uses it.
/// </summary>
public enum Transition
{
NullTransition = 0, // Use this transition to represent a non-existing transition in your system
StartButtonClick,
PauseButtonClick
}
/// <summary>
/// Place the labels for the States in this enum.
/// Don't change the first label, NullTransition as FSMSystem class uses it.
/// 游戲狀態(tài) 枚舉類型
/// </summary>
public enum StateID
{
NullStateID = 0, // Use this ID to represent a non-existing State in your system
Menu,
Play,
Pause,
GameOver
}
/// <summary>
/// This class represents the States in the Finite State System.
/// Each state has a Dictionary with pairs (transition-state) showing
/// which state the FSM should be if a transition is fired while this state
/// is the current state.
/// Method Reason is used to determine which transition should be fired .
/// Method Act has the code to perform the actions the NPC is supposed do if it's on this state.
/// </summary>
public abstract class FSMState:MonoBehaviour
{
protected Ctrl ctrl;
public Ctrl CTRL { set { ctrl = value; } } //設(shè)置值
protected FSMSystem fsm;
public FSMSystem FSM { set { fsm = value; } } //屬性設(shè)置值
protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
protected StateID stateID;
public StateID ID { get { return stateID; } }
public void AddTransition(Transition trans, StateID id)
{
// Check if anyone of the args is invalid
if (trans == Transition.NullTransition)
{
Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
return;
}
if (id == StateID.NullStateID)
{
Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
return;
}
// Since this is a Deterministic FSM,
// check if the current transition was already inside the map
if (map.ContainsKey(trans))
{
Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
"Impossible to assign to another state");
return;
}
map.Add(trans, id);
}
/// <summary>
/// This method deletes a pair transition-state from this state's map.
/// If the transition was not inside the state's map, an ERROR message is printed.
/// </summary>
public void DeleteTransition(Transition trans)
{
// Check for NullTransition
if (trans == Transition.NullTransition)
{
Debug.LogError("FSMState ERROR: NullTransition is not allowed");
return;
}
// Check if the pair is inside the map before deleting
if (map.ContainsKey(trans))
{
map.Remove(trans);
return;
}
Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
" was not on the state's transition list");
}
/// <summary>
/// This method returns the new state the FSM should be if
/// this state receives a transition and
/// </summary>
public StateID GetOutputState(Transition trans)
{
// Check if the map has this transition
if (map.ContainsKey(trans))
{
return map[trans];
}
return StateID.NullStateID;
}
/// <summary>
/// This method is used to set up the State condition before entering it.
/// It is called automatically by the FSMSystem class before assigning it
/// to the current state.
/// </summary>
public virtual void DoBeforeEntering() { }
/// <summary>
/// This method is used to make anything necessary, as reseting variables
/// before the FSMSystem changes to another one. It is called automatically
/// by the FSMSystem before changing to a new state.
/// </summary>
public virtual void DoBeforeLeaving() { }
/// <summary>
/// This method decides if the state should transition to another on its list
/// NPC is a reference to the object that is controlled by this class
/// 改成虛函數(shù)
/// </summary>
public virtual void Reason() { }
/// <summary>
/// This method controls the behavior of the NPC in the game World.
/// Every action, movement or communication the NPC does should be placed here
/// NPC is a reference to the object that is controlled by this class
/// 改成虛函數(shù)
/// </summary>
public virtual void Act() { }
} // class FSMState
/// <summary>
/// FSMSystem class represents the Finite State Machine class.
/// It has a List with the States the NPC has and methods to add,
/// delete a state, and to change the current state the Machine is on.
/// </summary>
public class FSMSystem
{
private List<FSMState> states;
// The only way one can change the state of the FSM is by performing a transition
// Don't change the CurrentState directly
private StateID currentStateID;
public StateID CurrentStateID { get { return currentStateID; } }
private FSMState currentState;
public FSMState CurrentState { get { return currentState; } }
public FSMSystem()
{
states = new List<FSMState>();
}
/// <summary>
/// 設(shè)置當(dāng)前默認(rèn)狀態(tài) 和ID
/// </summary>
public void SetCurrentState(FSMState s) {
currentState = s;
currentStateID = s.ID;
s.DoBeforeEntering(); //調(diào)用默認(rèn)的狀態(tài) 之后切換自己調(diào)用
}
/// <summary>
/// This method places new states inside the FSM,
/// or prints an ERROR message if the state was already inside the List.
/// First state added is also the initial state.
/// </summary>
public void AddState(FSMState s,Ctrl ctrl)
{
// Check for Null reference before deleting
if (s == null)
{
Debug.LogError("FSM ERROR: Null reference is not allowed");
}
s.FSM = this;
s.CTRL = ctrl;
// First State inserted is also the Initial state,
// the state the machine is in when the simulation begins
if (states.Count == 0)
{
states.Add(s);
// currentState = s;
// currentStateID = s.ID;
return;
}
// Add the state to the List if it's not inside it
foreach (FSMState state in states)
{
if (state.ID == s.ID)
{
Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() +
" because state has already been added");
return;
}
}
states.Add(s);
}
/// <summary>
/// This method delete a state from the FSM List if it exists,
/// or prints an ERROR message if the state was not on the List.
/// </summary>
public void DeleteState(StateID id)
{
// Check for NullState before deleting
if (id == StateID.NullStateID)
{
Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state");
return;
}
// Search the List and delete the state if it's inside it
foreach (FSMState state in states)
{
if (state.ID == id)
{
states.Remove(state);
return;
}
}
Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() +
". It was not on the list of states");
}
/// <summary>
/// This method tries to change the state the FSM is in based on
/// the current state and the transition passed. If current state
/// doesn't have a target state for the transition passed,
/// an ERROR message is printed.
/// </summary>
public void PerformTransition(Transition trans)
{
// Check for NullTransition before changing the current state
if (trans == Transition.NullTransition)
{
Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition");
return;
}
// Check if the currentState has the transition passed as argument
StateID id = currentState.GetOutputState(trans);
if (id == StateID.NullStateID)
{
Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " +
" for transition " + trans.ToString());
return;
}
// Update the currentStateID and currentState
currentStateID = id;
foreach (FSMState state in states)
{
if (state.ID == currentStateID)
{
// Do the post processing of the state before setting the new one
currentState.DoBeforeLeaving();
currentState = state;
// Reset the state to its desired condition before it can reason or act
currentState.DoBeforeEntering();
break;
}
}
} // PerformTransition()
} //class FSMSystem
2、MenuState
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MenuState : FSMState {
private void Awake() {
stateID = StateID.Menu;
AddTransition(Transition.StartButtonClick,StateID.Play); //監(jiān)聽狀態(tài)是否切換
}
/// <summary>
/// 重寫方法 顯示菜單欄
/// </summary>
public override void DoBeforeEntering()
{
ctrl.view.ShowMenu();
Debug.Log("執(zhí)行了--ctrl.view.ShowMenu();");
ctrl.cameraManager.ZoomOut();
}
/// <summary>
/// 隱藏菜單欄
/// </summary>
public override void DoBeforeLeaving()
{
ctrl.view.HideMenu();
}
/// <summary>
/// 點(diǎn)擊開始按鈕
/// </summary>
public void OnStartButtonClick() {
ctrl.audioManager.PlayCursor(); //播放鼠標(biāo)的聲音
fsm.PerformTransition(Transition.StartButtonClick);
}
/// <summary>
/// 點(diǎn)擊排行榜的按鈕
/// </summary>
public void OnRankButtonClick() {
ctrl.audioManager.PlayCursor(); //播放鼠標(biāo)的聲音
ctrl.view.ShowRankUI(ctrl.model.Score,ctrl.model.HighScore,ctrl.model.NumberGame);
}
/// <summary>
/// 點(diǎn)擊排行榜的清空按鈕
/// </summary>
public void OnDestroyButtonClick() {
//ctrl.audioManager.PlayLineClear();
ctrl.model.ClearData();
OnRankButtonClick();
}
/// <summary>
/// 重新開始的功能
/// </summary>
public void OnRestartButtonClick() {
ctrl.model.ClearData();
ctrl.gameManager.CleraShape();
fsm.PerformTransition(Transition.StartButtonClick);
}
}
3、PlayState
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayState : FSMState
{
private void Awake() {
stateID = StateID.Play;
AddTransition(Transition.PauseButtonClick,StateID.Menu);
}
public override void DoBeforeEntering() //進(jìn)入這個(gè)狀態(tài)之前
{
ctrl.view.ShowGameUI(ctrl.model.Score,ctrl.model.HighScore); //顯示分?jǐn)?shù)
ctrl.cameraManager.ZoomIn(); //放大
ctrl.gameManager.StartGame(); //開始游戲
}
public override void DoBeforeLeaving() //關(guān)閉這個(gè)狀態(tài)之后
{
ctrl.view.HideGameUI();
ctrl.view.ShowRestartButton();
ctrl.gameManager.PauseGame();
}
/// <summary>
/// 暫停按鈕
/// </summary>
public void OnPauseButtonClick() {
ctrl.audioManager.PlayCursor();
fsm.PerformTransition(Transition.PauseButtonClick);
}
/// <summary>
/// 游戲結(jié)束重新開始時(shí) 重新開始的按鈕
/// </summary>
public void OnRestartButtonClick() {
ctrl.view.HideGameOverUI(); //隱藏游戲結(jié)束的界面
ctrl.model.Restart();//執(zhí)行重新開始的邏輯
ctrl.gameManager.StartGame();//執(zhí)行游戲重新開始的方法
ctrl.view.UpdateGameUI(0,ctrl.model.HighScore); //執(zhí)行分?jǐn)?shù)更新的UI 改變分?jǐn)?shù) 參數(shù)1:0 參數(shù)2:最高分
}
}
三、效果展示



