초보 코린이의 성장 일지

UE4 Parkour 본문

언리얼

UE4 Parkour

코오린이 2023. 7. 18. 17:57

1. 파쿠르를 구현하기에 앞서서 맵을 늘려주고, 기물들을 배치해준다.


1. Actor Component를 상속받아 클래스를 생성해준다.

2. 테이블에 배열을 활용하여 파쿠르 동작을 구현해볼 것인데, BP에서는 불가능하고 C++에서만 가능한 방법이다.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Engine/DataTable.h"
#include "CParkourComponent.generated.h"

UENUM(BlueprintType)
enum class EParkourArrowType : uint8
{
	// 추적할 화살표 Type
	Center = 0, Ceil, Floor, Left, Right, Land, Max,
};

UENUM(BlueprintType)
enum class EParkourType : uint8
{
	// 파쿠르 수행할 Type
	Climb = 0, Fall, Slide, Short, Normal, Wall, Max,
};

USTRUCT(BlueprintType)
// FTableRowBase로 상속을 받아야 불러지는 곳에서 노출이된다.
struct FParkourData : public FTableRowBase
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere)
		EParkourType Type;

	UPROPERTY(EditAnywhere)
		UAnimMontage* Montage;

	UPROPERTY(EditAnywhere)
		float PlayRatio = 1;

	UPROPERTY(EditAnywhere)
		FName SectionName; // 섹션 시작지점을 정하는 용도.

	UPROPERTY(EditAnywhere)
		float MinDistance; // 최소거리

	UPROPERTY(EditAnywhere)
		float MaxDistance; // 최대거리

	UPROPERTY(EditAnywhere)
		float Extent; // 부피 및 높이

	UPROPERTY(EditAnywhere)
		bool bFixedCamera; // 카메라 고정

public:
	void PlayMontage(class ACharacter* InCharacter);

};

UCLASS()
class U2212_06_API UCParkourComponent : public UActorComponent
{
	GENERATED_BODY()

private:
	UPROPERTY(EditAnywhere, Category = "Data")
		UDataTable* DataTable;

public:	
	UCParkourComponent();

protected:
	virtual void BeginPlay() override;

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

private:
	// 배열이 return 되도록 map 사용
	TMap<EParkourType, TArray<FParkourData>> DataMap;

};
#include "Parkour/CParkourComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/ArrowComponent.h"

void FParkourData::PlayMontage(class ACharacter* InCharacter)
{
	InCharacter->PlayAnimMontage(Montage, PlayRatio, SectionName);

}
///////////////////////////////////////////////////////////////////////////////

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

}

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

}

void UCParkourComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}

1. 세팅해줄 값들만 선언해주고, 우선은 컴파일을 해준다.

2. 데이터 테이블을 우선적으로 넣어주기 위함.


1. 사용할 Parkour 데이터 테이블을 임포트 해준다.


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Characters/ICharacter.h"
#include "Parkour/CParkourComponent.h"	
#include "CPlayer.generated.h"

UCLASS()
class U2212_06_API ACPlayer 
	: public ACharacter
	, public IICharacter
{
	GENERATED_BODY()
    
private:
	UPROPERTY(VisibleDefaultsOnly)
		class UCParkourComponent* Parkour;

private:
	UPROPERTY(VisibleDefaultsOnly)
		class USceneComponent* ArrowGroup;

	UPROPERTY(VisibleDefaultsOnly)
		class UArrowComponent* Arrows[(int32)EParkourArrowType::Max];
};
#include "Characters/CPlayer.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/InputComponent.h"
#include "Components/CWeaponComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"
#include "Components/ArrowComponent.h"

ACPlayer::ACPlayer()
{
	GetCharacterMovement()->RotationRate = FRotator(0, 720, 0);

	// CapsuleComponent에 붙여서 생성
	CHelpers::CreateComponent<USceneComponent>(this, &ArrowGroup, "ArrowGroup", GetCapsuleComponent());
	for (int32 i = 0; i < (int32)EParkourArrowType::Max; i++)
	{
		FString name = StaticEnum<EParkourArrowType>()->GetNameStringByIndex(i); // enum을 문자열로 바꾸기
		// 문자열로 바꾸게 되면 이름이 나오므로 FName(name)로 만들어준다.
		CHelpers::CreateComponent<UArrowComponent>(this, &Arrows[i], FName(name), ArrowGroup);

		switch ((EParkourArrowType)i) // 각 화살표 색과 위치 설정.
		{
		case EParkourArrowType::Center:
			Arrows[i]->ArrowColor = FColor::Red;
			break;

		case EParkourArrowType::Ceil:
			Arrows[i]->ArrowColor = FColor::Green;
			Arrows[i]->SetRelativeLocation(FVector(0, 0, 100));
			break;

		case EParkourArrowType::Floor:
			Arrows[i]->ArrowColor = FColor::Blue;
			Arrows[i]->SetRelativeLocation(FVector(0, 0, -80));
			break;

		case EParkourArrowType::Left:
			Arrows[i]->ArrowColor = FColor::Magenta;
			Arrows[i]->SetRelativeLocation(FVector(0, -30, 0));
			break;

		case EParkourArrowType::Right:
			Arrows[i]->ArrowColor = FColor::Magenta;
			Arrows[i]->SetRelativeLocation(FVector(0, 30, 0));
			break;

		case EParkourArrowType::Land: // 캐릭터 일정 전방 거리에서, 아래를 향한 화살표 방향이므로, 회전까지 준다.
			Arrows[i]->ArrowColor = FColor::Yellow;
			Arrows[i]->SetRelativeLocation(FVector(200, 0, 100));
			Arrows[i]->SetRelativeRotation(FRotator(-90, 0, 0));
			break;
		}

	}
}

1. 체크해줄 Arrow를 가져와준다.

2. enum을 문자열로 변경한 이름으로 생성.

3. 각 화살표 방향이 어디에 위치할건지, 색상을 정해준다.

1. 화살표 방향이 잘 나온걸 확인할 수 있다.


#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Engine/DataTable.h"
#include "CParkourComponent.generated.h"

UENUM(BlueprintType)
enum class EParkourArrowType : uint8
{
	// 추적할 화살표 Type
	Center = 0, Ceil, Floor, Left, Right, Land, Max,
};

UENUM(BlueprintType)
enum class EParkourType : uint8
{
	// 파쿠르 수행할 Type
	Climb = 0, Fall, Slide, Short, Normal, Wall, Max,
};

USTRUCT(BlueprintType)
// FTableRowBase로 상속을 받아야 불러지는 곳에서 노출이된다.
struct FParkourData : public FTableRowBase
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere)
		EParkourType Type;

	UPROPERTY(EditAnywhere)
		UAnimMontage* Montage;

	UPROPERTY(EditAnywhere)
		float PlayRatio = 1;

	UPROPERTY(EditAnywhere)
		FName SectionName; // 섹션 시작지점을 정하는 용도.

	UPROPERTY(EditAnywhere)
		float MinDistance; // 최소거리

	UPROPERTY(EditAnywhere)
		float MaxDistance; // 최대거리

	UPROPERTY(EditAnywhere)
		float Extent; // 부피 및 높이

	UPROPERTY(EditAnywhere)
		bool bFixedCamera; // 카메라 고정

public:
	void PlayMontage(class ACharacter* InCharacter);

};

UCLASS()
class U2212_06_API UCParkourComponent : public UActorComponent
{
	GENERATED_BODY()

private:
	UPROPERTY(EditAnywhere, Category = "Data")
		UDataTable* DataTable;

private:
	UPROPERTY(EditAnywhere, Category = "Trace")
		float TraceDistance = 600;

	UPROPERTY(EditAnywhere, Category = "Trace")
		TEnumAsByte<EDrawDebugTrace::Type> DebugType;

	UPROPERTY(EditAnywhere, Category = "Trace")
		float AvailableFrontAngle = 15;

public:	
	UCParkourComponent();

protected:
	virtual void BeginPlay() override;

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

private:
	void LineTrace(EParkourArrowType InType);

private:
	void CheckTrace_Center();

private:
	// 배열이 return 되도록 map 사용
	TMap<EParkourType, TArray<FParkourData>> DataMap;

private:
	class ACharacter* OwnerCharacter;
	class UArrowComponent* Arrows[(int32)EParkourArrowType::Max]; // 화살표 담을 곳

	FHitResult HitResults[(int32)EParkourArrowType::Max]; // 화살표 충돌 결과 저장

private:
	// 기본 충돌에 관한 정보들
	AActor* HitObstacle;
	FVector HitObstacleExtent;

	float HitDistance;
	float ToFrontYaw;
};
#include "Parkour/CParkourComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/ArrowComponent.h"

void FParkourData::PlayMontage(class ACharacter* InCharacter)
{
	InCharacter->PlayAnimMontage(Montage, PlayRatio, SectionName);

}
///////////////////////////////////////////////////////////////////////////////

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

	CHelpers::GetAsset<UDataTable>(&DataTable, "DataTable'/Game/Parkour/DT_Parkour.DT_Parkour'");
}

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

	TArray<FParkourData*> datas;
	DataTable->GetAllRows<FParkourData>("", datas); // 데이터 테이블을 가져와준다.

	for (int32 i = 0; i < (int32)EParkourType::Max; i++)
	{
		// 파쿠르 데이터를 배열로 만들어주고,
		TArray<FParkourData> temp;

		//해당 파쿠르 타입을 넣어준다.
		for (FParkourData* data : datas)
		{
			// 캐스팅한 i와 같다면 타입이 같다는 의미
			if (data->Type == (EParkourType)i)
				temp.Add(*data); // FParkourData에 포인터로 받았기 때문에 객체에도 *를 사용해야한다.
		}

		DataMap.Add((EParkourType)i, temp);
	}

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

	// 화살표 생성, 그룹 가져오기
	USceneComponent* arrow = CHelpers::GetComponent<USceneComponent>(OwnerCharacter, "ArrowGroup");

	TArray<USceneComponent*> components; // 배열을 통해서 USceneComponent다 가져오기.
	arrow->GetChildrenComponents(false, components); // 자식에 자식까진 갈 필요 없으므로, false

	// 배열로 찾아서 캐스팅해주기.
	for (int32 i = 0; i < (int32)EParkourArrowType::Max; i++)
		Arrows[i] = Cast<UArrowComponent>(components[i]);

}

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

	// 값 초기화
	HitObstacle = NULL;
	HitObstacleExtent = FVector::ZeroVector;
	HitDistance = 0;
	ToFrontYaw = 0;

	CheckTrace_Center();
}

void UCParkourComponent::LineTrace(EParkourArrowType InType)
{
	// 화살표 가져오기
	UArrowComponent* arrow = Arrows[(int32)InType];
	FLinearColor color = FLinearColor(arrow->ArrowColor); // 화살표 색저장

	FTransform transform = arrow->GetComponentToWorld(); // 위치 바꿔놓기

	FVector start = transform.GetLocation(); 
	FVector end = start + OwnerCharacter->GetActorForwardVector() * TraceDistance;

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

	// TraceTypeQuery3을 추적하기 위해 BP에서 채널을 생성해줄 것이다.
	UKismetSystemLibrary::LineTraceSingle(GetWorld(), start, end, ETraceTypeQuery::TraceTypeQuery3, false, ignores, DebugType, HitResults[(int32)InType], true, color, FLinearColor::White);

}

void UCParkourComponent::CheckTrace_Center()
{
	// 센터가 먼저 체크되면 나머지 필요한 값들 저장


	EParkourArrowType type = EParkourArrowType::Center;
	LineTrace(type);

	const FHitResult& hitResult = HitResults[(int32)type];
	CheckFalse(hitResult.bBlockingHit);

	// StaticMesh에 충돌체가 있으므로 얻어오기
	UStaticMeshComponent* mesh = CHelpers::GetComponent<UStaticMeshComponent>(hitResult.GetActor());
	CheckNull(mesh);

	HitObstacle = hitResult.GetActor(); // 부피 얻어오기

	FVector minBound, maxBound;
	mesh->GetLocalBounds(minBound, maxBound);

	float x = FMath::Abs(minBound.X - maxBound.X);
	float y = FMath::Abs(minBound.Y - maxBound.Y);
	float z = FMath::Abs(minBound.Z - maxBound.Z);
	HitObstacleExtent = FVector(x, y, z);

	HitDistance = hitResult.Distance; // 거리 얻어오기

	// ImpactNormal 충돌체에 노말벡터
	// 플레이어가 서있는 방향과 hit된 객체와 몇도 차이가 나는지 확인용.
	ToFrontYaw = UKismetMathLibrary::MakeRotFromX(-hitResult.ImpactNormal).Yaw;

#ifdef LOG_UCParkourComponent
	CLog::Print(HitObstacle, 10);
	CLog::Print(HitObstacleExtent, 11);
	CLog::Print(HitDistance, 12);
	CLog::Print(ToFrontYaw, 13);
#endif //LOG_UCParkourComponent
}

1. 파쿠르 체크할 화살표를 담아주고 생성.

2. 그룹에 속해있는 화살표 캐스팅 후 사용.

3. Trace에 TraceQuery3을 아래에서 생성해 줄 것인데 미리 체크해주기.

4. 어떠한 객체와 hit된걸 Trace로 판별해주고, 그 거리와 얼마나 각도가 차이나는지 확인.

 


1. 프로젝트 세팅 -> 콜리전으로 가서 생성해 준다.

2. Channel과 Preset를 위에 사진처럼 생성해준다.


1. 파쿠르에 사용될 객체들을 전부 선택해서 콜리전 프리셋에 만들어준 Parkour로 변경해준다.


#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Characters/ICharacter.h"
#include "Parkour/CParkourComponent.h"	
#include "CPlayer.generated.h"

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

private:
	UPROPERTY(VisibleDefaultsOnly)
		class UCParkourComponent* Parkour;

};
#include "Characters/CPlayer.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/InputComponent.h"
#include "Components/CWeaponComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"
#include "Components/ArrowComponent.h"

ACPlayer::ACPlayer()
{
	// 생성
	CHelpers::CreateActorComponent<UCParkourComponent>(this, &Parkour, "Parkour");

}

1. 파쿠르 컴포넌트를 생성해준다.


#include "Parkour/CParkourComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/ArrowComponent.h"

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

	CHelpers::GetAsset<UDataTable>(&DataTable, "DataTable'/Game/Parkour/DT_Parkour.DT_Parkour'");
}

1. 데이터 테이블 기본으로 하나 불러놓아준다.


1. Player로가서 DataTable 들어간걸 확인해주고, Trace Debug Type를 눈으로 볼 수 있도록 설정해준다.


https://www.youtube.com/watch?v=imulvdu7oQQ 

Trace 채널이 등록된 객체에만 충돌되는걸 알 수 있다.

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

UE4 Parkour Final, Feet IK  (0) 2023.07.20
UE4 Parkour Climb  (0) 2023.07.19
UE4 Bow Arrow  (0) 2023.07.13
UE4 Bow Aim Bend  (0) 2023.07.10
UE4 Bow Aim (2)  (0) 2023.07.07
Comments