경사진 각도에서의 일반적인 상황
무릎 각도 조절
Foot Rotation까지 조절
전체적인 흐름 보기
Foot IK 적용법
1. 허리에서부터 바닥으로 LineTrace
2. LineTrace로 얻은 바닥의 Normal값을 Rotator로 변환하여 발바닥의 Rotator에 적용시킴
3. LineTrace로 양발의 발바닥에서 충돌 지점까지의 길이를 구해준다 (충돌지점 -> Trace끝지점까지의 길이 - 발바닥 -> Trace끝지점의 길이)


4. 3에서 구한 값중 작은 값(위에서는 -19로 오른발 값이 더 작음)만큼 캐릭터의 위치를 내려줌
5. 3에서 구해준 양발의 offset만큼 발의 높이를 변경해줌
실제 코드 작성
C++로 블루프린트에서 사용할 Offset값 구하기
ActorComponent을 상속받은 C++클래스를 생성 후 아래와 같이 변수들을 추가함
typedef struct SkeletonIKTraceInfo
{
float Offset;
FVector ImpactLocation;
};
public:
float capsuleHalfHeight;
class ATPSPlayer* player;
UPROPERTY(blueprintReadOnly, category = FootIK)
FRotator LeftFootRotation;
UPROPERTY(blueprintReadOnly, category = FootIK)
FRotator RightFootRotation;
UPROPERTY(blueprintReadOnly, category = FootIK)
float LeftFootOffset;
UPROPERTY(blueprintReadOnly, category = FootIK)
float RightFootOffset;
UPROPERTY(blueprintReadOnly, category = FootIK)
float HipsOffset;
1 ~ 2
허리에서부터 바닥으로 LineTrace 후 바닥의 Normal값을 Rotator로 변환하여 발바닥의 Rotator에 적용
SkeletonIKTraceInfo leftFootTrace = FootTrace(50.f, "foot_l");
SkeletonIKTraceInfo rightFootTrace = FootTrace(50.f, "foot_r");
struct SkeletonIKTraceInfo UFootIkActorComponent::FootTrace(float TraceDistance, FName socketName)
{
// 반환할 struct
SkeletonIKTraceInfo TraceInfo;
// 양 발의 위치를 가져옴
FVector socketLocation = player->GetMesh()->GetSocketLocation(socketName);
// LineTrace의 StartPoint = 발의 x,y 좌표 + 캐릭터의 중간 높이
FVector startPoint = FVector(socketLocation.X, socketLocation.Y, player->GetActorLocation().Z);
// LineTrace의 endPoint = 발의 x,y좌표 + (캐릭터의 발바닥 높이 - TraceDistance)만큼의 높이
FVector endPoint = FVector(socketLocation.X, socketLocation.Y,
(player->GetActorLocation().Z - capsuleHalfHeight) - TraceDistance);
FHitResult pHitResult;
TArray<AActor*> pIgnore;
pIgnore.Add(GetOwner());
bool bResult = UKismetSystemLibrary::LineTraceSingle(GetWorld(), startPoint, endPoint,
UEngineTypes::ConvertToTraceType(ECC_Visibility), true, pIgnore, EDrawDebugTrace::ForOneFrame, pHitResult, true);
// 충돌 지점의 Normal vector를 저장
TraceInfo.ImpactLocation = pHitResult.Normal;
// Offset = 충돌지점->Trace의 끝지점까지의 거리 - Trace길이
if (pHitResult.IsValidBlockingHit())
TraceInfo.Offset = (pHitResult.ImpactPoint - pHitResult.TraceEnd).Size() - TraceDistance + 3.0f;
else
TraceInfo.Offset = 0.0f;
return TraceInfo;
}
Normal Vector를 Rotator로 변환
UpdateFootRotation(DeltaTime, NormalToRotator(leftFootTrace.ImpactLocation), &LeftFootRotation, 13.0f);
UpdateFootRotation(DeltaTime, NormalToRotator(rightFootTrace.ImpactLocation), &RightFootRotation, 13.0f);
FRotator UFootIkActorComponent::NormalToRotator(FVector Vector)
{
// DegAtan2는 두 인자 간 비율의 역탄젠트를 계산하고 결과를 도 단위로 반환해줌
// vector의 Y와 Z로 Roll을 계산
float fAtan2_1 = UKismetMathLibrary::DegAtan2(Vector.Y, Vector.Z);
// vector의 X와 Z로 Pitch를 계산
float fAtan2_2 = UKismetMathLibrary::DegAtan2(Vector.X, Vector.Z);
fAtan2_2 *= -1.0f;
FRotator pResult = FRotator(fAtan2_2, 0.0f, fAtan2_1);
return pResult;
}
일부 좌표계나 게임 엔진에서는 yaw 축(보통 "Z"축으로 표시됨)을 따라 회전하는 방향이
Unreal Engine의 규칙에 비해 반전될 수 있습니다.
"fAtan2_2"에 -1.0f를 곱하면 이 함수는 해당 값을 무효화하여
yaw 축을 기준으로 회전 방향을 반대로 바꿉니다.
void UFootIkActorComponent::UpdateFootRotation(float DeltaTime, FRotator TargetValue, FRotator* FootRotatorValue, float InterpSpeed)
{
FRotator pInterpRotator = UKismetMathLibrary::RInterpTo(*FootRotatorValue, TargetValue, DeltaTime, InterpSpeed);
*FootRotatorValue = pInterpRotator;
}
"RInterpTo" 함수는 지정된 InterpSpeed를 사용하여 지정된 DeltaTime에 대해
Current 및 Target 회전 사이를 보간하여 새 회전을 계산하며 보간된 회전을 "FRotator" 개체로 반환합니다.
3 ~ 5
LineTrace로 양발의 발바닥에서 충돌 지점까지의 길이를 구해주고 구한 값중 더 작은 값만큼 캐릭터의 위치를 내려줌
+ 양 발의 Offset만큼 발의 높이를 변경
float tempHipsOffset = UKismetMathLibrary::Min(leftFootTrace.Offset, rightFootTrace.Offset);
if (tempHipsOffset < 0.0f == false)
tempHipsOffset = 0.0f;
UpdateFootOffset(DeltaTime, tempHipsOffset, &HipsOffset, 13.0f);
UpdateFootOffset(DeltaTime, leftFootTrace.Offset - tempHipsOffset, &LeftFootOffset, 13.0f);
UpdateFootOffset(DeltaTime, -1 * (rightFootTrace.Offset - tempHipsOffset), &RightFootOffset, 13.0f);
void UFootIkActorComponent::UpdateFootOffset(float DeltaTime, float TargetValue, float* EffectorValue, float InterpSpeed)
{
float InterpValue = UKismetMathLibrary::FInterpTo(*EffectorValue, TargetValue, DeltaTime, InterpSpeed);
*EffectorValue = InterpValue;
}
"RInterpTo" 함수는 지정된 InterpSpeed를 사용하여 지정된 DeltaTime에 대해
Current 및 Target 회전 사이를 보간하여 새 회전을 계산하며 보간된 회전을 "FRotator" 개체로 반환합니다.
void UFootIkActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
SkeletonIKTraceInfo leftFootTrace = FootTrace(50.f, "foot_l");
SkeletonIKTraceInfo rightFootTrace = FootTrace(50.f, "foot_r");
UpdateFootRotation(DeltaTime, NormalToRotator(leftFootTrace.ImpactLocation), &LeftFootRotation, 13.0f);
UpdateFootRotation(DeltaTime, NormalToRotator(rightFootTrace.ImpactLocation), &RightFootRotation, 13.0f);
float tempHipsOffset = UKismetMathLibrary::Min(leftFootTrace.Offset, rightFootTrace.Offset);
UKismetSystemLibrary::PrintString(GetWorld(), FString::Printf(TEXT("HipsOffset : %f"), tempHipsOffset));
if (tempHipsOffset < 0.0f == false)
tempHipsOffset = 0.0f;
UpdateFootOffset(DeltaTime, tempHipsOffset, &HipsOffset, 13.0f);
UpdateFootOffset(DeltaTime, leftFootTrace.Offset - tempHipsOffset, &LeftFootOffset, 13.0f);
UpdateFootOffset(DeltaTime, -1 * (rightFootTrace.Offset - tempHipsOffset), &RightFootOffset, 13.0f);
}
참고한 사이트
UE4 Foot IK을 C++로 구현하기
Github : https://github.com/CatDarkGame/UE4SimpleFootIKcpp Kinematics(운동역학) 이란 ? IK를 설명하기전에 우선 애니메이션 분야에서 Kinematics(운동역학)이라는 개념이 존재한다. 모델에 리깅을 하여 관절부 움직
darkcatgame.tistory.com
'UE5' 카테고리의 다른 글
UE C++ LineTraceSingle (0) | 2023.04.18 |
---|---|
UE Setting Niagara User Parameters in C++ (0) | 2023.04.17 |
UE C++ 함수를 블루프린트에서 오버라이드 (0) | 2023.03.30 |
UE C++ Component를 블루프린트에서 추가하는 법 (0) | 2023.03.29 |
UE Delegate (0) | 2023.03.29 |