客户端 Rendering:View、动画、特效与 UI private
客户端 Rendering:View、动画、特效与 UI
本篇只讲“逻辑结果怎样变成玩家看见和听见的内容”。战斗规则和状态归属见 23;制作资源和工具的 Editor 代码见 25。
1. Rendering 的范围
本项目的 Rendering 层包括:
- CoreEntity 到 GameObject/Prefab 的实例化与绑定。
- Transform、NavMeshAgent、Animator/Animation 和 Spine。
- MeshRenderer、SkinnedMeshRenderer、材质、阴影和 Lightmap。
- VFX、Trail、LineRenderer、音效和镜头反馈。
- 世界空间 UI、战斗 HUD 和普通 Panel。
- 远端实体的缓存后重放、加速和位置修正。
主要代码位置:
| 目录 | 内容 |
|---|---|
| Assets/Code/Game/OGame/Object | CoreEntity 与 GameObjectEntity 桥接 |
| Assets/Code/Game/OServices/View | ObjectFactory、ObjectView、ViewSettings |
| Assets/Code/Game/OLogic/Actor | ActorEntity、Hero/Enemy 表现和动画 |
| Assets/Code/Game/OLogic/Graphic | 阴影、Renderer 和质量 |
| Assets/Code/Game/OLogic/Effect | VFX、Trail、LineRenderer |
| Assets/Code/Game/UI、OLogic/UI | Panel、ViewModel、战斗 HUD |
这些目录与 Logic 有历史混合,本文按责任阅读。
2. CoreEntity 怎样变成 GameObject
ObjectAssetSystem 监听具有 ObjectType + EntityID 的 CoreEntity:
flowchart LR
Core[CoreEntity] --> Group[Entitas Group Added]
Group --> Delay[DelayFrame 到帧末]
Delay --> GOE[GameObjectEntity]
GOE --> Link[AddLinkedCore]
Link --> Factory[ObjectFactory.CreateAsync]
Factory --> Instance[IObject / Actor Prefab]
Instance --> Bind[Link + Actor.ReInit]
Bind --> View[View / Animator / Agent / VFX]
var gameObject = _gameObjectContext.CreateEntity();
var idRecord = core.entityID.Value;
gameObject.AddLinkedCore(core.entityID.Value, core, idRecord);
var instance = await _objectFactory.CreateAsync(
core.objectType.Value,
position,
rotation,
GetObjectFactoryType(core));
异步返回后必须重查:
- GameObjectEntity 是否仍有效。
- linkedCore.IdRecord 是否仍等于创建前 ID。
- CoreEntity 是否仍有效。
对象池和切图可能在 Await 期间销毁或复用实体,新增异步加载不能省略这些检查。
3. LinkedCore 是跨层索引
GameObjectContext 通过 LinkedCore 主索引从 entityId 找到 View:
var gameObject =
gameObjectContext.GetEntityWithLinkedCore(core.entityID.Value);
GameObjectEntity 保存 Core 引用、ID 记录、GameObject/IObject、IViewObject、创建失败和激活标记。Logic 不应遍历场景层级寻找角色,而应先按 entityId 找桥接实体。
4. IViewObject 是表现端口
Assets/Code/Game/OGame/Object/Components/ViewObjectComponent.cs 定义常用表现操作:
public interface IViewObject
{
int State { get; set; }
int ServerState { get; }
TimeSpan StateTime { get; }
void ApplyHpChangedToView(...);
void OnEntityDestroy();
void OnTeleport(float3 position);
void OnRelocate(float3 position);
void OnSyncPosition(float3 position, bool isTeleport);
void OnSyncRotation(quaternion rotation);
void Active();
void Deactive();
}
状态同步、回滚和 HP 系统通过这个端口驱动 View,不需要知道 Prefab 内具体 Animator 或血条节点。
5. 位置的三份状态
移动至少涉及:
- CoreEntity/Actor 逻辑位置。
- GameObject Transform。
- NavMeshAgent 内部位置和 nextPosition。
ActorEntity.OnSyncPosition:
if (IsLocal)
MoveController?.End();
OnSyncPos(position, isTeleport);
if (NavMeshAgent != null)
{
NavMeshAgent.updatePosition = false;
if (IsLocal) NavMeshAgent.autoBraking = true;
NavMeshAgent.Warp(position);
if (IsLocal) NavMeshAgent.autoBraking = false;
}
只改 transform.position 可能让 Agent 和逻辑位置继续分叉,所以同步修正使用 Warp。传送、远端 5 米纠偏、MoveFail 对 isTeleport 的取值不完全相同,视觉跳变要继续追 OnSyncPos。
6. 本地和远端表现
| 对象 | 位置来源 | 延迟策略 |
|---|---|---|
| 本地 Hero | 输入和本地 MoveController | 先表现,失败再修正 |
| 远端 Hero | Battle 的 SC_MoveBegin | PacketCache、策略重放 |
| Enemy/Boss | AI/主机或 Battle 广播 | EnemyPacketCache |
| 私有对象 | 本地规则 | 不进入联机缓存 |
远端 CharacterDirectionMove 可被配置转换为 CharacterNavMove;弱网时速度可能临时变为 1.5x/3x/4x。完整机制见 22 联机同步。
7. 动画控制边界
flowchart LR
Logic[Actor State / Skill / Move] --> StateMachine[角色状态机]
StateMachine --> Controller[动画封装/Animator 参数]
Controller --> Animator[Animator / Animation / Spine]
Animator --> Event[Animation Event]
Event --> Feedback[VFX / 音效 / 命中窗口]
必须区分:
- 逻辑状态:能否移动、技能是否成立、HP 是否归零。
- 动画状态:Clip、过渡、播放时间。
- 表现事件:脚步、挥刀特效、镜头、音效。
Animation Event 可以触发表现时间点,但不能单独决定权威伤害或服务器技能成功。玩家 Animator 与出生初始化详见 18 玩家控制、创建、出生点与动画。
8. HP、受击和死亡
HpChanged event
-> CoreEntity HP
-> GetEntityWithLinkedCore
-> IViewObject.ApplyHpChangedToView
-> 受击动画 / 飘字 / 血条 / VFX / 死亡
数值正确但表现不对时检查:
- View 未创建或 hasViewObject 为 false。
- changedSourcePlayerId、killerId、reason 错。
- 对象正处于 Deactive/Destruct。
- 高优先级动画覆盖受击。
- UI/VFX 资源加载失败。
9. ViewSettings 与资源
Services.View.ViewSettings 是 ViewSettingGenerator 生成的 ScriptableObject 类型,文件头明确标记不要手改。新增 ObjectType/View 时检查:
- 配置和枚举是否存在。
- ViewSettings 生成器是否识别资源标签。
- Factory 能否按 LOCAL、REMOTE、LOCALDUMMY 加载。
- Prefab 是否实现 Link/Unlink 所需组件。
- Addressables/AssetBundle 索引是否更新。
10. 销毁与对象池
CoreEntity 移除后,ObjectAssetSystem:
- 用旧 entityId 找 GameObjectEntity。
- 本地控制实体需要时汇总 FrameRemoveActor。
- 调用 IObject.Unlink 或从 World 移除 Character。
- 销毁 GameObjectEntity。
- 实际 Prefab 由资源系统/对象池回收。
View 必须取消 UniRx、DOTween、协程、动画事件和 VFX 句柄,否则对象池复用后会带着旧实体状态。
11. UI 的边界
- 大厅 UI 消费 Lobby 业务结果和 ViewModel。
- 战斗 HUD 消费 World/Core/Actor 状态和领域消息。
“包到了但 UI 没变”按 协议回调 -> MessageCenter/ViewModel -> Panel 生命周期 -> 控件刷新 排查。Panel 不应直接拥有战斗规则。
12. 图形子模块
| 模块 | 典型能力 |
|---|---|
| OLogic/Graphic | ShadowProjector、质量和 Renderer 控制 |
| OLogic/Effect | Effector、Trail、LineRenderer、特效生命周期 |
| Lightmap | 地图局部烘焙纹理和运行时绑定 |
| Camera | 跟随、遮挡、震动和剧情镜头 |
| UI | Panel、HUD、世界空间提示 |
光照制作与加载见 19 光照与 Lightmap 烘焙。
13. 排错矩阵
| 现象 | Rendering 优先检查 |
|---|---|
| CoreEntity 有但画面无对象 | Factory 异常、ViewSettings/索引、CreateFailed |
| 角色位置抖动 | Logic/Transform/Agent 三份位置、Warp、缓存加速 |
| Animator 一直 Idle | Actor 状态到参数桥接、Controller、状态名 |
| 受击不播放 | ViewObject、reason、不可打断状态 |
| 切图后特效残留 | Unlink、Tween/订阅/协程未清理 |
| 远端突然冲刺 | PacketCache accelerationRatio |
| Lightmap 丢失 | Lightmap 场景/纹理未同批加载 |
14. 验收清单
- 本地 Hero、远端 Hero、Enemy 分别验证。
- 创建后立即销毁,覆盖异步竞态。
- 切图、重连后检查重复订阅。
- 低帧、弱网检查远端加速。
- 传送、回滚、Portal 检查 Agent/Transform。
- 对象池复用检查 Animator、材质、Trail、粒子、UI 是否清空。
- Editor、Android 真机和不同质量档分别验证。