프로그래밍 공부
작성일
2024. 5. 14. 16:55
작성자
WDmil
728x90

언리얼에서 IK기능을 활용하여 애니메이션을 원하는대로 수정, 적용할 수 있다.

 

IK기능과 FK기능이 무엇인지, 어떤역할로 어떤방식으로 동작하는지 설명할 수 있다.


IK란 무엇인가?

 

IK가 무엇인가? 이를 이해하려면 FK와 IK의 차이점을 이해해야 한다.

 


Forward Kinematics(FK)

여기서 H를 움직인다고 가정한다.

FK방식에서는 Root->SP1->SP2->A->K->H 순서대로 움직임을 제어한다.

 

즉, Root이 움직이면 그 하위에 있는 SP1부터 H까지의 모든 관절이 같이 움직이게 된다.

가장 최우선의 관절부터 객체를 움직이게 되는걸 FK라고 한다.

 

동작방식

  • 순차적 관절 회전 
    • 캐릭터의 각 관절을 순차적으로 회전시켜 원하는 포즈를 만든다.
  • 직접 제어
    • 에니메이터가 관절별로 직접적으로 위치와 회전을 제어한다.
  • 예측 가능성
    • 각 관절의 회전이 직접적으로 영향을 주기 때문에, 결과를 예측하기 쉽다.

장점

  • 단순하다.
    • 계산이 비교적 단순하기에 리소스가 적게든다. Root부터 나머지 본으로 연산을 더해버리면 되기때문.
  • 정밀 제어
    • 개별 관절을 직관적으로 정밀하게 제어할 수 있다.

단점

  • 복잡하다.
    • 단순히 손만 움직인다고 하더라도, Root부터 나머지 본을 전부 탐색연산해야한다.
  • 제어 어려움
    • Root부터 이동하기 때문에, H를 움직이려고 할때 전에위치한 본 때문에 원하는 동작이 나타나지 않을 수 있다.

 

Inverse Kinematics(IK)

 

여기서 H를 움직인다고 가정한다.

IK방식에서는 H를 움직이면, H -> K -> A -> SP2 ->SP1 -> Root 순서대로 움직임을 제어한다.

 

즉, H가 움직이면 그 상위에 있는 움직임에 한단계 씩 움직임을 제어하게 되는것 이다.

움직이려는 관절부터 움직이면서 상위본 으로 움직임을 전파하는것을 IK라고 한다.

 

동작방식

  • 말단 관절 제어 
    • 캐릭터의 손이나 발 같은 말단 관절의 위치를 먼저 지정한다
  • 자동 계산
    • 나머지 관절의 회전값은 지정된 관절 위치에 도달하도록 자동으로 계산된다.
  • 편리성
    • 단순히 말단 관절을 움직이면 중간 관절들이 자동으로 적절히 배치된다.

장점

  • 직관적이다.
    • 말단의 관절의 위치만 지정하고 움직이면 됨으로 직관적이고 쉽게 제어할 수 있다.
  • 시간 절약
    • 많은 관절을 쉽고 빠르게 제어할 수 있어서 시간절약 이 가능하다.

단점

  • 복잡한 계산이 필요함
    • 복잡한 수식계산이 필요함으로 리소스가 많이 필요하다.
  • 불안정성
    • 복잡한 수식으로 자동연산이 이루어짐 으로 사용자가 예상하지 못한, 특정 포즈에서 예기치않은 결과가 나타날 수 있다.

언리얼에 IK 적용하기

 

우선, Base_Charcter에 일부분 데이터를 추가해야한다.

	FVector IKTargetLocation;
	bool bUseIK;

FVector형 변수와 bool형변수 를 추가한다.

 

IKTargetLocation은, IK를 동작시킬 때 지정된 본을 이 변수의 위치에 이동시키기 위해 사용된다.

bUseIK는 지정된 상황에서만 IK를 동작시키기 위해 사용된다.

void ABaseCharacter::InteractCanceled()
{
	bCanInteract = true;
	InteractObject = nullptr;

	bUseIK = true;
}

void ABaseCharacter::InteractComplete()
{
	bInteractProcessing = false;

	bUseIK = false;
}

Interact 함수를 지정한다.

 

InteractCancled를 실행할 때는 실행한다는 의미이기 때문에, Interact가 실행되면서 bUseIK를 True로 만들고

InteractComplete를 실행했을 때는, 실행이 종료되었다는 의미이기 때문에, Interact가 종료되면서, bUseIK를 false로 만든다.

 


Base_Weapon

 

무기에서 Interact될 때, 이 무기의 위치를 Player에 반환해주어야 한다.

void ABaseWeapon::Interact(ABaseCharacter* InteractingActor)
{
	// Play Anim Montage
	{
		enum
		{
			Low,
			Mid
		};
	
		const uint8 Index = InteractingActor->GetActorLocation().Z > this->GetActorLocation().Z ?
			static_cast<uint8>(Low) : static_cast<uint8>(Mid);
	
		InteractingActor->PlayBehaviorAnimMontage(ECharacterPose::Stand, EBehavior::PickUp, Index);
	}
	


	InteractingActor->IKTargetLocation = GetActorLocation();
	InteractingActor->bUseIK = true;

	InteractingActor->RequestPickUpWeapon(this);

	if (InteractingActor->FinishInteract.IsBound())
		InteractingActor->FinishInteract.Execute();


}

vector변수를, 무기를 줍는 캐릭터에 전달하게 작업한다.

 

주의해야 할 점은, 캐릭터에 현재 위치를 전달한 다음에, RequestPickUpWeapon을 실행해야 한다는 점 이다.

순서를 맞추기 어렵다면, 몽타주에 노티파이를 추가해서 노타피아기 RequestPickUPWeapon을 실행하게 해도 좋다.

 

코드를 작성할 때

위와같이 정의해도 코드가 정상적으로 동작하나

델리게이트 를 통해서 IK 를 동작시키기 위한 Vector값을 지정해도 무방하다. 코드 작성법은 자유

 


ABP에서 IK 적용하기.

 

언리얼5에서는 쉽고 간단하게 IK를 적용할 수 있도록 되어있다.

언리얼4 에서는 엄청 고생하면서 넣어야 함으로 참고하자.

 

ABP가 상속받게되는 부모의 코드를 수정하자.

 

변수값만 추가하면 된다.

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "DefaultAnimInstance.generated.h"

class ABaseCharacter;
/**
 * 
 */
UCLASS()
class FTPSGAME_API UDefaultAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = BlendSpace, meta=(AllowPrivateAccess="true"))
	bool bFalling;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = BlendSpace, meta=(AllowPrivateAccess="true"))
	float Speed;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = BlendSpace, meta=(AllowPrivateAccess="true"))
	float Direction;


	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = IK, meta = (AllowPrivateAccess = "true"))
	FVector IKTargetLocation;

	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = IK, meta = (AllowPrivateAccess = "true"))
	bool bUseIK;

	UPROPERTY()
	TObjectPtr<ABaseCharacter> Owner;

public:
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

private:
	void UpdateBlendSpaceValue();
};

 

bUseIK와 IKTargetLocation을 블루프린트 에서 읽을 수 있도록 생성한다.

 

#include "Animation/DefaultAnimInstance.h"
#include "Utilities/Helper.h"

#include "Actors/BaseCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "KismetAnimationLibrary.h"

void UDefaultAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);

	Owner = Cast<ABaseCharacter>(TryGetPawnOwner());

	if(Owner == nullptr) return;

	UpdateBlendSpaceValue();

	IKTargetLocation = Owner->IKTargetLocation;
	bUseIK = Owner->bUseIK;
	
}

void UDefaultAnimInstance::UpdateBlendSpaceValue()
{
	bFalling = Owner->GetCharacterMovement()->IsFalling();

	Speed = Owner->GetVelocity().Size2D();

	FRotator Rotation = FRotator(0, Owner->GetControlRotation().Yaw, 0);
	Direction = UKismetAnimationLibrary::CalculateDirection(Owner->GetVelocity(), Rotation);
}

 

그리고, 이벤트 값을 업데이트하기 위해 에니메이션 틱 마다 IKTargetLocation과 bUseIK를 업데이트 받으면 된다.


ABP 블루프린트 수정

 

전에 구성하였던 ABP를 위와같이 바꾸면 된다.

 

에니메이션 레이어로 IK를 구성하고, BlendPoses by bool을 통해 지정된 상황에서만 IK가 실행되도록 구성하자.

 

IK를 위와같이 정의할 수 있다. 

 

Pose를 받아와서, TwoBoneIK를 통해 몽타주의 지정된 커브의 Alpha값을 이용하여 IK의 Location을 보간하여 수정해준다. 

 

Curve Value의 이름은 몽타주에서 정의한 커브의 이름을 넣어주면 된다.

 

IK에 사용되는 커브데이터는 몽타주에서 생성할 수 있다.

새 커브 생성을 통해 새 변수 커브를 생성할 수 있다.

 

이 때 위의 ABP에서 사용되는 Alpha값은 1이 최대, 0이 최소 이기 때문에 무기를 줍는 순간이 1이되도록 한 뒤에 필터값을 선형이 아닌 곡선으로 넣어주면 된다.


테스트

 

728x90