Unity 框架QFramework v1.0 使用指南 架构篇:05. 引入 Utility | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏

Posted 凉鞋的笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity 框架QFramework v1.0 使用指南 架构篇:05. 引入 Utility | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏相关的知识,希望对你有一定的参考价值。

05. 引入 Utility

在这一篇,我们来支持 CounterApp 的存储功能。

其代码也非常简单,只需要修改一部分 Model 的代码即可,如下:

    // 定义一个 Model 对象
    public class CounterAppModel : AbstractModel
    
        private int mCount;

        public int Count
        
            get => mCount;
            set
            
                if (mCount != value)
                
                    mCount = value;
                    PlayerPrefs.SetInt(nameof(Count),mCount);
                
            
        

        protected override void OnInit()
        
            Count = PlayerPrefs.GetInt(nameof(Count), mCount);
        
    

这样就支持了非常基本的数据存储功能。

当然还是有一些问题,如果时候未来我们需要存储的数据非常多的时候,Model 层就会充斥大量存储、加载相关的代码。

还有就是,我们以后如果不想使用 PlayperPrefs 了,想使用 EasySave 或者 SQLite 的时候,就会造成大量的修改工作量。

于是 QFramework 提供了一个 Utility 层,专门用来解决上述两个问题的,使用方法非常简单,如下:

using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example

    
    // 1. 定义一个 Model 对象
    public class CounterAppModel : AbstractModel
    
        private int mCount;

        public int Count
        
            get => mCount;
            set
            
                if (mCount != value)
                
                    mCount = value;
                    PlayerPrefs.SetInt(nameof(Count),mCount);
                
            
        

        protected override void OnInit()
        
            var storage = this.GetUtility<Storage>();

            Count = storage.LoadInt(nameof(Count));

            // 可以通过 CounterApp.Interface 监听数据变更事件
            CounterApp.Interface.RegisterEvent<CountChangeEvent>(e =>
            
                this.GetUtility<Storage>().SaveInt(nameof(Count), Count);
            );
        
    

    // 定义 utility 层
    public class Storage : IUtility
    
        public void SaveInt(string key, int value)
        
            PlayerPrefs.SetInt(key,value);
        

        public int LoadInt(string key, int defaultValue = 0)
        
            return PlayerPrefs.GetInt(key, defaultValue);
        
    


    // 2.定义一个架构(提供 MVC、分层、模块管理等)
    public class CounterApp : Architecture<CounterApp>
    
        protected override void Init()
        
            // 注册 Model
            this.RegisterModel(new CounterAppModel());
            
            // 注册存储工具的对象
            this.RegisterUtility(new Storage());
        
    
    
    // 定义数据变更事件
    public struct CountChangeEvent // ++
    
        
    
    
    // 引入 Command
    public class IncreaseCountCommand : AbstractCommand 
    
        protected override void OnExecute()
        
            this.GetModel<CounterAppModel>().Count++;
            this.SendEvent<CountChangeEvent>(); // ++
        
    
    
    public class DecreaseCountCommand : AbstractCommand
    
        protected override void OnExecute()
        
            this.GetModel<CounterAppModel>().Count--;
            this.SendEvent<CountChangeEvent>(); // ++
        
    

    // Controller
    public class CounterAppController : MonoBehaviour , IController /* 3.实现 IController 接口 */
    
        // View
        private Button mBtnAdd;
        private Button mBtnSub;
        private Text mCountText;
        
        // 4. Model
        private CounterAppModel mModel;

        void Start()
        
            // 5. 获取模型
            mModel = this.GetModel<CounterAppModel>();
            
            // View 组件获取
            mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
            mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
            mCountText = transform.Find("CountText").GetComponent<Text>();
            
            
            // 监听输入
            mBtnAdd.onClick.AddListener(() =>
            
                // 交互逻辑
                this.SendCommand<IncreaseCountCommand>();
            );
            
            mBtnSub.onClick.AddListener(() =>
            
                // 交互逻辑
                this.SendCommand(new DecreaseCountCommand(/* 这里可以传参(如果有) */));
            );
            
            UpdateView();
            
            // 表现逻辑
            this.RegisterEvent<CountChangeEvent>(e =>
            
                UpdateView();

            ).UnRegisterWhenGameObjectDestroyed(gameObject);
        
        
        void UpdateView()
        
            mCountText.text = mModel.Count.ToString();
        

        // 3.
        public IArchitecture GetArchitecture()
        
            return CounterApp.Interface;
        

        private void OnDestroy()
        
            // 8. 将 Model 设置为空
            mModel = null;
        
    


代码非常简单,我们运行下 Unity 看下结果:

运行正确。

这样当我们,想要将 PlayerPrefs 方案替换成 EasySave 的时候,只需要对 Storage 里的代码进行修改即可。

最后给出流程图,如下:

好了,这篇就介绍到这里。

更多内容

Unity 框架QFramework v1.0 使用指南 架构篇:19. 心中有架构 | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏

QFramework.cs 提供了 MVC、分层、CQRS、事件驱动、数据驱动等工具,除了这些工具,QFramework.cs 还提供了架构使用规范。

而当使用 QFramework 熟练到一定的程度之后,就可以达到心中有架构的境界。

如果达到这个境界,你就早已不是当年的你了(开玩笑)。

心中有架构的境界,具体是指可以不依赖 QFramework.cs 就可以再项目中实践 QFramework.cs 架构。

具体的示例如下:

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

namespace QFramework.Example

    public class ArchitectureInHeartExample : MonoBehaviour
    

        #region Framework

        public interface ICommand
        
            void Execute();
        

        public class BindableProperty<T>
        
            private T mValue = default;

            public T Value
            
                get => mValue;
                set
                
                    if (mValue != null && mValue.Equals(value)) return;
                    mValue = value;
                    OnValueChanged?.Invoke(mValue);
                
            

            public event Action<T> OnValueChanged = _ =>  ;
        

        #endregion


        #region 定义 Model

        public static class CounterModel
        
            public static BindableProperty<int> Counter = new BindableProperty<int>()
            
                Value = 0
            ;
        
        
        #endregion

        #region 定义 Command
        public struct IncreaseCountCommand : ICommand
        
            public void Execute()
            
                CounterModel.Counter.Value++;
            
        
        
        public struct DecreaseCountCommand : ICommand
        
            public void Execute()
            
                CounterModel.Counter.Value--;
            
        
        #endregion


        private void OnGUI()
        
            if (GUILayout.Button("+"))
            
                new IncreaseCountCommand().Execute();
            

            GUILayout.Label(CounterModel.Counter.Value.ToString());

            if (GUILayout.Button("-"))
            
                new DecreaseCountCommand().Execute();
            
        
    

上图是一个计数器应用的实现。

在这个实现里,没有使用 QFramework.cs 里的任何内容,但是也写出来了符合 QFramework.cs 架构规范的计数器应用实现。

当大家使用 QFramework.cs 到一定程度之后,在未来不使用 QFramework.cs ,也可以按照 QFramework.cs 架构规范来写项目,而到此时,对于大家来说有没有 QFramework.cs 就无所谓了,因为 QFramework.cs 的架构规范已经刻在大家的骨子里了。

当大家熟练使用 QFramework.cs 之后,有一天如果大家去研究 网页前端、服务器、App 开发,会发现它们的很多框架与 QFramework.cs 架构有共通之处,甚至说,通过 QFramework.cs 中积累的开发经验可以直接照搬到其他领域的开发中。

这是因为 QFramework.cs 最初的设计目的,就是为了糅合和简化大量其他领域的架构概念,比如 React 中的 Redux(Flux)、.Net Core 开发中的领域驱动设计、CQRS、仓储模式等、App 开发中的 MVC、MVP、MVVM 等。

我们简单看一下这些图,大家就清楚了。

首先是前端 React 中的 Redux 的工作流程,如下:

其中 React Components 对应的是 QFramework.cs 中的 Controller。

Action + Reducers 对应的是 QFramework.cs 中的 Command

Store 对应的是 QFramework.cs 中的 Model。

接着是领域驱动设计:

其中 Interface 对应的是 IController。

Application 对应的是 ISystem。

Domain 对应的是 Model。

Infrustracture 对应的是 Utility + 一部分 Model。

接着看下 CQRS,CQRS 一般是领域驱动设计包含的模式,如下图所示:

其中 User Interface 对应的是 IController。

Command 和 Query 对应的是 Command 和 Query。

Domain Model 和 Data 对应的是 Model

Event 对应的是 Event。

非常接近。

接着看下仓储模式:

仓储模式没有具体的图,而此图是从网上随便找的,很清晰地表达出了仓储模式的结构。

其中 IRepository 对应的是 IModel。

Repository 对应的是 AbstractModel。

IBookRepository 对应的是 ICounterModel。

BookRespository 对应的是 CounterModel。

使用 ICounterModel 和 CounterModel 举例不是很合适,因为 CounterModel 只有一个 Counter 数据。

更适合的举例是 IStudentModel,StudentModel ,因为 StudentModel 会维护一个 Student 的 List。

仓储模式的优势在于,可以让上层(System、Controller)专注于数据的增删改查功能,而不是具体的增删改查实现,因为在服务器端,数据都是存储在数据库中的,数据库有很多类型,比如 MySQL、MongoDB 等,而在服务器端开发时,很有可能在开发阶段用 SQLite 或者 MongoDB,而在生产环境用的是 MySQL、PostgreSQL,所以在静态类型语言中,仓储模式会和 ORM 一起配合,让开发者专注在数据的增删改查和数据之间的关联上,而不是具体的查询语句,这样能提高开发效率。

最后,MVC、MVP、MVVM 这里就不介绍了,其中 MVP 和 MVVM 的实现会用 BindableProperty,有的会用反射的形式实现。

而 QFramework.cs 中的 BindableProperty 和 MVC 分层,则是来自这些架构中。

好了,此篇的内容就说完了。

大家可能会问,为什么 QFramework.cs 要糅合这些架构概念?

因为在 2019 年左右,笔者刚好在业余时间研究了一年 React 开发,用 React 前端开发做了一些 SideProject,服务器则是用的 .Net Core,再加上之前笔者也有做 iOS、Android 等开发经验。而在当时,笔者突然发现这些领域的架构概念很多都是相通的,可能在这个领域叫这个,在另一个领域只是换了一个名字而已,于是就产生了可不可以把这些架构概念都糅合在一起,然后去掉繁琐的保留有用的部分,于是就开始了 QFramework.cs 的设计。

杂糅和简化这些概念的 QFramework.cs 有什么好处呢?

首先 QFramework.cs 是非常容易上手的架构,因为其中的 MVC 三层概念让大家会觉得非常亲切,所以上手成本并不是很高。

其次 QFramework.cs 是一个能提高大家技术水平的架构,在架构方面,天花板是领域驱动设计的实现,是架构师必研究的内容,如果 QFramework.cs 用熟悉了,再去研究领域驱动设计会容易得多,而领域驱动设计不管在项目中有没有使用,只要去研究就会对架构水平有很大的提升,而 QFramework.cs 算是简化版本的领域驱动设计的实现。

然后 QFramework.cs 可以用来做系统设计、可以做游戏、做项目、做插件都是很适合的,因为笔者自己的很多项目、插件、服务器都是用 QFramework.cs 架构来做的。

最后 QFramework.cs 本身是很强大的,易上手、简单、代码精简、可维护性强、开发效率高、可定制性强、扩展性强,因为 QFramework.cs 吸取了大量其他领域架构的优点,同时也经历过大量项目的打磨而成,总体的代码精简到了 900 行左右。

如果大家想在更进一步强化这些概念,最好的方式就是尝试去学习其他领域的架构,比如:

  • React 与 RedU型
  • Java/.Net Core 与 DDD 实现,CQRS、仓储模式
  • App 开发中的 MVC、MVP、MVVM

好了,这篇内容就说到这里。

更多内容

以上是关于Unity 框架QFramework v1.0 使用指南 架构篇:05. 引入 Utility | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏的主要内容,如果未能解决你的问题,请参考以下文章

Unity 框架QFramework v1.0 使用指南 工具篇:09. SingletonKit 单例模板套件 | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏

Unity 框架QFramework v1.0 使用指南 架构篇:05. 引入 Utility | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏

Unity 框架QFramework v1.0 使用指南 工具篇:05. ResKit 资源管理&开发解决方案 | Unity 游戏框架 | Unity 游戏开发 | Unity 独立游戏

OpenSourceC#Unity框架-QFramework

Unity热更模块基于 HybridCLR + Addressable

Unity 解决QFramework WebGL报错