언리얼

[UE5] Interface

mumu3997 2024. 6. 15. 10:20

언리얼에서 인터페이스를 사용하는 방법에 대해 정리해보자.

 

커서를 가져다 대면 highlight 시켜주는 기능을 위해 인터페이스를 사용한다. 

 

위 기능 구현을 위해 한번 간략하게 정리해보자.

  1. 커서 아래에 뭐가 있는지를 일단 추적한다.
  2. 액터가 있다면 액터의 인터페이스를 통해 highlight해준다.

 

EnemyInterface라는 C++ 인터페이스 클래스를 만든다.

 

<EnemyInterface.h>

// Copyright mumu

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "EnemyInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UEnemyInterface : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
class AURA_API IEnemyInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	// Pure Virtual function, Consider as abstract class
	virtual void HighlightActor() = 0;
	virtual void UnHighlightActor() = 0;
};

virtual void HighlightActor() = 0; 처럼 추상 메서드를 선언했다.

 

이렇게 추상 메서드로 선언을 해주는 경우 c++에서 따로 구현해주지 않아도 되는 장점이 있다.

 

이제 인터페이스를 상속해보자.

 

 

AuraEnemy클래스에서 상속

 

<AuraEnemy.h>

// Copyright mumu

#pragma once

#include "CoreMinimal.h"
#include "Character/AuraCharacterBase.h"
#include "Interaction/EnemyInterface.h"
#include "AuraEnemy.generated.h"

/**
 * 
 */
UCLASS()
class AURA_API AAuraEnemy : public AAuraCharacterBase, public IEnemyInterface
{
	GENERATED_BODY()
public:
	virtual void HighlightActor() override;
	virtual void UnHighlightActor() override;

	UPROPERTY(BlueprintReadOnly)
	bool bHighlighted = false;
};

 

상속 받은 클래스들에 순수 가상함수를 둘순 없으니 여기서는 c++로 구현을 해주자.

 

 

<AuraEnemy.cpp>

// Copyright mumu


#include "Character/AuraEnemy.h"

void AAuraEnemy::HighlightActor()
{
	bHighlighted = true;
}

void AAuraEnemy::UnHighlightActor()
{
	bHighlighted = false;
}

 

bHighlighted가 true면 블루프린트에서 Highlight가 되도록 처리해주면 될듯하다.

 

마우스 커서를 추적해 추적한 액터의 인터페이스를 통해 highlight를 해준다.

 

<AuraPlayerController.h>

// Copyright mumu

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "AuraPlayerController.generated.h"

class IEnemyInterface;
/**
 * 
 */
UCLASS()
class AURA_API AAuraPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	virtual void PlayerTick(float DeltaTime) override;

private:
	void CursorTrace();

	TScriptInterface<IEnemyInterface> LastActor;
	TScriptInterface<IEnemyInterface> ThisActor;
};

 

TOjectPtr처럼 언리얼에서 공식으로 제공하는 키워드다.

 

원시 포인터 대신 TScriptInterface를 사용하는게 '권장'된다.

 

 

<AuraPlayerController.cpp>

// Copyright mumu


#include "Player/AuraPlayerController.h"
#include "Interaction/EnemyInterface.h"

AAuraPlayerController::AAuraPlayerController()
{
	bReplicates = true;
}

void AAuraPlayerController::PlayerTick(float DeltaTime)
{
	Super::PlayerTick(DeltaTime);

	CursorTrace();
}

void AAuraPlayerController::CursorTrace()
{
	FHitResult CursorHit;
	GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);
	if(!CursorHit.bBlockingHit) return;

	// If you use old pointer, need to Cast IEnemyInterface from AActor. Instead, Set directly by TScriptInterface.
	LastActor = ThisActor;
	ThisActor = CursorHit.GetActor();

	/*
	 *  Line trace from cursor. There are several scenarios:
	 *		A. LastActor is null && ThisActor is null
	 *			- DO nothing.
	 *		B. LastActor is null && ThisActor is valid
	 *			- Highlight ThisActor
	 *		C. LastActor is valid && ThisActor is null
	 *			- UnHighlight LastActor
	 *		D. Both actors are valid, but LastActor != ThisActor
	 *			- UnHighlight LastActor, and Highlight ThisActor
	 *		E. Both actors are valid, and are the same actor
	 *			- Do nothing
	 */

	if(LastActor == nullptr)
	{
		if(ThisActor != nullptr)
		{
			// Case B
			ThisActor->HighlightActor();
		}
		else
		{
			// Case A - both are null, do nothing
		}
	}
	else // LastActor is valid
	{
		if(ThisActor == nullptr)
		{
			// Case C
			LastActor->UnHighlightActor();
		}
		else // both actors are valid
		{
			if(LastActor != ThisActor)
			{
				// Case D
				LastActor->UnHighlightActor();
				ThisActor->HighlightActor();
			}
			else
			{
				// Case E - do nothing
			}
		}
	}
}

 

FHitResult CursorHit;
GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);
if(!CursorHit.bBlockingHit) return;

// If you use old pointer, need to Cast IEnemyInterface from AActor. Instead, Set directly by TScriptInterface.
LastActor = ThisActor;
ThisActor = CursorHit.GetActor();

 

해당 부분을 보면 알 수 있는데, 원시 포인터는 AActor에서 Inaterface로 Cast를 해줘야하지만 TScriptInterface는 따로 Cast를 해줄 필요가 없다.

 

만약 다음과 같이

IEnemyInterface* LastActor;
IEnemyInterface* ThisActor;

원시포인터였다면..

ThisActor = Cast<IEnemyInterface>(CursorHit.GetActor());

이렇게 Cast를 해줘야 한다.

 

/*
 *  Line trace from cursor. There are several scenarios:
 *     A. LastActor is null && ThisActor is null
 *        - DO nothing.
 *     B. LastActor is null && ThisActor is valid
 *        - Highlight ThisActor
 *     C. LastActor is valid && ThisActor is null
 *        - UnHighlight LastActor
 *     D. Both actors are valid, but LastActor != ThisActor
 *        - UnHighlight LastActor, and Highlight ThisActor
 *     E. Both actors are valid, and are the same actor
 *        - Do nothing
 */

커서를 다른 적으로 옮기면 원래 highlight를 없애고 옮긴 적을 highlight 해주기 위해 if else문으로 케이스를 나눴다.

 

 

'언리얼' 카테고리의 다른 글

[UE5] Enhanced Input  (1) 2024.06.09
[UE5] Animation Blueprint 기초  (0) 2024.06.09