Unity3D开发技术研究构建框架PureMVC项目之Flappy Bird(上篇)

一、案例说明

《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)

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

推荐阅读更多精彩内容