做ue demo 骨骼动画作业时想到的功能,记录一下。实现效果如下:角色会看向摄像机方向。NPC会看向玩家方向。本期动画素材使用了ue商店中的AdvancedLocomotionSystemV。
实现步骤
首先实现了身体部分的正常的动画状态机:包括站立,走路,下蹲,跳跃等。
因为角色头部转向是与身体运动分开的。所以需要再新建一个动画状态,专门用来处理头部转向,并使用骨骼分层混合将他们合并起来。在设置混合指定骨骼时,选中对应的骨骼(这里是“Head”),并设置对应的权重。
观察素材中给的人物转头动画。实际上是一个additive的动画序列(30帧1秒),动画播放位置的不同(float数值0到1)对应了人物从左向右看。那么我们只需要计算出对应的目标与自身角色的夹角,并将夹角大小映射为浮点型[0,1]的数值范围即可。
如何使用浮点数量化两个夹角大小呢?容易想到使用使用两个单位向量的点乘判断大小。我们。因为等于1时对应动画位置是向右看,点乘两个重叠的单位向量大小也正好为1,所以我们只需取角色的局部右方向对目标方向进行点乘即可。
新建一个C++组件,搭载到角色Actor上,负责计算自己与需要看向目标的方向夹角,lookAtPoint为需要播放的动画位置点(float数值0-1对应看向的左到右,0.5时代表玩家看向自己的正前方)
使用transform.right(即actor->GetActorLocation())与角色看的方向的单位向量的点乘判断夹角来赋值给动画.如果点乘结果更接近1,说明角色更看向右边,对应的动画序列位置为1.点乘结果更接近-1,说明角色更看向左边,对应的动画序列位置为0.得到关系: 动画序列位置 = (点乘结果 + 1) * 0.5f
同时,使用transform.forward,判断目标是否在角色前面.如果与方向向量点乘结果<0,说明目标在角色后面,则角色不需要进行看向操作(动画序列位置为0.5)
class SKEPROJECT_API UMyAnimCompoment : public UActorComponent
{
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float lookAtPoint; //暴露给动画蓝图进行编辑
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Follow Target")
AActor* target; //需要看向的目标
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Follow Target")
bool targetIsPlayer;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Follow Target")
bool targetIsCamera;
protected:
virtual void BeginPlay() override;
UActorComponent* cylinderCollider;
}
void UMyAnimCompoment::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (target == nullptr)
{
GLog->Log("target Not Set");
return;
}
//得到角色的胶囊碰撞体
UPrimitiveComponent* collider = GetOwner()->FindComponentByClass<UPrimitiveComponent>(); //得到自己的胶囊碰撞体
FVector selfpos = collider->GetComponentLocation(); //得到自己的位置
FVector tarPos = target->GetActorLocation(); //得到目标的位置
FVector direction = (tarPos - selfpos).GetSafeNormal(); //得到自己到目标方向的单位向量
//使用局部右方向,使用点乘判断自己与目标方向的夹角,返回[-1,1]
//这里的局部右方向要用GetForwardVector()??可能和原本组件自带的旋转有关
float dir = FVector::DotProduct(-collider->GetForwardVector(), direction);
//将点乘[-1,1]的结果映射回动画播放中需要用到的[0,1]的范围
dir = (dir + 1) * 0.5f;
//判断目标是否在角色前,如果目标在角色后面就不需要角色看向了
bool isForward = (FVector::DotProduct(collider->GetRightVector(), direction) > 0);
//得到动画混合结果(0为最左,0.5为中间,1为最右)
lookAtPoint = isForward ? dir : 0.5f;
}