六一的部落格


关关难过关关过,前路漫漫亦灿灿。




概览


已实现动作

  • Walk: 从静止到跑起来支持加速,使用 BS_Locomotion_Walk_1D
  • Run: BS_Locomotion_Run
  • Jump: JumpStart , JumpEnd , JumpLoop

本节内容

  • 重新实现走路动画,根据游戏角色前进方向和速度方向的夹角,增加转向和后退动画
  • 之后还有 装弹 Reloading , 死亡 Death 和瞄准 Aiming 动画

2D混合空间

Blend Space

拥有两个轴, Vertial AxisHorizontal Axis


建模


游戏角色朝向

-
前进和后退 ForwardVector
左右 RightVector
  • 游戏角色前后左右移动有使用到这两个方向向量
  • 我们水平移动鼠标时, ForwardVectorRightVector 随之发生改变
  • 在视觉上, ForwardVector 一直向前, RightVector 一直向右,二者在水平面垂直

数学基础


点乘


  • 计算向量夹角
  • 其中一个向量为 0向量 时,点乘结果为 0 ,而反余弦计算得到其夹角为 PI/2

叉乘


  • 将向量夹角从[0, PI]扩充到[-PI, PI]

  • theta0 PI-PI 时,叉乘结果为 0 ,此时无法通过向量确定平面,也就无法得到平面法向量


  • 向量c的z坐标 c.z 的符号说明向量的方向


速度方向

VelocityVector

  • Actor 的速度是一个矢量,其表现是单位时间内的位移方向

  • 当速度不为 0VelocityVectorForwardVector 的夹角与此时 Actor 应该展现的动画有关



计算游戏角色前进方向和速度方向的夹角

VelocityVectorForwardVector 的夹角通过向量点乘反余弦结果和向量叉乘结果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

Velocity0 ,( 0, 任意 ) 对应 Idle ,可以显示正确动画


速度不为0,夹角为0或PI时

  • 叉乘结果为 0 ,即sign(c.z)为 0 ,即计算夹角得到 0 ,对应动画 RunFwd
  • 夹角为 0 使用 RunFwd 正确,夹角为 PI 应该使用 RunBwd
  • 夹角为 PI 时,不使用乘积,使用反余弦结果

可以在蓝图中实现夹角的计算,也可以在C++中实现


绘制ForwardVector,RightVector和VelocityVector

虚幻编辑器

使用 DrawDebugArrow : Duration0 ,每帧都进行绘画

-
起点 Actor 当前位置
终点 起点 + 偏移

绘制ForwardVector

-
偏移 放大的前进方向向量



绘制RightVector

-
偏移 放大的向右方向向量



绘制VelocityVector

-
偏移 放大的速度方向单位向量



效果图

一直显示 ForwardVectorRightVector ,有速度才会显示 VelocityVector



优化显示

项目设置 > Engine > Rendering > Default Settings > Auto Exposure,取消勾选

  • 旨在模拟人眼适应不同亮度
  • 看不出区别, 取消设置

添加走路转向动画

虚幻编辑器


创建2D混合空间资产作为走路转向动画

Blend Space

  1. 创建2D混合空间资产, 命名为BS_Locomotion_Walk


  2. 设置轴

    • 横轴: 速度
      -
      Name Velocity
      范围 [ 0, 600 ]
    • 纵轴: ForwardVector 和 VelocityVector 的夹角
      -
      Name Direction
      范围 [ -180, 180 ]


  3. 设置动画

    ( 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

  1. 将BS_Locomotion_Walk设置为Walk状态的输入

  2. 变量Velocity作为横轴Velocity的输入

  3. 添加变量 Direction

    -
    类型 float
    默认值 0


  4. 变量Direction作为纵轴Direction的输入



在蓝图中计算夹角

虚幻编辑器


在蓝图中计算夹角并设置变量

计算点乘时, ForwardVectorVelocityVector 都是单位向量


计算夹角

当前并未在夹角为PI时做处理,后退动画偶有闪动但看似正常



接着设置IsRunning, 设置Direction



输出

  1. 输出点乘的反余弦结果,静止时输出90
  2. 输出叉乘结果,静止、前进或后退时,坐标为 0 但是有正负
  3. 输出 Sign 结果,静止时为 0 ,前进或后退时,其结果或为 1 或为 -1 ,飘忽不定,但不为 0 ,所以后退动画看似正常

    可以做出合理推测:显示为 0 但不意味着为 0 ,数值很小

    叉乘时传入未单位化的 Velocity 无改善
  4. 同时按下后退和向左,或者后退和向右,正负 3PI/4 的动画较为刻意

    ( PI/2, PI ) 和 ( -PI, -PI/2 ) 时可以只显示 Run_Bwd

绘制ForwardVectr和VelocityVector的叉乘结果

-
偏移 放大的叉乘结果


运动时才有向量显示

向前和向后运动时,偶有不明显的显示


取消变量的设置

接下来在代码计算夹角


在代码中计算夹角


添加接口, 返回Direction

C++

  1. 添加函数声明

    public

    可在蓝图中调用,也可供其他类使用

    ShootThemUp: Player/STUBaseCharacter.h

    1UFUNCTION(BlueprintCallable)
    2float GetDirection() const;
  2. 实现

    • 速度为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}

代码优化: 前后左右移动的回调函数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 有效性



添加走路转向动画



概览


已实现动作

  • Walk: 从静止到跑起来支持加速,使用 BS_Locomotion_Walk_1D
  • Run: BS_Locomotion_Run
  • Jump: JumpStart , JumpEnd , JumpLoop

本节内容

  • 重新实现走路动画,根据游戏角色前进方向和速度方向的夹角,增加转向和后退动画
  • 之后还有 装弹 Reloading , 死亡 Death 和瞄准 Aiming 动画

2D混合空间

Blend Space

拥有两个轴, Vertial AxisHorizontal Axis


建模


游戏角色朝向

-
前进和后退 ForwardVector
左右 RightVector
  • 游戏角色前后左右移动有使用到这两个方向向量
  • 我们水平移动鼠标时, ForwardVectorRightVector 随之发生改变
  • 在视觉上, ForwardVector 一直向前, RightVector 一直向右,二者在水平面垂直

数学基础


点乘


  • 计算向量夹角
  • 其中一个向量为 0向量 时,点乘结果为 0 ,而反余弦计算得到其夹角为 PI/2

叉乘


  • 将向量夹角从[0, PI]扩充到[-PI, PI]

  • theta0 PI-PI 时,叉乘结果为 0 ,此时无法通过向量确定平面,也就无法得到平面法向量


  • 向量c的z坐标 c.z 的符号说明向量的方向


速度方向

VelocityVector

  • Actor 的速度是一个矢量,其表现是单位时间内的位移方向

  • 当速度不为 0VelocityVectorForwardVector 的夹角与此时 Actor 应该展现的动画有关



计算游戏角色前进方向和速度方向的夹角

VelocityVectorForwardVector 的夹角通过向量点乘反余弦结果和向量叉乘结果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

Velocity0 ,( 0, 任意 ) 对应 Idle ,可以显示正确动画


速度不为0,夹角为0或PI时

  • 叉乘结果为 0 ,即sign(c.z)为 0 ,即计算夹角得到 0 ,对应动画 RunFwd
  • 夹角为 0 使用 RunFwd 正确,夹角为 PI 应该使用 RunBwd
  • 夹角为 PI 时,不使用乘积,使用反余弦结果

可以在蓝图中实现夹角的计算,也可以在C++中实现


绘制ForwardVector,RightVector和VelocityVector

虚幻编辑器

使用 DrawDebugArrow : Duration0 ,每帧都进行绘画

-
起点 Actor 当前位置
终点 起点 + 偏移

绘制ForwardVector

-
偏移 放大的前进方向向量



绘制RightVector

-
偏移 放大的向右方向向量



绘制VelocityVector

-
偏移 放大的速度方向单位向量



效果图

一直显示 ForwardVectorRightVector ,有速度才会显示 VelocityVector



优化显示

项目设置 > Engine > Rendering > Default Settings > Auto Exposure,取消勾选

  • 旨在模拟人眼适应不同亮度
  • 看不出区别, 取消设置

添加走路转向动画

虚幻编辑器


创建2D混合空间资产作为走路转向动画

Blend Space

  1. 创建2D混合空间资产, 命名为BS_Locomotion_Walk


  2. 设置轴

    • 横轴: 速度
      -
      Name Velocity
      范围 [ 0, 600 ]
    • 纵轴: ForwardVector 和 VelocityVector 的夹角
      -
      Name Direction
      范围 [ -180, 180 ]


  3. 设置动画

    ( 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

  1. 将BS_Locomotion_Walk设置为Walk状态的输入

  2. 变量Velocity作为横轴Velocity的输入

  3. 添加变量 Direction

    -
    类型 float
    默认值 0


  4. 变量Direction作为纵轴Direction的输入



在蓝图中计算夹角

虚幻编辑器


在蓝图中计算夹角并设置变量

计算点乘时, ForwardVectorVelocityVector 都是单位向量


计算夹角

当前并未在夹角为PI时做处理,后退动画偶有闪动但看似正常



接着设置IsRunning, 设置Direction



输出

  1. 输出点乘的反余弦结果,静止时输出90
  2. 输出叉乘结果,静止、前进或后退时,坐标为 0 但是有正负
  3. 输出 Sign 结果,静止时为 0 ,前进或后退时,其结果或为 1 或为 -1 ,飘忽不定,但不为 0 ,所以后退动画看似正常

    可以做出合理推测:显示为 0 但不意味着为 0 ,数值很小

    叉乘时传入未单位化的 Velocity 无改善
  4. 同时按下后退和向左,或者后退和向右,正负 3PI/4 的动画较为刻意

    ( PI/2, PI ) 和 ( -PI, -PI/2 ) 时可以只显示 Run_Bwd

绘制ForwardVectr和VelocityVector的叉乘结果

-
偏移 放大的叉乘结果


运动时才有向量显示

向前和向后运动时,偶有不明显的显示


取消变量的设置

接下来在代码计算夹角


在代码中计算夹角


添加接口, 返回Direction

C++

  1. 添加函数声明

    public

    可在蓝图中调用,也可供其他类使用

    ShootThemUp: Player/STUBaseCharacter.h

    1UFUNCTION(BlueprintCallable)
    2float GetDirection() const;
  2. 实现

    • 速度为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}

代码优化: 前后左右移动的回调函数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 有效性