📘Unreal Engine/📝Implement

[Unreal Engine] 바닥 재질별 다른 발소리 내기

주으기 2025. 2. 5. 09:04
728x90
반응형

피지컬 머티리얼을 확장하여 각 재질 타입별로 사운드를 할당한다.

캐릭터의 발 소켓을 기준으로 아래를 향해 라인 트레이싱을 한다.

감지된 바닥의 피지컬 머티리얼을 가져와, 해당 사운드를 재생하는 로직을 짠다.

이를 AnimNotify를 통해 호출하여, 애니메이션에서 발이 땅에 닿는 프레임마다 알림을 준다.

 

 

피지컬 머티리얼

피지컬 머티리얼(PhysicalMaterial)을 활용하면, 표면 재질에 따른 상호작용 소리를 다르게 재생할 수 있다.

 

먼저, 새 피지컬 머티리얼 클래스를 생성한다.

 

 

재질 별로, 다른 발소리를 재생할 수 있도록, USoundBase를 받는 변수를 선언한다.

// AG_PhysicalMaterial.h

public:
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = PhysicalMaterial)
	TObjectPtr<class USoundBase> FootStepSound = nullptr;

 

 

언리얼에서는 최대 62가지의 표면 재질 타입을 지정할 수 있다.

나무(Wood) 타입을 하나 추가한다.

 

 

표면 재질에 따른 피지컬 머티리얼을 생성한다.

기본(Default)나무(Wood) 두 재질이 있기 때문에 피지컬 머티리얼을 두 개 생성했다.

 

설정에서 Surface Type을 할당한다.

PM_Wood

 

 

테스트 레벨의 바닥 머티리얼을 복사하고 다른 재질을 씌운다.

 

각 머티리얼에 피지컬 머티리얼을 할당한다.

 

 


 

 

발소리 추가하기
(재질 별로 다른 발소리)

캐릭터의 발소리를 추가하기 위해, 새 액터 컴포넌트 클래스를 추가한다.

 

 

발소리를 낼 타이밍을 알리기 위한 Anim Notify 클래스도 추가한다.

 

 

캐릭터 클래스에서 발소리 컴포넌트를 추가하고, 생성자에서 CDO로 초기화한다.

외부에서 가져올 수 있도록, Getter 함수도 만든다.

// ActionGameCharacter.h

public:
	FORCEINLINE class UFootStepComponent* GetFootStepComponent() const { return FootStepComponent; }

private:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class UFootStepComponent> FootStepComponent;
// ActionGameCharacter.cpp

#include "ActorComponents/FootStepComponent.h"

AActionGameCharacter::AActionGameCharacter(const FObjectInitializer& ObjectInitializer)
	:Super(ObjectInitializer.SetDefaultSubobjectClass<UAG_CharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
{
	FootStepComponent = CreateDefaultSubobject<UFootStepComponent>(TEXT("FootStepComponent"));
}

 

 

데이터 타입 클래스에서, 발소리 알림이 왼쪽인지 오른쪽인지를 구분하기 위한 열거형을 추가한다.

// ActionGameTypes.h

UENUM(BlueprintType)
enum class EFoot : uint8
{
	Left	UMETA(DisplayName = "Left"),
	Right	UMETA(DisplayName = "Right"),
};

 

 

이제 발소리 컴포넌트에서 발소리를 내는 로직을 구현해 보자.

 

발소리 로직을 구현할 함수를 선언한다.

각 발의 위치를 구하기 위해서 소켓 이름을 지정한다. (따로 소켓 추가해도 됨)

// FootStepComponent.h

#include "ActionGameTypes.h"

public:	
	UFootStepComponent();

	void HandleFootStep(EFoot Foot);

protected:
	virtual void BeginPlay() override;
	
	UPROPERTY(EditDefaultsOnly)
	FName LeftFootSocketName = TEXT("foot_l_Socket");

	UPROPERTY(EditDefaultsOnly)
	FName RightFootSocketName = TEXT("foot_r_Socket");

 

 

먼저, 발소리를 내는 함수를 어딘가에서 호출할 것이다. 이때, 어느 발에서 소리를 낼지 열거형으로 받는다.

열거형으로 발을 구분하여, 해당 소켓의 위치를 가져온다.

 

발이 땅에 닿아야 발소리가 나는 게 정상이기 때문에, 지면을 향해 라인 트레이싱을 할 준비를 한다.

트레이싱의 시작 위치는, 넉넉하게 발의 위치로부터 20 정도 위쪽으로 잡는다.

트레이싱의 콜리전 파라미터 객체를 만들어, 트레이싱된 대상으로부터 피지컬 머티리얼을 받아오도록 하고, 자신은 트레이싱 대상에서 제외한다.

 

트레이싱 시작 위치로부터 -50 정도 아래를 기준으로 라인 트레이싱을 한다.

부딪힌 지면으로부터 피지컬 머티리얼을 가져오고, 해당 피지컬 머티리얼의 사운드를 재생한다.

// FootStepComponent.cpp

#include "ActionGameCharacter.h"
#include "PhysicalMaterials/AG_PhysicalMaterial.h"
#include "Kismet/GameplayStatics.h"
#include "DrawDebugHelpers.h"

UFootStepComponent::UFootStepComponent()
{
	PrimaryComponentTick.bCanEverTick = false;
}

void UFootStepComponent::HandleFootStep(EFoot Foot)
{
	if (AActionGameCharacter* Character = Cast<AActionGameCharacter>(GetOwner()))
	{
		if (USkeletalMeshComponent* Mesh = Character->GetMesh())
		{
			const FVector SocketLocation = Mesh->GetSocketLocation(Foot == EFoot::Left ? LeftFootSocketName : RightFootSocketName);
			const FVector Location = SocketLocation + FVector::UpVector + 20;

			FHitResult HitResult;

			// 콜리전 쿼리 파라미터 만들기
			// 피지컬 머티리얼 반환, 자기 자신은 충돌 제외
			FCollisionQueryParams QueryParam;
			QueryParam.bReturnPhysicalMaterial = true;
			QueryParam.AddIgnoredActor(Character);

			// 딛고있는 바닥의 충돌을 감지
			if (GetWorld()->LineTraceSingleByChannel(HitResult, Location, Location + FVector::UpVector * -50.f, ECollisionChannel::ECC_WorldStatic, QueryParam))
			{
				if (HitResult.bBlockingHit)
				{
					if (HitResult.PhysMaterial.Get())
					{
						UAG_PhysicalMaterial* PhysicalMaterial = Cast<UAG_PhysicalMaterial>(HitResult.PhysMaterial.Get());

						if (PhysicalMaterial)
						{
							UGameplayStatics::PlaySoundAtLocation(this, PhysicalMaterial->FootStepSound, Location, 1.f);
						}
					}
				}
			}
		}
	}
}

 

 

다음으로, AnimNotify 클래스에서 발소리를 내도록 해보자.

AnimNotify의 핵심 함수인 Notify 함수를 재정의한다. (Notify 호출 시 호출됨)

발 유형을 알기 위해 EFoot 열거형 변수도 선언한다.

// AnimNotify_Step.h

#include "ActionGameTypes.h"

public:
	virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;

	UPROPERTY(EditAnywhere)
	EFoot Foot;

 

애니메이션에서 Notify를 호출하면, 지정한 발에 따라 트레이싱을 진행해 발소리를 출력한다.

// AnimNotify_Step.h

#include "ActionGameCharacter.h"
#include "ActorComponents/FootStepComponent.h"

void UAnimNotify_Step::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
	Super::Notify(MeshComp, Animation);

	AActionGameCharacter* Character = MeshComp ? Cast<AActionGameCharacter>(MeshComp->GetOwner()) : nullptr;
	
	if (Character)
	{
		if (UFootStepComponent* FootStepComponent = Character->GetFootStepComponent())
		{
			FootStepComponent->HandleFootStep(Foot);
		}
	}
}

 

 

컴파일 후, 다음과 같이 애니메이션 시퀀스에 Notify를 추가한다.

 

 

 

 

 

728x90
반응형