1. Project Creation

목차

  1. AuraCharacterBase 클래스 생성
  2. AuraCharacterBase 파생 클래스 AuraCharacter/AuraEnemy 생성
  3. 각 클래스별 필요한 코드 및 블루프린트 설정
  4. 캐릭터 이동
  5. Interface 클래스를 이용한 Enemy 하이라이트 표시

1.1 BaseCharacterClass 생성

Character 파생 클래스인 AuraCharacterBase 클래스를 생성한다.

필요에 따라 `AuraCharacterBase` 파생 클래스를 생성하여 좀 더 관리하기 쉽게 한다.
Ex) Enemy 클래스와 기본 캐릭터 클래스가 동일한 멤버변수나 기능을 필요로 할 경우
`AuraCharacterBase` 클래스에 공통된 멤버변수나 함수를 선언하고 파생 클래스에서 `override`
생성경로: c++/Aura/Public/Character

AuraCharacter , AuraEnemy 의 부모 클래스로만 사용할 것이므로, 직접적인 월드 배치를 막기 위해 UCLASS() 매크로를 UCLASS(Abstract) 로 수정한다.
또한 SetupPlayerINputComponent()Tick() 함수들도, 사용하지 않는 파생 클래스가 있으므로, 헤더파일과 cpp 파일에서 제거하고 생성자에서 PrimaryActorTick.bCanEverTick = false; 로 수정한다.

1.2 AuraCharacterBase 파생 클래스 AuraCharacter/AuraEnemy 생성

1.1 에서 생성한 AuraCharacterBase 클래스의 파생 클래스 AuraCharacter , AuraEnemy 를 생성한다.

생성경로: c++/Aura/public/Character

1.3 각 클래스별 필요한 코드 및 블루프린트 설정

1.3.1 BP_AuraCharacter/BP_AuraEnemy 무기 코드 추가

언리얼5에서부터는 기존의 `UObject` 원시 포인터가 아닌 `TObjectPtr<T>` 사용을 권장.
기능 자체는 동일함.
  • AuraCharacterBase.h
#include "..."

UCLASS(Abstract)
class AURA_API AAuraCharacterBase : public ACharacter
{
	GENERATED_BODY()

public:
	...
    
protected:
	...

	UPROPERTY(EditAnywhere, Category = "Combat")
	TObjectPtr<USkeletalMeshComponent> Weapon;
}
  • AuraCharacterBase.cpp
#include "..."

AAuraCharacterBase::AAuraCharacterBase()
{
	PrimaryActorTick.bEvenEverTick = false;
    
    /** 코드 추가 */
    Weapon = CreateDefaultSubobject<USkeletalMeshComponent>("Weapon");
	Weapon->SetupAttachment(GetMesh(), FName("WeaponHandSocket"));
    // 무기 콜리전 발생 방지
	Weapon->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    /** 코드 추가 */
}

...

1.3.2 BP_AuraCharacter 생성 및 무기 스켈레탈 메시 추가

코드 작성이 완료되었다면 에디터에서 BP_Auracharacter 블루프린트를 생성한다.

생성경로: All/Content/Blueprints/Character/Aura


BP_AuraCharacter 에서 캐릭터 스켈레탈 메시를 추가하고, 캡슐 컴포넌트와 위치를 맞추고, 전방방향에 맞게 스켈레탈 메시를 회전시킨다.

사용할 캐릭터의 스켈레탈 메시 파일을 열고, 왼손에 WeaponHandSocket 소켓을 추가한다.

추가한 소켓을 우클릭한 후 Add Preview Asset 을 선택하면 해당 소켓에 장착할 스켈레탈 메시를 미리 확인할 수 있다.
또한 Preview Scene Settings 탭에서 Preview Controller : Use specific Animation 을 선택하고 아무 애니메이션을 선택하면 해당 애니메이션이 재생되는데, 이때 애니메이션 재생을 멈추고 생성한 소켓을 적절히 움직이면 자연스럽게 socket을 배치할 수 있다.

여러 애니메이션을 번갈아가면서 적절한 위치에 생성한 소켓을 배치시킨다.
마지막으로 BP_AuraCharacterComponents -> Weapon 선택 후 스켈레탈 메시에 사용될 SKM_Staff 를 적용시킨다.

1.3.3 BP_AuraEnemy 생성 및 무기 스켈레탈 메시 추가

생성경로: Blueprints/Characters/Goblin_Spear

동일하게 BP_Goblin_Spear 를 생성하고 스켈레탈 메시에 소켓을 추가한다.

메시 트랜스폼

캡슐 크기

이미 소켓을 보유하고 있으므로 소켓명만 WeaponHandSocket 으로 변경한다.

마지막으로 BP_Goblin_SpearComponents -> Weapon 선택 후 스켈레탈 메시에 사용될 SKM_Spear 를 적용시킨다.

1.3.4 BP_AuraCharacter 애니메이션 추가

ABP_Aura 애니메이션 블루프린트 파일을 생성한다.

생성경로: Blueprints/Characters/Aura


State Machine : Main States 을 추가하고, DefaultSlot 을 경유하여 Output Pose 노드와 연결시킨다.

추후 애니메이션 몽타주 추가를 하기 위해서 추가.


Main States 에서 IdleWlakRun state를 추가하고

IdleWalkRun 블렌드 스페이스를 추가해준다.

필요에 따라 아래와 같이 블렌드 스페이스를 통해 애니메이션을 구현하거나
`Main States` 에서 여러 `Idle` , `Walk` , `Run` state를 각각 추가하여 
Transition Rule` 을 통해 구현해도 무방.


IdleWalkRun 애니메이션은 Speed 에 따라 재생 애니메이션이 달라지므로 Speed 변수가 필요하다.
EventGraph 에서 Blueprint Initialize Animation 함수를 override하고

BP_AuraCharcter 로의 캐스팅을 진행한 후, 변수로 승격시킨다.

GetCharacterMovement() 도 변수로 승격시킨다.

위 과정은 AnimInstance 클래스에서 하던 것과 동일한 과정이므로 코드로도 구현 가능하다.
(구현 가능하지만 블루프린트 노드로 구한 값 사용)

Animinstance.h

// AnimInstance.h

private:
	AAuraCharacter* AuraCharacter;
    UCharacterMovementComponent* AuraMovementComponent;

    ...

AnimInstance.cpp

// AnimInstance.cpp

void UWarriorAnimInstance::NativeInitializeAnimation()
{
	Super::NativeInitializeAnimation();

	AuraCharacter = Cast<AAuraCharacter>(GetCharacter());
    if(AuraCharacter)
    {
    	AuraMovementComponent = Aura->GetCharacterMovement();
    }
}

Event Blueprint Update Animation 노드에서 BP_AuraCharacter 가 유효한 경우, Character Movement 를 통해 캐릭터의 velocity를 얻어 GroundSpeed 에 초기화하도록 한다.

위 과정 또한 AnimInstance 클래스에서 코드로 구현 가능하다
(구현 가능하지만 블루프린트 노드로 구한 값 사용)

AnimInstance.h

// AnimInstance.h
private:
	UPROPERTY(BlueprintReadOnly, Category = "State", meta = (AllowPrivateAccess = "true"))
    float GroundSpeed;
    ...

AnimInstance.cpp

// AnimInstance.cpp
#include "Kismet/KismetMathLibrary.h"
...
void UCLASSNAME::NativeUpdateAnimaiton(float DeltaTime)
{
	Super::NativeUpdateAnimaiton(DeltaTime);

    if(AuraMovementComponent)
    {
    	GroundSpeed = USkismetMathLibrary::VSizeXY(AuraMovementComponent->Velocity);
    }
}

IdleRunWalk state에서 Speed 핀에 초기화한 GroundSpeed 를 연결하면 속력에 따른 애니메이션이 재생된다.

마지막으로 BP_AuraCharacter 에서 ABP_Aura 를 사용하도록 설정해준다.

1.3.5 BP_AuraEnemy 애니메이션 추가

ABP_Enemy 애니메이션 블루프린트 파일을 생성한다.

생성경로: Blueprints/Characters

AuraEnemySlingShotSpear 두가지 무기를 사용하도록 할 예정이다. 일부(무기 등)만 다른 여러 적에게 동일한 애니메이션을 적용시키기 위해서 Template 형태로 블루프린트 를 생성한다.
ABP_AuraEnemy 파생 블루프린트 클래스를 생성하여 각각의 AuraEnemy 들에게 적용 가능하다.

State Machine : Main States 을 추가하고, DefaultSlot 을 경유하여 Output Pose 노드와 연결시킨다.

Main States 에서 IdleWlakRun state를 추가하고

BlendSpace Player 노드를 추가해준다.

Event Blueprint Initialize Animation 노드에서 AuraEnemy 가 유효할 경우 Character Movement 컴포넌트에 접근가능하도록 변수로 승격시킨다.

Character Movement 컴포넌트가 유효할 경우 Velocity를 얻고 Ground Speed 에 초기화하도록 한다.

IdleRunWalk state에서 X 핀에 초기화한 GroundSpeed 를 연결하면 속력에 따른 애니메이션이 재생된다.

템플릿 애니메이션 블루프린트 생성을 완료하였으므로 ABP_Goblin_Spear 를 생성한다.

`SK_Golbin` 과 하단의 `Parent Class: ABP_Enemy` 를 선택.


파일을 열고 Asset Override Editr -> BlendSpace Player : BS_GoblinSpear_IdleRun 을 선택한다.

마지막으로 BP_Giblin_Spear 에서 해당 애니메이션 블루프린트를 사용하도록 설정해준다.

동일한 과정을 통해 BP_Goblin_Slingshot 을 생성하여 애니메이션을 적용시킨다.

생성경로: Bluepirnts/Chaacter/Goblin_Slingshot


메시 트랜스폼

캡슐 크기

무기 스켈레탈 메시 적용

템플릿 애니메이션 블루프린트를 이용한 ABP_Goblin_Slingshot 생성

`SK_Golbin` 과 하단의 `Parent Class: ABP_Enemy` 를 선택.


파일을 열고 Asset Override Editr -> BlendSpace Player : BS_Goblin_Slingshot_IdleRun 을 선택

마지막으로 BP_Giblin_Slingshot 에서 해당 애니메이션 블루프린트를 사용하도록 설정

1.4 캐릭터 이동

1.4.1 InputAction/InputMappingContext 생성

Input Action 블루프린트 IA_Move 를 생성한다.

생성경로: Blurpints/Input/InputActions


파일을 열고, xy축에 대한 2차원적인 이동만 하므로 Value Type : Axis2D(Vector2D) 로 설정한다.

Input Mapping Context 블루프린트 IMC_AuraContext 를 생성한다.

생성경로: Blueprints/Input


파일을 열고 MappingIA_Move 를 추가한 뒤, AD 키를 할당한다.

좌우로 움직일 수 있도록 A 키와 D 키를 할당한다.
그리고 A 키의 경우 D 의 반대로 캐릭터가 움직여야 하므로 Modifiers : Negate 를 적용시킨다.
그리고 A 키는 x축에 대해서만 Negate 하면 되므로 나머지 축은 비활성화시킨다.

앞뒤로 움직일 수 있도록 W 키와 S 키를 할당한다.
이때 Modifiers : Swizzle Input Axis Values 를 활성화 시켜주는데 이는 W 키나 S 키 입력시 입력축을 변경하기 위함이다.

Modifiers : Swizzle Input Axis Values

1.4.2 PlayerController 생성

PlayerController 파생 클래스 AuraPlayerController 를 생성한다.

생성경로: c++/Aura/Public/Player

  • 코드 작성전 Replicate에 대한 설명
    요약하자면 Replicate 지정자가 포함된 프로퍼티는 값이 변경될 때마다 서버에 값이 전달되고, 서버에서는 해당 값을 각 클라이언트에 업데이트를 전송한다.

언리얼 공식 홈페이지에서 서술되있는 내용
각 액터에는 Replicated 지정자를 포함하는 모든 프로퍼티 목록이 유지됩니다. 서버는 리플리케이트된 프로퍼티의 값이 변할 때마다 각 클라이언트에 업데이트를 전송하며, 클라이언트는 액터의 로컬 버전에 적용합니다. 이 업데이트는 서버에서만 받으며, 클라이언트는 프로퍼티 업데이트를 서버나 다른 클라이언트로 절대 전송하지 않습니다.

먼저 UInputMappingContext 를 사용하기 위한 모듈을 추가해야 한다

  • Aura.Build.cs
public class Aura : ModuleRules
{
	public Aura(ReadOnlyTargetRules Target) : base(Target)
	{
		...
		// EnhancedInput 추가
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" });

		...
	}
}
  • AuraPlayerController.h
#include "..."

class UInputMappingComtext;

UCLASS()
class AURA_API AAuraPlayerController : public APlyaerController
{
	GENERATED_BODY()
public:
	AAuraPlayerController();
    
protected:
	virtual void BeginPlay() override;
    
private:
	UPROPERTY(EditAnywhere, Category = "Input")
	TObjectPtr<UInputMappingContext> AuraContext;
};
  • AuraPlayerController.cpp
#include "..."
#include "EnhancedInputSubsystems.h"
// 위 헤더파일 include 불가할 경우, 에러부분 우클릭후 `Alt + Enter` 로 추가
// #include "E:/Unreal Engine/UE_5.3/Engine/Plugins/EnhancedInput/Source/EnhancedInput/Public/EnhancedInputSubsystems.h"

AAuraPlayerController::AAuraPlayerController()
{
	// 리플리케이션 플래그 활성화
	bReplicates = true;
}

void AAuraPlayerController::BeginPlay()
{
	Super::BeginPlay();
    
    check(AuraContext);
    // InputMappingContext를 추가하기 위해서 EnhancedInputLocalPlayerSubsystem에 access해야함
    UEnhancedInputLocalPlayerSubsystem* SubSystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer());
    check(SubSystem);
    SubSystem->AddMappingContext(AuraContext, 0);
    
    // 마우스커서 상시 보이도록 수정 및 기본 마우스커서 적용
    bShowMouseCursor = true;
    DefaultMouseCursor = EMouseCursor::Default;
    
    // InputModeDate를 통해 키보드와 마우스로부터의 input을 사용가능하게 함
    FInputModeGameAndUI InputModeData;
    // 마우스를 뷰포트에 고정할건지 자유롭게 놔둘건지 지정
    InputModeData.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
    // 캡쳐 모드에서 마우스를 숨길건지에 대한 지정
    InputModeData.SetHideCursorDuringCapture(false);
    SetInputMode(InputModeData);
}

1.4.3 SetupInputComponent() 설정

PlayerController의 기본적인 설정이 끝났다면 이제 SetupInputComponent() 를 통해 입력을 받고 콜백함수를 바인딩해야한다.

  • AuraPlayerController.h
#include "..."

class ...;
/** 코드 추가 : class UInputAction, struct FInputActionValue */
class UInputAction;

struct FInputActionValue;
/** 코드 추가 */

UCLASS()
class AURA_API AAuraPlayerController : public APlayerController
{
	GENERATED_BODY()
    
public:
	...

protected:
	...
    /** 코드 추가 : SetupInputComponent() */
    virtual void SetupInputComponent() override;
    /** 코드 추가 */

private:
	...
    
    /** 코드 추가 : MoveAction, Move(...) */
    // 기존 에디터에서 사용한 매핑과 동일한 역할
    UPROPERTY(EditAnywhere, Category = "Input")
    TObjectPtr<UInputAction> MoveAction;
    
    // 레퍼런스를 매개변수로 받기 때문에, 값변경을 방지하기 위한 const 선언
    // FInputActionValue 사용을 위한 전방선언 필요
    void Move(const FInputActionValue& InputActionValue);
    /** 코드 추가 */
};
  • AuraPlayerController.cpp
#include "..."
#include "EnhancedInputComponent.h
// 위 헤더파일 include 불가할 경우, 에러부분 우클릭후 `Alt + Enter` 로 추가
// #include "E:/Unreal Engine/UE_5.3/Engine/Plugins/EnhancedInput/Source/EnhancedInput/Public/EnhancedInputComponent.h"

...

void AAuraPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();
    
    UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
    
    // 기존에 에디터에서 Mapping해둔 키워드 사용 대신 헤더파일에서 선언한 MoveAction 사용
    // 기존에 사용하던 `IE_Pressed` 대신 `ETriggerEvent::VALUE` 사용, 버튼 클릭시 트리거 발동
    EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AAuraPlayerController::Move);
}

void AAuraPlayerController::Move(const FInputActionValue& InputActionValue)
{
	const FVector2D InputAxisVector = InputActionValue.Get<FVector2D>();
    const FRotator Rotation = GetControlRotation();
    const FRotator YawRotation(0.f, Rotation.Yaw, 0.f);
    
    const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
    
    if(APawn* ControlledPawn = GetPawn<APawn>())
    {
    	ControlledPawn->AddMovementInput(ForwardDirection, InputAxisVector.Y);
        ControlledPawn->AddMovementInput(RightDirection, InputAxisVector.X);
    }
}

1.4.4 PlayerContrller 블루프린트 생성

AuraPlayerController 기반 BP_AuraPlayerController 를 생성한다.

생성경로: Blurpints/Player


Details 탭에서 생성해둔 IM_MoveIMC_AuraContext 를 적용시킬 수 있다.

1.4.5 GameModeBase클래스 생성 및 Player Controller Class 설정

GameModeBase 파생 클래스 AuraGameModeBase 를 생성한다.

생성경로: c++/Aura/Public/Game


GameModeBase 기반 BP_AuraGameModeBase 를 생성한다.

생성경로: Blueprints/Game


파일을 열고 Player Controller Class : BP_AuraPlayerController , Default Pawn Class : BP_AuraCharacter 를 적용시킨다.

뷰포트에서 우측 World Settings -> Game Mode -> GameMode Override : BP_AuraGameModeBase 로 적용시킨다.

월드에 PlayerStart 를 배치시키고

플레이버튼을 누르면 정상적으로 캐릭터가 생성되고, ASDW 키를 통해 움직이는 것을 확인할 수 있다.

1.4.6 AuraCharacter 기타 설정

SpringArm ,Camera 를 달아준다.

  • AuraCharacter.h
#include "..."

class USpringArmComponent;
class UCameraComponent;

UCLASS()
class AURA_API AAuraCharacter : public AAuraCharacterBase
{
	GENERATED_BODY()

public:
	AAuraCharacter();

protected:
	
private:
	UPROPERTY(EditDefaultsOnly, Category = "Settings")
	USpringArmComponent* SpringArm;

	UPROPERTY(EditDefaultsOnly, Category = "Settings")
	UCameraComponent* Camera;
}
  • AuraCharacter.cpp
// AuraCharacter.cpp

...
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"

AAuraCharacter::AAuraCharacter()
{
	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
    SpringArm->SetupAttachment(GetRootComponent());
    SpringArm->TargetArmLength = 750.f;
    SpringArm->bUsePawnControlRotation = false;
    SpringArm->bEnableCameraLag = false;
    SpringArm->bInheritPitch = false;
	SpringArm->bInheritYaw = false;
	SpringArm->bInheritRoll = false;
    
    Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	Camera->SetupAttachment(SpringArm);
	Camera->bUsePawnControlRotation = false;
    
    GetCharacterMovement()->bOrientRotationToMovement = true;
    GetCharacterMovement()->RotationRate = FRotator(0.f, 400.f, 0.f);
    // 평면에서의 이동만 허용
    GetCharacterMovement()->bConstrainToPlane = true;
    // 시작시 평면이 아닐 경우 가장 가까운 평면에서 시작
    GetCharacterMovement()->bSnapToPlaneAtStart = true;
    
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;
}

BP_AuraCharacter 파일에서 SpringArm 의 각도를 적당히 조절해준다.

1.4.7 번외(오류)

블렌드 스페이스 사용시 캐릭터의 애니메이션이 부자연스러울수도 있다.
(해당 강의에서는 걷는 애니메이션이 끝나면 머리부분이 휘릭거리는 문제 발생)
기존의 IdleWalkiRun state는 Running state로 변경하고, Idle state를 추가하여 Entry 노드와 연결시킨다.

Idle State 에서 Idle Animation이 재생되도록 변경한다.

Idle -> RunningRunning -> Idle 전환을 위한 Transition Rule 추가를 위해 bool 타입의 ShouldMove 변수를 추가하고 GroundSpeed 값에 따른 bool값 변화를 주도록 한다.
Idle -> RunningRunning -> Idle

1.5 Interface 클래스를 이용한 Enemy 하이라이트표시

1.5.1 마우스커서를 통한 DrawSphere 표시

Unreal Interface 파생 클래스 EnemyInterface 를 생성한다.

생성경로: c++/Aura/Public/Interaction


마우스를 위에 가져다 놓았을 때, 빨간색 하이라이트 처리를 위한 코드를 작성한다.

  • EnemyInterface.h
#include "..."

...

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:
	// 선언된 함수를 0으로 초기화함으로써 pure function으로 만들고
	// EnemyInterface 클래스에서 정의하는 것이 아닌 Enemy 클래스에서 정의
	virtual void HighlightActor() = 0;
	virtual void UnHighlightActor() = 0;
};

AuraEnemy 클래스에서 override하여 사용가능하도록 한다.

  • AuraEnemy.h
#include "..."
#include "Interaction/EnemyInterface.h"

UCLASS()
class AURA_API AAuraEnemy : public AAuraCharacterBase, public IEnemyInterface
{
	GENERATED_BODY()

public:
	virtual void HighlightActor() override;
    virtual void UnHighlightActor() override;
};
  • AuraEnemy.cpp
// AuraEnemy.cpp

void AAuraEnemy::HighlightActor()
{

}

void AAuraEnemy::UnHighlightActor()
{

}

다음 코드를 작성하기 전에 위의 두 함수가 어느 타이밍에 호출되어야 하는지 파악해야 한다.
우선 액터를 가르키는 두 변수인 LastActorThisActor 가 있다고 가정한다.
1. 틱함수에 의해 LastActorThisActor 는 지속적으로 갱신된다.
2. 여기서 함수 호출의 조건이 발생한다.

  • 첫번째 조건 : LastActor = null && ThisActor == null
    둘다 null값을 가지고 있으므로 마우스커서는 어떤 액터도 가리키지 않은 상태이다.
    HighlightActor()UnHighlightActor() 함수 또한 호출되지 않는다.
  • 두번째 조건 : LastActor = null && ThisActor = valid
    이전 틱에서는 아무것도 가리키지 않아서 LastActor 가 null이었던 반면, 틱이 변화하고 마우스가 무언가를 가리켜서 ThisActor 가 값을 가지게 되었다.
    이런 경우에는 HighlightActor() 를 호출해야 한다.
  • 세번째 조건 : LastActor = valid && ThisActor = null
    이전에 무언가를 가리켜서 LastActor 가 valid이었던 반면, 틱이 변화하고 마우스가 아무것도 가리키지 않아서 ThisActor 가 null을 가지게 되었다.
    이런 경우에는 UnHighlightActor() 를 호출해야 한다.
  • 네번째 조건 : LastActor = valid && ThisActor = valid , (LastActor != ThisActor)
    둘다 무언가 가리켜서 valid이지만 대상이 다르다.
    이런 경우에는 LastActor 에서 UnHighLightActor() 호출, ThisActor 에서 HighlightActor() 를 호출해야 한다.
  • 다섯번째 조건 : LastActor = valid && ThisActor = valid
    둘다 동일한 대상을 가리켜서 valid인 상태이다.
    이미 두번째 조건에 의해 HighlightActor() 함수가 호출되어 하이라이트 표시가 된 상태이므로 아무것도 하지 않는다.

마우스가 액터 위에 있는지 확인하는 코드를 AuraPlayerController 클래스에 작성한다.

  • AuraPlayerController.h
#include "..."

class ...;
class IEnemyInterface;

struct ...;

UCLASS()
class AURA_API AAuraPlayerController : public APlayerController
{
	GENERATED_BODY()
    
public:
	...
    /** 코드 추가 : Tick(...) */
    virtual void Tick(float DeltaTime) override;
    /** 코드 추가 */

protected:
	...
    
private:
	...
    
    /** 코드 추가 : CurSorTrace(), LastActor, ThisActor */
    void CursorTrace();
    /**
	* 기존 포인터 방식이 아닌 TObjectPtr와 유사한 새로운 방식, 캐스팅 필요x
	*/
    // 틱 변경전 마우스커서가 가리키는 액터
    TScriptInterface<IEnemyInterface> LastActor;
    // 틱 변경후 마우스커서가 가리키는 액터
    TScriptInterface<IEnemyInterface> ThisActor;
    /** 코드 추가 */
};
  • AuraPlayerController.cpp
#include "..."
#include "Interaction/EnemyInterface.h"

...

void AAuraPlayerController::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
    
    CursorTrace();
}

...

void AAuraPlayerController::CursorTrace()
{
	// FHitResult 타입을 이용해 마우스커서가 액터를 가리키는지 확인
	FHitResult CursorHit;
    GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);
    if(!CursorHit.bBlockingHit) return;
    
    LastActor = ThisActor;
    ThisActor = CursorHit.GetActor();
    
    if(LastActor == nullptr)
    {
    	if(ThisActor != nullptr)
        {
        	// 두번째 조건
        	ThisActor->HighlightActor();
        }
        else
        {
        	// 첫번째 조건
            // 아무것도 하지 않으므로 else문 필요없음
            // 각각의 조건에 따른 코드를 보여주기 위해서 작성함
        }
    }
    else
    {
    	if(ThisActor == nullptr)
        {
        	// 세번째 조건
            LastActor->UnHighlightActor();
        }
        else
        {
        	if(LastActor != ThisActor)
            {
            	// 네번째 조건
                LastActor->UnHighlightActor();
                ThisActor->HighlightActor();
            }
            else
            {
            	// 다섯번째 조건
                // 아무것도 하지 않으므로 else문 필요없음
            	// 각각의 조건에 따른 코드를 보여주기 위해서 작성함
            }
        }    
    }
}

AuraEnmy 클래스로 돌아와 bool 타입 변수를 추가하고, HightlightActor() / UnHighlightActor() 함수를 통해 해당 값을 변경하도록 코드를 작성한다.

  • AuraEnemy.h
#include "..."

UCLASS()
class AURA_API AAuraEnemy : public AAuraCharacterBase, public IEnemyInterface
{
	GENERATED_BODY()

public:
	...
    
    /** 코드 추가 : bHighlighted */
    UPROPERTY(BlueprintReadOnly)
    bool bHighlighted = false;
    /** 코드 추가 */
};
  • AuraEnemy.cpp
#include "..."

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

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

BP_Goblin_SpehreEventGraph 탭에서 아래와 같이 노드를 구성해준다.

마우스 커서는 ECC_Visibility 채널에 대헤서 Block 하기 때문에 BP_Goblin_Spear 메시Collision Presets : Custom 으로 변경 후 Visibility 채널을 Block 하도록 설정한다.

실행하면 정상적으로 동작하는 것을 확인할 수 있다.

추가
BP_AuraEnemy 에서 생성한 Draw Debug Sphere 관련 노드 삭제
(CursorHit 확인을 위한 노드)

1.5.2 Enemy관련 설정을 공유하는 ABP_EnemyBase 생성 및 아웃라인 구현

AuraEnemy 기반 BP_EnemyBase 를 생성한다.

생성경로: Blueprints/Character


BP_Goblin_SlingShot , BP_Goblin_Spear 둘다 Class Settings 선택 후 BP_EnemyBase 를 부모클래스로 변경한다.

액터의 아웃라인을 구현해야한다.
Post Process Volume 을 월드에 배치하고, 범위를 레벨 전체에 적용시키기 위해 Infinite Extend(Unbound) 를 활성화한다.

Post Process volume 선택 후 Post Process Materials 를 추가하고

여기서 사용하는 매테리얼은 CustomDepthStencil 이라는 노드를 사용하는데 이는 액터를 렌더링할 때 일반적인 액터의 색이 아닌 깊이와 스텐실 정보를 렌더링하도록 한다.
즉 해당 노드를 통해 외곽선 렌더링을 가능하게 한다.

Project Settings 에서 Custom Depth-Stencil Pass:Enabled with Stencil 로 변경한다.

월드에 배치된 AuraEnmey 를 선택하고 Render CustomDepth Pass 를 활성화시켜준 뒤, CuystomDepth Stencil Value = 250 으로 적용시키면 메쉬 외곽에 아웃라인이 생기는 것을 확인할 수 있다.

추가
필요에 따라 매테리얼의 두께또한 변경시킬 수 있다.(아래는 1.6->7.0)

어떤 방식으로 적용되는지 확인하였으므로 코드로 작성한다.
먼저 매크로를 추가해 통해 Depth 를 호출할 수 있도록 한다.

  • Aura.h
#include "CoreMinimal.h"

#define	CUSTOM_DEPTH_RED 250
  • AuraEnemy.h
#include "..."

UCLASS()
class AURA_API AAuraEnemy : public AAuraCharacterBase, public IEnemyInterface
{
	GENERATED_BODY()

public:
	AAuraEnemy();
	
    ...
    
    /** 코드 삭제 */
    //UPROPERTY(BlueprintReadOnly)
	//bool bHighlighted = false;
    /** 코드 삭제
};
  • AuraEnemy.cpp
#include "..."
#include "Aura/Aura.h"

AAuraEnemy::AAuraEnemy()
{
    GetMesh()->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
}

void AAuraEnemy::HighlightActor()
{
	GetMesh()->SetRenderCustomDepth(true);
    GetMesh()->SetCustomDepthStencilValue(CUSTOM_DEPTH_RED);
    Weapon->SetRenderCustomDepth(true);
    Weapon->SetCustomDepthStencilValue(CUSTOM_DEPTH_RED);
}

void AAuraEnemy::UnHighlightActor()
{
	GetMesh()->SetRenderCustomDepth(false);
    Weapon->SetRenderCustomDepth(false);
}

컴파일후 BP_EnemyBase 메시의 콜리전 설정이 Visibility 채널에서 block되도록 변경된 것을 확인할 수 있다.

간혹 블루프린트가 c++ 클래스를 override하지 않는 경우도 있는데, 직접 collision을 커스텀설정하여 visibility 채널을 block하도록 변경하면 된다.

실행시 정상적으로 적 주변에 하이라이트가 생성되는 것을 확인할 수 있다.

추가
SpringArm->TargetArmLength = 435.f 로 변경

AAuraCharacter::AAuraCharacter()
{
	...
    /** 코드 수정 */
	SpringArm->TargetArmLength = 435.f;
    ...
    /** 코드 수정 */
}

0개의 댓글