초보 코린이의 성장 일지
UE4 Parkour 본문
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 |