一说到动画,可能很多朋友会觉得那是美术的事情,和程序没关系。其实恰恰相反。要做出效果比较好的角色动画,还真少不了程序。在这里我把角色动画制作过程中遇到的坑做一个总结。
(注意,在资源制作之前一定要记得调好单位,比如骨骼的单位是cm,动画的单位是inch,会导致一系列bug,比如动画通知无法触发。在动画制作之前调好单位cm)
首先先介绍一下角色动画:
【资源导入】
第一步就是资源的导入问题。虚幻对动画资源有严格的要求,特别是骨骼。骨骼数据的层级关系如下图
只能有一个根骨,如果是人物的话,根骨位置只能是在脚底。因为如果不在脚底,做根骨位移的时候会出问题(根骨位移后面再讨论)。虚幻官方也有提供maya绑定插件。
如果使用这个插件的话,就不需要担心这些问题了。但是对与使用max的朋友,那么就需要注意这一点了,特别是使用bip的朋友。我这里有一个bip骨骼的解决方案,直接自己做一根新骨头放在脚底,并且作为根骨。(根骨的旋转位置缩放这些记得归零)
做好这些设置之后就可以导出了。导出设置很有讲究,下面是我的导出设置。经过长期的实验和参考官方,下面的导出设置是比较完善的
不管是导出动画还是模型,都要全选骨骼和skin模型。以前我在导出动画的时候喜欢只导出骨骼数据,但是会有几率性地遇见各种bug。所以建议不管是导出动画还是骨骼模型,都将骨骼和模型全选导出,对于动画的话,在导入虚幻的时候只导入动画通道的数据即可。
下面就是导入后的一些设置了
【AnimInstance】
我喜欢先在c++里做一个AnimIncetance父类然后再做动画蓝图继承这个父类。下图列出了两个经常使用的函数virtual void NativeUpdateAnimation(float DeltaSeconds) override;和virtual void NativeInitializeAnimation() override;第一个是每帧刷新,第二个是native初始化。其实也可以用构造函数初始化一些成员变量。不过为了保险起见还是把初始化工作交给 NativeInitializeAnimation()好一点
然后用动画蓝图继承这个类,这样c++层的逻辑就能方便地和动画蓝图交互了
【BlendSpace】
blendspace给人最大的印象就是站立待机和走路跑步的平滑切换了。下面我来分享一下让这个功能能加完美的经验
大家建立个第三人称工程,里面角色的移动时大家是否会感觉角色的blendspace切换有点僵硬呢。下面咋们来做一下调整,首先来到角色蓝图的movementcomponent
将MaxAcceleration调整为800 GroundFriction调整为4 BrakingDeceleration Walking调整为0 maxSpeed调整为600
然后来到Blendspace1D 把X Axis Range改为0到600.你再启动游戏看看,就会发现有平滑的加速和减速过程了。以上数值是我实验很多次的效果我个人比较满意
现在解决了blendspace1D之后你可能还会发现有个blendSpace。
相对于blendspace1D。这个BlendSpace我感觉可以理解为BlendSpace2D。因为是XY两个方向上对动画数据进行混合。那么这个东西怎么用呢。下面我来举一个实例,制作一个类似黑魂那种的半锁定战斗移动系统
首先我们要准备一系列动画
战斗待机,战斗状态向前跑,战斗状态向后退,向左移动,向右移动,向左前方向跑,向左后方向跑。然后加到BlendSpace里面。
然后来到角色状态机,下面我们要做的就是刷新X和Y方向上的数据了
我在AnimInstance里面做了两个函数float CaculateXVelocityValue();和float CaculateYVelocityValue();
用这两个函数在NativeUpdateAnimation(float DeltaSeconds)中刷新CharacterSpeed_X和CharacterSpeed_Y
到目前为止,一切都看似很顺利,那么到底该如何计算CharacterSpeed_X和CharacterSpeed_Y呢。对于BlendSpace1D是CharacterSpeed = AnimOwner->GetVelocity().Size();但是对于BlendSpace的话是XY两个方向上的,那么可以使用CharacterSpeed_X=AnimOwner->GetVelocity().X和CharacterSpeed_Y=AnimOwner->GetVelocity().Y吗?答案是否定的原理看下图
可以看到CharacterSpeed_X=AnimOwner->GetVelocity().X和CharacterSpeed_Y=AnimOwner->GetVelocity().Y并不是我们想要的结果,而且角色的blendSpace还有左右前后正负方向之分,那么怎么可能使用这种相对于世界坐标的速度分解方式呢?所以我们需要自己为角色建立一个坐标系,如下图方式分解速度。
于是我的计算速度的那两个函数是这样的
float USDFAnimInstance_001::CaculateYVelocityValue()
{
if (AnimOwner == NULL)return -888.0f;
if (AnimOwner->GetVelocity().Size() <= 10.0f)return 0.0f;
FVector FowardVector = AnimOwner->GetArrowComponent()->GetForwardVector();
FVector FowardVectorScaled = FowardVector*10000.0f;
FVector FowardVelocity = UKismetMathLibrary:rojectVectorOnToVector(AnimOwner->GetVelocity(), FowardVectorScaled);
return FVector:otProduct(FowardVelocity, FowardVector);
}
//向左和向右
float USDFAnimInstance_001::CaculateXVelocityValue()
{
if (AnimOwner == NULL)return -888.0f;
if (AnimOwner->GetVelocity().Size() <= 0.0f)return 0.0f;
FVector RightVector = UKismetMathLibrary::Cross_VectorVector(AnimOwner->GetArrowComponent()->GetForwardVector(), FVector(0.0f, 0.0f, 1.0f));
FVector RightVectorScaled = RightVector*10000.0f;
FVector RightVelocity = UKismetMathLibrary:rojectVectorOnToVector(AnimOwner->GetVelocity(), RightVectorScaled);
return FVector:otProduct(RightVelocity, RightVector);
}
最后再AnimOwner->bUseControllerRotationYaw = true;就实现了半锁定的操作系统
还可以对cameraboom做如下设置,让镜头更加有手感
【AnimMontage】
动画蒙太奇最主要的作用就是给程序直接调用。
比如我做的这个翻滚动作
void ASDFCharacter_001::RollFront()
{
//如果角色不是在地面上,那么是无法执行的
if (GetCharacterMovement()->IsMovingOnGround() == false)return;
GetMesh()->GetAnimInstance()->Montage_Play(SDFCharacterAnimMontage, 1.0f);
GetMesh()->GetAnimInstance()->Montage_JumpToSection(TEXT("Roll"), SDFCharacterAnimMontage);
}
动画蒙太奇还能将动画通知摆放在下面,不过记得在动画蓝图里面做如下设置
但是有的朋友觉得这样打坏了动画状态机的层次结构。利弊自己权衡吧。不过这样通过程序来调用一些动画的确方便。
【动画通知 AnimNotify】
动画通知有以下几个坑(虚幻引擎版本4.11.2)
第一就是最前面说的那个单位。如果skeleton的缩放和animation的不一样,那么动画通知可能会失效。第二个就是蓝图里建的动画通知去调用c++层级的逻辑,引擎会崩溃。
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;动画执行到动画通知所在的那个地方时会调用这个函数
在动画通知里面声明的变量能在动画编辑器里面使用哦。我的战斗连击系统就是基于这个方式实现的。
void USDFCha_001Skill_002::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
ASDFCharacter_001* SDFPlayer = Cast<ASDFCharacter_001>(MeshComp->GetOwner());
if (SDFPlayer == NULL)return;
if (SkillSection == 0)
{
SDFPlayer->SetSDFMouseLKeyState(ESDFMouseLKeyState::VE_NormalAttack_01);
SDFPlayer->SetbIsCanUseUseBotton_LM(true);
return;
}
if (SkillSection == 1)
{
//重置可点性
SDFPlayer->SetbIsCanUseUseBotton_LM(true);
return;
}
if (SkillSection == 2)
{
//重置可点性
SDFPlayer->SetbIsCanUseUseBotton_LM(true);
return;
}
if (SkillSection == 3)
{
//重置可点性
SDFPlayer->SetbIsCanUseUseBotton_LM(true);
return;
}
if (SkillSection == 4)
{
//重置可点性
SDFPlayer->SetbIsCanUseUseBotton_LM(true);
SDFPlayer->GetCharacterMovement()->SetMovementMode(MOVE_Walking);
return;
}
}
【IK,FK】
我在群里看过很多朋友,只要是动画的四肢都加上IK,觉得IK什么都好。下面就来说一下IK和FK
对于IK下面有一个示意图
左图中的C和D和右图动画蓝图中的C和D是相对应的。那么C和D有什么用呢,请看下面一连串的实例图
组1
组2
组3
我们再结合虚幻的动画蓝图看看,Effectorlocation就是C的位置,就是上图那个十字叉的控制器的位置。JointTarget就是那个绿色箭头的位置
IK解算器会根据Effecttorlocation的位置计算骨头位置和旋转。JointTarget就是骨骼三角形的朝向,对于人物来说就是膝盖的朝向。如果Effectorlocation一直位于地面,那么人物就会有比较完美的下蹲运动。
IK反向动力学解算器,这个东西一般用在着力点是固定的或者沿着锁定方向的。比如人的脚和地面的作用,人的手扶着栏杆向下滑动,这些都是IK解算器排上作用的时候。
虚幻里面实现IK的方式就是刚才那张图。注意IK节点的骨骼空间哦!
【VerticalAnimation】
顶点动画目前虚幻大概有两种方法(如果还有其他的求分享哦)
第一个是 目标变形,第二个就是通过shader的方式了。前段时间看到有缓冲帧的方式,不过没试验过。
目标变形动画需要制作多个变形目标,max是使用这个修改器进行制作。
shader的方法就是使用worldoffset来实现。虚幻官方提供了几个插件来制作序列贴图
插件位置在D:\ue4\Epic Games\4.14\Engine\Extras\3dsMaxScripts
效果如下图,用来模拟果冻这些还是可以的。
|