CD Code Digest Hub /GTA V · v2

游戏动画接入:fwAnimDirector、MoVE 接口、Clip Set、AnimScene 与 IK

80 分钟阅读 15 个章节 3 张图

本页说明 RAGE 动画核心(见 rage-animation-and-move.md)如何被框架层(rage/framework/src/fwanimation)和游戏层(game/animation、game/ik)装配成每个实体的动画栈:fwAnimDirector 及其组件、三棵动作树的内容、MoVE 参数的跨线程数据流、clip 字典/clip set/network def 的流式与驻留策略、CMovePed 的网络槽位、面部动画接入、AnimScene(游戏内编排场景)、以及 IK 管理器与求解器池。

背景与相关页面#

相关源码文件#

文件内容
rage/framework/src/fwanimation/animdirector.h/.cppfwAnimDirector、预渲染/更新入口、资产回调、Queue
rage/framework/src/fwanimation/directorcomponent*.h/.cpp组件基类与 7 种组件(Creature、MotionTree、Move、Expressions、RagDoll、SyncedScene、FacialRig)
rage/framework/src/fwanimation/move.h/.cppfwMoveNetworkInterface、fwMoveNetworkPlayer、fwMove
rage/framework/src/fwanimation/clipsets.h/.cpp/.pscfwClipSet、fwClipSetManager、clip_sets.meta 格式
rage/framework/src/fwanimation/animmanager.h/.cppfwAnimManager(clip 字典 store 的静态门面)
rage/framework/src/fwanimation/facialclipsetgroups.*、expressionsets.*、syncedscene.*面部 clip set 组、表达式集、synced scene 描述数据
rage/framework/src/fwscene/stores/{clipdictionarystore,networkdefstore,expressionsdictionarystore,framefilterdictionarystore,posematcherstore}.*动画相关资产 store(归属 04 页,这里只引用)
game/animation/Move.h、MovePed.*、MoveObject.*、MoveVehicle.*CMove 族与网络 id 表 CClipNetworkMoveInfo
game/animation/AnimManager.*、EventTags.*、FacialData.*、GestureManager.*、PMDictionaryStore.*、ClipDictionaryStoreInterface.*游戏动画管理、事件标签、面部数据
game/animation/AnimScene/**AnimScene 系统
game/animation/debug/**动画调试工具(__BANK)
game/ik/IkManager.*、IkRequest.h、ikmanagersolvertypes.psc、solvers/*IK 管理器与求解器
game/scene/DynamicEntity.cpp创建 creature/director 与每帧动画入口

1. fwAnimDirector:每实体的动画容器#

fwAnimDirector 负责“动画系统初始化、MoVE 与调度器管理、高层动画特性与更新”src/dev_ng/rage/framework/src/fwanimation/animdirector.h:56-59。它由组件组合而成:最多 sm_MaxNumComponents = 12 个组件及其阶段,另有 (类型数-1)×(阶段数+1) 的 u8 索引表加速按类型+阶段查找 src/dev_ng/rage/framework/src/fwanimation/animdirector.h:445-450。

1.1 组件、阶段与消息#

枚举值证据
eComponentTypeMotionTree、Creature、Move、Expressions、RagDoll、SyncedScene、FacialRigdirectorcomponent.h:29-40
eMessageIdUpdate、ShuttingDown、MotionTreePreDelegate/MidDelegate/PostDelegate、CreatureChanged、FrameDataChanged、SkeletonChangeddirectorcomponent.h:44-56
ePhasekPhasePrePhysics=0、kPhaseMidPhysics、kPhasePreRender;kPhaseAll=-1、kPhaseNone=-2directorcomponent.h:89-97

组件类型在 fwAnimDirectorComponent::InitClass 中逐一 RegisterClass,再调用每种类型的 InitDerivedClass 与 InitPoolClass src/dev_ng/rage/framework/src/fwanimation/directorcomponent.cpp:110-143。注册宏 FW_ANIM_DIRECTOR_IMPLEMENT_COMPONENT_TYPE 为每种组件生成一个 *PooledObject 派生类和 fwPool;池满时回退到普通堆分配(GetNoOfFreeSpaces() > 0 才用池)src/dev_ng/rage/framework/src/fwanimation/directorcomponent.h:245-281。

池容量(ANIMDIRECTOR_POOL_MAX 在 PC/PS4/XB1 为 1024、其他 310;ANIMDIRECTOR_POOL_PED_MAX 为其 1/3 或 110)src/dev_ng/rage/framework/src/fwanimation/animdirector.h:47-50:

组件池大小证据
CreaturePOOL_MAXdirectorcomponentcreature.cpp:45
MotionTreePOOL_MAX*2 + PED_MAXdirectorcomponentmotiontree.cpp:36
MovePOOL_MAXdirectorcomponentmove.cpp:43
ExpressionsPOOL_MAX/3 + PED_MAXdirectorcomponentexpressions.cpp:44
RagDoll、FacialRig、SyncedScenePED_MAXdirectorcomponentragdoll.cpp:42、directorcomponentfacialrig.cpp:91、directorcomponentsyncedscene.cpp:64

1.2 创建与初始化#

游戏实体在 CDynamicEntity::CreateAnimDirector 中先创建 crCreature(使用框架全局的组件工厂、帧数据工厂、加速器)并按需添加 creature 组件:ExtraDofs(2 个 mover DOF + 7 个额外 DOF:第一人称相机权重、左右手约束辅助权重、左右手 CH 骨骼的旋转/平移)、ShaderVars、BlendShapes;随后让 fwDynamicEntityComponent 创建 director,并加入起始动画 scene-update 标志 src/dev_ng/game/scene/DynamicEntity.cpp:1360-1436。ped 以 (ragdoll=true, facialRig=true) 创建 src/dev_ng/game/Peds/Ped.cpp:2871。

fwAnimDirector::Init(withRagDoll, withFacialRig) 的顺序 src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:80-117:

  1. Creature 组件;
  2. InitPrePhysicsMotionTree:pre-physics 动作树组件 + Move 组件(kPhasePrePhysics)src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1079-1089;
  3. InitPreRenderMotionTree:pre-render 动作树,内容为 Pose 节点,若实体 InitIkRequest 返回真则外包一个 Ik 节点 src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1107-1135(ped 总是用自己的 crKinematics 初始化 IK 请求 src/dev_ng/game/Peds/Ped.cpp:32784-32788);
  4. 有 facial rig 时 InitMidPhysicsMotionTree:mid-physics 动作树(不 finalize creature)+ FacialRig 组件 src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1093-1103;
  5. InitExpressions;
  6. 需要时 RagDoll 组件(kPhasePrePhysics)。

图证据:pre-physics 树的根由 mvNetwork::Init 生成(见核心页 11.3);mid-physics 与 pre-render 中的面部链由 fwAnimDirectorComponentFacialRig::Init 请求 src/dev_ng/rage/framework/src/fwanimation/directorcomponentfacialrig.cpp:114-170;Pose/Ik 见上;表达式节点由 CreateComponentExpressions 挂到 pre-render 树或面部表达式根(AddExpression)src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1403-1436。pre-render 树中各子链的确切拼接位置(Merge 如何与 Pose/IK 结合)未逐一追踪 request 插入目标,图中为并列表示。

1.3 表达式装配#

InitExpressions 先等待所有本 director 的活动树完成,然后(ped 专用,硬编码 GetType()==4)用 default 表达式字典中的 upperbody_fixup、upperbody_shadow、independent_mover 等表达式仅为 creature 添加额外 DOF(InitializeCreature,并不追加执行)src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1139-1216;之后若实体有 fwExpressionSet 则用其字典与表达式列表,否则用 archetype 的表达式字典/哈希;名为 pack:/facial.expr 的表达式放入 mid-physics 阶段,其余放入 pre-render 阶段 src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1257-1340。AddExpression(expr, midPhase) 在 mid 阶段需要时会按需创建 mid-physics 树与 facial rig,并以面部表达式根为插入点 src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1403-1436。ped 另有 CPed::InitExpressions,先追加面部绑定转换表达式 faceinit(mid 阶段)src/dev_ng/game/Peds/Ped.cpp:32790-32797。

2. 更新入口、锁与等待#

入口作用证据
PreUpdate(dt)清 m_Updated/m_PoseUpdated,断言上帧已把自己推入队列animdirector.cpp:191-199
StartUpdatePrePhysics(dt, ignoreVis, parent) → UpdateMove向 pre-physics 阶段组件发 kMessageUpdate(Move 组件在此执行 fwMove::Update 并交换输入缓冲),首次时 sm_PrePhysicsQueue.Inc(),再以实体给出的优先级调度 pre-physics 树(父 director 的树作为父依赖)animdirector.cpp:203-208、animdirector.cpp:381-417
EndUpdatePrePhysics(dt)施加动画速度/角速度,删除无引用网络玩家,交换 MoVE 输出缓冲,然后 StartUpdateMidPhysicsanimdirector.cpp:212-227
StartUpdateMidPhysics(dt)若存在 mid-physics 树:发 kMessageUpdate 并以优先级 0 调度animdirector.cpp:286-295
StartUpdatePostCamera(dt)相机后可见性补救:未更新则完整 UpdateMove;已更新未合成则仅调度 composeanimdirector.cpp:242-282
StartUpdatePreRender(dt, parent, copyLastBoundMatrices)设置 frag 碰撞体“复制上次矩阵”,向 pre-render 阶段组件发 kMessageUpdate,调度 pre-render 树animdirector.cpp:299-316
SetPosed(false)若当前处于全局锁且 pre-physics 树已完成,立即走 post-camera 补合成animdirector.cpp:320-336
WaitFor* / WaitForAllUpdatesToComplete单树/本 director 全部/全局等待(后者委托调度器 WaitOnAllComplete)animdirector.cpp:1613-1654

动作树组件与 delegate:fwAnimDirectorComponentMotionTree::Schedule 断言未重复调度且全局锁已持有,置 m_Locked,并以 PreDelegate/MidDelegate(仅当 update)与 PostDelegate 调用全局调度器 src/dev_ng/rage/framework/src/fwanimation/directorcomponentmotiontree.cpp:149-161。三个 delegate 在工作线程上把 kMessageMotionTree{Pre,Mid,Post}Delegate 广播给同阶段组件,PreDelegate 还执行 director 的预委托回调,PostDelegate 结束时清 m_Locked src/dev_ng/rage/framework/src/fwanimation/directorcomponentmotiontree.cpp:199-249。pre-physics 的 PostDelegate 消息处理中:若需要则 SetActivePoseFromSkel,并把实体 Push 进 sm_PrePhysicsQueue(与之前的 Inc 配对)src/dev_ng/rage/framework/src/fwanimation/directorcomponentmotiontree.cpp:90-113。

fwAnimDirector::Queue:单消费者/多生产者无锁 FIFO(sysLockFreeRing<fwEntity,512> + 信号量),Inc 只能在消费者(主线程)调用以登记“将会有一个实体到来”,Pop 在临时耗尽时等待,返回 NULL 表示全部处理完 src/dev_ng/rage/framework/src/fwanimation/animdirector.h:471-510。这正是主线程“按动画完成顺序执行 AI 更新”的机制(调用点见核心页时序表步骤 4)。

全局锁:SetLockedGlobal(locked, phase) 只是设置两个静态变量 src/dev_ng/rage/framework/src/fwanimation/directorcomponentmotiontree.h:136-154,调用点除 CGameWorld、CPostScan 外,还有相机管理器、ped 头像管理器、前端 ped 预览、CPed 即时更新等需要“离线”跑一次动画的地方(例如头像管理器依次锁 PrePhysics/MidPhysics/PreRender 并同步等待)src/dev_ng/game/Peds/rendering/PedHeadshotManager.cpp:2753-2811、src/dev_ng/game/frontend/UIMenuPed.cpp:470-477、src/dev_ng/game/camera/system/CameraManager.cpp:338-341。

3. MoVE 接口层:fwMove / fwMoveNetworkPlayer#

  • fwMoveNetworkInterface 是游戏侧访问网络的接口:设置/读取 float、bool、flag、clip、PM、expressions、filter、frame、observer;插入子网络;广播 request;强制状态;设置 clip set 变量映射;读取输出参数与事件等 src/dev_ng/rage/framework/src/fwanimation/move.h:38-215。
  • fwMoveNetworkPlayer 包装一个 mvSubNetwork,可插入主网络槽位 src/dev_ng/rage/framework/src/fwanimation/move.h:254-281。
  • fwMove 是实体主网络(m_MotionTree、m_MainNetwork、m_Director),提供 Update/PostUpdate/PreMotionTreeUpdate/FinishUpdate/PostFinishUpdate 钩子与 synced scene 网络插入接口,按实体类型派生 src/dev_ng/rage/framework/src/fwanimation/move.h:435-504。
  • Move 组件 Init 通过实体虚函数 CreateMoveObject() 与 GetAnimNetworkMoveInfo() 选择 fwMove 子类与 network def src/dev_ng/rage/framework/src/fwanimation/directorcomponentmove.cpp:54-59;ped 返回 CMovePedPooledObject 与 CClipNetworkMoveInfo::ms_NetworkPed src/dev_ng/game/Peds/Ped.cpp:32728-32736。

MoVE 参数跨线程数据流(双缓冲协议):

图证据:Move 组件对 kMessageUpdate/PreDelegate/MidDelegate 的处理 src/dev_ng/rage/framework/src/fwanimation/directorcomponentmove.cpp:100-167;PreMotionTreeUpdate/PostMotionTreeUpdate/SwapInputBuffers/SwapOutputBuffers 与 fwMove::PreMotionTreeUpdate 调 PrepareBuffers src/dev_ng/rage/framework/src/fwanimation/move.h:899-974;输出交换 src/dev_ng/rage/framework/src/fwanimation/directorcomponentmove.cpp:358-376;根状态拷贝输出 src/dev_ng/rage/suite/src/move/move_state.cpp:918-930。Move 组件注释说明 m_OutputBuffersSwapped 用于防止同一帧任务提前阻塞时重复翻转输出缓冲 src/dev_ng/rage/framework/src/fwanimation/directorcomponentmove.cpp:105-107。命令缓冲管理器每帧在组件类更新中 Update src/dev_ng/rage/framework/src/fwanimation/directorcomponentmove.cpp:400。

4. 资产流式:clip 字典、clip set、MoVE 网络与过滤器#

4.1 Store 一览#

资产Store驻留策略证据
clip 字典g_ClipDictionaryStore(fwClipDictionaryStore),fwAnimManager 是其静态门面(FindSlot/StreamingRequest/AddRef/Get…)按 clip set 元数据:单人/多人常驻或流式src/dev_ng/rage/framework/src/fwscene/stores/clipdictionarystore.h:115、src/dev_ng/rage/framework/src/fwanimation/animmanager.h:38-120
MoVE network defg_NetworkDefStore地图加载后全部加载并 AddRef(常驻)src/dev_ng/rage/framework/src/fwscene/stores/networkdefstore.h:22、animdirector.cpp:1830-1839
pose matcherg_PoseMatcherStore同上全部常驻animdirector.cpp:1841-1850
表达式字典g_ExpressionDictionaryStore(fwAssetRscStore<crExpressionsDictionary>)default 字典常驻,其余按实体引用src/dev_ng/rage/framework/src/fwscene/stores/expressionsdictionarystore.h:13、animdirector.cpp:1852-1867
帧过滤器字典g_FrameFilterDictionaryStore(容量参数 292)全部常驻;BANK 下另读 assets:/anim/filters/*_filtermap.xml 调试名src/dev_ng/rage/framework/src/fwscene/stores/framefilterdictionarystore.cpp:21、animdirector.cpp:1869-1915
PM 字典g_PmDictionaryStore(CPmDictionaryStore : fwAssetRscStore<crpmParameterizedMotionDictionary>)—src/dev_ng/game/animation/PMDictionaryStore.h:39-54

以上常驻加载发生在 fwAnimDirector::InitStatic(INIT_AFTER_MAP_LOADED),非 FINAL 版还会枚举 common:/non_final/anim/move/dofmaps/ 建立 DOF 名称表,并初始化 synced scene 静态数据 src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1826-1936。游戏通过 CClipDictionaryStoreInterface : fwClipDictionaryStoreGameInterface 接收 clip 字典的加载/移除/引用回调 src/dev_ng/game/animation/ClipDictionaryStoreInterface.h:13。

4.2 Clip set(clip_sets.meta)#

Clip set 是“逻辑 clip id → clip 字典中的 clip”的命名映射,带回退链与元数据,用于让同一 MoVE 网络对不同角色/情境替换动画。

  • 数据格式(parser psc):fwClipSet{m_fallbackId, m_clipDictionaryName, m_clipItems: atBinaryMap<hash, fwClipItem*>, m_moveNetworkFlags};fwClipItemWithProps 带 eAnimPlayerFlag 位集、eAnimPriority、骨骼遮罩;fwDirectedClipItemWithProps 加方向;fwClipSetWithGetup 加起身/死亡相关 set src/dev_ng/rage/framework/src/fwanimation/clipsets.psc:61-90。
  • 字典元数据 fwClipDictionaryMetadata{m_streamingPolicy (SP_STREAMING/SP_SINGLEPLAYER_RESIDENT/SP_MULTIPLAYER_RESIDENT), m_memoryGroup, m_streamingPriority},以及内存组预算 fwMemoryGroupMetadata、内存情境调整 fwMemorySituation、变体集 fwClipVariationSet;顶层 fwClipSetManager 持有这些映射 src/dev_ng/rage/framework/src/fwanimation/clipsets.psc:48-58、src/dev_ng/rage/framework/src/fwanimation/clipsets.psc:92-136。
  • 加载:INIT_CORE 时从 platform:/data/anim/clip_sets/clip_sets.<meta> 加载(开发可用参数从 XML 加载并另存 PSO)src/dev_ng/rage/framework/src/fwanimation/clipsets.cpp:26-49、src/dev_ng/rage/framework/src/fwanimation/clipsets.cpp:1736-1759;DLC 可追加,并在会话间移除 src/dev_ng/rage/framework/src/fwanimation/clipsets.cpp:1354。
  • 常驻:INIT_AFTER_MAP_LOADED 时把所有 SP_SINGLEPLAYER_RESIDENT 字典以 STRFLAG_DONTDELETE|STRFLAG_PRIORITY_LOAD 请求并 LoadAllRequestedObjects,加载后 AddRef 并登记为常驻对象 src/dev_ng/rage/framework/src/fwanimation/clipsets.cpp:1192-1269;进入/离开多人会话时由 StartNetworkSession/EndNetworkSession 切换多人常驻集合 src/dev_ng/rage/framework/src/fwanimation/clipsets.cpp:1586-1735。
  • 查找:fwClipSet::GetClip(clipId, CRO_FALLBACK)——若 set 未列出条目则直接在其字典中查;否则仅当条目存在时查字典;找不到且允许回退时递归查询 fallback set src/dev_ng/rage/framework/src/fwanimation/clipsets.cpp:238-285。请求(Request_DEPRECATED)同样沿回退链请求所有字典 src/dev_ng/rage/framework/src/fwanimation/clipsets.cpp:478-517。
  • 游戏还有两级按情境/优先级的 clip set 预流式器(CSituationalClipSetStreamer、CPrioritizedClipSetStreamer),在 Streaming 更新组中运行 src/dev_ng/game/Core/game.cpp:2760-2763(实现位于 game/task 等目录,未在本页展开)。

4.3 MoVE 运行时的资产回调#

MoVE clip 节点的 clip 来源有四种上下文:VariableClipSet、ClipDictionary、AbsoluteClipSet、LocalFile src/dev_ng/rage/suite/src/move/move_clip.h:31-34。fwAnimDirector::RetrieveClip 对 VariableClipSet:经 mvMotionWeb::GetMoveNetworkInterface() 找到 fwMoveNetworkInterface,把 clip set 变量 id 映射为实际 clip set id,再 fwClipSetManager::GetClipSet(...)->GetClip(clipId)(含回退),命中则 AddRef 后返回 src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:648-700。这就是任务通过 SetClipSet(clipSetId, clipSetVarId) 让同一网络播放不同动画的机制 src/dev_ng/rage/framework/src/fwanimation/move.cpp:205。同类回调还有 RetrieveExpression/RetrieveNetworkDef/RetrieveFilter/RetrieveTransitionFilter src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:839-1050,它们在 CMovePed::InitClass 中注册给 MoVE src/dev_ng/game/animation/MovePed.cpp:875-880。

5. 初始化链(gameSkeleton 注册点)#

顺序注册/调用作用证据
INIT_CORECGtaAnimManager::Init → CGtaAnimManager::InitClass(建 g_AnimManager,非打包版读 ClipUserData.meta)、fwAnimManager::InitCore、CDynamicEntity::InitClass、CPedBoneTagConvertor::Init动画管理器src/dev_ng/game/Core/game.cpp:1734-1735、src/dev_ng/game/animation/AnimManager.cpp:37-79
同上CDynamicEntity::InitClass → fwAnimDirector::InitClass(crAnimation/crClip/crpm/crExpressions/crCreature/crmtMotionTree 与非核心节点 InitClass、组件注册)→ CBaseIkManager::InitClass → CMovePed::InitClass(mvMove::InitClass)RAGE 动画与 MoVEsrc/dev_ng/game/scene/DynamicEntity.cpp:1525-1534、animdirector.cpp:340-356
同上fwClipSetManager::Init(INIT_CORE)读取 clip set 元数据game.cpp:1735
场景初始化CBaseIkManager::InitPoolsIK 求解器池src/dev_ng/game/scene/scene.cpp:455
INIT_BEFORE_MAP_LOADEDfwAnimManager::InitBeforeMap → g_ClipDictionaryStore.Shutdown()重置字典 storesrc/dev_ng/rage/framework/src/fwanimation/animmanager.cpp:66-69
INIT_AFTER_MAP_LOADEDfwClipSetManager::Init、fwAnimDirector::InitStatic常驻字典、network def、过滤器、pose matchergame.cpp:2177-2178

6. 游戏层 CMove 族#

  • CClipNetworkMoveInfo 集中定义所有 network def id(ms_NetworkPed、ms_NetworkMotionPed、ms_NetworkTaskOnFootHuman、ms_NetworkTaskCutScene、ms_NetworkSyncedScene、ms_NetworkObject、各类瞄准/掩体/载具进出/攀爬/降落伞网络等)src/dev_ng/game/animation/Move.h:37-130。CMove : fwMove 是游戏基类,另有 CMoveAnimatedBuilding(播放单 clip 的动画建筑)src/dev_ng/game/animation/Move.h:152、src/dev_ng/game/animation/Move.h:230-265。
  • CMovePed:ped 主网络,提供分层网络槽位——motion task(运动)、task(主任务,可带过滤器与 tag 同步标志)、secondary(上半身)、additive、facial、synced scene、左右手动画;BlendStatus 位跟踪各槽淡入/淡出与面部状态;ePedState 区分 animated/static frame/ragdoll src/dev_ng/game/animation/MovePed.h:7-57、src/dev_ng/game/animation/MovePed.h:143-250。ped 状态切换通过 request ChangePedState 与 flag PedStateRagdoll/Animated/StaticFrame 等信号驱动,并使用 RagdollFilter/StaticFrameFilter 自定义过滤器 src/dev_ng/game/animation/MovePed.cpp:39-55。任务与上半身/additive/facial 槽通过 CSimpleBlendHelper 回调插入 src/dev_ng/game/animation/MovePed.cpp:57-76。
  • CMoveObject : CMove(物件,含 primary/secondary 网络槽与外部覆盖帧)、CMoveVehicle : CMoveObject(载具机构动画:开/关/暂停状态位、机构 clip 与相位/速率信号)src/dev_ng/game/animation/MoveObject.h:27-110、src/dev_ng/game/animation/MoveVehicle.h:18-76。各类均有 *PooledObject 池化派生。

任务如何构造 fwMoveNetworkPlayer(CMoveNetworkHelper)并插入这些槽位属于任务系统,见 ../07-ai-tasks/task-catalogue.md。

7. 事件标签与 clip 元数据#

CClipEventTags 以 fwMvPropertyId 为键定义游戏识别的 clip 属性/标签(MoveEvent、Fire、CriticalFrame、viseme TimeOfPreDelay 等)src/dev_ng/game/animation/EventTags.h:23-47,并为结构化标签提供 crProperty 派生访问器:CFireEventTag、CObjectEventTag、CBlendOutEventTag、CIkEventTag、CArmsIKEventTag、CLegsIKEventTag、CLegsIKOptionsEventTag、CLookIKEventTag、CCameraShakeTag、CPadShakeTag、CFacialAnimEventTag、CBlockCutsceneSkippingTag、CApplyForceTag、CBlockRagdollTag、CAudioEventTag、CLoopingAudioEventTag、CMoverFixupEventTag、CDistanceMarkerTag、CPairedClipTag、CFirstPersonCameraEventTag、CMotionBlurEventTag 等,以及 CEventTag : crTag 和三种迭代器 src/dev_ng/game/animation/EventTags.h:48-771。CMotionBlurEventTag 在 EndAnimationUpdate 末尾做每帧更新 src/dev_ng/game/scene/world/GameWorld.cpp:1188-1189。网络玩家的音频与面部触发事件由 fwMoveNetworkInterface::UpdateAudioAndTriggeredFacialEvents 处理 src/dev_ng/rage/framework/src/fwanimation/move.cpp:315。框架层 fwAnimDirector 还提供在 MoVE 网络输出或 clip 相位区间中查找“脚本可见”属性的接口(VisibleToScript 键)src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:63、src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:499-647。

8. 面部动画接入#

面部动画分三层:

  1. 游戏决策:CFacialDataComponent(每 ped)管理面部 idle 类型、按哈希/音频触发面部 clip、疼痛/死亡面部、面部 clip set 与 idle 覆盖,并在任务前后与每帧 Update src/dev_ng/game/animation/FacialData.h:56-167。面部 clip set 变体组由 fwFacialClipSetGroupManager 加载/追加 src/dev_ng/rage/framework/src/fwanimation/facialclipsetgroups.h:38-126,每帧在 Streaming 组更新 src/dev_ng/game/Core/game.cpp:2774。
  2. 信号:CMovePed::SetFacialIdleClip / PlayOneShotFacialClip / PlayVisemeClip 广播 request(FacialIdleClipChanged、PlayFacialOneShot、PlayViseme)并设置 clip 与时长/速率参数(FacialIdleClip、FacialOneShotClip、FacialVisemeClip、...BlendDuration、FacialVisemeRate);网络游戏中 one-shot 面部可被引用计数阻断 src/dev_ng/game/animation/MovePed.cpp:272-345。
  3. 框架执行:fwAnimDirectorComponentFacialRig::Init 在 mid-physics 树中按“低→高优先级”建立 idle(循环)、one-shot、synced scene、viseme、mouth(语音驱动嘴型,循环)五级 merge-blend 链并 Capture 到共享帧 m_Frame;五个 FacialNodeController 以与上面相同的参数键从参数缓冲读取 clip/相位/速率/混合时长;在 pre-render 树中以 Frame(m_Frame) → Filter(FacialFilter) → Merge 把面部结果并入最终姿态 src/dev_ng/rage/framework/src/fwanimation/directorcomponentfacialrig.cpp:95-171。FacialFilter 只放行面部骨骼、动画法线贴图与控制值 DOF src/dev_ng/rage/framework/src/fwanimation/directorcomponentfacialrig.h:89-115、src/dev_ng/rage/framework/src/fwanimation/directorcomponentfacialrig.cpp:427-472。语音驱动嘴型的权重通过 SetVoiceDrivenMouthMovementBlend/UpdateVoiceDrivenMouthMovement 控制 src/dev_ng/rage/framework/src/fwanimation/directorcomponentfacialrig.cpp:820-906。

GestureManager(CGestureManager/g_pGestureManager)管理对话手势 src/dev_ng/game/animation/GestureManager.h:33-63,本页未展开其选择逻辑。

9. Synced scene 与 AnimScene#

9.1 Synced scene(框架层)#

Synced scene 是“游戏内伪过场”:多个实体在同一场景原点上同步播放各自的 clip。fwAnimDirectorComponentSyncedScene 的全局 SyncedScene 记录场景原点(位置/朝向)、时间/上一时间/时长/速率、音频、暂停/绝对/活动/循环/保持末帧/中止标志、销毁回调、引用计数(所有实体移除后才销毁)、可附着的父实体与骨骼、所属脚本线程(脚本退出则中止)src/dev_ng/rage/framework/src/fwanimation/directorcomponentsyncedscene.h:325-376;每实体的 SyncedInstance 持有 clip、面部 clip、播放用的 fwMoveNetworkPlayer、场景 id 与“阻断 mover 更新/场景结束退出/场景内循环”位 src/dev_ng/rage/framework/src/fwanimation/directorcomponentsyncedscene.h:306-323。场景数组 sm_SynchronizedScenes 的容量来自 gameconfig 池 SyncedScenes(默认 1)src/dev_ng/rage/framework/src/fwanimation/directorcomponentsyncedscene.cpp:74-82、src/dev_ng/rage/framework/src/fwanimation/directorcomponentsyncedscene.h:437-443;池满时会尝试释放一个“无主”场景 src/dev_ng/rage/framework/src/fwanimation/directorcomponentsyncedscene.cpp:1070、src/dev_ng/rage/framework/src/fwanimation/directorcomponentsyncedscene.cpp:1128。场景相位在 StartAnimationUpdate 开头统一推进(UpdateSyncedScenes),mover 在 SU_AFTER_ALL_MOVEMENT 回调中更新 src/dev_ng/game/scene/world/GameWorld.cpp:1168-1169、src/dev_ng/game/scene/world/GameWorld.cpp:2698-2701、src/dev_ng/game/scene/world/GameWorld.cpp:2743-2755。

9.2 AnimScene(游戏层编排)#

AnimScene 是数据驱动的“带时间轴的场景”:由 entities(ped/vehicle/object/camera/boolean)和分段(section)中的 events 组成,可选播放列表。

  • 管理器:CAnimSceneManager::Init(INIT_CORE) 读取 gameconfig 池 AnimScenes(至少 1)创建 g_AnimSceneManager,并因 AnimScene 含 map 而禁用 parser 原地加载 src/dev_ng/game/animation/AnimScene/AnimSceneManager.cpp:55-78。创建场景时从 g_fwMetaDataStore 中的 CAnimSceneDictionary 同步强制加载(STRFLAG_FORCE_LOAD+LoadAllRequestedObjects),拷贝模板场景,合并 flags,分配实例 id 并加入映射;超出上限则失败 src/dev_ng/game/animation/AnimScene/AnimSceneManager.cpp:164-219。注意代码注释写“intersect”但实际调用 Union 合并 flags src/dev_ng/game/animation/AnimScene/AnimSceneManager.cpp:198-202。
  • 更新阶段:AnimSceneUpdatePhase = PreAnim, PreAi, PostAi, PostMovement, PostScript, PostCamera, PreRender, ShuttingDown src/dev_ng/game/animation/AnimScene/AnimSceneEvent.h:58-69。调用点:PreAnim game.cpp:2747、PreAi GameWorld.cpp:1914、PostAi GameWorld.cpp:2003、PostMovement GameWorld.cpp:2206、PostScript src/dev_ng/game/script/script.cpp:1170、PostCamera GameWorld.cpp:1303、PreRender GameWorld.cpp:2553。管理器只在游戏未暂停(或单步)时更新所有场景 src/dev_ng/game/animation/AnimScene/AnimSceneManager.cpp:106-129。
  • 场景状态机:State_Initial → LoadMetadata → PreLoadAssets → Loaded → LeadIn → Running ⇄ Paused / Skipping → Finish src/dev_ng/game/animation/AnimScene/AnimScene.h:207-218。FSM 只在 PreAnim 阶段推进,状态变化只允许在 OnUpdate 中发生,循环最多 100 次以检测死循环;每个阶段都会让所有 section 按阶段触发事件,并递归更新子场景 src/dev_ng/game/animation/AnimScene/AnimScene.cpp:620-695、src/dev_ng/game/animation/AnimScene/AnimScene.cpp:761-837。
  • 事件与实体:事件类型 PlayAnim, SyncedScene, SyncedSceneAudio, CreatePed/Object/Vehicle, PlayScene, PlaySyncedCamera, PlaySyncedAnim, PlayVfx, ForceMotionState, InternalLoop, LeadIn, PlayCameraAnim src/dev_ng/game/animation/AnimScene/AnimSceneEvent.h:40-56;事件生命周期为 OnEnterPreload/OnUpdatePreload/OnExitPreload 与按阶段的 OnEnter/OnUpdate/OnExit,外加 OnInit/OnShutdown/OnStartOfScene src/dev_ng/game/animation/AnimScene/AnimSceneEvent.h:136-233。实体类型 CAnimScenePed/Vehicle/Object/Boolean/Camera src/dev_ng/game/animation/AnimScene/entities/AnimSceneEntities.h:29-375。
  • 播放动画的实现:CAnimScenePlayAnimEvent 预加载阶段通过请求助手请求 clip 字典 src/dev_ng/game/animation/AnimScene/events/AnimScenePlayAnimEvent.cpp:50-61;OnEnter 为实体创建 CTaskScriptedAnimation(中优先级)并标记所属场景,ped 走 ped 任务、物件走物件任务、载具放入次级任务树 src/dev_ng/game/animation/AnimScene/events/AnimScenePlayAnimEvent.cpp:139-199;OnUpdate 每帧把任务时间强制对齐为“场景时间 − 事件起点”并同步速率 src/dev_ng/game/animation/AnimScene/events/AnimScenePlayAnimEvent.cpp:203-257。即 AnimScene 不直接驱动动作树,而是通过任务系统驱动,时间由场景主导。
  • 编辑器 CAnimSceneEditor(220 KB,__BANK)与时间轴滚动条 CAnimSceneScrollbar 提供 RAG 内编辑;管理器在 BANK 下创建 “Anim Scenes” 调试 bank src/dev_ng/game/animation/AnimScene/AnimSceneManager.cpp:72-76。

10. IK 系统#

10.1 求解器类型与池#

求解器类型由 psc 生成的位集枚举 IKManagerSolverTypes::Flags 定义:Arm, BodyLook, BodyRecoil, Head, Leg, Torso, TorsoReact, TorsoVehicle, RootSlopeFixup(QuadLeg、QuadrupedReact 被注释)src/dev_ng/game/ik/ikmanagersolvertypes.psc:5-18;四足求解器由 IK_QUAD_LEG_SOLVER_ENABLE/IK_QUAD_REACT_SOLVER_ENABLE 关闭(均为 0)src/dev_ng/game/ik/IkConfig.h:40-56。

求解器类基类 / RAGE 类型池上限优先级(执行序)证据
CTorsoIkSolvercrIKSolverBase / CUSTOM642TorsoSolver.cpp:24、TorsoSolver.cpp:57-60
CBodyRecoilIkSolverCUSTOM163BodyRecoilSolver.cpp:17、BodyRecoilSolver.cpp:57-61
CRootSlopeFixupIkSolverCUSTOM323RootSlopeFixupSolver.cpp:22、RootSlopeFixupSolver.cpp:54-69
CTorsoReactIkSolverCUSTOM164TorsoReactSolver.cpp:25、TorsoReactSolver.cpp:110-113
CTorsoVehicleIkSolver(经 CTorsoVehicleIkSolverProxy,代理池 32)CUSTOM165TorsoVehicleSolver.cpp:23、TorsoVehicleSolver.cpp:57-62、TorsoVehicleSolverProxy.cpp:12-22
CLegIkSolver(经 CLegIkSolverProxy,代理池 40)crIKSolverLegs / LEGS106LegSolver.cpp:29、LegSolver.h:49、LegSolver.cpp:348、LegSolverProxy.cpp:12-23
CHeadIkSolverCUSTOM0(ENABLE_HEAD_SOLVER = 0 HeadSolver.h:10)6HeadSolver.cpp:25、HeadSolver.cpp:78-100
CArmIkSolvercrIKSolverArms / ARMS647ArmIkSolver.cpp:20-51、ArmIkSolver.h:23
CBodyLookIkSolver(经 CBodyLookIkSolverProxy,代理池 40)CUSTOM168BodyLookSolver.cpp:25、BodyLookSolver.cpp:241、BodyLookSolver.cpp:316、BodyLookSolverProxy.cpp:13-28

池通过 FW_INSTANTIATE_CLASS_POOL[_SPILLOVER] 定义(带 spillover 系数的池满后可溢出)src/dev_ng/game/ik/solvers/ArmIkSolver.cpp:23、src/dev_ng/game/ik/solvers/LegSolver.cpp:33,在 CBaseIkManager::InitPools 中初始化;对 BodyLook/Leg/TorsoVehicle 三类代理型求解器,额外把“真实求解器池大小”登记到 ms_SolverPoolSizes 作为可分配配额 src/dev_ng/game/ik/IkManager.cpp:420-458、src/dev_ng/game/ik/IkManager.cpp:61。

10.2 管理器与代理分配#

  • CBaseIkManager 每 ped 一个,持有 crKinematics m_Kinematics 与 m_Solvers[ikSolverTypeCount],AddSolver/GetOrAddSolver/RemoveSolver,以及 PreIkUpdate/PostIkUpdate 钩子 src/dev_ng/game/ik/IkManager.h:177-353;Init 以 ped 骨架初始化 kinematics src/dev_ng/game/ik/IkManager.cpp:139-143。CIkManager 在其上提供游戏 API(LookAt、SetIkTarget、各类 Request(CIkRequest*)、腿部 IK 模式 OFF/PARTIAL/FULL/FULL_MELEE、头部 look-at 标志等)src/dev_ng/game/ik/IkManager.h:72-176、src/dev_ng/game/ik/IkManager.h:360-652;请求对象有 CIkRequestBodyLook/Arm/ResetArm/BodyReact/BodyRecoil/Leg/TorsoVehicle src/dev_ng/game/ik/IkRequest.h:119-445。look-at 请求按优先级竞争,低于当前求解器优先级的请求被拒绝 src/dev_ng/game/ik/IkManager.cpp:1527-1546。
  • AddSolver(type):仅当对应池有空位才 rage_new 求解器(代理型创建代理而非真实求解器),成功后 RefreshKinematics;池满静默返回 false src/dev_ng/game/ik/IkManager.cpp:201-346。
  • RefreshKinematics:清空 kinematics,按类型从大到小加入(代理若已分到真实求解器则加入真实求解器,否则代理本身占位)src/dev_ng/game/ik/IkManager.cpp:392-416;crKinematics::AddSolver 在 HACK_GTA4(GTAV 构建为 1,如 src/dev_ng/rage/base/src/forceinclude/win64_beta.h:515)下按 GetPriority() 升序排序 src/dev_ng/rage/base/src/crbody/kinematics.cpp:20-27、src/dev_ng/rage/base/src/crbody/kinematics.cpp:110-119,Iterate/Solve 均按数组顺序执行 src/dev_ng/rage/base/src/crbody/kinematics.cpp:51-77。故执行序即上表优先级列(Torso→Recoil/RootSlope→TorsoReact→TorsoVehicle→Leg/Head→Arm→BodyLook;同优先级的相对顺序依赖非稳定 std::sort)。
  • ProcessSolvers()(每帧,ProcessAfterCameraUpdate 末尾):用 ped AI LOD 的评分顺序(最多 256 个 ped)排序;先对每个 ped 移除已完成求解器,并按配额为代理设置 Blocked(评分靠前者优先拿到真实求解器);再为未阻断且尚无真实求解器的代理 AllocateSolver,有变化则 RefreshKinematics src/dev_ng/game/ik/IkManager.cpp:492-563、src/dev_ng/game/scene/world/GameWorld.cpp:2529-2533。

10.3 执行位置(哪个线程、哪个阶段)#

图证据:PreIkUpdate/StartUpdatePreRender/PostIkUpdate 调用点 src/dev_ng/game/Peds/Ped.cpp:23386、src/dev_ng/game/Peds/Ped.cpp:23419-23431、src/dev_ng/game/Peds/Ped.cpp:23576;CIkManager::PreIkUpdate 分派到各求解器 src/dev_ng/game/ik/IkManager.cpp:966-1103;crmtNodeIk::Update src/dev_ng/rage/suite/src/crmotiontree/nodeik.cpp:64-69;CLegIkSolver::Iterate 注释“Motiontree thread update”并调用 Update(dt,false) src/dev_ng/game/ik/solvers/LegSolver.cpp:470-491;compose 中 Ik 节点的依赖序列与 dependency_ik 的类型分派见核心页 10.3。

要点:

  • 自定义求解器(CUSTOM)的 Solve 不会在 compose 作业中执行(dependency_ik 的 default: break)。因此它们的工作必须在主线程 PreIkUpdate 或工作线程 Iterate 中完成。例如 CBodyLookIkSolver::Iterate 函数体为空(Update() 被注释掉)src/dev_ng/game/ik/solvers/BodyLookSolver.cpp:373-397;CTorsoIkSolver::Iterate 注释写明在 motion tree 线程调用 src/dev_ng/game/ik/solvers/TorsoSolver.cpp:282-291。这表明 CUSTOM 求解器在 pre-render 树 update 阶段直接修改骨架局部矩阵,随后 compose 的 Pose 节点从骨架反求帧时把这些修改带入(推断:基于 Pose/Ik 节点的合成顺序,未逐个阅读每个 CUSTOM 求解器写骨架的代码)。
  • ARMS/LEGS 类型求解器的 Iterate 计算目标,crIKSolverArms/Legs::Solve 在 compose 依赖中执行 RAGE 的两骨 IK src/dev_ng/rage/base/src/crbody/iksolver.h:197-254。crIKSolverBase 注释将 Iterate 标为“需在 PPU 运行”、Solve 标为“可在 SPU 运行”src/dev_ng/rage/base/src/crbody/iksolverbase.h:68-77,在现世代平台上两者都在工作线程。
  • 第一人称等需要“立即”IK 的情形会在主线程再次 PreIkUpdate(0) 并以 dt=0 重新调度 pre-render 树 src/dev_ng/game/ik/IkManager.cpp:3350-3358。
  • 动画通过 CArmsIKEventTag/CLegsIKEventTag/CLookIKEventTag 等 clip 标签允许/阻断 IK(第 7 节);腿部 IK 调优通过 CLegIkSolver::Tunables(CTuning 派生,自动注册)数据驱动 src/dev_ng/game/ik/Metadata.psc:12-40。

11. 动画调试工具(__BANK/__DEV)#

文件类用途编译条件
debug/AnimViewer.*(约 550 KB)CAnimViewerRAG 动画查看器:选择实体、播放 clip、查看动作树/MoVE、各阶段表达式调试(含 mid-physics)__BANK AnimViewer.h:10;每帧 CAnimViewer::Process game.cpp:2850
debug/AnimDebug.*CDebugRenderer 等动画调试绘制与工具函数__BANK
debug/AnimPlacementTool.*CAnimPlacementTool动画放置/对齐工具__BANK
debug/ClipEditor.*CAnimClipEditorclip 标签/属性编辑__BANK
debug/ClipTagMetadata.*ClipTagAttribute 等标签元数据定义(psc)__BANK
debug/GestureEditor.*—手势编辑__BANK
debug/AnimChecks.*CAnimChecks资产检查__DEV

另有框架层 movedump.*(MoVE 网络状态转储的 parser 结构)与 fwAnimDirector::DebugDump src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:2142,以及 sm_DebugEnableMotionTreeLogging(在 post-delegate 中转储被查看实体的动作树)src/dev_ng/rage/framework/src/fwanimation/directorcomponentmotiontree.cpp:116-133。调试器总体见 ../23-debug-security/debug-tools.md。

附:rage/framework/src/fwactuator。该目录与动画无关:只有 fwRecoilEffect : ioActuatorEffect,是支持扳机振动的手柄上的“枪械后坐力”力反馈效果(持续时间、触发扳机 NONE/LEFT/RIGHT、强度)src/dev_ng/rage/framework/src/fwactuator/recoileffect.h:15-49,由游戏控制层 CControl 使用 src/dev_ng/game/system/control.cpp:2746-2755。输入/力反馈详见 ../22-input-platform/input-and-control-mapping.md。

12. 约束与边界行为#

  • 组件与 IK 求解器池满时:director 组件回退到堆分配;IK AddSolver 静默失败(代理型则在 ProcessSolvers 中被阻断,表现为该 ped 暂时没有该 IK)。
  • 调度前必须持有全局动作树锁;锁期间禁止读写带 director 的骨架局部矩阵与删除实体(见核心页 12)。
  • MoVE 信号必须在 kMessageUpdate(SwapInputBuffers)之前设置才进入本帧;输出在 EndUpdatePrePhysics 交换后才对游戏可见——因此 AI 读到的 MoVE 事件至少滞后一次动作树更新(推断,基于双缓冲协议)。
  • InitExpressions 中 ped 类型以字面量 4 判断(注释 ENTITY_TYPE_PED = 4)src/dev_ng/rage/framework/src/fwanimation/animdirector.cpp:1170-1171,属于框架对游戏枚举的硬编码依赖。
  • network def、帧过滤器、pose matcher 全部常驻;新增网络会直接增加常驻内存。

13. 未决问题 / 证据不足#

  • pre-render 树中面部 Merge 子链、Pose/IK 子链与表达式节点的确切拼接(request target)未逐一追踪。
  • fwAnimDirectorComponentRagDoll、fwAnimDirectorComponentExpressions 的内部实现、ragdoll 帧与 NM 的交互未在本页展开(见物理页)。
  • CMovePed::Update/FinishUpdate 中的具体逻辑(槽位混合状态更新、面部状态机 RequestFacialState)只做了接口级阅读。
  • CSituationalClipSetStreamer/CPrioritizedClipSetStreamer、CGestureManager、CFacialDataComponent::Update 的选择逻辑未展开。
  • 各 CUSTOM IK 求解器(Torso/BodyLook/RootSlope/TorsoReact/TorsoVehicle/Recoil)的数学与写骨架位置未逐个验证。