客户端 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/ObjectCoreEntity 与 GameObjectEntity 桥接
Assets/Code/Game/OServices/ViewObjectFactory、ObjectView、ViewSettings
Assets/Code/Game/OLogic/ActorActorEntity、Hero/Enemy 表现和动画
Assets/Code/Game/OLogic/Graphic阴影、Renderer 和质量
Assets/Code/Game/OLogic/EffectVFX、Trail、LineRenderer
Assets/Code/Game/UI、OLogic/UIPanel、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));

异步返回后必须重查:

  1. GameObjectEntity 是否仍有效。
  2. linkedCore.IdRecord 是否仍等于创建前 ID。
  3. 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. 位置的三份状态

移动至少涉及:

  1. CoreEntity/Actor 逻辑位置。
  2. GameObject Transform。
  3. 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先表现,失败再修正
远端 HeroBattle 的 SC_MoveBeginPacketCache、策略重放
Enemy/BossAI/主机或 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 时检查:

  1. 配置和枚举是否存在。
  2. ViewSettings 生成器是否识别资源标签。
  3. Factory 能否按 LOCAL、REMOTE、LOCALDUMMY 加载。
  4. Prefab 是否实现 Link/Unlink 所需组件。
  5. Addressables/AssetBundle 索引是否更新。

10. 销毁与对象池

CoreEntity 移除后,ObjectAssetSystem:

  1. 用旧 entityId 找 GameObjectEntity。
  2. 本地控制实体需要时汇总 FrameRemoveActor。
  3. 调用 IObject.Unlink 或从 World 移除 Character。
  4. 销毁 GameObjectEntity。
  5. 实际 Prefab 由资源系统/对象池回收。

View 必须取消 UniRx、DOTween、协程、动画事件和 VFX 句柄,否则对象池复用后会带着旧实体状态。

11. UI 的边界

  • 大厅 UI 消费 Lobby 业务结果和 ViewModel。
  • 战斗 HUD 消费 World/Core/Actor 状态和领域消息。

“包到了但 UI 没变”按 协议回调 -> MessageCenter/ViewModel -> Panel 生命周期 -> 控件刷新 排查。Panel 不应直接拥有战斗规则。

12. 图形子模块

模块典型能力
OLogic/GraphicShadowProjector、质量和 Renderer 控制
OLogic/EffectEffector、Trail、LineRenderer、特效生命周期
Lightmap地图局部烘焙纹理和运行时绑定
Camera跟随、遮挡、震动和剧情镜头
UIPanel、HUD、世界空间提示

光照制作与加载见 19 光照与 Lightmap 烘焙。

13. 排错矩阵

现象Rendering 优先检查
CoreEntity 有但画面无对象Factory 异常、ViewSettings/索引、CreateFailed
角色位置抖动Logic/Transform/Agent 三份位置、Warp、缓存加速
Animator 一直 IdleActor 状态到参数桥接、Controller、状态名
受击不播放ViewObject、reason、不可打断状态
切图后特效残留Unlink、Tween/订阅/协程未清理
远端突然冲刺PacketCache accelerationRatio
Lightmap 丢失Lightmap 场景/纹理未同批加载

14. 验收清单

  • 本地 Hero、远端 Hero、Enemy 分别验证。
  • 创建后立即销毁,覆盖异步竞态。
  • 切图、重连后检查重复订阅。
  • 低帧、弱网检查远端加速。
  • 传送、回滚、Portal 检查 Agent/Transform。
  • 对象池复用检查 Animator、材质、Trail、粒子、UI 是否清空。
  • Editor、Android 真机和不同质量档分别验证。

15. 关联阅读


上一篇:客户端 Logic · 下一篇:Unity Editor 扩展