본문 바로가기

Unreal Engine 4/C++

<Unreal C++> 58 - Action RPG (Targeting)

 

필요한 개념


<Targeting>

타켓팅 작업은 플레이어든 적이든 어느 캐릭터에도 붙여서 사용할 수 있도록 ActorComponent로 제작함
이 안에 타겟팅 변수가 있다면 수행하고 아니라면 타켓팅을 수행하지 않는 컴포넌트

C에서는 Interpolation(보간)을 사용하여 부드럽게 처리함

* CTargetComponent 생성

SphereTraceMultiByProfile()로 일정 반경(TraceRadius)안에 있는 객체들을 찾아옴


// UCTargetComponent.h
private:
	// 컴포넌트에서 공개는 EditAnywhere가 좋다.
	// 추적할 반경
	UPROPERTY(EditAnywhere)
		float TraceRadius = 1000.0f;

	// Trace Draw Debug 지속할 건지
	// Type이 enum이어서 크기를 알 수 없음 
	// 그래서 TEnumAsByte로 크기를 알려줌
	UPROPERTY(EditAnywhere)
		TEnumAsByte<EDrawDebugTrace::Type> Debug;

	// 추적할 수 있는 타켓 목록
	TArray<class ACharacter*> TraceTargets



// UCTargetComponent.cpp
void UCTargetComponent::SetTraceTargets()
{
	FVector start = OwnerCharacter->GetActorLocation();
	// Sphere Multi 사용할 건데, 이건 위치가 같으면 안나옴(그래서 위치만 살짝 높임)
	FVector end = FVector(start.X, start.Y, start.Z + 1);

	TArray<AActor*> ignoreActors;
	ignoreActors.Add(OwnerCharacter);

	// SphereTraceMulti를 사용할 거라 여러개의 충돌결과가 나옴
	TArray<FHitResult> hitResults;
	// Profile : 프리셋의 이름으로 딱 한종류만 추적하기 위해 사용(프로젝트 설정 -> 콜리전 -> 프리셋)
	// 2 : 시작 위치 3 : 끝 위치 4 : 반경 5 : 어떤 거를 찾아올건지 6 : 복합 충돌 할건지 7 : 충돌 무시 액터 8 : 디버그 지정, 9 : 반환받을 충돌결과(TArray<HitResult>) 10 : 자가자신 제외할 건지 11 : 디버그 컬러 12 : 충돌된 애들 디버그 컬러 13 : 시간
	UKismetSystemLibrary::SphereTraceMultiByProfile(GetWorld(), start, end, TraceRadius, "Pawn", false, ignoreActors, Debug, hitResults, true, FLinearColor::Green, FLinearColor::Red, 1.0f);

	// 반경안에 추적할 객체들
	for (const FHitResult& result : hitResults)
	{
		if (result.GetActor()->GetClass() == OwnerCharacter->GetClass())
		continue;

	ACharacter* character = Cast<ACharacter>(result.GetActor());

	// casting 되었다면 AddUnique()로 중복 안되게 넣어줌
	if (!!character)
		TraceTargets.AddUnique(character);

	}
}




타켓팅 : 컨트롤러에 제일 가까운 적을 선택(우리는 카메라 방향으로 세팅(캐릭터 방향 x))



* 프로젝트 세팅 -> 입력 -> 액션 매핑에 Target 입력키 세팅

* CPlayer에 TargetComponent 맴버로 추가 & 액션 매핑 세팅

PlayerInputComponent->BindAction("Target", EInputEvent::IE_Pressed, this, &ACPlayer::OnTarget);




- 적을 여러개 월드에 배치했다면, 그 중 하나를 타켓팅하는 것이다.

* 이전에는 적만을 타켓팅해서 카메라 컨트롤러를 사용했지만, 이번에는 플레이어나 적 모두 타켓팅으로 사용할 수 있으므로 캐릭터가 가지고 있는 컨트롤러의 회전을 이용할 것임

이전에는 선정할때 카메라의 정면에서 가장 가까운 애를 선택했다. 이제는 플레이어, Enemy도 가질 수 있어서 내용은 같지만 약간 다름
OwnerCharacter의 Controller를 사용해서 정면에 있는것을 찾는다.

void UCTargetComponent::SetTarget()
{
	float angle = -2.0f;
	ACharacter* target = NULL;

	for (ACharacter* character : TraceTargets)
	{
		// 플레이어는 플레이어 컨트롤러여서, 플레이어 컨트롤러는 카메라랑 관련있어서
		// 카메라의 방향이 나옴
		// 적이면 AIController에 의해서 회전해서, 즉 적의 회전 방향이 나온다.
		// 전방방향을 가지고 옴
		// UseControlRotationYaw을 켜 놓으면 어차피 전방이라 액터의 방향을 설정해도 되지만 
		// 끌 수도 있다. 적도 마찬가지로 타켓을 잡아서 플레이어를 바라볼 수 도 있지만, 아닐 수도 있음
		// 즉 -> GetContolRotation()을 추가 한 이유는 캐릭터의 전방방향이 아닌
		// 컨트롤러의 전방 방향을 사용한다.
		FVector direction = FQuat(OwnerCharacter->GetControlRotation()).GetForwardVector();
		FVector offset = character->GetActorLocation() - OwnerCharacter->GetActorLocation();

		// direction은 GetFowardVector()는 어차피 Normallize()된 백터로 리턴하니깐
		offset = offset.GetSafeNormal();

		// return X*V.X + Y*V.Y + Z*V.Z;
		// 내적은 각 요소의 x,y,z를 곱하고 더함
		// 언리얼에서는 | operator를 제공(FVector::DotProduct()와 동일)
		float temp = direction | offset;

		// angle보다 작다면 안함
		if (temp < angle)
			continue;

		// 크다면 세팅
		// 내적의 값이 가장 큰것이 바라보는 방향과 제일 일치하다는 얘기
		angle = temp;
		target = character;

		// 전방백터를 이용해서 전방백터에 가장가까운 애를 찾음

		/*
		float AActor::GetDotProductTo(const AActor* OtherActor) const
		{
		if (OtherActor)
		{	
		FVector Dir = GetActorForwardVector(); // 전방 백터 가져오고
		FVector Offset = OtherActor->GetActorLocation() - GetActorLocation(); // 플레이어가 OtherActor을 쳐다보는 방향이 만들어짐
		Offset = Offset.GetSafeNormal(); // 단위 백터가 됨
		return FVector::DotProduct(Dir, Offset); // 두 값을 내적함(
		}
		return -2.0;
		}
		*/
		//AActor::GetDotProductTo()

		// 즉 -> DotProduceTo()에서 GetControlRoation() 부분만 추가됨
	}
}



결과를 보면 카메라의 정면에 가까운 애들이 선택된다.(일치한다면 내적 값이 가장 크다, But 두 벡터가 이루는 각이 90도라면, 일치하는 정도가 없기 때문에, 0이 나옴)

 

* GetDotProductTo() 수식 설명

 




해골 마크 이펙트를 추가해서 이 이펙트가 추가된 애가 Target으로 잡힌 액터이다.

<Target으로 잡힌 액터에 해골 마크 추가>

 

* Skel_Mannequin에 spine_03에 Spine_Target 소켓 추가



// 타켓에 해골모양 파티클 붙이기 위한
UPROPERTY(EditAnywhere)
	class UParticleSystem* Particle;

// 타겟을 지정 + 거기에 커서를 붙임
void ChangeCursor(class ACharacter* InTarget);








Tip)
UGameplayStatics의 Spawn으로 시작되는 함수들은 실행하는 액터나 어태치되는 액터에 자동으로 컴포넌트를 생성해서 플레이하고, 플레이 종료가 일어나면 자동으로 삭제한다.
-> 하지만 무한 루프일 경우 필요할 때, 프로그래머가 제거할 수 있도록 자동으로 생성된 컴포넌트를 리턴해준다.


// Spawn되는 함수의 임시생성되는 컴포넌트를 저장(제거 안됨 방지)
class UParticleSystemComponent* Attached;



* 타겟 지정 + 거기에 커서를 붙임
SetTarget()에서 ChangeCursor() 함수를 호출해서 InTarget을 넘겨줌

void UCTargetComponent::ChangeCursor(class ACharacter* InTarget)
{
	if (!!InTarget)
	{
		// 이미 컴포넌트가 존재한다면 제거해라
		if (!!Attached)
			Attached->DestroyComponent();

		// 소켓에다 파티클을 붙임
		Attached = UGameplayStatics::SpawnEmitterAttached(Particle, InTarget->GetMesh(), "Spine_Target");

		// 타겟 지정
		Target = InTarget;

		return;
	}

	// Null이라면 EndTargeting으로 간다.
	EndTargeting();
}



void UCTargetComponent::EndTargeting()
{
	// EndTargeting에서는 초기화 해줌
	Target = NULL;
	TraceTargets.Empty();

	if (!!Attached)
		Attached->DestroyComponent();
}



<타겟을 잡고 Q(Left), E(Right)를 누르면 옆 타겟이 이동하는 작업>

void UCTargetComponent::ChangeTarget(bool InRight)
{
	CheckNull(Target);

	// 거리값이자 방향값 float
	TMap<float, ACharacter*> map;

	for (ACharacter* character : TraceTargets)
	{
		if (Target == character)
			continue;

		FVector targetLocation = character->GetActorLocation();
		FVector ownerLocation = OwnerCharacter->GetActorLocation();
		// owner가 target을 바라봐야 하니까
		FVector ownerToTarget = targetLocation - ownerLocation;

		// 카메라의 방향 값
		FQuat quat = FQuat(OwnerCharacter->GetControlRotation());
		// forwar, up 구해줌
		FVector forward = quat.GetForwardVector();
		FVector up = quat.GetUpVector();

		/*
		^은 외적 operator
		Y * V.Z - Z * V.Y,
		Z * V.X - X * V.Z,
		X * V.Y - Y * V.X
		*/
		// 나오는 수직 백터의 값이 거리이자 방향이다.
		FVector cross = forward ^ ownerToTarget;
		// cross와 up을 내적해서 z값만 가지고 옴
		float dot = cross | up;

		map.Add(dot, character);
	}

	float min = FLT_MAX;
	ACharacter* target = NULL;

	TArray<float> keys;
	map.GetKeys(keys);
	for (float key : keys)
	{
		if (InRight == true)
		{
			// 키가 0보다 작거나 왼쪽 키값이다.
			if (key < 0.0f)
				continue;
		}

		// 이번에는 left니까 0보다 큰 애들을 잘라냄
		else
		{
			if (key > 0.0f)
				continue;
		}

		// 크다면 넘김(최소값을 구할거라)
		if (FMath::Abs(key) > min)
			continue;

		// 가장 가까운 = 최소값의 target을 넣음
		min = FMath::Abs(key);
   		target = *map.Find(key);
	}

	ChangeCursor(target);
}



*Player에 입력 이벤트 연결

PlayerInputComponent->BindAction("TargetLeft", EInputEvent::IE_Pressed, this, &ACPlayer::OnTargetLeft);
PlayerInputComponent->BindAction("TargetRight", EInputEvent::IE_Pressed, this, &ACPlayer::OnTargetRight);



void ACPlayer::OnTargetLeft()
{
	Target->ChangeTargetLeft();
}

void ACPlayer::OnTargetRight()
{
	Target->ChangeTargetRight();
}



* 버그 발생
Q, E를 누르면 왼쪽, 오른쪽으로 각각 가다가 왔다갔다하는 증상이 있다.
-> 카메라가 회전을 안해서 그럼
그래서 ControlRotation을 조금 돌려줄 것이다.
적도 AI에 의해서 회전 값을 포커스에 맞도록 이동해줄 것이다.

void UCTargetComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	CheckNull(Target);

	// 즉, 바라보는 방향의 회전값으로 세팅되도록 만듬(left, right target 버그 해결)

	FVector start = OwnerCharacter->GetActorLocation();
	FVector target = Target->GetActorLocation();

	// start에서 target을 바라보는 회전값이 만들어짐
	FRotator rotator = UKismetMathLibrary::FindLookAtRotation(start, target);

	// SetControlRotation()은 private이어서 Contoller를 통해 가져와야함
	OwnerCharacter->GetController()->SetControlRotation(rotator);
}




* 부드럽게 회전할 값 만듬

 

UPROPERTY(EditAnywhere)
	float interopSpeed = 2.5f;


1이하면 버그가 생김(값이 너무 크면 부드러워 지면, 카메라가 너무 천천히 이동하면 Targeting에 버그가 생김)

버그 해결하고 싶다면, 옮겨질려는 애한테 어느정도 회전값이 들어왔는지 판단한 다음, 하면 됨

FRotator current = OwnerCharacter->GetControlRotation();

// RInterpTo(Rotation Interpolation To) : 시작 방향에서 종료 방향까지 시간에 비례한 속도로 자연스럽게 보간해주는 함수
rotator = UKismetMathLibrary::RInterpTo(current, rotator, DeltaTime, interopSpeed);



* 버그가 생김 : 너무 빠르게 누를시, Target이 사라짐
회전 속도를 올려주거나, 회전이 완료되지 않았다면 키 입력을 무시


*Tip) BP의 컴포넌트 디테일이 안뜨는 경우가 있음
-> BP 우클릭 -> 애셋액션 -> 리로드 하거나

 


-> 새로 BP를 생성하면 됨

 

 

 

 

 

CTargetComponent.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "CTargetComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UONLINE_04_ACTION_API UCTargetComponent : public UActorComponent
{
	GENERATED_BODY()

private:
	// 컴포넌트에서 공개는 EditAnywhere가 좋다.
	// 추적할 반경
	UPROPERTY(EditAnywhere)
		float TraceRadius = 1000.0f;

	UPROPERTY(EditAnywhere)
		float interopSpeed = 2.5f;

	// Trace Draw Debug 지속할 건지
	// Type이 enum이어서 크기를 알 수 없음 	
	// 그래서 TEnumAsByte로 크기를 알려줌
	UPROPERTY(EditAnywhere)
		TEnumAsByte<EDrawDebugTrace::Type> Debug;

	// 타켓에 해골모양 파티클 붙이기 위한
	UPROPERTY(EditAnywhere)
		class UParticleSystem* Particle;

public:	
	UCTargetComponent();

protected:
	virtual void BeginPlay() override;

public:
	// 한번 누르면 타켓팅, 누르지 않으면 타켓팅이 풀림
	void ToggleTarget();
	void ChangeTargetLeft();
	void ChangeTargetRight();

private:
	void StartTargeting();
	void EndTargeting();

	// 일정반경안에 있는지 보고 타켓을 잡을 수 있는 객체들을 찾아옴(BP와 똑같음)
	void SetTraceTargets();

	// 목록 중에 하나의 타겟을 정함
	void SetTarget();

	// 왼, 오른쪽 타겟으로 이동하기 위한 작업
	void ChangeTarget(bool InRight);

	// 타겟을 지정 + 거기에 커서를 붙임
	void ChangeCursor(class ACharacter* InTarget);

public:	
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

private:
	// Player든, 적이든 타켓팅컴포넌트를 사용 가능하도록
	class ACharacter* OwnerCharacter;
	// 타켓팅될 캐릭터
	class ACharacter* Target;

	// Spawn되는 함수의 임시생성되는 컴포넌트를 저장(제거 안됨 방지)
	class UParticleSystemComponent* Attached;

	// 추적할 수 있는 타켓 목록
	TArray<class ACharacter*> TraceTargets;
};

 

 

 

 

 

 

 

CTargetComponent.cpp


더보기
#include "CTargetComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Particles/ParticleSystem.h"
#include "Particles/ParticleSystemComponent.h"

UCTargetComponent::UCTargetComponent()
{
	PrimaryComponentTick.bCanEverTick = true;

	// 기본 값 설정
	CHelpers::GetAsset<UParticleSystem>(&Particle, "ParticleSystem'/Game/Effects/P_Enrage_Base.P_Enrage_Base'");
}


void UCTargetComponent::BeginPlay()
{
	Super::BeginPlay();

	OwnerCharacter = Cast<ACharacter>(GetOwner());

}

void UCTargetComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	CheckNull(Target);

	// 즉, 바라보는 방향의 회전값으로 세팅되도록 만듬(left, right target 버그 해결)

	FVector start = OwnerCharacter->GetActorLocation();
	FVector target = Target->GetActorLocation();

	// start에서 target을 바라보는 회전값이 만들어짐
	FRotator rotator = UKismetMathLibrary::FindLookAtRotation(start, target);
	FRotator current = OwnerCharacter->GetControlRotation();

	// RInterpTo : 시작 방향에서 종료 방향까지 시간에 비례한 속도로 자연스럽게 보간해주는 함수
	rotator = UKismetMathLibrary::RInterpTo(current, rotator, DeltaTime, interopSpeed);

	// SetControlRotation()은 private이어서 Contoller를 통해 가져와야함
	OwnerCharacter->GetController()->SetControlRotation(rotator);
}


void UCTargetComponent::ToggleTarget()
{
	if (!!Target)
	{
		EndTargeting();

		return;
	}

	// 기존 타켓팅이 없다면 수행함
	StartTargeting();
}

void UCTargetComponent::StartTargeting()
{
	SetTraceTargets();
	SetTarget();
}

void UCTargetComponent::EndTargeting()
{
	// EndTargeting에서는 초기화 해줌
	Target = NULL;
	TraceTargets.Empty();

	if (!!Attached)
		Attached->DestroyComponent();
}

void UCTargetComponent::SetTraceTargets()
{
	FVector start = OwnerCharacter->GetActorLocation();
	// Sphere Multi 사용할 건데, 이건 위치가 같으면 안나옴(그래서 위치만 살짝 높임)
	FVector end = FVector(start.X, start.Y, start.Z + 1);

	TArray<AActor*> ignoreActors;
	ignoreActors.Add(OwnerCharacter);

	// SphereTraceMulti를 사용할 거라 여러개의 충돌결과가 나옴
	TArray<FHitResult> hitResults;
	// Profile : 프리셋의 이름으로 딱 한종류만 추적하기 위해 사용(프로젝트 설정 -> 콜리전 -> 프리셋)
	// 2 : 시작 위치 3 : 끝 위치 4 : 반경 5 : 어떤 거를 찾아올건지 6 : 복합 충돌 할건지 7 : 충돌 무시 액터 8 : 디버그 지정, 9 : 반환받을 충돌결과(TArray<HitResult>) 10 : 자가자신 제외할 건지 11 : 디버그 컬러 12 : 충돌된 애들 디버그 컬러 13 : 시간
	UKismetSystemLibrary::SphereTraceMultiByProfile(GetWorld(), start, end, TraceRadius, "Pawn", false, ignoreActors, Debug, hitResults, true, FLinearColor::Green, FLinearColor::Red, 1.0f);

	// 반경안에 추적할 객체들
	for (const FHitResult& result : hitResults)
	{
		if (result.GetActor()->GetClass() == OwnerCharacter->GetClass())
			continue;

		ACharacter* character = Cast<ACharacter>(result.GetActor());

		// casting 되었다면 AddUnique()로 중복 안되게 넣어줌
		if (!!character)
			TraceTargets.AddUnique(character);
	}
}

void UCTargetComponent::SetTarget()
{
	float angle = -2.0f;
	ACharacter* target = NULL;

	for (ACharacter* character : TraceTargets)
	{
		// 플레이어는 플레이어 컨트롤러여서, 플레이어 컨트롤러는 카메라랑 관련있어서
		// 카메라의 방향이 나옴
		// 적이면 AIController에 의해서 회전해서, 즉 적의 회전 방향이 나온다.
		// 전방방향을 가지고 옴
		// UseControlRotationYaw을 켜 놓으면 어차피 전방이라 액터의 방향을 설정해도 되지만 
		// 끌 수도 있다. 적도 마찬가지로 타켓을 잡아서 플레이어를 바라볼 수 도 있지만, 아닐 수도 있음
		// 즉 -> GetContolRotation()을 추가 한 이유는 캐릭터의 전방방향이 아닌
		// 컨트롤러의 전방 방향을 사용한다.
		FVector direction = FQuat(OwnerCharacter->GetControlRotation()).GetForwardVector();
		FVector offset = character->GetActorLocation() - OwnerCharacter->GetActorLocation();

		// direction은 GetFowardVector()는 어차피 Normallize()된 백터로 리턴하니깐
		offset = offset.GetSafeNormal();

		// return X*V.X + Y*V.Y + Z*V.Z;
		// 내적은 각 요소의 x,y,z를 곱하고 더함
		// 언리얼에서는 | operator를 제공(FVector::DotProduct()와 동일)
		float temp = direction | offset;

		// angle보다 작다면 안함
		if (temp < angle)
			continue;

		// 크다면 세팅
		// 이러면 가장 작은 Angle을 가진 캐릭터만 남음
		// 내적의 값이 가장 작은게 바라보는 방향과 제일 일치하다는 얘기
		// 내적 값이 0이면 완전 일치라서
		angle = temp;
		target = character;

		// 전방백터를 이용해서 전방백터에 가장가까운 애를 찾음

		/*
			float AActor::GetDotProductTo(const AActor* OtherActor) const
			{
				if (OtherActor)
				{
					FVector Dir = GetActorForwardVector(); // 전방 백터 가져오고
					FVector Offset = OtherActor->GetActorLocation() - GetActorLocation(); // 플레이어가 OtherActor을 쳐다보는 방향이 만들어짐
					Offset = Offset.GetSafeNormal(); // 단위 백터가 됨
					return FVector::DotProduct(Dir, Offset); // 두 값을 내적함(
				}
				return -2.0;
			}	
		*/
		//AActor::GetDotProductTo()

		// 즉 -> DotProduceTo()에서 GetControlRoation() 부분만 추가됨
		
	}

	//CLog::Print(target->GetActorLabel());
	ChangeCursor(target);
}

void UCTargetComponent::ChangeTargetLeft()
{
	ChangeTarget(false);
}

void UCTargetComponent::ChangeTargetRight()
{
	ChangeTarget(true);
}

void UCTargetComponent::ChangeTarget(bool InRight)
{
	CheckNull(Target);

	// 거리값이자 방향값 float
	TMap<float, ACharacter*> map;

	for (ACharacter* character : TraceTargets)
	{
		if (Target == character)
			continue;

		FVector targetLocation = character->GetActorLocation();
		FVector ownerLocation = OwnerCharacter->GetActorLocation();
		// owner가 target을 바라봐야 하니까
		FVector ownerToTarget = targetLocation - ownerLocation;

		// 카메라의 방향 값
		FQuat quat = FQuat(OwnerCharacter->GetControlRotation());
		// forwar, up 구해줌
		FVector forward = quat.GetForwardVector();
		FVector up = quat.GetUpVector();

		/*
			^은 외적 operator
			Y * V.Z - Z * V.Y,
			Z * V.X - X * V.Z,
			X * V.Y - Y * V.X
		*/
		// 나오는 수직 백터의 값이 거리이자 방향이다.
		FVector cross = forward ^ ownerToTarget;
		// cross와 up을 내적해서 z값만 가지고 옴
		float dot = cross | up;

		map.Add(dot, character);
	}

	float min = FLT_MAX;
	ACharacter* target = NULL;

	TArray<float> keys;
	map.GetKeys(keys);
	for (float key : keys)
	{
		if (InRight == true)
		{
			// 키가 0보다 작거나 왼쪽 키값이다.
			if (key < 0.0f)
				continue;
		}

		// 이번에는 left니까 0보다 큰 애들을 잘라냄
		else
		{
			if (key > 0.0f)
				continue;
		}

		// 크다면 넘김(최소값을 구할거라)
		if (FMath::Abs(key) > min)
			continue;

		// 가장 가까운 = 최소값의 target을 넣음
		min = FMath::Abs(key);
		target = *map.Find(key);
	}

	ChangeCursor(target);
}

void UCTargetComponent::ChangeCursor(class ACharacter* InTarget)
{
	if (!!InTarget)
	{
		// 이미 컴포넌트가 존재한다면 제거해라
		if (!!Attached)
			Attached->DestroyComponent();

		// 소켓에다 파티클을 붙임
		Attached = UGameplayStatics::SpawnEmitterAttached(Particle, InTarget->GetMesh(), "Spine_Target");
	
		// 타겟 지정
		Target = InTarget;

		return;
	}
	
	// Null이라면 EndTargeting으로 간다.
	EndTargeting();
}

 

 

CPlayer.h 수정된 내용


더보기
...

UCLASS()
class UONLINE_04_ACTION_API ACPlayer : public ACharacter, public IICharacter
{
	GENERATED_BODY()

private:
	...
	UPROPERTY(VisibleDefaultsOnly)
		class UCTargetComponent* Target;

private:
	void OnTarget();
	void OnTargetLeft();
	void OnTargetRight();
};

 

 

 

 

 

 

 

CPlayer.cpp 수정된 내용


더보기
...
#include "Components/CTargetComponent.h"

ACPlayer::ACPlayer()
{
	...
	CHelpers::CreateActorComponent<UCTargetComponent>(this, &Target, "Target");
}

void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	...
    
	PlayerInputComponent->BindAction("Target", EInputEvent::IE_Pressed, this, &ACPlayer::OnTarget);
	
	PlayerInputComponent->BindAction("TargetLeft", EInputEvent::IE_Pressed, this, &ACPlayer::OnTargetLeft);
	PlayerInputComponent->BindAction("TargetRight", EInputEvent::IE_Pressed, this, &ACPlayer::OnTargetRight);
}

void ACPlayer::OnTarget()
{
	Target->ToggleTarget();
}

void ACPlayer::OnTargetLeft()
{
	Target->ChangeTargetLeft();
}

void ACPlayer::OnTargetRight()
{
	Target->ChangeTargetRight();
}

 

 

 

 

결과


카메라의 방향으로 적을 타켓팅 한다.