Unity MyFramework 用法说明(十四):使用 ComponentOwner 和 GameComponent 拆分对象能力

📅 2026/7/22 21:41:54
Unity MyFramework 用法说明(十四):使用 ComponentOwner 和 GameComponent 拆分对象能力
角色、界面和管理器的功能不断增加后所有逻辑都堆在一个类中会产生大量字段、生命周期函数和条件判断。MyFramework 提供了一套纯 C# 组件系统通过ComponentOwner和GameComponent把一个复杂对象拆分为多个相对独立的能力组件。项目地址https://github.com/ZHOURUIH/MyFramework一、为什么还需要一套组件系统Unity 已经提供了MonoBehaviour组件但并不是所有业务对象都适合挂在 GameObject 上。例如一个玩家角色可能包含模型管理 动作播放 状态机 技能数据 背包数据 装备数据 输入控制全部放进角色类后很容易变成public class CharacterPlayer { protected Animator mAnimator; protected ListSkill mSkillList; protected ListItem mPackItems; protected ListEquip mEquipList; protected CharacterState mState; protected bool mCanControl; protected bool mModelLoaded; public void update(float elapsedTime) { updateAnimation(elapsedTime); updateSkill(elapsedTime); updateState(elapsedTime); updateController(elapsedTime); } }随着功能增加这个类会不断扩大。使用组件后可以拆分为CharacterPlayer ├── COMCharacterAvatar ├── COMCharacterAnimation ├── COMCharacterStateMachine ├── COMCharacterSkill ├── COMCharacterPack ├── COMCharacterEquip └── COMCharacterController角色只负责组合这些能力不再直接实现所有细节。二、ComponentOwner 和 GameComponent框架中的核心关系是ComponentOwner └── 拥有多个 GameComponentComponentOwner负责创建和保存组件 控制组件启用状态 按照顺序更新组件 销毁并回收组件 根据类型查找组件GameComponent负责实现一个具体能力public abstract class GameComponent : ClassObject { protected ComponentOwner mComponentOwner; protected bool mIgnoreTimeScale; protected bool mDefaultActive; protected bool mActive; public virtual void init(ComponentOwner owner) { mComponentOwner owner; } public virtual void update(float elapsedTime) { } public virtual void fixedUpdate(float elapsedTime) { } public virtual void lateUpdate(float elapsedTime) { } }它不是 Unity 的Component也不会被挂到 GameObject 上。它是一个由框架创建、更新和回收的纯 C# 对象。三、创建一个角色生命值组件先创建一个简单的生命值组件public class COMCharacterHealth : GameComponent { protected int mHP; protected int mMaxHP; public void setHealth(int hp, int maxHP) { mHP hp; mMaxHP maxHP; } public void setHP(int hp) { mHP hp; } public int getHP() { return mHP; } public int getMaxHP() { return mMaxHP; } public bool isDead() { return mHP 0; } public override void resetProperty() { base.resetProperty(); mHP 0; mMaxHP 0; } }组件继承自ClassObject会由对象池复用所以必须在resetProperty()中清理全部字段。四、在角色中添加组件Character本身继承自ComponentOwner可以重写initComponents()public class CharacterPlayer : Character { protected COMCharacterHealth mCOMHealth; protected COMCharacterPack mCOMPack; protected COMCharacterEquip mCOMEquip; protected override void initComponents() { base.initComponents(); addInitComponent(out mCOMHealth, true); addInitComponent(out mCOMPack, true); addInitComponent(out mCOMEquip, true); } public override void resetProperty() { base.resetProperty(); mCOMHealth null; mCOMPack null; mCOMEquip null; } public COMCharacterHealth getCOMHealth() { return mCOMHealth; } }参数中的true表示组件创建后默认启用addInitComponent(out mCOMHealth, true);组件初始化顺序就是添加顺序基类组件 ↓ 生命值组件 ↓ 背包组件 ↓ 装备组件Character.init()会自动调用initComponents()不需要业务代码再次手动初始化每个组件。五、组件的自动更新只要拥有者每帧调用base.update(elapsedTime);ComponentOwner就会按照添加顺序更新全部启用组件Owner.update ↓ 组件1.update ↓ 组件2.update ↓ 组件3.update例如创建一个自动恢复生命值的组件public class COMCharacterHealthRecover : GameComponent { protected float mRecoverTimer; public override void update(float elapsedTime) { mRecoverTimer elapsedTime; if (mRecoverTimer 1.0f) { return; } mRecoverTimer 0.0f; CharacterPlayer character getOwner() as CharacterPlayer; COMCharacterHealth health character?.getCOMHealth(); if (health null || health.isDead() || health.getHP() health.getMaxHP()) { return; } health.setHP(health.getHP() 1); } public override void resetProperty() { base.resetProperty(); mRecoverTimer 0.0f; } }角色类不需要再增加updateHealthRecover(elapsedTime);只要把组件加入角色它就会自动获得更新。六、动态添加和获取组件并不是所有组件都需要初始化时创建。例如技能组件可以在角色第一次获得技能时再创建COMCharacterSkill skill character.getOrAddComponentCOMCharacterSkill();getOrAddComponentT()的规则是组件已经存在 返回已有组件 组件不存在 创建、初始化并返回新组件只查询、不自动创建COMCharacterSkill skill character.getComponentCOMCharacterSkill();只获取当前启用的组件COMCharacterSkill skill character.getActiveComponentCOMCharacterSkill();因此三种接口用途不同getComponent 只查询 getActiveComponent 只查询当前启用的组件 getOrAddComponent 没有时自动创建同一个拥有者不能添加两个完全相同类型的组件。七、启用和停用组件暂时不需要某个能力时可以停用组件character.activeComponentCOMCharacterHealthRecover(false);重新启用character.activeComponentCOMCharacterHealthRecover();停用后的组件仍然存在只是不再执行update fixedUpdate lateUpdate它保存的数据不会被销毁。例如进入安全区后停止自动战斗character.activeComponentCOMCharacterAutoBattle(false);离开安全区后重新启用character.activeComponentCOMCharacterAutoBattle();这比反复创建和销毁组件更适合短时间关闭某项能力。八、只暂停组件更新有时不希望改变组件自身的启用状态只想暂时跳过更新可以使用character.addDisableComponentCOMCharacterController();恢复更新character.removeDisableComponentCOMCharacterController();它和activeComponent(false)的区别是activeComponent(false) 真正修改组件的激活状态 addDisableComponent 只让拥有者暂时跳过该类型组件的更新例如播放一段不可操作的剧情时可以暂停控制器更新但不需要销毁或重新初始化控制器。九、让子类取消基类组件基类可能默认创建了一些组件但某个子类不需要。例如Character默认会创建COMCharacterAvatar COMCharacterAnimation COMCharacterStateMachine某种特殊角色不需要这些能力可以在调用基类初始化前声明public override void init() { addDontAutoCreateCOMCharacterStateMachine(); addDontAutoCreateCOMCharacterAnimation(); addDontAutoCreateCOMCharacterAvatar(); base.init(); }当基类执行addInitComponent(out mStateMachine, true);框架会检测该类型是否被标记为不自动创建。这种方式允许基类统一声明默认能力同时允许子类按需裁剪。十、组件的销毁与回收拥有者销毁时会自动销毁它拥有的全部组件ComponentOwner.destroy ↓ 逐个调用组件 destroy ↓ 调用 resetProperty ↓ 归还 ClassPool组件内部如果注册了事件或持有外部对象应在destroy()中清理public override void destroy() { mEventSystem?.unlistenEvent(this); base.destroy(); }普通字段则在resetProperty()中重置public override void resetProperty() { base.resetProperty(); mTarget null; mTimer 0.0f; }角色中缓存的组件引用也需要重置为null避免对象池复用后保留旧引用。十一、什么时候适合使用 GameComponent适合拆成组件的功能通常具备以下特点属于某个对象而不是全局系统 拥有相对独立的数据和生命周期 可能只在部分对象上存在 可以独立启用或停用 需要跟随拥有者自动更新和销毁典型例子包括角色状态机 角色动画 角色背包 角色技能 摄像机跟随 对象拖拽 自动旋转 透明度渐变 界面交互 场景音效如果功能是整个游戏唯一的全局管理器更适合使用FrameSystem。如果只是一个短暂的业务操作更适合封装成 Command。十二、总结MyFramework 的组件系统由两部分组成ComponentOwner 负责组合、更新和销毁组件 GameComponent 负责实现一项具体能力基本使用流程是1. 创建 GameComponent 子类 2. 在 resetProperty 中重置字段 3. 在拥有者的 initComponents 中添加 4. 由 ComponentOwner 自动更新 5. 按需启用、停用或动态创建 6. 拥有者销毁时自动回收它与 Unity 的MonoBehaviour组件思想相似但完全运行在框架的纯 C# 对象体系中。通过这种方式角色、界面、场景和管理器都可以由多个小组件组合而成避免一个类承担越来越多互不相关的职责。