Unity3D开发技术研究构建框架PureMVC项目之笨鸟先飞(上篇)

一、案例说明

《Flappy Bird》是一款由来自越南的独立[游戏开发者]Dong Nguyen所开发的作品,它的玩法极为简单,开发技术也容易,但它却在上线的短短几天之内几乎占据了80%的欧美手机游戏用户,并且最终获得四千万的融资。笔者以这款简单的游戏作为载体,深入浅出理解PureMVC框架思想,并且将这种框架思想应用在以后项目开发中去。

二、框架视图

三、关键代码

1、逻辑控制层 Control

Ctrl_StartGameCommond

using System.Collections;
using System.Collections.Generic;
using PureMVC.Patterns;
using UnityEngine;

namespace PureMVCDemo
{
    public class Ctrl_StartGameCommond : MacroCommand
    {
        protected override void InitializeMacroCommand()
        {
            //注册模型与视图Command
            AddSubCommand(typeof(Ctrl_RigistModelAndViewCommand));
            //注册重新开始command
            AddSubCommand(typeof(Ctrl_ReStartGameCommond));
        }
    }
}

Ctrl_EndGameCommand

using System.Collections;
using System.Collections.Generic;
using PureMVC.Interfaces;
using PureMVC.Patterns;
using SUIFW;
using UnityEngine;

namespace PureMVCDemo
{
    public class Ctrl_EndGameCommand :SimpleCommand{


        public override void Execute(INotification notification)
        {
            //停止脚本运行。
            StopScriptRuning();
            //关闭当前UI窗体,回到“玩家指导”UI窗体
            CloseCurrentUIForm();
            //保存当前最高分数
            Model_GameDataProxy gameData=Facade.RetrieveProxy(Model_GameDataProxy.NAME) as Model_GameDataProxy;
            gameData.SaveHightestScores();
            //重置游戏时间
            gameData.ResetGameTime();
        }

        /// <summary>
        /// 停止脚本运行
        /// </summary>
        private void StopScriptRuning()
        {
            //主角停止运行
            GameObject.FindGameObjectWithTag(ProjectConsts.Player).GetComponent<Ctrl_HeroControl>().StopGame();
            //得到层级视图根节点。
            GameObject goEnviromentRoot = GameObject.Find(ProjectConsts.MainGameScenes);
            //管道组停止运行
            UnityHelper.FindTheChildNode(goEnviromentRoot, "GamePipes").GetComponent<Ctrl_PipesMoving>().StopGame();
            //获取时间脚本,然后停止运行
            goEnviromentRoot.GetComponent<Ctrl_GetTime>().StopGame();
            //“金币”道具停止
            for (int i = 1; i <= 3; i++)
            {
                //道具开始运行               
                UnityHelper.FindTheChildNode(goEnviromentRoot, "Pipe" + i + "_Trigger").GetComponent<Ctrl_Golds>().StopGame();
            }

        }

       // 关闭当前UI窗体,回到“玩家指导”UI窗体
        private void CloseCurrentUIForm()
        {
            UIManager.GetInstance().CloseUIForms(ProjectConsts.GamePlayingUIForm);
          
        }
    }
}

Ctrl_ReStartGameCommond

using System.Collections;
using System.Collections.Generic;
using PureMVC.Interfaces;
using PureMVC.Patterns;
using SUIFW;
using UnityEngine;

namespace PureMVCDemo
{
    public class Ctrl_ReStartGameCommond : SimpleCommand
    {
        public override void Execute(INotification notification)
        {            
            //主角重新运行
            GameObject.FindGameObjectWithTag(ProjectConsts.Player).GetComponent<Ctrl_HeroControl>().StartGame();
            //得到层级视图根节点。
            GameObject goEnviromentRoot = GameObject.Find(ProjectConsts.MainGameScenes);    
            //管道组启动运行
            UnityHelper.FindTheChildNode(goEnviromentRoot, "GamePipes").GetComponent<Ctrl_PipesMoving>().StartGame();
            //获取时间脚本
            goEnviromentRoot.GetComponent<Ctrl_GetTime>().StartGame();
            //“金币”道具启动
            for (int i =1; i <=3; i++)
            {
                //道具开始运行               
                UnityHelper.FindTheChildNode(goEnviromentRoot, "Pipe"+i+"_Trigger").GetComponent<Ctrl_Golds>().StartGame();
            }
        }
    }
}

Ctrl_RigistModelAndViewCommand

using System.Collections;
using System.Collections.Generic;
using PureMVC.Interfaces;
using PureMVC.Patterns;
using UnityEngine;

namespace PureMVCDemo
{
    public class Ctrl_RigistModelAndViewCommand : SimpleCommand
    {
        public override void Execute(INotification notification)
        {
            Facade.RegisterProxy(new Model_GameDataProxy());
            Facade.RegisterMediator(new Vew_GamePlayingMediator());
        }
    }
}

Ctrl_GetTime


using System.Collections;
using System.Collections.Generic;
using PureMVC.Patterns;
using UnityEngine;

namespace PureMVCDemo
{
    public class Ctrl_GetTime : MonoBehaviour {
        //模型代理
        public Model_GameDataProxy dataProxy = null;
        //游戏是否开始
        private bool IsStartGame = false;


        /// <summary>
        /// 游戏开始
        /// </summary>
        public void StartGame()
        {
            //得到模型层类的对象实例
            //dataProxy = Facade.Instance.RetrieveProxy(Model_GameDataProxy.NAME) as Model_GameDataProxy;
            IsStartGame = true;
        }

        /// <summary>
        /// 游戏结束
        /// </summary>
        public void StopGame()
        {
            IsStartGame = false;
        }


        void Start () {
            //得到模型层类的对象实例
            dataProxy = Facade.Instance.RetrieveProxy(Model_GameDataProxy.NAME) as Model_GameDataProxy;
            //启动协程,每隔一秒种,往PureMVC 模型层发送一条消息。
            StartCoroutine("GetTime");
        }

        /// <summary>
        /// 协程,得到时间
        /// </summary>
        /// <returns></returns>
        private IEnumerator GetTime()
        {
            yield return new WaitForEndOfFrame();
            while (true)
            {
                yield return new WaitForSeconds(1F);
                if (dataProxy != null && IsStartGame)
                {
                    //调用(Mediator)模型层方法
                    dataProxy.AddGameTime();
                }
            }

        }



    }
}

Ctrl_Golds


using System.Collections;
using System.Collections.Generic;
using PureMVC.Patterns;
using UnityEngine;

namespace PureMVCDemo
{
    public class Ctrl_Golds : MonoBehaviour {
        //模型代理
        private Model_GameDataProxy _proxyObj;  
        //是否开始
        private bool _IsStartGame = false;



        /// <summary>
        /// 开始游戏
        /// </summary>
        public void StartGame()
        {
            _proxyObj = Facade.Instance.RetrieveProxy(Model_GameDataProxy.NAME) as Model_GameDataProxy;
            //开始游戏
            _IsStartGame = true;
        }

        /// <summary>
        /// 结束游戏
        /// </summary>
        public void StopGame()
        {
            _IsStartGame = false;
        }

        /// <summary>
        /// 触发检测
        /// </summary>
        /// <param name="collision"></param>
        public void OnTriggerEnter2D(Collider2D collision)
        {
            if (_IsStartGame)
            {
                if (collision.gameObject.tag==ProjectConsts.Player)
                {
                    _proxyObj.AddScores();
                }

            }
        }
    }
}

Ctrl_BirdControl


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

namespace PureMVCDemo
{
    [RequireComponent(typeof(Rigidbody2D))]
    public class Ctrl_BirdControl : MonoBehaviour {
        //升力
        public float floUPPower = 3F;
        //2D刚体
        private Rigidbody2D rd2D;
        //主角原始位置
        private Vector2 _VecHeroOriginalPosition;
        //游戏是否开始
        private bool _IsGameStart = false;



        //游戏开始
        public void StartGame()
        {
            _IsGameStart = true;
            //启用2D刚体
            this.gameObject.GetComponent<Rigidbody2D>().isKinematic = false;
            //恢复小鸟的原始方位
            this.gameObject.transform.position = _VecHeroOriginalPosition;
        }

        //游戏结束
        public void StopGame()
        {
            _IsGameStart = false;
            //恢复小鸟的原始方位
            //this.gameObject.transform.position = _VecHeroOriginalPosition;
            //禁用2D刚体。
            DisableRigibody2D();
        }


        void Start () {
            //保存原始的方位
            _VecHeroOriginalPosition = this.gameObject.transform.position;
            //获取2D刚体
            rd2D = this.gameObject.GetComponent<Rigidbody2D>();
            //禁用2D刚体。
            DisableRigibody2D();
        }

        /// <summary>
        /// 接收玩家的输入
        /// </summary>
        void Update()
        {
            if(_IsGameStart)
            {
                if (Input.GetButton(ProjectConsts.Fire1))
                {
                    rd2D.velocity = Vector2.up*floUPPower;
                }
            }        
        }

        //禁用2D刚体。
        private void DisableRigibody2D()
        {
            this.gameObject.GetComponent<Rigidbody2D>().isKinematic = true;
        }

    }
}

Ctrl_LandMoving


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

namespace PureMVCDemo
{
    public class Ctrl_LandMoving : MonoBehaviour
    {
        public float floMovingSpeed = 1F;                   //陆地移动速度
        private Vector2 _VecOriginalPostion;                //陆地原始位置
        //测试
        //private int intTestNumber = 0;

        void Start ()
        {
            //Log.Write("普通脚本LandMoving=" + this.GetHashCode());
            //保存陆地原始位置
            _VecOriginalPostion = this.gameObject.transform.position;
        }

        void Update()
        {
            //intTestNumber++;
            //Log.Write("LandMoving.cs/Update() intTestNumber" + intTestNumber);

            //陆地循环移动,到了“临界值”,回复原始方位
            if (this.gameObject.transform.position.x<-5F)
            {
                this.gameObject.transform.position = _VecOriginalPostion;
            }
            //移动
            this.gameObject.transform.Translate(Vector2.left*Time.deltaTime*floMovingSpeed);
        }

        
        
    }
}

Ctrl_PipeAndLand


using System.Collections;
using System.Collections.Generic;
using PureMVC.Patterns;
using UnityEngine;

namespace PureMVCDemo
{

    [RequireComponent(typeof(BoxCollider2D))]
    public class Ctrl_PipeAndLand : MonoBehaviour {

        /// <summary>
        /// 2D碰撞检测
        /// </summary>
        /// <param name="collision"></param>
        public void OnCollisionEnter2D(Collision2D collision)
        {
            if (collision.gameObject.tag==ProjectConsts.Player)
            {
                //通过PureMVC的通知,游戏结束。
                Facade.Instance.SendNotification(ProjectConsts.Reg_EndGameCommand);
            }
        }
    }
}

Ctrl_PipesMoving


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

namespace PureMVCDemo
{
    public class Ctrl_PipesMoving : MonoBehaviour {
        public float floMovingSpeed = 1F;                   //移动速度
        private Vector2 _VecOriginalPostion;                //原始位置
        private bool _IsStartGame = false;                  //是否开始游戏


        //游戏开始
        public void StartGame()
        {
            _IsStartGame = true;
        }

        //游戏结束
        public void StopGame()
        {
            _IsStartGame = false;
            //管道复位
            ResetPipesPosition();
        }

        void Start()
        {
            //保存原始位置
            _VecOriginalPostion = this.gameObject.transform.position;
        }

        void Update()
        {
            if (_IsStartGame)
            {
                //陆地循环移动,到了“临界值”,回复原始方位
                if (this.gameObject.transform.position.x < -20F)
                {
                    this.gameObject.transform.position = _VecOriginalPostion;
                }
                //移动
                this.gameObject.transform.Translate(Vector2.left * Time.deltaTime * floMovingSpeed);            
            }
        }

        /// <summary>
        /// 管道复位
        /// </summary>
        private void ResetPipesPosition()
        {
            this.gameObject.transform.position = _VecOriginalPostion;
        }

    }
}
2、数据模型层 Model

Model_GameData

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

namespace PureMVCDemo
{
    public class Model_GameData {
        //游戏时间
        public int GameTime { set; get; }
        //游戏分数
        public int Scores { set; get; }
        //游戏最高分数
        public int HightestScores { set; get; }

    }
}

Model_GameDataProxy


using System.Collections;
using System.Collections.Generic;
using PureMVC.Patterns;
using SUIFW;
using UnityEngine;

namespace PureMVCDemo
{
    public class Model_GameDataProxy:Proxy
    {
        //类的名称
        public new const string NAME = "Model_GameDataProxy";
        //游戏数据实体类
        private Model_GameData _GameData;
        //测试
        private int intTestNumber = 0;



        public Model_GameDataProxy():base(NAME)
        {
            Log.Write("代理类 Model_GameDataProxy GetHashCode=" + this.GetHashCode());
            _GameData=new Model_GameData();
            //得到最高分数
            _GameData.HightestScores = PlayerPrefs.GetInt(ProjectConsts.GameHighestScores);
        }

        //增加游戏的时间
        public void AddGameTime()
        {
            //intTestNumber++;
            //Log.Write("代理类 intTestNumber=" + intTestNumber);

            ++_GameData.GameTime;
            //数值发送到视图层。
            SendNotification(ProjectConsts.Msg_DisplayGameInfo, _GameData);
        }

        /// <summary>
        /// 重置游戏时间
        /// </summary>
        public void ResetGameTime()
        {
            _GameData.GameTime = 0;
        }

        //增加分数
        public void AddScores()
        {
            ++_GameData.Scores;
            //更新最高分数
            GetHightestScores();
        }

        //得到最高分数
        public int GetHightestScores()
        {
            if (_GameData.Scores>_GameData.HightestScores)
            {
                _GameData.HightestScores = _GameData.Scores;
            }
            return _GameData.HightestScores;
        }

        //保存最高分数
        public void SaveHightestScores()
        {
            if (_GameData.HightestScores > PlayerPrefs.GetInt(ProjectConsts.GameHighestScores))
            {
                PlayerPrefs.SetInt(ProjectConsts.GameHighestScores, _GameData.HightestScores);
            }
        }

    }
}
3、视图交互层

Vew_GamePlayingMediator


using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net.Mime;
using Boo.Lang;
using PureMVC.Interfaces;
using PureMVC.Patterns;
using SUIFW;
using UnityEngine;
using UnityEngine.UI;

namespace PureMVCDemo
{
    public class Vew_GamePlayingMediator : Mediator
    {
        public new const string NAME = "Vew_GamePlayingMediator";
        //控件定义
        private Text _TxtGameTime;                          //游戏时间
        private Text _TxtShowGameTime;                      //显示游戏时间
        private Text _TxtGameScore;                         //游戏分数
        private Text _TxtShowGameScore;                     //显示最高分数
        private Text _TxtGameHighestScore;                  //游戏最高分数
        private Text _TxtShowGameHighestScore;              //显示游戏最高分数


        /// <summary>
        /// 构造函数
        /// </summary>
        public Vew_GamePlayingMediator():base(NAME)
        {
            Log.Write("Mediator hashcode="+this.GetHashCode());
        }


        /// <summary>
        /// 初始化字段
        /// </summary>
        private void InitMediatorFiled()
        {
            //得到层级视图根节点
            GameObject goRootCanvase = GameObject.Find("Canvas(Clone)");
            //得到文字控件
            _TxtGameTime = UnityHelper.GetTheChildNodeComponetScripts<Text>(goRootCanvase, "TxtTime");
            _TxtGameScore = UnityHelper.GetTheChildNodeComponetScripts<Text>(goRootCanvase, "TxtScores");
            _TxtGameHighestScore = UnityHelper.GetTheChildNodeComponetScripts<Text>(goRootCanvase, "TxtHighScores");
            _TxtGameTime.text = "时间:";
            _TxtGameScore.text = "分数:";
            _TxtGameHighestScore.text = "最高:";
            //得到文字控件数值显示
            _TxtShowGameTime = UnityHelper.GetTheChildNodeComponetScripts<Text>(goRootCanvase, "TxtTimeShow");
            _TxtShowGameScore = UnityHelper.GetTheChildNodeComponetScripts<Text>(goRootCanvase, "TxtScoresShow");
            _TxtShowGameHighestScore = UnityHelper.GetTheChildNodeComponetScripts<Text>(goRootCanvase, "TxtHighScoresShow");        
        
        }


        /// <summary>
        /// 允许可以接收的消息列表
        /// </summary>
        /// <returns></returns>
        public override IList<string> ListNotificationInterests()
        {
            IList<string> liResult=new System.Collections.Generic.List<string>();
            liResult.Add(ProjectConsts.Msg_DisplayGameInfo);
            liResult.Add(ProjectConsts.Msg_InitGamePlayingMediatorFiled);
            return liResult;
        }


        /// <summary>
        /// 处理接收到的消息列表
        /// </summary>
        /// <param name="notification"></param>
        public override void HandleNotification(INotification notification)
        {
            Model_GameData gameData = null;

            switch (notification.Name)
            {
                case ProjectConsts.Msg_InitGamePlayingMediatorFiled:
                    InitMediatorFiled();
                    break;

                case ProjectConsts.Msg_DisplayGameInfo:
                    gameData = notification.Body as Model_GameData;
                    if (gameData!=null)
                    {
                        if (_TxtShowGameTime && _TxtShowGameScore && _TxtShowGameHighestScore)
                        {
                            _TxtShowGameTime.text = gameData.GameTime.ToString();
                            _TxtShowGameScore.text = gameData.Scores.ToString();
                            _TxtShowGameHighestScore.text = gameData.HightestScores.ToString();
                        }
                    }
                    break;
                default:
                    break;
            }
        }
    }
}

StartUIForm

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

namespace PureMVCDemo
{
    public class StartUIForm : BaseUIForm {


        void Awake()
        {
            //按钮注册
            RigisterButtonObjectEvent("ImgBackground",p=>
                OpenUIForm(ProjectConsts.GameGuideUIForm)
                );
        }

        void Start()
        {
            //启动MVC 框架
            new ApplicationFacade();
        }
    }
}

GameGuideUIForm

using System.Collections;
using System.Collections.Generic;
using PureMVC.Patterns;
using SUIFW;
using UnityEngine;

namespace PureMVCDemo
{
    public class GameGuideUIForm : BaseUIForm {

        void Awake()
        {
            //本窗体类型
            CurrentUIType.UIForms_ShowMode = UIFormShowMode.HideOther;

            //注册按钮事件
            RigisterButtonObjectEvent("BtnGuideOK", p =>
            {
                OpenUIForm(ProjectConsts.GamePlayingUIForm);
                //MVC 启动命令
                Facade.Instance.SendNotification(ProjectConsts.Reg_StartGameCommand);
                //对视图层字段初始化
                Facade.Instance.SendNotification(ProjectConsts.Msg_InitGamePlayingMediatorFiled);
            }
            );
        }

    }
}

GamePlayingUIForm

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

namespace PureMVCDemo
{
    public class GamePlayingUIForm : BaseUIForm {


        void Awake () {
            //UI窗体类型
            CurrentUIType.UIForms_ShowMode = UIFormShowMode.HideOther;
        }
        
    }
}

四、效果展示

五、PureMVC源码下载地址

[https://github.com/PureMVC/puremvc-csharp-standard-framework](https://github.com/PureMVC/puremvc-csharp-standard-framework)

推荐阅读更多精彩内容