UE5

UE Blueprint와 C++을 이용한 Foot IK

우대비 2023. 4. 8. 16:27
반응형

경사진 각도에서의 일반적인 상황

발이 바닥에 박혀있음

 무릎 각도 조절

바닥이 경사진 만큼 무릎을 구부려서 땅에 발이 박히는 상황을 없앰
발바닥이 바닥과 평행하지 않음

 

Foot Rotation까지 조절

발바닥이 바닥과 평행

 

 

전체적인 흐름 보기

Foot IK 적용법

1. 허리에서부터 바닥으로 LineTrace

2. LineTrace로 얻은 바닥의 Normal값을 Rotator로 변환하여 발바닥의 Rotator에 적용시킴

AnimationBlueprint-AnimGraph내에서 가능
본 트랜스폼(변경) 내부 디테일

 

3. LineTrace로 양발의 발바닥에서 충돌 지점까지의 길이를 구해준다 (충돌지점 -> Trace끝지점까지의 길이 - 발바닥 -> Trace끝지점의 길이) 

양발의 offset은 0
 
왼발은 0 오른발은 - 19가 나옴

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에 대해

CurrentTarget 회전 사이를 보간하여 새 회전을 계산하며 보간된 회전을 "FRotator" 개체로 반환합니다.

 

AnimationBlueprint-AnimGraph내에서 가능

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

 

반응형
LIST

'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