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# 对象体系中。通过这种方式角色、界面、场景和管理器都可以由多个小组件组合而成避免一个类承担越来越多互不相关的职责。

相关新闻

Python计算机毕设之个人学术文献库智能整理管理系统 基于 Python 的文献检索与归档服务系统(完整前后端代码+说明文档+LW,调试定制等)

Python计算机毕设之个人学术文献库智能整理管理系统 基于 Python 的文献检索与归档服务系统(完整前后端代码+说明文档+LW,调试定制等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/26 2:51:37 阅读更多 →
2025年AI写小说完全指南:5种最佳工具对比与赚钱技巧详解

2025年AI写小说完全指南:5种最佳工具对比与赚钱技巧详解

说实话,2025年的今天,AI辅助创作早就不是什么秘密武器,而是行业标配了。但AI写小说和人类写作的根本区别在于:人类负责灵魂和创意,AI负责效率和执行。在用AI写小说之前最好先了解一下它的原理,知道了原理才…

2026/10/6 5:17:59 阅读更多 →
袁艺凡-FDE前沿工程师:AI Agent从需求拆解到生产交付的工程实践

袁艺凡-FDE前沿工程师:AI Agent从需求拆解到生产交付的工程实践

一、FDE解决的不是模型问题,而是交付问题 FDE的标准英文通常是Forward Deployed Engineer,业内常译为“前线部署工程师”或“一线交付工程师”。本文沿用“FDE前沿工程师”这一人物定位,强调该角色处于AI技术与业务落地的交叉前沿。 袁艺凡是…

2026/10/15 13:20:39 阅读更多 →

最新新闻

基于单片机的电阻炉温控系统:DS18B20与PID调参实战

基于单片机的电阻炉温控系统:DS18B20与PID调参实战

简介:基于单片机的电阻炉温控制系统设计,是一套面向电子、自动化专业学生及嵌入式初学者的完整课程设计/毕设方案,涵盖DS18B20数字测温、单片机温度曲线动态跟踪和上位机通信三大环节。方案从硬件原理图、下位机控制程序到VB上位机界面均有实…

2026/10/15 15:21:06 阅读更多 →
北京密云区油烟机维修|油烟机维修|不启动不开机就近上门检修|欧米到家客服电话

北京密云区油烟机维修|油烟机维修|不启动不开机就近上门检修|欧米到家客服电话

快速导航:本文系统梳理北京地区油烟机九大高发故障的成因、表现与自查方法,帮助您快速判断问题、避开维修陷阱、选择正规靠谱的上门服务。 24小时服务热线:400-996-9791 | 官网:www.oumidj.com 🗺️ 一、欧米到家北…

2026/10/15 15:21:06 阅读更多 →
CTF竞赛高效工具链:逆向/PWN/Web三类题型最小可用组合

CTF竞赛高效工具链:逆向/PWN/Web三类题型最小可用组合

简介:本资源是面向CTF网络安全竞赛选手与初学者的综合性工具软件合集,覆盖隐写分析、Web渗透(含蚁剑、御剑等主流工具)、二进制逆向、杂项解题(Misc)、数据库操作及辅助开发等核心赛题场景,有效…

2026/10/15 15:21:06 阅读更多 →
python的机器人基础原理工业场景模拟第三十二篇:码垛工位,给笛卡尔起点终点,生成直线路径,逐点调用逆运动学换算关节角度。

python的机器人基础原理工业场景模拟第三十二篇:码垛工位,给笛卡尔起点终点,生成直线路径,逐点调用逆运动学换算关节角度。

《Python机器人基础原理》第三十二篇:码垛工位——给笛卡尔起点终点生成直线路径,逐点调用逆运动学换算关节角度1. 实际工业应用场景周三下午,某电商物流分拣中心。一条传送带将码放整齐的纸箱送到抓取工位,一台4-DOF码垛机器人需…

2026/10/15 15:21:06 阅读更多 →
从客服机制看金融平台可信度:常态化服务与用户信任的系统设计

从客服机制看金融平台可信度:常态化服务与用户信任的系统设计

开头 “XX 平台靠谱吗”几乎是消费金融领域最容易被问到、也最难回答的问题。难在哪里?因为“靠谱”不是一个可以量化的单一指标,它包含了资质合规、资金安全、利率透明、隐私保护、催收规范、客服响应等一系列维度。普通用户往往只能看到广告和 App 首页…

2026/10/15 15:21:06 阅读更多 →
Python数据分析实战:从工具链到工程化实践

Python数据分析实战:从工具链到工程化实践

1. 项目概述:为什么Python是数据分析师的首选工具十年前我刚入行数据分析时,主流工具还是Excel和SPSS。直到2015年第一次用Python处理百万行销售数据,5分钟完成同事需要通宵操作的报表,才真正体会到"工欲善其事必先利其器&qu…

2026/10/15 15:20:03 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →