Unreal引擎——动画系统详解-其二

发布于:2025-07-10 ⋅ 阅读:(10) ⋅ 点赞:(0)

我们继续之前的内容,这是整个动画系统大致的分类:

我们从动画图运行时部分开始:

AnimGraphRuntime

AnimGraphRuntime是虚幻引擎动画系统的运行时执行层,负责在游戏运行时处理所有动画节点的计算和姿势生成。

动画图运行时(AnimGraph Runtime)本质上是虚幻引擎动画系统的核心执行引擎和数据流协调器。它由三大核心组件构成:动画节点系统(包括序列播放器、混合空间播放器、各种混合节点和修改器节点,这些节点不是动画资源的简单实例,而是具备运行时逻辑的智能控制器,负责引用动画资源并将其转换为可执行的姿态数据流)、骨骼控制器系统(包括各种IK算法、骨骼修改器、物理模拟等,这些控制器本质上是数学算法的实现,专门处理骨骼变换计算而非骨骼网格组件的实例)、以及上下文管理系统(通过FAnimationUpdateContext、FPoseContext等上下文类型管理整个动画图的执行状态、数据传递、权重控制和同步机制)。整个系统采用基于FAnimNode_Base的统一节点架构,通过Initialize、Update、Evaluate三个核心生命周期方法实现高效的并行动画计算,并通过复杂的数据流管道将各种动画资源、算法和控制逻辑无缝整合为一个统一的动画执行框架。

动画图运行时与核心引擎实现构成了典型的分层架构关系:核心引擎层提供基础设施(UAnimSequenceBase、UBlendSpace、USkeletalMeshComponent、FBoneContainer等动画资源类和数据结构),定义了动画数据的存储格式和基本操作接口;而动画图运行时层则在这些基础设施之上构建了完整的执行逻辑,通过动画节点引用和操作核心引擎的动画资源,通过骨骼控制器处理核心引擎的骨骼变换数据,通过上下文系统协调核心引擎组件之间的数据流转,最终形成了一个从静态动画资源到动态运行时执行的完整转换链条,使得核心引擎的底层能力能够以高效、灵活、可扩展的方式服务于复杂的动画逻辑需求。

动画节点系统 (Animation Node System)

动画节点系统是动画图的基础构建单元,负责处理各种动画数据源(序列、混合空间、状态机等)并将其转换为可执行的姿态数据流。每个节点都是一个独立的数据处理器,具备特定的功能和逻辑。

// 基础动画节点
class FAnimNode_Base
{
public:
    virtual void Update(const FAnimationUpdateContext& Context) {}
    virtual void Evaluate(FPoseContext& Output) {}
    virtual void Initialize(const FAnimationInitializeContext& Context) {}
};

// 混合节点示例
class FAnimNode_BlendSpacePlayer : public FAnimNode_Base
{
    UBlendSpace* BlendSpace;
    float X, Y;  // 混合参数
    
    virtual void Evaluate(FPoseContext& Output) override
    {
        // 根据X,Y参数从BlendSpace采样姿态
        BlendSpace->GetAnimationPose(X, Y, Output.Pose);
    }
};

定义了动画节点的基类 FAnimNode_Base 及其派生类,基类提供更新、评估和初始化方法,派生类如混合空间播放器(从混合空间采样姿态)、骨骼控制基类(修改特定骨骼)、双骨骼IK控制器(计算IK解算修改骨骼链),是动画系统的核心执行单元。 

骨骼控制器系统 (Bone Controller System)

骨骼控制器系统实现各种骨骼变换算法,包括IK求解、物理模拟、约束处理等。它们接收姿态数据,应用数学算法,输出修改后的骨骼变换,实现程序化的骨骼控制。

// 骨骼控制基类
class FAnimNode_SkeletalControlBase : public FAnimNode_Base
{
protected:
    FBoneReference BoneToModify;
    
public:
    virtual void EvaluateSkeletalControl(FComponentSpacePoseContext& Output) = 0;
};

// IK控制器示例
class FAnimNode_TwoBoneIK : public FAnimNode_SkeletalControlBase
{
    FVector EffectorLocation;  // 目标位置
    
    virtual void EvaluateSkeletalControl(FComponentSpacePoseContext& Output) override
    {
        // 计算IK解算,修改骨骼链
        SolveIK(EffectorLocation, Output.Pose);
    }
};

首先, FAnimNode_SkeletalControlBase 是一个继承自 FAnimNode_Base 的抽象基类,它为所有骨骼控制节点提供了基础框架。其中,受保护的 BoneToModify 成员变量用于指定要修改的骨骼引用,而纯虚函数 EvaluateSkeletalControl 则强制要求子类必须实现骨骼控制的具体逻辑,该函数接收 FComponentSpacePoseContext& Output 参数,用于存储修改后的骨骼姿态信息。

接下来, FAnimNode_TwoBoneIK 作为继承自 FAnimNode_SkeletalControlBase 的具体子类,专门实现了两骨IK(反向运动学)功能。IK技术是动画中常用的一种方法,通过指定末端效应器(effector)的目标位置,来自动计算骨骼链中各个关节的旋转角度,从而实现更加自然和精确的动画效果。在这个类中, EffectorLocation 成员变量用于存储效应器的目标位置,而 override 的 EvaluateSkeletalControl 方法则是具体的实现逻辑,它通过调用 SolveIK 函数,根据 EffectorLocation 计算出IK解算结果,并修改 Output.Pose 中的骨骼链,最终实现骨骼的精确控制。

上下文管理系统 (Context Management System)

管理动画更新和评估的上下文信息:

// 动画上下文基类
struct FAnimationBaseContext
{
    class UAnimInstance* AnimInstance;
    float DeltaTime;
    float CurrentTime;
};

// 更新上下文
struct FAnimationUpdateContext : public FAnimationBaseContext
{
    void SetDeltaTime(float InDeltaTime) { DeltaTime = InDeltaTime; }
};

// 姿态上下文
struct FPoseContext : public FAnimationBaseContext
{
    FCompactPose Pose;        // 当前姿态
    FBlendedCurve Curve;      // 曲线数据
    
    void Initialize(UAnimInstance* InAnimInstance)
    {
        AnimInstance = InAnimInstance;
        Pose.SetBoneContainer(&AnimInstance->GetRequiredBones());
    }
};

定义了上下文基类 FAnimationBaseContext 及更新上下文、姿态上下文,包含动画实例、时间、当前姿态和曲线数据等关键信息,为动画执行提供必要的数据支撑。 

动画事件通知系统 (Animation Event Notification System)

处理动画播放过程中的事件触发:

// 动画通知基类
class UAnimNotify : public UObject
{
public:
    virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) {}
    virtual FString GetNotifyName() const { return TEXT("AnimNotify"); }
};

// 音效通知示例
class UAnimNotify_PlaySound : public UAnimNotify
{
    USoundBase* Sound;
    
    virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override
    {
        if (Sound)
        {
            UGameplayStatics::PlaySoundAtLocation(MeshComp->GetWorld(), Sound, 
                                                   MeshComp->GetComponentLocation());
        }
    }
};

// 事件处理
void UAnimInstance::TriggerAnimNotifies(float DeltaTime)
{
    for (auto& NotifyEvent : QueuedAnimEvents)
    {
        NotifyEvent.Notify->Notify(GetSkelMeshComponent(), GetCurrentAnimSequence());
    }
    QueuedAnimEvents.Empty();
}

定义了通知基类 UAnimNotify 及播放音效的派生类,用于在动画播放过程中触发特定事件(如播放音效),实现动画与其他系统的交互。

编辑器扩展

动画系统的编辑器扩展部分主要作用是提供可视化交互工具和属性编辑界面,让开发者能够直观地调整动画节点参数、骨骼变换和曲线数据,同时通过编译时扩展确保编辑结果与运行时逻辑一致,最终实现所见即所得的动画创作流程。

虚幻引擎动画系统的编辑器扩展主要由以下几个核心部分组成,结合代码实现和底层逻辑分析如下:

编辑模式扩展(ModifyBoneEditMode)

ModifyBoneEditMode.cpp是核心实现的文件,该模块实现了骨骼修改的交互编辑功能,允许开发者在编辑器中通过Gizmo直接调整骨骼变换。核心逻辑包括:

UE::Widget::EWidgetMode FModifyBoneEditMode::ChangeToNextWidgetMode(UE::Widget::EWidgetMode InCurWidgetMode)
{
    UE::Widget::EWidgetMode NextWidgetMode = GetNextWidgetMode(InCurWidgetMode);
    CurWidgetMode = FindValidWidgetMode(NextWidgetMode);
    return CurWidgetMode;
}

通过 ChangeToNextWidgetMode 和 FindValidWidgetMode 实现平移/旋转/缩放模式的切换,并根据节点属性自动过滤不可用模式(如已通过蓝图引脚连接的属性会被禁用Gizmo编辑)。

ECoordSystem FModifyBoneEditMode::GetWidgetCoordinateSystem() const
{
    EBoneControlSpace Space = BCS_BoneSpace;
    switch (CurWidgetMode)
    {
    case UE::Widget::WM_Rotate: Space = GraphNode->Node.RotationSpace; break;
    case UE::Widget::WM_Translate: Space = GraphNode->Node.TranslationSpace; break;
    case UE::Widget::WM_Scale: Space = GraphNode->Node.ScaleSpace; break;
    }
    // 根据空间类型返回世界坐标系或局部坐标系
}

根据节点设置的变换空间(骨骼空间/组件空间/世界空间)动态切换Gizmo的坐标系统,确保编辑操作与运行时行为一致。

void FModifyBoneEditMode::DoRotation(FRotator& InRotation)
{
    FQuat DeltaQuat = ConvertCSRotationToBoneSpace(SkelComp, InRotation, RuntimeNode->ForwardedPose, GraphNode->Node.BoneToModify.BoneName, GraphNode->Node.RotationSpace);
    FQuat PrevQuat(RuntimeNode->Rotation);
    RuntimeNode->Rotation = (DeltaQuat * PrevQuat).Rotator();
    GraphNode->SetDefaultValue(GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_ModifyBone, Rotation), RuntimeNode->Rotation);
}

将Gizmo操作转换为骨骼空间的变换增量,并实时更新节点属性和预览视图,实现所见即所得的编辑体验。

属性访问扩展(AnimBlueprintExtension_PropertyAccess)

该扩展实现了动画蓝图中属性访问的编译时扩展,允许节点通过路径访问蓝图属性。核心逻辑包括:

FPropertyAccessResolveResult Result = PropertyAccessEditor.ResolvePropertyAccess(
    InCompilerContext.Blueprint->SkeletonGeneratedClass, InSourcePath, Args);

通过 IPropertyAccessEditor 接口解析属性路径(如 "CharacterMovement.MaxWalkSpeed" ),生成对应的K2节点链。

UK2Node_VariableGet* VariableGetNode = InCompilerContext.SpawnIntermediateNode<UK2Node_VariableGet>(SourceNode, InParentGraph);
VariableGetNode->VariableReference.SetSelfMember(InPropertyName);
VariableGetNode->AllocateDefaultPins();

根据属性类型自动生成变量获取节点、数组访问节点或函数调用节点,并建立节点间连接,实现复杂属性路径的编译时展开。

if(TOptional<UEdGraphSchema_K2::FSearchForAutocastFunctionResults> AutoCastResults = K2Schema->SearchForAutocastFunction(CurrentPin->PinType, InTargetPin->PinType))
{
    UK2Node_CallFunction* AutoCastNode = InCompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(SourceNode, InParentGraph);
    // 自动连接转换节点
}

当属性类型不匹配时,自动插入类型转换节点。 

节点细节面板定制(AnimNextEdGraphNodeCustomization)

该模块为AnimNext节点提供自定义细节面板,支持复杂属性的可视化编辑。核心实现包括:

struct FCategoryDetailsData
{
    enum class EType : uint8 { TraitStack, RigVMNode, Invalid };
    EType Type;
    FName Name;
    TArray<TWeakObjectPtr<UAnimNextEdGraphNode>> EdGraphNodes;
};

通过分类数据结构组织不同类型的节点属性(如TraitStack和RigVMNode),实现细节面板的模块化展示。

static void GenerateMemoryStorage(const TArray<TWeakObjectPtr<URigVMPin>> & ModelPinsToDisplay, FRigVMMemoryStorageStruct& MemoryStorage);

将RigVM节点的内存存储结构转换为可编辑的属性视图,允许开发者直接修改动画节点的内部状态。

编辑器工具栏扩展(ICurveEditorExtension)

该接口定义了曲线编辑器的扩展点,允许添加自定义命令和工具栏按钮:

virtual void BindCommands(TSharedRef<FUICommandList> CommandBindings) = 0;

通过实现此接口注册自定义命令,如动画曲线的批量编辑工具。

virtual TSharedPtr<FExtender> MakeToolbarExtender(const TSharedRef<FUICommandList>& InCommandList) { return nullptr; }

返回工具栏扩展器(FExtender)以添加自定义按钮,例如在动画曲线编辑器中添加"烘焙动画"按钮。

先这样吧,后续再来补充和更正,感觉问题挺多的。


网站公告

今日签到

点亮在社区的每一天
去签到