虚幻动画详解【1】

  [复制链接]
查看16025 | 回复39 | 2017-1-2 14:49:30 | 显示全部楼层 |阅读模式
一说到动画,可能很多朋友会觉得那是美术的事情,和程序没关系。其实恰恰相反。要做出效果比较好的角色动画,还真少不了程序。在这里我把角色动画制作过程中遇到的坑做一个总结。

(注意,在资源制作之前一定要记得调好单位,比如骨骼的单位是cm,动画的单位是inch,会导致一系列bug,比如动画通知无法触发。在动画制作之前调好单位cm)

首先先介绍一下角色动画:
【资源导入】
    第一步就是资源的导入问题。虚幻对动画资源有严格的要求,特别是骨骼。骨骼数据的层级关系如下图
2.PNG

只能有一个根骨,如果是人物的话,根骨位置只能是在脚底。因为如果不在脚底,做根骨位移的时候会出问题(根骨位移后面再讨论)。虚幻官方也有提供maya绑定插件。
1.PNG
如果使用这个插件的话,就不需要担心这些问题了。但是对与使用max的朋友,那么就需要注意这一点了,特别是使用bip的朋友。我这里有一个bip骨骼的解决方案,直接自己做一根新骨头放在脚底,并且作为根骨。(根骨的旋转位置缩放这些记得归零)
3.PNG

做好这些设置之后就可以导出了。导出设置很有讲究,下面是我的导出设置。经过长期的实验和参考官方,下面的导出设置是比较完善的
4.PNG 5.PNG

不管是导出动画还是模型,都要全选骨骼和skin模型。以前我在导出动画的时候喜欢只导出骨骼数据,但是会有几率性地遇见各种bug。所以建议不管是导出动画还是骨骼模型,都将骨骼和模型全选导出,对于动画的话,在导入虚幻的时候只导入动画通道的数据即可。

下面就是导入后的一些设置了

【AnimInstance】
我喜欢先在c++里做一个AnimIncetance父类然后再做动画蓝图继承这个父类。下图列出了两个经常使用的函数virtual void NativeUpdateAnimation(float DeltaSeconds) override;和virtual void NativeInitializeAnimation() override;第一个是每帧刷新,第二个是native初始化。其实也可以用构造函数初始化一些成员变量。不过为了保险起见还是把初始化工作交给 NativeInitializeAnimation()好一点
6.PNG
然后用动画蓝图继承这个类,这样c++层的逻辑就能方便地和动画蓝图交互了

【BlendSpace】
blendspace给人最大的印象就是站立待机和走路跑步的平滑切换了。下面我来分享一下让这个功能能加完美的经验
大家建立个第三人称工程,里面角色的移动时大家是否会感觉角色的blendspace切换有点僵硬呢。下面咋们来做一下调整,首先来到角色蓝图的movementcomponent
将MaxAcceleration调整为800   GroundFriction调整为4  BrakingDeceleration Walking调整为0  maxSpeed调整为600
然后来到Blendspace1D 把X Axis Range改为0到600.你再启动游戏看看,就会发现有平滑的加速和减速过程了。以上数值是我实验很多次的效果我个人比较满意
8.PNG 7.PNG

现在解决了blendspace1D之后你可能还会发现有个blendSpace。
9.PNG 10.PNG

相对于blendspace1D。这个BlendSpace我感觉可以理解为BlendSpace2D。因为是XY两个方向上对动画数据进行混合。那么这个东西怎么用呢。下面我来举一个实例,制作一个类似黑魂那种的半锁定战斗移动系统

首先我们要准备一系列动画
11.PNG
战斗待机,战斗状态向前跑,战斗状态向后退,向左移动,向右移动,向左前方向跑,向左后方向跑。然后加到BlendSpace里面。
12.PNG 14.PNG
然后来到角色状态机,下面我们要做的就是刷新X和Y方向上的数据了

13.PNG

我在AnimInstance里面做了两个函数float CaculateXVelocityValue();和float CaculateYVelocityValue();
15.PNG

用这两个函数在NativeUpdateAnimation(float DeltaSeconds)中刷新CharacterSpeed_X和CharacterSpeed_Y
16.PNG
到目前为止,一切都看似很顺利,那么到底该如何计算CharacterSpeed_X和CharacterSpeed_Y呢。对于BlendSpace1D是CharacterSpeed = AnimOwner->GetVelocity().Size();但是对于BlendSpace的话是XY两个方向上的,那么可以使用CharacterSpeed_X=AnimOwner->GetVelocity().X和CharacterSpeed_Y=AnimOwner->GetVelocity().Y吗?答案是否定的原理看下图
17.png
可以看到CharacterSpeed_X=AnimOwner->GetVelocity().X和CharacterSpeed_Y=AnimOwner->GetVelocity().Y并不是我们想要的结果,而且角色的blendSpace还有左右前后正负方向之分,那么怎么可能使用这种相对于世界坐标的速度分解方式呢?所以我们需要自己为角色建立一个坐标系,如下图方式分解速度。
18.png
于是我的计算速度的那两个函数是这样的
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);
}
20.PNG
最后再AnimOwner->bUseControllerRotationYaw = true;就实现了半锁定的操作系统
还可以对cameraboom做如下设置,让镜头更加有手感
21.PNG

【AnimMontage】

动画蒙太奇最主要的作用就是给程序直接调用。
比如我做的这个翻滚动作
void ASDFCharacter_001::RollFront()
{
    //如果角色不是在地面上,那么是无法执行的
    if (GetCharacterMovement()->IsMovingOnGround() == false)return;

    GetMesh()->GetAnimInstance()->Montage_Play(SDFCharacterAnimMontage, 1.0f);
    GetMesh()->GetAnimInstance()->Montage_JumpToSection(TEXT("Roll"), SDFCharacterAnimMontage);

}
22.PNG
动画蒙太奇还能将动画通知摆放在下面,不过记得在动画蓝图里面做如下设置
23.PNG 但是有的朋友觉得这样打坏了动画状态机的层次结构。利弊自己权衡吧。不过这样通过程序来调用一些动画的确方便。

【动画通知 AnimNotify】
动画通知有以下几个坑(虚幻引擎版本4.11.2)
第一就是最前面说的那个单位。如果skeleton的缩放和animation的不一样,那么动画通知可能会失效。第二个就是蓝图里建的动画通知去调用c++层级的逻辑,引擎会崩溃。
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;动画执行到动画通知所在的那个地方时会调用这个函数
25.PNG 24.PNG
在动画通知里面声明的变量能在动画编辑器里面使用哦。我的战斗连击系统就是基于这个方式实现的。
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下面有一个示意图

26.png AnimGraph.jpg

左图中的C和D和右图动画蓝图中的C和D是相对应的。那么C和D有什么用呢,请看下面一连串的实例图
组1
27.PNG    29.PNG
组2
28.PNG    31.PNG
组3
30.PNG

我们再结合虚幻的动画蓝图看看,Effectorlocation就是C的位置,就是上图那个十字叉的控制器的位置。JointTarget就是那个绿色箭头的位置


IK解算器会根据Effecttorlocation的位置计算骨头位置和旋转。JointTarget就是骨骼三角形的朝向,对于人物来说就是膝盖的朝向。如果Effectorlocation一直位于地面,那么人物就会有比较完美的下蹲运动。
32.PNG 33.PNG
IK反向动力学解算器,这个东西一般用在着力点是固定的或者沿着锁定方向的。比如人的脚和地面的作用,人的手扶着栏杆向下滑动,这些都是IK解算器排上作用的时候。
虚幻里面实现IK的方式就是刚才那张图。注意IK节点的骨骼空间哦!

【VerticalAnimation】

顶点动画目前虚幻大概有两种方法(如果还有其他的求分享哦)
第一个是 目标变形,第二个就是通过shader的方式了。前段时间看到有缓冲帧的方式,不过没试验过。
目标变形动画需要制作多个变形目标,max是使用这个修改器进行制作。
34.PNG

shader的方法就是使用worldoffset来实现。虚幻官方提供了几个插件来制作序列贴图 35.PNG
插件位置在D:\ue4\Epic Games\4.14\Engine\Extras\3dsMaxScripts

效果如下图,用来模拟果冻这些还是可以的。

36.PNG


19.PNG
wuyukang 发表于 2017-1-2 15:42
请问楼主怎么搞掂角色的脚部在地面的放置(脚部IK)?这个我一直搞不掂,有一个视频“Animation Techniques ...

https://docs.unrealengine.com/la ... IKSetups/index.html   你看看这个然后结合我讲的你就懂啦
回复 支持 反对

使用道具 举报

__________ | 2017-1-2 17:17:23 | 显示全部楼层
踩坑是一个漫长的过程,感谢楼主分享干货帖!
回复 支持 反对

使用道具 举报

cqzj70 | 2017-1-2 18:59:36 | 显示全部楼层
谢谢分享
回复

使用道具 举报

lifei000 | 2017-1-2 20:19:34 | 显示全部楼层
感谢楼主的分享!!!!!!!
回复 支持 反对

使用道具 举报

lifei000 | 2017-1-2 20:20:29 | 显示全部楼层
希望还能看到楼主后续的~
回复 支持 反对

使用道具 举报

birllove | 2017-1-2 20:31:47 | 显示全部楼层
请问一下楼主,带移动的动画楼主是使用什么方式移动呢?比如说你那个蒙太奇翻滚动作,是使用动画本身的位移还是 一边播放原地滚动动画一边执行MoveComponentTo?
回复 支持 反对

使用道具 举报

snafu | 2017-1-2 22:59:51 | 显示全部楼层
关于blend space 那里,LZ没有必要用点乘来求,引擎内置了一个FRotatonMatrix的方法。直接可以用这个求x、y轴的速率
回复 支持 反对

使用道具 举报

bboyweichun | 2017-1-3 07:11:43 | 显示全部楼层
感谢高手分享~~~~~~~~~~~
回复 支持 反对

使用道具 举报

unrealplay | 2017-1-3 08:12:08 | 显示全部楼层
感谢分享!!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

7

主题

21

回帖

23

积分

初始化成员

积分
23