AR开发实战Vuforia项目之俄罗斯方块(MVC架构)下

一、框架视图

二、关键代码

Model

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 模型 业务逻辑
/// </summary>
public class Model : MonoBehaviour {

    //定义常量
    public const int NORMAl_ROWS = 20; //正常的行数
    public const int MAX_ROWS = 23; //判断最上面的行数 多出了一个图形
    public const int MAX_COLUMNS = 10;  //列数

    //定义二维数组  保存地图 或者格子  方便检测下落的方块是否超出格子之外,否则要暂停
    private Transform[,] map = new Transform[MAX_COLUMNS,MAX_ROWS];

    private int score = 0; //分数
    private int highScore = 0;  //最高分数
    private int numberGame = 0; //游戏次数

    //设置相关属性值 分数 最高分 游戏次数
    public int Score { get { return score; } }
    public int HighScore { get { return highScore; } }
    public int NumberGame { get { return numberGame; } }


    public bool isDataUpdate = false;   //是否更新分数 以及最高分


    void Awake() {
        //把分数加载进来
        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>
    /// 游戏是否结束
    /// </summary>
    /// <returns></returns>
    public bool IsGameOver() {

        for (int i = NORMAl_ROWS; i < MAX_ROWS; i++) //判断游戏是否结束 从20行开始判断  到23行 是否填满了 就结束
        {

            for (int j = 0; j < MAX_COLUMNS; j++)
            {
                if (map[j,i]!=null)
                {
                    numberGame++;
                    SaveData();
                    return true;
                }
            }
        }


        return false;
    }

    /// <summary>
    /// 是否在地图有效位置内
    /// </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 表示满 应该消除
            {
                count++; //用来计算分数的
                DeleteRow(i); //满行执行销毁的函数
                MoveDownRowAbove(i+1); //剩下的方块往下移
                i--; //减1  重新开始判断是否有满行 再次判断
            }

        }

        if (count>0)
        {
            score += (count*100);   //消除行之后增加分数
            if (score>highScore) //更新最高分数
            {
                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行数 表示不变  判断列数是否满   如果是空表示不满 返回
        }

        return true;
    }

    /// <summary>
    /// 删除行数 
    /// </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++)  //行数
        {
            
            MoveDownRow(i);//调用往下移动的函数
        }

    }

   /// <summary>
   /// 把这一行往下移
   /// </summary>
   /// <param name="row">通过行数来判断</param>
    private void MoveDownRow(int row) {

        for (int i = 0; i < MAX_COLUMNS; i++)  //列数
        {
            if (map[i,row]!=null)
            {
                map[i, row - 1] = map[i, row]; //把这一行赋值给下一个位置
                map[i, row] = null;//把这一行的置空
                map[i, row - 1].position += new Vector3(0,-1,0); //位置信息往下  把y位置减1
            }
        }
    }


    /// <summary>
    /// 加载分数
    /// </summary>
    private void LoadData() {

        highScore = PlayerPrefs.GetInt("HighScore",0);
        numberGame = PlayerPrefs.GetInt("NumberGame",0);
    
    }

    /// <summary>
    /// 储存数据
    /// </summary>
    private void SaveData() {

        PlayerPrefs.SetInt("HighScore",highScore);
        PlayerPrefs.SetInt("NumberGame",numberGame);
    }

    /// <summary>
    /// 重新开始的 方法  游戏结束点击重新开始按钮执行的方法
    /// </summary>
    public void Restart() {

        //清空地图
        for (int i = 0; i < MAX_COLUMNS; i++) //列数
        {
            for (int j = 0; j < MAX_ROWS; j++)  //行数
            {
                if (map[i,j]!=null)
                {
                    Destroy(map[i,j].gameObject);
                    map[i, j] = null;
                }
            }
        }

        score = 0; //分数重置

    }

    /// <summary>
    /// 清除数据
    /// </summary>
    public void ClearData() {

        score = 0;
        highScore = 0;
        numberGame = 0;
        SaveData(); //清除完之后把之前预先储存的也根据清空后的数据进行保存

    }
}

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;
    //游戏对象
    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分数
    /// </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>
    /// 显示游戏分数的界面
    /// </summary>
    public void ShowGameUI(int score=0,int highScore=0) {

        this.score.text = score.ToString(); //更新分数
        this.highScore.text = highScore.ToString();//更新最高分数
        gameUI.gameObject.SetActive(true);
        gameUI.DOAnchorPosY(-168.8f,0.5f);


    }

    /// <summary>
    /// 隐藏游戏分数的界面
    /// </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>
    /// 显示游戏结束的分数
    /// </summary>
    /// <param name="score">把分数传递过来赋值</param>
    public void ShowGameOverUI(int score=0) {

        gameOverUI.SetActive(true);
        gameOverScore.text = score.ToString();

    }

    /// <summary>
    /// 隐藏游戏结束的界面  点击重新开始隐藏的UI
    /// </summary>
    public void HideGameOverUI() {

        gameOverUI.SetActive(false);
    }

    /// <summary>
    /// 点击主页按钮
    /// </summary>
    public void OnHomeButtonClick() {

        ctrl.audioManager.PlayCursor();
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); //加载当前场景

    }

    /// <summary>
    /// 点击设置按钮
    /// </summary>
    public void OnSettingButtonClick() {
        ctrl.audioManager.PlayCursor();
        settingUI.SetActive(true);

    }

    /// <summary>
    /// 设置是否可以静音
    /// </summary>
    /// <param name="isActive"></param>
    public void SetMuteAtcive(bool isActive) {

        mute.SetActive(isActive);
    }

    /// <summary>
    /// 点击设置UI的界面其他地方时候  把UI界面隐藏掉
    /// </summary>
    public void OnSettingUIClick() {
        ctrl.audioManager.PlayCursor();
        settingUI.SetActive(false);
    }

    /// <summary>
    /// 显示排行榜的分数界面
    /// </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>
    /// 点击排行榜其他地方  隐藏排行榜 
    /// </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;

    //有限状态机
    private FSMSystem fsm;


    /// <summary>
    /// 持有model层和视觉层引用
    /// </summary>
    void Awake()
    {
        //通过便签去寻找对应的游戏对象挂在的脚本
        model = GameObject.FindGameObjectWithTag("Model").transform.GetComponent<Model>();  //业务逻辑层

        view = GameObject.FindGameObjectWithTag("View").transform.GetComponent<View>(); //视觉层

        cameraManager = GetComponent<CameraManager>();  //持有摄像机的管理引用

        audioManager = GetComponent<AudioManager>();    //持有音效类管理的引用
    }


    void Start () {
        MakeFSM();  //构造状态机  放在awake可能会空指针
    }
    

    void Update () {
        
    }

    /// <summary>
    /// 构造有限状态机
    /// </summary>
    void MakeFSM() {

        fsm = new FSMSystem(); //实例化状态机

        FSMState[] states = GetComponentsInChildren<FSMState>();    //获取子物体下所有状态机的状态

        foreach (FSMState state in states)
        {
            fsm.AddState(state,this);
        }

        //设置当前状态是菜单状态
        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>
    /// 播放光标的音效
    /// </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>
    /// 播放音效的函数
    /// </summary>
    /// <param name="clip"></param>
    private void PlayAudio(AudioClip clip) {

        if (isMute) return;
        audioSource.clip = clip;
        audioSource.Play();

    }

    /// <summary>
    /// 点击静音按钮
    /// </summary>
    public void OnAudioButtonClick() {

        isMute = !isMute; //取反
        ctrl.view.SetMuteAtcive(isMute); //调用是否显示斜线的状态
        if (isMute==false)  //是否播放声效  取消的时候播放声效
        {
            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("相机缩小了");
    }

}

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;  //设置当前的形状为空

    private Ctrl ctrl;  //申明控制类

    private Transform blockHolder;  //方块的处理

    public  Shape[] shapes; //方块数组

    public Color[] colors;  //颜色的数组


    private void Awake() {

        ctrl = GetComponent<Ctrl>();
        blockHolder = transform.Find("BlockHolder");
    }


    void Update() {
        if (isPause) return;
        if (currentShape == null)
        {
            SpawnShape(); //执行生成方块的方法
        }
    }

    /// <summary>
    /// 清除方块  就是重新开始之后清空方块的方法
    /// </summary>
    public void CleraShape() {

        if (currentShape != null)
        {
            Destroy(currentShape.gameObject);  //把当前的图形清空
            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;
        
        //下落过程中 判断是否更新分数
        if (ctrl.model.isDataUpdate)
        {
            ctrl.view.UpdateGameUI(ctrl.model.Score,ctrl.model.HighScore);
        }

        //消除子物体之后 把相关的父类也销毁   相当于垃圾清理回收
        foreach (Transform t in blockHolder)
        {
            if (t.childCount<=1)  //判断底下只有一个节点的话 就销毁掉
            {
                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;  //设置图形的锚点

    private Ctrl ctrl;  //声明控制类

    private GameManager gameManager;  //游戏管理类

    private bool isPause = false; //是否暂停游戏

    private bool isSpeedup = false; //上升速度

    private float timer = 0;    //设置时间间隔

    private float stepTime =0.8f;   //每一步的所需要的时间

    private int mutiple = 15;  //往下掉落的加速倍数

    void Start () {

        pivot = transform.Find("Pivot");  //获取锚点 用来控制旋转的
    }
    

    void Update () {

        if (isPause) return;
        timer += Time.deltaTime; //暂停的话直接返回
        if (timer>stepTime)  //计时器大于每一格设定的时间外  执行下落的函数
        {
            timer = 0;  //计时器归零
            Fall(); //下落的函数
        }
        InputControl(); //监听输入控制的方法
    }

    /// <summary>
    /// 实例化方块颜色
    /// </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();
            }


        }

        //控制旋转
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            transform.RotateAround(pivot.position,Vector3.forward,-90);     //可旋转 参数1:围绕那个点旋转 参数2:围绕Z轴旋转  参数3:旋转角度
            if (ctrl.model.IsVaildMapPosition(this.transform)==false) //不可旋转 把角度调回去
            {
                transform.RotateAround(pivot.position,Vector3.forward,90);
            }
            else
            {
                ctrl.audioManager.PlayControl();//播放控制的声音
            }

            //下降加速
            if (Input.GetKeyDown(KeyCode.DownArrow))
            {
                isSpeedup = true;
                stepTime /= mutiple;    //下降时间加速
            }
        } 
    }

    /// <summary>
    /// 暂停
    /// </summary>
    public void Pause() {

        isPause = true;

    }

    /// <summary>
    /// 恢复
    /// </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 有限状态机

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.
/// 游戏状态  枚举类型
/// </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; } } //设置值

    protected FSMSystem fsm;
    public FSMSystem FSM { set { fsm = value; } }   //属性设置值

    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
    /// 改成虚函数
    /// </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
    /// 改成虚函数
    /// </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>
    /// 设置当前默认状态  和ID
    /// </summary>
    public void SetCurrentState(FSMState s) {

        currentState = s;
        currentStateID = s.ID;
        s.DoBeforeEntering();  //调用默认的状态  之后切换自己调用
    }



    /// <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);  //监听状态是否切换

    }

    /// <summary>
    /// 重写方法 显示菜单栏
    /// </summary>
    public override void DoBeforeEntering()
    {
        ctrl.view.ShowMenu();
        Debug.Log("执行了--ctrl.view.ShowMenu();");
        ctrl.cameraManager.ZoomOut();

    }

    /// <summary>
    /// 隐藏菜单栏
    /// </summary>
    public override void DoBeforeLeaving()
    {
        ctrl.view.HideMenu();
    }

    /// <summary>
    /// 点击开始按钮
    /// </summary>
    public void OnStartButtonClick() {

        ctrl.audioManager.PlayCursor(); //播放鼠标的声音
        fsm.PerformTransition(Transition.StartButtonClick);

    }

    /// <summary>
    /// 点击排行榜的按钮
    /// </summary>
    public void OnRankButtonClick() {
        ctrl.audioManager.PlayCursor(); //播放鼠标的声音
        ctrl.view.ShowRankUI(ctrl.model.Score,ctrl.model.HighScore,ctrl.model.NumberGame);
    }

    /// <summary>
    ///  点击排行榜的清空按钮
    /// </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()  //进入这个状态之前
    {
        ctrl.view.ShowGameUI(ctrl.model.Score,ctrl.model.HighScore); //显示分数
        ctrl.cameraManager.ZoomIn();    //放大
        ctrl.gameManager.StartGame();   //开始游戏
    }

    public override void DoBeforeLeaving()  //关闭这个状态之后
    {
        ctrl.view.HideGameUI();
        ctrl.view.ShowRestartButton();
        ctrl.gameManager.PauseGame();
    }

    /// <summary>
    /// 暂停按钮
    /// </summary>
    public void OnPauseButtonClick() {

        ctrl.audioManager.PlayCursor();
        fsm.PerformTransition(Transition.PauseButtonClick);

    }

    /// <summary>
    /// 游戏结束重新开始时 重新开始的按钮
    /// </summary>
    public void OnRestartButtonClick() {
        ctrl.view.HideGameOverUI(); //隐藏游戏结束的界面
        ctrl.model.Restart();//执行重新开始的逻辑
        ctrl.gameManager.StartGame();//执行游戏重新开始的方法
        ctrl.view.UpdateGameUI(0,ctrl.model.HighScore); //执行分数更新的UI  改变分数  参数1:0  参数2:最高分
    }


}

三、效果展示

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容