ECGameplay|01 组件式编程
概览
EC,即Entity - Component,是一种代码组织方式
Unity也是基于组件式编程的,一个GameObject上可以挂载多个相同或者不同的组件
我们的Entity,可以类比于一个空的GameObject
同样的地方在于,Entity本身没有任何行为和数据,他的特性由他挂载的所有脚本决定
不同的地方在于,Entity只是一个基类,提供一些通用方法,而我们需要实现自己所需要的所有具体Entity,Entity是数据和行为的结合体
Component代表了某一种行为,而Entity则是多种行为的结合体
Entity
基础架构
主要就是记录父子节点,以及维持自身的组件映射表
namespace ECGameplay
{
public abstract partial class Entity
{
// ID
public long Id { get; set; }
// 名称
private string name;
public string Name
{
get => name;
set
{
name = value;
#if UNITY_EDITOR
// 后文说明
GetComponent<GameObjectComponent>()?.OnNameChanged(name);
#endif
}
}
// 实例ID
public long InstanceId { get; set; }
// 是否释放
public bool IsDispose => InstanceId == 0;
// 父实体
private Entity parent;
public Entity Parent => parent;
// 孩子实体
public List<Entity> Children { get; private set; } = new List<Entity>();
public Dictionary<long, Entity> Id2Children { get; private set; } = new Dictionary<long, Entity>();
public Dictionary<Type, List<Entity>> Type2Children { get; private set; } =
new Dictionary<Type, List<Entity>>();
// 持有的组件
public Dictionary<Type, Component> Components { get; private set; } = new Dictionary<Type, Component>();
}
}
生命周期
和Mono几乎一致
namespace ECGameplay
{
public abstract partial class Entity
{
public Entity()
{
#if UNITY_EDITOR
// 后文说明
if (this is MasterEntity)
return;
AddComponent<GameObjectComponent>();
#endif
}
public virtual void Awake()
{
}
public virtual void Awake(object initData)
{
}
public virtual void Update()
{
}
public virtual void OnDestroy()
{
}
private void Dispose()
{
if (Children.Count > 0)
{
for (int i = Children.Count - 1; i >= 0; i--)
{
Destroy(Children[i]);
}
Children.Clear();
Type2Children.Clear();
}
Parent?.RemoveChild(this);
foreach (var component in Components.Values)
{
component.Enable = false;
Component.Destroy(component);
}
Components.Clear();
InstanceId = 0;
if (Master.Entities.ContainsKey(GetType()))
{
Master.Entities[GetType()].Remove(this);
}
}
}
}
组件方法
添加删除各种组件的方法,组件是不可以重复添加的
namespace ECGameplay
{
public abstract partial class Entity
{
public T AddComponent<T>() where T : Component
{
var component = Activator.CreateInstance<T>();
component.Entity = this;
component.IsDisposed = false;
Components.Add(typeof(T), component);
Master.AllComponents.Add(component);
component.Awake();
component.Enable = component.DefaultEnable;
#if UNITY_EDITOR
GetComponent<GameObjectComponent>()?.OnAddComponent(component);
#endif
return component;
}
public T AddComponent<T>(object initData) where T : Component
{
var component = Activator.CreateInstance<T>();
component.Entity = this;
component.IsDisposed = false;
Components.Add(typeof(T), component);
Master.AllComponents.Add(component);
component.Awake(initData);
component.Enable = component.DefaultEnable;
#if UNITY_EDITOR
GetComponent<GameObjectComponent>()?.OnAddComponent(component);
#endif
return component;
}
public void RemoveComponent<T>() where T : Component
{
var component = Components[typeof(T)];
if (component.Enable) component.Enable = false;
Component.Destroy(component);
Components.Remove(typeof(T));
#if UNITY_EDITOR
GetComponent<GameObjectComponent>()?.OnRemoveComponent(component);
#endif
}
public T GetComponent<T>() where T : Component
{
if (Components.TryGetValue(typeof(T), out var component))
{
return component as T;
}
return null;
}
public bool HasComponent<T>() where T : Component
{
return Components.TryGetValue(typeof(T), out var component);
}
}
}
子节点
namespace ECGameplay
{
public abstract partial class Entity
{
public T GetParent<T>() where T : Entity
{
return parent as T;
}
public T As<T>() where T : class
{
return this as T;
}
public void RemoveChild(Entity child)
{
Children.Remove(child);
Id2Children.Remove(child.Id);
if (Type2Children.ContainsKey(child.GetType()))
Type2Children[child.GetType()].Remove(child);
}
public T AddChild<T>() where T : Entity
{
var entity = NewEntity(typeof(T));
SetupEntity(entity, this);
return entity as T;
}
public T AddChild<T>(object initData) where T : Entity
{
var entity = NewEntity(typeof(T));
SetupEntity(entity, this, initData);
return entity as T;
}
public T GetChild<T>(int index = 0) where T : Entity
{
if (Type2Children.ContainsKey(typeof(T)) == false)
{
return null;
}
if (Type2Children[typeof(T)].Count <= index)
{
return null;
}
return Type2Children[typeof(T)][index] as T;
}
public Entity[] GetChildren()
{
return Children.ToArray();
}
public T[] GetTypeChildren<T>() where T : Entity
{
return Type2Children[typeof(T)].ConvertAll(x => x.As<T>()).ToArray();
}
public Entity Find(string name)
{
foreach (var item in Children)
{
if (item.name == name) return item;
}
return null;
}
public T Find<T>(string name) where T : Entity
{
if (Type2Children.TryGetValue(typeof(T), out var chidren))
{
foreach (var item in chidren)
{
if (item.name == name) return item as T;
}
}
return null;
}
}
}
Create
MasterEntity
我们需要一个根节点管理我们所有的Entity
namespace ECGameplay
{
public class MasterEntity : Entity
{
// 记录所有的实体,和所有的组件
public Dictionary<Type, List<Entity>> Entities { get; private set; } = new Dictionary<Type, List<Entity>>();
public List<Component> AllComponents { get; private set; } = new List<Component>();
// 单例
private static MasterEntity instance;
public static MasterEntity Instance
{
get
{
if (instance==null)
{
instance = new MasterEntity();
var go = instance.AddComponent<GameObjectComponent>().GameObject;
Object.DontDestroyOnLoad(go);
}
return instance;
}
private set => instance = value;
}
// 禁止实例化
private MasterEntity()
{
}
public static void Destroy()
{
Destroy(Instance);
Instance = null;
}
public override void Update()
{
if (AllComponents.Count == 0)
{
return;
}
for (int i = AllComponents.Count - 1; i >= 0; i--)
{
var item = AllComponents[i];
if (item.IsDisposed)
{
AllComponents.RemoveAt(i);
continue;
}
if (item.Disable)
{
continue;
}
item.Update();
}
}
}
}
IdFactory
用于创建唯一ID的一个简单工具类
namespace ECGameplay
{
public static class IdFactory
{
public static long BaseRevertTicks { get; set; }
public static long NewInstanceId()
{
if (BaseRevertTicks == 0)
{
var now = DateTime.UtcNow.Ticks;
var str = now.ToString().Reverse();
BaseRevertTicks = long.Parse(string.Concat(str));
}
BaseRevertTicks++;
return BaseRevertTicks;
}
}
}
Entity.Create
namespace ECGameplay
{
public abstract partial class Entity
{
public static MasterEntity Master => MasterEntity.Instance;
public static Entity NewEntity(Type entityType, long id = 0)
{
var entity = Activator.CreateInstance(entityType) as Entity;
entity.InstanceId = IdFactory.NewInstanceId();
if (id == 0) entity.Id = entity.InstanceId;
else entity.Id = id;
if (!Master.Entities.ContainsKey(entityType))
{
Master.Entities.Add(entityType, new List<Entity>());
}
Master.Entities[entityType].Add(entity);
return entity;
}
public static T Create<T>() where T : Entity
{
var entity = NewEntity(typeof(T));
SetupEntity(entity, Master);
return entity as T;
}
public static T Create<T>(object initData) where T : Entity
{
var entity = NewEntity(typeof(T));
SetupEntity(entity, Master, initData);
return entity as T;
}
public static void Destroy(Entity entity)
{
entity.OnDestroy();
entity.Dispose();
}
private static void SetupEntity(Entity entity, Entity parent)
{
parent.SetChild(entity);
{
entity.Awake();
}
}
private static void SetupEntity(Entity entity, Entity parent, object initData)
{
parent.SetChild(entity);
{
entity.Awake(initData);
}
}
private void SetChild(Entity child)
{
Children.Add(child);
Id2Children.Add(child.Id, child);
if (!Type2Children.ContainsKey(child.GetType()))
Type2Children.Add(child.GetType(), new List<Entity>());
Type2Children[child.GetType()].Add(child);
child.SetParent(this);
}
private void SetParent(Entity parent)
{
var preParent = Parent;
preParent?.RemoveChild(this);
this.parent = parent;
#if UNITY_EDITOR
parent.GetComponent<GameObjectComponent>()?.OnAddChild(this);
#endif
}
}
}
Component
相比Entity更加简单
namespace ECGameplay
{
public class Component
{
// 持有该组件的实体
public Entity Entity { get; set; }
// 是否释放
public bool IsDisposed { get; set; }
// 默认是否启用
public virtual bool DefaultEnable { get; set; } = true;
// 是否启用
private bool enable = false;
public bool Enable
{
get => enable;
set
{
if (enable == value) return;
enable = value;
if (enable) OnEnable();
else OnDisable();
}
}
public bool Disable => enable == false;
public T GetEntity<T>() where T : Entity
{
return Entity as T;
}
public virtual void Awake()
{
}
public virtual void Awake(object initData)
{
}
public virtual void OnEnable()
{
}
public virtual void OnDisable()
{
}
public virtual void Update()
{
}
public virtual void OnDestroy()
{
}
public static void Destroy(Component component)
{
component.Enable = false;
component.OnDestroy();
component.IsDisposed = true;
}
}
}
一些基础组件
ComponetView
这不是一个Component,这是一个Mono类,单纯用于编辑器可视化的
大部分都是可视化方法(单纯为了好看)
用到了Odin的几个特性哈,Odin不多介绍了
namespace ECGameplay
{
public class ComponentView : MonoBehaviour
{
[HideInInspector] public List<string> componts = new List<string>();
private Random random = new Random();
private List<Color> colors = new List<Color>();
[OnInspectorGUI]
private void OnInspectGUI()
{
PrepareColor();
GUI.enabled = false;
for (int i = 0; i < componts.Count; i++)
{
GUI.color = colors[i];
EditorGUILayout.TextField(componts[i]);
}
GUI.color = Color.white;
GUI.enabled = true;
}
private void PrepareColor()
{
if (colors.Count < componts.Count)
{
for (int i = colors.Count; i < componts.Count; i++)
{
colors.Add(GetRandomColor());
}
}
}
private Color GetRandomColor()
{
var r = (float)random.NextDouble();
var g = (float)random.NextDouble();
var b = (float)random.NextDouble();
return new Color(r, g, b, 1);
}
}
}
GameObjectComponent
一个标准的Componenet,不过也是用于编辑器可视化的
namespace ECGameplay.BasicComponent
{
public class GameObjectComponent : Component
{
public GameObject GameObject { get;private set; }
public override void Awake()
{
GameObject = new GameObject(Entity.GetType().Name);
}
public override void OnDestroy()
{
base.OnDestroy();
UnityEngine.GameObject.Destroy(GameObject);
}
public void OnNameChanged(string name)
{
GameObject.name = $"{Entity.GetType().Name}: {name}";
}
public void OnAddComponent(Component component)
{
var view = GameObject.AddComponent<ComponentView>();
view.Type = component.GetType().Name;
view.Component = component;
}
public void OnRemoveComponent(Component component)
{
var comps = GameObject.GetComponents<ComponentView>();
foreach (var item in comps)
{
if (item.Component == component)
{
UnityEngine.GameObject.Destroy(item);
}
}
}
public void OnAddChild(Entity child)
{
if (child.GetComponent<GameObjectComponent>() != null)
{
child.GetComponent<GameObjectComponent>().GameObject.transform.SetParent(GameObject.transform);
}
}
}
}
UpdateComponent
我们所有Entity上的所有Component都被集中到MasterEntity里了,每一帧都会遍历更新
虽然我们的Entity也有Update方法, 但默认是没地方调用的,这个组件的作用就让Entity也可以Update
namespace ECGameplay
{
public class UpdateComponent : Component
{
public override void Update()
{
Entity.Update();
}
}
}
测试
public class Test : MonoBehaviour
{
private MasterEntity MasterEntity => MasterEntity.Instance;
public class TestEntity : Entity
{
public override void Update()
{
Debug.Log(Time.time);
}
}
private void Start()
{
var entity = MasterEntity.AddChild<TestEntity>();
entity.AddComponent<UpdateComponent>();
}
private void Update()
{
MasterEntity.Update();
}
}