Unity framework learning--1

From shallow to deep, slowly evolve the implementation framework

The implementation codes of the two classes are exactly the same. Only when the class names or types are different, and when they need to be continuously expanded (various events will be added in the future), then use generics + inheritance to extract, and inheritance solves the problem of expansion. Generics solve the problem of consistent implementation code and inconsistent classes . This is a refactoring technique.

Performance and data should be separated

In most cases, data needs to be shared between multiple scenes, interfaces, and game objects . This data not only needs to be shared in space, but also needs to be shared in time (it needs to be stored), so here, development The consensus among readers is to extract the data part and put it in a separate place for maintenance. The more common development architecture is to use the MVC development architecture. We will not use the MVC development architecture first, but only use one of the MVC Concept is Model.

Model is to manage data and store data. Managing data means that data can be added, deleted, modified, and checked through Model objects or classes, and sometimes it can also be stored.

public class GameModel
    {
        public static int KillCount = 0;

        public static int Gold = 0;

        public static int Score = 0;

        public static int BestScore = 0;
    }
  • Extract Event tool class using generics + inheritance
  • Child nodes can also use events to notify parent nodes (depending on the situation)
  • Separation of presentation and data that needs to be shared
  • Correct code must be placed in the correct location

If the data is shared , put it in the Model. If it is not shared, there is no need.

The data shared here can be configuration data, data that needs to be stored, and data that needs to be accessed in multiple places .

As for configuration data, the scenes, GameObjects, and Prefabs in the game are also a kind of configuration data before running the game, because they are essentially stored on the disk using Yaml (data format similar to json and xml).

The data that needs to be stored is data shared from the time dimension, that is, the data stored at a certain time in the past can now be accessed.

 

 Reusable bindable properties

using System;

namespace FrameworkDesign
{
    public class BindableProperty<T> where T : IEquatable<T>
    {
        private T mValue;

        public T Value
        {
            get => mValue;
            set
            {
                if (!mValue.Equals(value))
                {
                    mValue = value;
                    OnValueChanged?.Invoke(value);
                }
            }
        }

        public Action<T> OnValueChanged;
    }
}

BidableProperty is a combination of data + data change events. It not only stores data and acts as a property in C#, but also allows other places to monitor its data change events, which will reduce a lot of boilerplate code.

  • Performance logic is suitable for using events or delegates
  • Using method calls for presentation logic will cause many problems. The Controller is bloated and difficult to maintain.
  • Model and View have a bottom-up relationship
  • Bottom-up using events or delegates
  • Top-down method call

command mode

The logic written in a method is implemented using an object, and this object has only one execution method.

We first define an interface called ICommand, the code is as follows:

namespace FrameworkDesign
{
    public interface ICommand
    {
        void Execute();
    }
}

Implement interface:

public struct AddCountCommand : ICommand
    {
        public void Execute()
        {
            CounterModel.Count.Value++;
        }
    }

Command mode means that logical invocation and execution are separated.

The method of space separation is to put the calling place and the execution place in two files.

The method of time separation is that after it is called, the Command will be executed after a while.

Since the Command mode has the feature of separating invocation and execution, we can use different data structures to organize Command invocations, such as command queues, or use a command stack to implement the undo function (ctrl + z).

Introduce singleton

  • Static classes have no access restrictions.
  • Use static to extend modules, and the module recognition is not high.
public class Singleton<T> where T : class
    {
        public static T Instance
        {
            get
            {
                if (mInstance == null)
                {
                    // 通过反射获取构造
                    var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
                    // 获取无参非 public 的构造
                    var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);

                    if (ctor == null)
                    {
                        throw new Exception("Non-Public Constructor() not found in " + typeof(T));
                    }

                    mInstance = ctor.Invoke(null) as T;
                }

                return mInstance;
            }
        }

        private static T mInstance;
    }

Modular optimization--Introducing IOC containers

The IOC container can be understood as a dictionary. This dictionary uses Type as the key and the object, namely Instance, as the value. It is very simple.

The IOC container has at least two core APIs, namely registering an instance based on Type and obtaining an instance based on Type.

Implement a simple IOC container

public class IOCContainer
    {
        /// <summary>
        /// 实例
        /// </summary>
        public Dictionary<Type, object> mInstances = new Dictionary<Type, object>();

        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="instance"></param>
        /// <typeparam name="T"></typeparam>
        public void Register<T>(T instance)
        {
            var key = typeof(T);

            if (mInstances.ContainsKey(key))
            {
                mInstances[key] = instance;
            }
            else
            {
                mInstances.Add(key,instance);
            }
        }

        /// <summary>
        /// 获取
        /// </summary>
        public T  Get<T>() where T : class
        {
            var key = typeof(T);
            
            object retObj;
            
            if(mInstances.TryGetValue(key,out retObj))
            {
                return retObj as T;
            }

            return null;
        }
    }

Use this code:

我们先创建一个 CounterApp 类,用于注册全部模块,代码如下:
using FrameworkDesign;

namespace CounterApp
{
    public class CounterApp
    {
        private static IOCContainer mContainer = null;

        // 确保 Container 是有实例的
        static void MakeSureContainer()
        {
            if (mContainer == null)
            {
                mContainer = new IOCContainer();
                Init();
            }
        }

        // 这里注册模块
        private static void Init()
        {
            mContainer.Register(new CounterModel());
        }
        
        // 提供一个获取模块的 API
        public static T Get<T>() where T : class
        {
            MakeSureContainer();
            return mContainer.Get<T>();
        }
    }
}
接着我们把 CounterApp 类应用起来,代码如下:
using FrameworkDesign;
using UnityEngine;
using UnityEngine.UI;

namespace CounterApp
{
    public class CounterViewController : MonoBehaviour
    {
        private CounterModel mCounterModel;
        
        void Start()
        {
            // 获取
            mCounterModel = CounterApp.Get<CounterModel>();
            
            // 注册
            mCounterModel.Count.OnValueChanged += OnCountChanged;

            transform.Find("BtnAdd").GetComponent<Button>()
                .onClick.AddListener(() =>
                {
                    // 交互逻辑
                    new AddCountCommand()
                        .Execute();
                });

            transform.Find("BtnSub").GetComponent<Button>()
                .onClick.AddListener(() =>
                {
                    // 交互逻辑
                    new SubCountCommand()
                        .Execute();
                });
            
            OnCountChanged(mCounterModel.Count.Value);
        }

        // 表现逻辑
        private void OnCountChanged(int newValue)
        {
            transform.Find("CountText").GetComponent<Text>().text = newValue.ToString();
        }

        private void OnDestroy()
        {
            // 注销
            mCounterModel.Count.OnValueChanged -= OnCountChanged;

            mCounterModel = null;
        }
    }

    /// <summary>
    /// 不需要是单例了
    /// </summary>
    public class CounterModel
    {
        public BindableProperty<int> Count = new BindableProperty<int>()
        {
            Value = 0
        };
    }
}
AddCountCommand.cs
using FrameworkDesign;

namespace CounterApp
{
    public struct AddCountCommand : ICommand
    {
        public void Execute()
        {
            CounterApp.Get<CounterModel>().Count.Value++;
        }
    }
}
SubCountCommand.cs
using FrameworkDesign;

namespace CounterApp
{
    public struct SubCountCommand : ICommand
    {
        public void Execute()
        {
            CounterApp.Get<CounterModel>().Count.Value--;
        }
    }
}

--dd, is this the framework orz?

The following code is easy to repeat

PiontGame.cs
namespace FrameworkDesign.Example
{
    public class PointGame 
    {
        private static IOCContainer mContainer = null;

        // 确保 Container 是有实例的
        static void MakeSureContainer()
        {
            if (mContainer == null)
            {
                mContainer = new IOCContainer();
                Init();
            }
        }

        // 这里注册模块
        private static void Init()
        {
            mContainer.Register(new GameModel());
        }
        
        // 提供一个获取模块的 API
        public static T Get<T>() where T : class
        {
            MakeSureContainer();
            return mContainer.Get<T>();
        }
    }
}

Optimize it: create a class named Architecture.cs with the following code:

namespace FrameworkDesign
{
    /// <summary>
    /// 架构
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class Architecture<T> where T : Architecture<T>, new()
    {
        #region 类似单例模式 但是仅在内部课访问
        private static T mArchitecture = null;
        
        // 确保 Container 是有实例的
        static void MakeSureArchitecture()
        {
            if (mArchitecture == null)
            {
                mArchitecture = new T();
                mArchitecture.Init();
            }
        }
        #endregion

        private IOCContainer mContainer = new IOCContainer();

        // 留给子类注册模块
        protected abstract void Init();

        // 提供一个注册模块的 API
        public void Register<T>(T instance)
        {
            MakeSureArchitecture();
            mArchitecture.mContainer.Register<T>(instance);
        }

        // 提供一个获取模块的 API
        public static T Get<T>() where T : class
        {
            MakeSureArchitecture();
            return mArchitecture.mContainer.Get<T>();
        }
    }
}

Guess you like

Origin blog.csdn.net/zaizai1007/article/details/132253776