# 虚幻4渲染编程(动画篇)【第六卷:自定义动画节点】 https://zhuanlan.zhihu.com/p/52266316 有时候会有一些特别的骨架解算控制需求,这时候就需要自己解算骨架数据了。在虚幻引擎中可以通过添加自定义的骨架解算节点来达到这个目的。 ![img](Customanimationnode.assets/v2-216eeb724bf897765fa21059b26d0d0e_hd.jpg) ![img](Customanimationnode.assets/v2-6cafada3c1313416ad662a4d5a1fbc2c_b.jpg) 我这里做了个粗略的解算,我把骨架的一端绑定在了一个动力学粒子上。 ![img](Customanimationnode.assets/v2-cd0aeb4fe7a4510bcf9300560a9dbb74_hd.jpg) **并且可以为动画节点加入碰撞,让动画能和场景交互。** ![img](Customanimationnode.assets/v2-8ccdd7f935652567c667afd0813463e4_b.jpg) 下面将一步一步完成这个节点。 ------ ![img](Customanimationnode.assets/v2-e08af3a02dd2898af04652af43570f36_hd.jpg) ![img](Customanimationnode.assets/v2-e19f9b1207bf30a7fcfc41606c1e3013_hd.jpg) 首先需要定义两个类,一个是AnimGraphNode类和AnimNode类。AnimGraphNode类是给编辑器用的,用于定义编辑器的节点。AnimNode类则是负责具体的执行逻辑,解算等工作。 AnimNode_Verlet.h ```text #pragma once #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "BoneIndices.h" #include "BoneContainer.h" #include "BonePose.h" #include "BoneControllers/AnimNode_SkeletalControlBase.h" #include "CommonAnimTypes.h" #include "VerletParticleNet.h" #include "AnimNode_Verlet.generated.h" /** * Verlet controller that make a bone to Perform verlet instigation */ USTRUCT(BlueprintInternalUseOnly) struct SDHVERLET_API FAnimNode_Verlet : public FAnimNode_SkeletalControlBase { GENERATED_USTRUCT_BODY() FAnimNode_Verlet(); virtual ~FAnimNode_Verlet(); //Bones to attach, VerletMeshNet will caculate these bone UPROPERTY(EditAnywhere, Category = "SkeletalControl") FBoneReference BonesToModify; // FAnimNode_Base interface virtual void GatherDebugData(FNodeDebugData& DebugData) override; // End of FAnimNode_Base interface // FAnimNode_SkeletalControlBase interface virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) override; virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override; // End of FAnimNode_SkeletalControlBase interface private: // FAnimNode_SkeletalControlBase interface virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override; // End of FAnimNode_SkeletalControlBase interface private: SimpleVerletTreeParticleNetMesh VerletParticleNet; }; ``` AnimNode_Verlet.cpp ```text #include "AnimNode/AnimNode_Verlet.h" #include "SceneManagement.h" #include "Engine/SkeletalMeshSocket.h" #include "Animation/AnimInstanceProxy.h" #include "AnimationCoreLibrary.h" #include "../Public/AnimNode/AnimNode_Verlet.h" #include "SceneManagement.h" #include "Engine/SkeletalMeshSocket.h" #include "Animation/AnimInstanceProxy.h" #include "AnimationCoreLibrary.h" #include "Engine/Engine.h" #include "Materials/MaterialInstanceDynamic.h" FAnimNode_Verlet::FAnimNode_Verlet() { //VerletParticleNet = new SimpleVerletTreeParticleNetMesh(); VerletParticleNet.bDrawDebug = true; } FAnimNode_Verlet::~FAnimNode_Verlet() { // if (VerletParticleNet != nullptr) // { // delete VerletParticleNet; // VerletParticleNet = nullptr; // } } void FAnimNode_Verlet::GatherDebugData(FNodeDebugData & DebugData) { } void FAnimNode_Verlet::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext & Output, TArray& OutBoneTransforms) { check(OutBoneTransforms.Num() == 0); const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer(); FCompactPoseBoneIndex CompactPoseBoneToModify = BonesToModify.GetCompactPoseIndex(BoneContainer); FTransform NewBoneTM = Output.Pose.GetComponentSpaceTransform(CompactPoseBoneToModify); FTransform ComponentTransform = Output.AnimInstanceProxy->GetComponentTransform(); VerletParticleNet.VerletInstigation(); VerletParticleNet.SolveConstraint(); FCollisionResponseContainer ResponsChannel; ResponsChannel.SetResponse(ECC_WorldDynamic, ECollisionResponse::ECR_MAX); VerletParticleNet.PerformCollision(GEngine->GetWorld(), ECollisionChannel::ECC_Camera, ResponsChannel); VerletParticleNet.DrawDebug(GEngine->GetWorld()); NewBoneTM.SetTranslation(VerletParticleNet.Particles[2].CurPos); OutBoneTransforms.Add(FBoneTransform(BonesToModify.GetCompactPoseIndex(BoneContainer), NewBoneTM)); } bool FAnimNode_Verlet::IsValidToEvaluate(const USkeleton * Skeleton, const FBoneContainer & RequiredBones) { return (BonesToModify.IsValidToEvaluate(RequiredBones)); } void FAnimNode_Verlet::InitializeBoneReferences(const FBoneContainer& RequiredBones) { BonesToModify.Initialize(RequiredBones); //Build Verlet particle mesh VerletParticleNet.BuildVerletparticleMesh(); } ``` 这些函数看名字其实就知道是干啥的了,这里主要关注一下EvaluateSkeletalControl_AnyThread这个函数,它是负责骨架更新的。 ![img](Customanimationnode.assets/v2-7b779c9f65a0b2fbfabe8f0ec04adee2_hd.jpg) 这段代码是通过骨架的引用找到骨架的index然后从BoneContainner中取出那根骨头。 ![img](Customanimationnode.assets/v2-dff3f0bc8293d09d1d91d155aa378b8b_hd.jpg) 最后这里把位置信息set进骨架里。 现在有了底层的解算类之后,还需要一个对应的负责对应编辑器那个UI的类AnimGraphNode类。 ![img](Customanimationnode.assets/v2-901cd58bc0009b03eaa4059067d96c09_hd.jpg) ![img](Customanimationnode.assets/v2-550de7e43d85dc126acd386d4a59d77e_hd.jpg) 非常简单的逻辑,主要是负责节点的名字信息,分类,接口等等。 最后记得在Build.cs里加入下面的模块 ![img](Customanimationnode.assets/v2-354adfbd6160ac0c7e30073377552ea8_hd.jpg) Enjoy it 编辑于 2018-12-13