프로그래밍 공부
작성일
2024. 5. 13. 19:58
작성자
WDmil
728x90

애니메이션 몽타주 Struct를 Map형태로 넣어서 Character에서 관리하게 할 수 있다.

 

일정 동작 시 필요에따라 몽타주를 실행시킬 수 있다.

 


Character Struct

 

캐릭터 몽타주 데이터를 집어넣을 TMap을 사용한다.

 

데이터 구조체의 순서는

자세 -> 행동 -> 몽타주 순서이다.

 

쉽게 이해하자면,

조준중 -> 줍기, 공격 -> 앉은상태 줍기, 서있는상태 줍기

서있음 -> 줍기, 공격 -> 앉은상태 줍기, 서있는상태 줍기

 

로 정리된다고 생각하면 된다.

#pragma once

#include "CoreMinimal.h"
#include "CharacterStruct.generated.h"

class UInputAction;
class UInputMappingContext;

USTRUCT(BlueprintType)
struct FInputStruct
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditDefaultsOnly)
	TObjectPtr<UInputMappingContext> InputMappingContext;

	UPROPERTY(EditDefaultsOnly)
	TMap<FString, UInputAction*> InputActionMap;
};

USTRUCT(BlueprintType)
struct FAnimMontageData
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditDefaultsOnly)
	UAnimMontage* AnimMontage;

	UPROPERTY(EditDefaultsOnly)
	float PlayRate = 1.0f;
};

USTRUCT(BlueprintType)
struct FAnimMontageArray
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditDefaultsOnly)
	TArray<FAnimMontageData> AnimMontageArray;
};

USTRUCT(BlueprintType)
struct FBehaviorAnimMap
{
	GENERATED_USTRUCT_BODY()
	
	UPROPERTY(EditDefaultsOnly)
	TMap<EBehavior, FAnimMontageArray> BehaviorAnimMap;
};

 

언리얼에서 Map에 TArray를 사용할 수 없기 때문에, Struct로 데이터구조체를 구현하고 해당 구조체를 집어넣어주어야 한다.

구현이미지

 

위와같이 생성된다.


Character Enum

 

캐릭터 상태값을 지정하는 Enum을 생성, 관리한다.

 

순수하게, TMap의 Key에 사용하기 위해 생성했다.

#pragma once

#include "CoreMinimal.h"

UENUM(BlueprintType)
enum class ECharacterPose : uint8
{
	None UMETA(Hidden),
	Stand,
	Aiming,
	Crouch
};

UENUM(BlueprintType)
enum class EBehavior : uint8
{
	None UMETA(Hidden),
	PickUp,
	Reloading,
};

ABase_Weapon

 

BaseWeapon의 함수에 코드를 추가한다.

ABase_Weapon에서 Player에 무기가 장착될 때, 플레이어에 무기장착 몽타주를 실행시킨다.

(사실 이렇게 구현하지 않고, 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->RequestPickUpWeapon(this);
	
	if (InteractingActor->FinishInteract.IsBound())
		InteractingActor->FinishInteract.Execute();
}

ABase_Character

 

Base_Character에 몽타주를 확인, 실행시켜주는 함수를 생성한다.

 

플레이어에 저장되어있는 몽타주를 무기에서 선택해서 실행하는 식으로 정의할 것 이다.

무기가 플레이어의 행동을 유발한다는게 구조적으로 조금 이상하지만, 플레이어의 코드가 난잡해지지 않는 효과가 있다.

조금 쉽게 생각해서, 사람이 물건을 주울 때 물체에 따라 다른 방식으로 든다고 생각하자.

 

void ABaseCharacter::PlayBehaviorAnimMontage(ECharacterPose InPose, EBehavior InBehavior, uint8 CustomIndex)
{
	FBehaviorAnimMap* BehaviorAnimMap = Montages.Find(InPose);
	FAnimMontageArray* AnimMontageArray = BehaviorAnimMap->BehaviorAnimMap.Find(InBehavior);
	UAnimMontage* AnimMontage = AnimMontageArray->AnimMontageArray[CustomIndex].AnimMontage;
	const float PlayRate = AnimMontageArray->AnimMontageArray[CustomIndex].PlayRate;
	
	if (AnimMontage)
		PlayAnimMontage(AnimMontage, PlayRate);
}

 

여기서 Montages는 Base_Character.h에 생성한 Map이다. 다음과 같다.

	UPROPERTY(EditDefaultsOnly, Category = AnimMontage, meta=(AllowPrivateAccess="true"))
	TMap<ECharacterPose, FBehaviorAnimMap> Montages;

 


ABP 재정의

 

ABP를 몽타주 실행시 뼈대에 따라 블랜딩 할 수 있도록 재정의해준다.

 

몽타주는 위와같이 재정의된다. 몽타주의 Rifle애니메이션을 RiflePos로 저장하고, 해당 Pos를 커스텀 에니메이션 레이어에서 재정의한 뒤, ResultPos로 전달한다.

몽타주의 레이어는 위와같다. 객체에서 UpperBody와 DefaultSlot을 통해 몽타주가 애니메이션에 지정된 뼈대에까지만 블랜딩될 수 있게 한다.


테스트

 

아래 영상에서의 동작사항의 순서는 다음과 같다.

플레이어의 무기Collision검사 -> 카메라의 중심반직선과 플레이어에 가장 가까운 객체 선택 -> 무기줍는 몽타주 실행 -> 무기 Attach

 

 

728x90