脚部IK(C++方式)
学习地址:UE4角色脚部IK教程_哔哩哔哩_bilibili
我们希望角色的脚部能够很好地贴合地面,以增加游戏的真实性,所以我们要对人物的脚部进行IK绑定。
创建变量
首先是使用位置,我们希望脚部IK不影响其他动画的播放,所以我们应该在角色处于站立静止的状态时调用,如果角色有其他行为则不使用,并且这是属于角色下半身的一个行为。为此,我们在原有的下半身动画基础上添加即可。
如图所示,在Idle动画之后添加如下节点,Two Bone IK,Transform Bone两种节点,Two Bone IK影响左腿右腿的运动,Transform Bone影响pelvis(相当于骨骼模型原点),左脚右脚的位置旋转。
Two Bone IK节点中设置的参数也需要注意,效果器中执行位置空间需要设置为骨骼空间,目标为脚部骨骼,关节目标中关节目标位置设置为骨骼空间,目标为大腿骨骼,并且将Jonint Target Location设置到膝盖正前方,让腿部弯曲是是向前弯折,而不是向后弯折,符合现实人体。
Transform Bone需要将影响pelvis的节点中平移这个设置一下,平移模式为添加至现有,平移空间为相对世界场景坐标(因为我们需要移动角色模型高度),脚部的修改旋转中的设置,修改内容相同,用于变换脚部与地面的贴合。
接下来就是如何计算这些参数了。
在character中创建如下变量以及方法
//脚部IK相关参数
float IK_LeftFootOffset;
float IK_RightFootOffset;
float IK_MeshOffset;
FRotator IK_LeftFootRotator;
FRotator IK_RightFootRotator;
//计算脚部IK相关参数
struct FFootIKInfo
{
float FootOffset;
FRotator FootRotator;
bool TraceSuccess;
};
FFootIKInfo CalculateFootIKInfo(FName InSocketName);
void CalculateFootIK();
如图所示,我们在两只脚的位置打射线检测,射线起始位置是胶囊体半高的高度,脚部的正上方,结束位置是我们设定的长度,让在下方的脚成为我们的MinOffset,在上方的脚就做出对应的偏移,如果射线没有检测到,则说明角色脚部离地面有较远距离,让它处于不变状态。脚部的旋转通过法线以及反正切函数转化为角度求得。
实现代码
APlayerCharacter::FFootIKInfo APlayerCharacter::CalculateFootIKInfo(FName InSocketName)
{
FFootIKInfo result;
//射线检测开始结束点
FVector StartVector;
FVector EndVector;
//计算脚部插槽位置
FVector SocketLocation = GetMesh()->GetSocketLocation(InSocketName);
//获取Actor的正中心的Z轴位置,即角色中心位置
float ActorZ = GetActorLocation().Z;
//获取胶囊体半高
float CapsuleHalfHeight = GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
//角色脚部上方差不多腰部位置
StartVector = FVector(SocketLocation.X, SocketLocation.Y, ActorZ);
//胶囊体半高1.5倍长度
EndVector = FVector(SocketLocation.X, SocketLocation.Y,ActorZ-(CapsuleHalfHeight*1.5));
//射线检测
FHitResult HitResult;
bool IsHit=UKismetSystemLibrary::LineTraceSingle(this,
StartVector,
EndVector,
UEngineTypes::ConvertToTraceType(ECC_Visibility),
false,
TArray<AActor*>(),
EDrawDebugTrace::None,
HitResult,
true,
FLinearColor::Red,
FLinearColor::Green,
1.0f);
//如果检测到则设置偏移量
if(IsHit){
result.TraceSuccess=true;
result.FootOffset = CapsuleHalfHeight-HitResult.Distance;
}
else
{
result.TraceSuccess=false;
result.FootOffset = 0;
}
//通过法线计算脚部旋转值,需要将弧度转化为角度
FVector a=HitResult.Normal;
result.FootRotator=FRotator(atan2(a.X,a.Z)*-1 * (180.0f / M_PI),0,atan2(a.Y,a.Z) * (180.0f / M_PI));
return result;
}
void APlayerCharacter::CalculateFootIK()
{
FFootIKInfo LeftFootIKInfo = CalculateFootIKInfo(TEXT("foot_l"));
FFootIKInfo RightFootIKInfo = CalculateFootIKInfo(TEXT("foot_r"));
float MinOffset = FMath::Min(LeftFootIKInfo.FootOffset,RightFootIKInfo.FootOffset);
float MeshTargetOffset = MinOffset;
float LeftFootTargetOffset;
float RightFootTargetOffset;
if(LeftFootIKInfo.TraceSuccess)
{
LeftFootTargetOffset=LeftFootIKInfo.FootOffset-MinOffset;
}
else
{
LeftFootTargetOffset=0;
}
if(RightFootIKInfo.TraceSuccess)
{
RightFootTargetOffset=RightFootIKInfo.FootOffset-MinOffset;
}
else
{
RightFootTargetOffset=0;
}
IK_MeshOffset=FMath::FInterpTo(IK_MeshOffset,MeshTargetOffset,GetWorld()->GetDeltaSeconds(),20.0f);
IK_LeftFootOffset=FMath::FInterpTo(IK_LeftFootOffset,LeftFootTargetOffset,GetWorld()->GetDeltaSeconds(),20.0f);
IK_RightFootOffset=FMath::FInterpTo(IK_RightFootOffset,RightFootTargetOffset,GetWorld()->GetDeltaSeconds(),20.0f);
IK_LeftFootRotator=LeftFootIKInfo.FootRotator;
IK_RightFootRotator=RightFootIKInfo.FootRotator;
}
使用接口将参数交给动画蓝图即可
//接口中声明
UFUNCTION(BlueprintNativeEvent,BlueprintCallable,Category="FootIKInfo")
void GetFootIKInfo(float& LeftFootOffset,float& RightFootOffset,float& MeshOffset,FRotator& LeftFootRotator,FRotator& RightFootRotator);
//character实现获取脚部IK参数接口
virtual void GetFootIKInfo_Implementation(float& LeftFootOffset,float& RightFootOffset,float& MeshOffset,FRotator& LeftFootRotator,FRotator& RightFootRotator) override;
void APlayerCharacter::GetFootIKInfo_Implementation(float& LeftFootOffset, float& RightFootOffset, float& MeshOffset,
FRotator& LeftFootRotator, FRotator& RightFootRotator)
{
LeftFootOffset=IK_LeftFootOffset;
RightFootOffset=IK_RightFootOffset;
MeshOffset=IK_MeshOffset;
LeftFootRotator=IK_LeftFootRotator;
RightFootRotator=IK_RightFootRotator;
}
动画蓝图创建好函数调用接口