客户端 Logic:状态、规则与系统调度 private
客户端 Logic:状态、规则与系统调度
本篇只讲运行时“游戏应该发生什么”。GameObject、Animator、Renderer、VFX 和 UI 如何表现见 24;菜单、Inspector、导入和生成工具见 25。
1. Logic 在本项目中的含义
Logic 不是某一个文件夹,而是一条责任边界:
- 保存可序列化、可同步、可复现的状态。
- 根据输入、配置、地图事件和网络消息推进规则。
- 产生“创建实体、移动、扣血、切状态、刷怪、结算”等结果。
- 不把材质、粒子、动画片段或 Inspector 操作当成规则真相。
当前工程的物理目录并未严格分层:
| 目录 | 内容 |
|---|---|
| Assets/Code/Game/OGame/Core | Core 组件和生命值等 ECS 系统 |
| Assets/Code/Game/OGame/Map | 地图实体、触发器、Spawner、导航资源事件 |
| Assets/Code/Game/OGame/Network | 网络上下文、快照和远端上下文 |
| Assets/Code/Game/OLogic/Actor | Hero、Enemy、Boss、Bullet、Buff、Skill、Move |
| Assets/Code/Game/World | 世界和 Gameplay 生命周期 |
| Assets/Code/Game/Temp | 仍在使用的 Battle/StateSync/Spawn 过渡系统 |
| Assets/Code/Game/OGame/Generated | Jenny 生成物,不手改 |
OLogic 中也有 Graphic、Effect、UI 等表现代码,不能仅凭目录名判断边界。
2. 四个 Entitas Context
| Context | 保存什么 | 不应该保存什么 |
|---|---|---|
| Core | Hero、Monster、Bullet、GPE 的逻辑数据和事件 | 材质、Animator Clip |
| Map | Spawner、Trigger、Portal、导航描述和地图事件 | 玩家长期数据 |
| Network | 玩家连接、远端实体和同步状态 | 具体角色 Prefab |
| GameObject | Core 与 Unity 对象之间的桥接句柄 | 战斗规则的唯一真相 |
flowchart LR
Input[输入/AI] --> Rule[Actor / Move / Skill 规则]
Packet[网络消息] --> Sync[StateSync / BattleDataSync]
MapData[地图数据] --> MapEntity[Map Context]
MapEntity --> Spawn[Spawner / Trigger System]
Spawn --> Core[CoreEntity]
Rule --> Core
Sync --> Core
Core --> Event[组件变化/事件组件]
Event --> Bridge[GameObjectContext 桥接]
Logic 的输出应尽量先落到 Entity/Actor 状态,再由桥接层驱动表现。
3. Zenject 装配,Entitas 调度
Assets/Code/Game/OGame/GameInstaller.cs 绑定四个 Context、Core/Map/Network/Object 系统、Factory、Optimizer 和 PacketCache。
Zenject 解决“对象从哪里来、依赖是什么”;Entitas System 解决“每帧或组件变化时执行什么”。Installer 不应该承载业务规则,Generated 目录也不应该手改。
var contexts = Contexts.sharedInstance;
Container.BindInstance(contexts.core);
Container.BindInstance(contexts.map);
Container.BindInstance(contexts.network);
Container.BindInstance(contexts.gameObject);
Container.Bind<StateSyncSystem>().AsTransient();
Container.Bind<RollbackSystem>().AsTransient();
Container.Bind<ObjectAssetSystem>().AsTransient();
4. System 生命周期
| 接口 | 调用时机 | 适合做什么 |
|---|---|---|
| IInitializeSystem | Feature 启动时一次 | 建组、订阅、初始化 |
| IExecuteSystem | 每个逻辑 Tick | 持续模拟和推进 |
| ReactiveSystem | 匹配组件变化时 | 出生、扣血、触发器等事件 |
| ICleanupSystem | 一帧末尾 | 清理一次性事件、批量发送 |
| ITearDownSystem | World/Feature 销毁 | 取消订阅、释放缓存 |
工程大量使用 UniRx 把 Entitas Group 变化转为 Observable。Subscribe 必须绑定 CompositeDisposable 并在 TearDown 释放,否则切地图后旧 World 仍会响应。
5. 主要逻辑链路
5.1 创建实体
地图 Spawner、服务端 SpawnActor 或本地玩法创建请求,最终建立带 EntityID、ObjectType、ActorType 的 CoreEntity。CoreEntity 是资产创建、网络索引和销毁的共同锚点。
创建阶段不能假设 View 已存在。资产加载是异步的,CoreEntity 会先于 GameObject 出现。
5.2 移动
- 输入或 AI 选择目标。
- MoveController 选择 MoveStrategy。
- Strategy 推进逻辑位置、方向和结束条件。
- 联网实体发布 SyncMoveData。
- 表现端同步 Transform、NavMeshAgent 和 Animator。
NavMesh 与策略见 20 客户端寻路,跨端消息见 22 联机同步。
5.3 生命值
合理顺序是:
Damage rule
-> HpChanged event
-> CoreEntity.Hp
-> Network/Replay
-> IViewObject.ApplyHpChangedToView
动画事件可以决定受击特效的时机,但不应成为最终 HP 数据源。
5.4 刷怪与地图事件
Map Context 保存 Spawner 和 Trigger。触发器产生 SpawnEvent,SpawnSystem 创建 CoreEntity,ObjectAssetSystem 再创建表现对象。制作配置见 13/15,Battle 权威见服务端分区。
6. 本地、远端和私有实体
| 类型 | Logic 来源 | 是否发 Battle | 表现特点 |
|---|---|---|---|
| 本地 Hero | 本地输入 + Battle 结果 | 是 | 立即响应,失败回滚 |
| 远端 Hero | Battle 广播 | 否 | PacketCache 后重放 |
| Enemy/Boss | AI 或主机/Battle 消息 | 取决于模式 | 需要明确控制权 |
| PrivateEntity | 本地临时逻辑 | 否 | 不进入共享战斗状态 |
| Dummy Hero | Editor 调试 | 专用路径 | 只在 UNITY_EDITOR 使用 |
新增实体类型时必须明确:谁创建 ID、谁控制、是否同步、何时销毁。
7. Logic 与 Network 的边界
flowchart LR
Packet[Protobuf Packet] --> Decode[解压/校验/索引]
Decode --> LogicEvent[逻辑消息/组件变化]
LogicEvent --> Rule[状态推进]
Rule --> SyncEvent[待发送意图]
SyncEvent --> Encode[量化/压缩]
Encode --> Battle[BattleClient]
网络包不是渲染命令。例如 SC_MoveBegin 先进入 PacketCache,再发布 ApplyMoveData,最后由移动控制器驱动远端表现。
8. Logic 与 Rendering 的桥接规则
- Logic 修改 Entity/Actor 状态或发布领域事件。
- Rendering 订阅组件变化或实现 IViewObject。
- 立即修正通过接口调用,不依赖具体 Prefab 子节点。
- View 可以读取展示状态,但 Transform/Animator 不是唯一逻辑真相。
当前 Actor 类仍同时持有 Transform、NavMeshAgent 和战斗状态,这是历史混合。新代码应减少规则层直接 transform.Find 和硬编码资源路径。
9. 新增玩法逻辑的落点
- 可同步状态:优先放 Core/Actor 数据。
- 组件变化触发:使用 ReactiveSystem。
- 持续逐帧推进:使用 ExecuteSystem 或明确控制器 Tick。
- 纯视觉反馈:放 Rendering。
- 只在制作期使用:放 Editor 扩展。
- 需要跨端:先定义权威端、协议、失败结果和兼容策略。
10. 常见错误
| 现象 | Logic 优先检查 |
|---|---|
| 切地图后事件执行两次 | 旧 Feature/UniRx 未 TearDown |
| CoreEntity 存在但无角色 | 转到 ObjectAssetSystem 和资源层 |
| 动画结束但状态没切 | 动画被当成规则真相,或事件未回写 |
| 远端 HP 对但飘字错 | Logic 正确,View 参数消费错误 |
| 本地移动但远端不动 | SyncMoveData 后转查 22 |
| 怪物触发两次 | Map 事件清理或重复订阅 |
11. 代码审查清单
- 业务真相是否在 Entity/Actor,而非 UI 文本、Animator 或 Transform。
- 一次性事件是否在 Cleanup 清理。
- Subscribe 是否在 TearDown Dispose。
- 异步创建返回时是否重新检查 Entity 和 ID。
- 本地、远端、Private、Dummy 分支是否明确。
- 网络失败是否有恢复状态。
- 新组件是否重新运行 Jenny,Generated 是否未手改。