添加走路转向动画
2023年6月11日 2024年1月11日
概览
已实现动作
- Walk: 从静止到跑起来支持加速,使用
BS_Locomotion_Walk_1D
- Run:
BS_Locomotion_Run
- Jump:
JumpStart
,JumpEnd
,JumpLoop
本节内容
- 重新实现走路动画,根据游戏角色前进方向和速度方向的夹角,增加转向和后退动画
- 之后还有 装弹
Reloading
, 死亡Death
和瞄准Aiming
动画
2D混合空间
Blend Space
拥有两个轴, Vertial Axis
和 Horizontal Axis
建模
游戏角色朝向
- | |
---|---|
前进和后退 | ForwardVector |
左右 | RightVector |
- 游戏角色前后左右移动有使用到这两个方向向量
- 我们水平移动鼠标时,
ForwardVector
和RightVector
随之发生改变 - 在视觉上,
ForwardVector
一直向前,RightVector
一直向右,二者在水平面垂直
数学基础
点乘
计算向量夹角
- 其中一个向量为
0向量
时,点乘结果为0
,而反余弦计算得到其夹角为PI/2
叉乘
-
将向量夹角从[0, PI]扩充到[-PI, PI]
-
theta
为0
PI
和-PI
时,叉乘结果为0
,此时无法通过向量确定平面,也就无法得到平面法向量 -
向量c的z坐标
c.z
的符号说明向量的方向
速度方向
VelocityVector
-
Actor
的速度是一个矢量,其表现是单位时间内的位移方向 -
当速度不为
0
,VelocityVector
和ForwardVector
的夹角与此时Actor
应该展现的动画有关
计算游戏角色前进方向和速度方向的夹角
VelocityVector
和 ForwardVector
的夹角通过向量点乘反余弦结果和向量叉乘结果z分量符号 sign(c.z)
的乘积得到
夹角 | 反余弦结果 | sign(c.z) | 目的动画 | 使用乘积效果 |
---|---|---|---|---|
0 | 0 | 0 | Fwd | o |
( 0, PI/2 ) | ( 0, PI/2 ) | 1 | Fwd > Right | o |
PI/2 | PI/2 | 1 | Right | o |
( PI/2, PI ) | ( PI/2, PI ) | 1 | Right > Bwd | o |
PI | PI | 0 | Bwd | x |
-PI | PI | 0 | Bwd | x |
( -PI, -PI/2 ) | ( PI/2, PI ) | -1 | Left > Bwd | o |
-PI/2 | PI/2 | -1 | Left | o |
( -PI/2, 0 ) | ( 0, PI/2 ) | -1 | Fwd > Left | o |
分析特殊情形
坐标 ( Velocity
, Angle
)
速度为0,夹角应该为0
Velocity
为 0
,( 0, 任意 ) 对应 Idle
,可以显示正确动画
速度不为0,夹角为0或PI时
- 叉乘结果为
0
,即sign(c.z)为0
,即计算夹角得到0
,对应动画RunFwd
- 夹角为
0
使用RunFwd
正确,夹角为PI
应该使用RunBwd
- 夹角为
PI
时,不使用乘积,使用反余弦结果
可以在蓝图中实现夹角的计算,也可以在C++中实现
绘制ForwardVector,RightVector和VelocityVector
虚幻编辑器
使用 DrawDebugArrow
: Duration
为 0
,每帧都进行绘画
- | |
---|---|
起点 | Actor 当前位置 |
终点 | 起点 + 偏移 |
绘制ForwardVector
- | |
---|---|
偏移 | 放大的前进方向向量 |
绘制RightVector
- | |
---|---|
偏移 | 放大的向右方向向量 |
绘制VelocityVector
- | |
---|---|
偏移 | 放大的速度方向单位向量 |
效果图
一直显示 ForwardVector
和 RightVector
,有速度才会显示 VelocityVector
优化显示
项目设置 > Engine > Rendering > Default Settings > Auto Exposure,取消勾选
- 旨在模拟人眼适应不同亮度
- 看不出区别, 取消设置
添加走路转向动画
虚幻编辑器
创建2D混合空间资产作为走路转向动画
Blend Space
-
创建2D混合空间资产, 命名为BS_Locomotion_Walk
-
设置轴
- 横轴: 速度
- Name Velocity 范围 [ 0, 600 ] - 纵轴: ForwardVector 和 VelocityVector 的夹角
- Name Direction 范围 [ -180, 180 ]
- 横轴: 速度
-
设置动画
( Velocity
,Direction
)动画资产 说明 ( 0, 0 ) Idle 初始状态 ( 600, 0 ) Run_Fwd 满速前进 ( 600, -180 ) 和 ( 600, 180 ) Run_Bwd 满速后退 ( 600, 90 ) Run_Rt 满速向右 ( 600, -90 ) Run_Lt 满速向左
将BS_Locomotion_Walk用作走路动画
ABP_BaseCharacter
-
将BS_Locomotion_Walk设置为Walk状态的输入
-
变量Velocity作为横轴Velocity的输入
-
添加变量
Direction
- 类型 float 默认值 0 -
变量Direction作为纵轴Direction的输入
在蓝图中计算夹角
虚幻编辑器
在蓝图中计算夹角并设置变量
计算点乘时, ForwardVector
和 VelocityVector
都是单位向量
计算夹角
当前并未在夹角为PI时做处理,后退动画偶有闪动但看似正常
接着设置IsRunning, 设置Direction
输出
- 输出点乘的反余弦结果,静止时输出90
- 输出叉乘结果,静止、前进或后退时,坐标为
0
但是有正负 - 输出
Sign
结果,静止时为0
,前进或后退时,其结果或为1
或为-1
,飘忽不定,但不为0
,所以后退动画看似正常
可以做出合理推测:显示为0
但不意味着为0
,数值很小
叉乘时传入未单位化的Velocity
无改善 - 同时按下后退和向左,或者后退和向右,正负
3PI/4
的动画较为刻意
( PI/2, PI ) 和 ( -PI, -PI/2 ) 时可以只显示Run_Bwd
绘制ForwardVectr和VelocityVector的叉乘结果
- | |
---|---|
偏移 | 放大的叉乘结果 |
运动时才有向量显示
向前和向后运动时,偶有不明显的显示
取消变量的设置
接下来在代码计算夹角
在代码中计算夹角
添加接口, 返回Direction
C++
-
添加函数声明
public
可在蓝图中调用,也可供其他类使用
ShootThemUp: Player/STUBaseCharacter.h
1UFUNCTION(BlueprintCallable) 2float GetDirection() const;
-
实现
- 速度为0时, 夹角为0
- 速度不为0, 叉乘结果为0时, 夹角为PI或-PI, 返回反余弦结果
ShootThemUp: Player/STUBaseCharacter.cpp
1float ASTUBaseCharacter::GetDirection() const 2{ 3 if (GetVelocity().IsZero()) 4 return 0.0f; 5 6 const FVector ForwardVector = GetActorForwardVector(); 7 const FVector VelocityNormalizedVector = GetVelocity().GetSafeNormal(); 8 9 const float DotProductResult = FVector::DotProduct(ForwardVector, VelocityNormalizedVector); 10 const float Theta = FMath::RadiansToDegrees(FMath::Acos(DotProductResult)); 11 12 const FVector CrossProductResult = FVector::CrossProduct(ForwardVector, VelocityNormalizedVector); 13 14 return (CrossProductResult.IsZero() ? Theta : FMath::Sign(CrossProductResult.Z) * Theta); 15}
- 速度为0时, 夹角为0
代码优化: 前后左右移动的回调函数MoveForward和MoveRight
C++
若 Amount
为0,直接返回
ShootThemUp: Player/STUBaseCharacter.cpp
1void ASTUBaseCharacter::MoveForward(float Amount) 2{ 3 IsForward = Amount > 0.0f; 4 if (Amount == 0.0f) 5 return; 6 AddMovementInput(GetActorForwardVector(), Amount); 7} 8 9void ASTUBaseCharacter::MoveRight(float Amount) 10{ 11 if (Amount == 0.0f) 12 return; 13 AddMovementInput(GetActorRightVector(), Amount); 14}
在蓝图中使用C++函数设置变量Direction
虚幻编辑器
ABP_BaseCharacter > EventGraph
阻断动画蓝图无效状态
当我们打开动画蓝图时,其以standalone方式运行。即使未在虚幻编辑器中运行游戏,在蓝图编辑器打开ABP_BaseCharacter,其处于运行状态。可以看到动画蓝图视口的 Actor
是有动画的。
从EventBlueprintUpdateAnimation出发,TryGetPawnOwner得到空指针,停在CastToCharacter
添加打印名,验证动画蓝图运行
未在虚幻编辑器运行游戏,保持动画蓝图在前台,日志窗口一直有输出
未运行游戏时,阻断动画蓝图
从 EventBlueprintUpdateAnimation
出发,添加 IsValid
宏,检查 Pawn
有效性