초보 코린이의 성장 일지

UE4 Parkour Climb 본문

언리얼

UE4 Parkour Climb

코오린이 2023. 7. 19. 16:43
#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();
	void CheckTrace_Ceil();
	void CheckTrace_Floor();
	void CheckTrace_LeftRight();

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 UCParkourComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

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

    CheckTrace_Center();

    // HitObstacle이 있다는건 hit가 되었다는 뜻
    if (!!HitObstacle)
    {
        CheckTrace_Ceil();
        CheckTrace_Floor();
        CheckTrace_LeftRight();
    }
}

void UCParkourComponent::CheckTrace_Ceil()
{
    LineTrace(EParkourArrowType::Ceil);
}

void UCParkourComponent::CheckTrace_Floor()
{
    LineTrace(EParkourArrowType::Floor);
}

void UCParkourComponent::CheckTrace_LeftRight()
{
    LineTrace(EParkourArrowType::Left);
    LineTrace(EParkourArrowType::Right);
}

1. 우선 추가된 코드로 각 화살표 위치에서 체크가 되도록 만들어준다.

2. Tick를 통해서 계속 판별해준다.

1. 충돌되는 객체에 다가가면 일정거리에서 전부위에 있는 화살표가 체크되는걸 확인할 수 있다.


#pragma once

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

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

private:
	// 파쿠르 동작들이 수행될 수 있는지 전체적으로 체크해주는 용도
	bool Check_Obstacle();

public:
	void DoParkour(bool bLanded = false); // 뛰어 내리는지 체크
	void End_DoParkour();

private:
	// 현재 수행중인 파쿠르 타입
	EParkourType Type = EParkourType::Max;
};
#include "Parkour/CParkourComponent.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/ArrowComponent.h"

bool UCParkourComponent::Check_Obstacle()
{
    // HitObstacle는 중앙에서 hit된 장애물이므로, null이 아니여야 한다.
    CheckNullResult(HitObstacle, false);

    // 중앙, 좌, 우 hit되어야 하므로 체크.
    bool b = true;
    b &= HitResults[(int32)EParkourArrowType::Center].bBlockingHit;
    b &= HitResults[(int32)EParkourArrowType::Left].bBlockingHit;
    b &= HitResults[(int32)EParkourArrowType::Right].bBlockingHit;
    CheckFalseResult(b, false);

    // 중앙, 좌, 우 Normal를 해주는 이유는 모서리인지 아닌지 판별해주기 위해
    FVector center = HitResults[(int32)EParkourArrowType::Center].Normal;
    FVector left = HitResults[(int32)EParkourArrowType::Left].Normal;
    FVector right = HitResults[(int32)EParkourArrowType::Right].Normal;

    // Equals로 좌우 모서리 판별 
    CheckFalseResult(center.Equals(left), false);
    CheckFalseResult(center.Equals(right), false);

    // hit된 지점과 플레이어 위치를 가지고 두개의 방향을 구하면, 플레이어가 hit된 지점을 바라보는 각도가 나온다
    FVector start = HitResults[(int32)EParkourArrowType::Center].ImpactPoint;
    FVector end = OwnerCharacter->GetActorLocation();
    float lookAt = UKismetMathLibrary::FindLookAtRotation(start, end).Yaw;

    // hit된 지점에 각
    FVector impactNormal = HitResults[(int32)EParkourArrowType::Center].ImpactNormal;
    // 절대 x공간 축을 기준으로 normal 각 구하기
    float impactAt = UKismetMathLibrary::MakeRotFromX(impactNormal).Yaw;

    float yaw = abs(abs(lookAt) - abs(impactAt)); // 위에 두개의 각차, 각차만 비교할것이므로 절대값 씌운다.

    // 수행할 수 있는 각도 안에 있나 판단, AvailableFrontAngle = 15
    CheckFalseResult(yaw <= AvailableFrontAngle, false);

    // 조건을 전부 패스했다면 수행
    return true;
}

void UCParkourComponent::DoParkour(bool bLanded)
{
    // 현재 수행중인 동작이 있다면 못하도록, Max가 수행중이 아닐때를 나타낸다  .
    CheckFalse(Type == EParkourType::Max);

    // 할 수 있는지 체크
    CheckFalse(Check_Obstacle());
    if (Check_ClimbMode())
    {
        DoParkour_Climb();

        return;
    }
}

void UCParkourComponent::End_DoParkour()
{
    // 현재 수행 중인 타입들 체크 후 실행, 끝이나면 다시 타입 돌려주기
    switch (Type)
    {
    case EParkourType::Climb:
        End_DoParkour_Climb();
        break;
    }

    Type = EParkourType::Max;
}

1. Check_Obstacle을 통해 한번에 파쿠르 동작을 수행할 수 있는지 없는지를 체크해준다.

2. 각 타입을 체크해주고, 동작 가능 여부를 체크

3. 각 체크를 통과했다면 그 동작을 실행하도록 설정.


#pragma once

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

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

private:
	bool Check_ClimbMode();
	void DoParkour_Climb();
	void End_DoParkour_Climb();

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

bool UCParkourComponent::Check_ClimbMode()
{
    CheckFalseResult(HitResults[(int32)EParkourArrowType::Ceil].bBlockingHit, false);

    // Map.Find가 값을 return 하는게 아닌 *를 return 하므로, const로 수정을 못하게 막아준다.
    // 그래서 아래에서 받아서 사용할때도 *를 붙여서 사용
    const TArray<FParkourData>* datas = DataMap.Find(EParkourType::Climb);
    CheckFalseResult((*datas)[0].MinDistance < HitDistance, false);
    CheckFalseResult((*datas)[0].MaxDistance > HitDistance, false);
    CheckFalseResult(FMath::IsNearlyEqual((*datas)[0].Extent, HitObstacleExtent.Z, 10), false);

    return true;
}

void UCParkourComponent::DoParkour_Climb()
{
    Type = EParkourType::Climb;

    // hit된 위치로 옮겨주고, 방향 바라보게 하기
    OwnerCharacter->SetActorLocation(HitResults[(int32)EParkourArrowType::Center].ImpactPoint);
    OwnerCharacter->SetActorRotation(FRotator(0, ToFrontYaw, 0));
    (*DataMap.Find(EParkourType::Climb))[0].PlayMontage(OwnerCharacter); // 파쿠르 동작 실행

    // 기어올라가야 하므로, 중력 꺼주기
    OwnerCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Flying);
}

void UCParkourComponent::End_DoParkour_Climb()
{
    // 착지하면 다시 Walk 모드로 변경
    OwnerCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}

1. Climb 동작 수행에 관하여 체크.

2. 동작에 필요한 요소인 Flying모드로 잠시 바꿔주고 다시 Walk로 돌려주기.


#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()

public:
	void Click_RightButton();

};
#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"

void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, this, &ACPlayer::Click_RightButton);
}

void ACPlayer::Click_RightButton()
{
	if (Weapon->IsUnarmedMode())
	{
		Parkour->DoParkour();

		return;
	}

	Weapon->SubAction_Pressed();

}

1. 오른쪽 누를때 동작이 수행될 수 있도록 모드를 체크해준다.


1. 노티파이를 하나 생성해준다.

2. End_DoParkour를 지정해줘야 Climb 동작이 끝난 후 공중에 안날라 다니게 할 수 있다. 

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "CAnimNotify_End_Parkour.generated.h"

UCLASS()
class U2212_06_API UCAnimNotify_End_Parkour : public UAnimNotify
{
	GENERATED_BODY()

public:
	FString GetNotifyName_Implementation() const override;

	virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;

};
#include "Notifies/CAnimNotify_End_Parkour.h"
#include "Global.h"
#include "Parkour/CParkourComponent.h"

FString UCAnimNotify_End_Parkour::GetNotifyName_Implementation() const
{
	return "Parkour";
}

void UCAnimNotify_End_Parkour::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
	Super::Notify(MeshComp, Animation);
	CheckNull(MeshComp);
	CheckNull(MeshComp->GetOwner());

	UCParkourComponent* parkour = CHelpers::GetComponent<UCParkourComponent>(MeshComp->GetOwner());
	CheckNull(parkour);

	parkour->End_DoParkour();
}

1. End_DoParkour를 콜해준다.


1. 사용할 몽타주 동작에 만들어 준 노티파이를 지정해준다.


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

 

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

UE4 Feet IK, Behavior, HealBar  (0) 2023.07.21
UE4 Parkour Final, Feet IK  (0) 2023.07.20
UE4 Parkour  (0) 2023.07.18
UE4 Bow Arrow  (0) 2023.07.13
UE4 Bow Aim Bend  (0) 2023.07.10
Comments