[UE] 파쿠르 구현하기

파쿠르 시스템은 캐릭터가 벽이나 장애물과 부딪힐 때의 처리를 통해 구현할 수 있다. Unreal Engine에서는 충돌 처리를 위해 Collision Component와 Physics Engine을 사용할 수 있다. 이를 통해 캐릭터가 벽이나 장애물과 부딪힐 때 알맞은 반응을 하도록 설정할 수 있다. 오늘은 Foot IK를 C++ 코드로 구현할 것이다. 발에 가상 본을 만들고 이 가상 본을 사용하여 Trace로 바닥과 충돌을 검사한다. 양발의 높낮이 차이를 이 Trace 충돌값으로 알아내고 Foot IK 구현에 활용한다.
목차
Plugins |
||||
Weapon |
||||
Resource |
||||
Icon128.png weapon_thumbnail_icon.png |
||||
Source |
||||
Weapon | ||||
SWeaponCheckBoxes.h .cpp SWeaponDetailsView.h .cpp SWeaponDoActionData.h .cpp SWeaponEquipmentData.h .cpp SWeaponHitData.h .cpp SWeaponLeftArea.h .cpp Weapon.Build.cs WeaponAssetEditor.h .cpp WeaponAssetFactory.h .cpp WeaponCommand.h .cpp WeaponContextMenu.h .cpp WeaponModule.h .cpp WeaponStyle.h .cpp |
||||
Source | ||
U2212_06 | ||
Characters | ||
CAnimInstance.h .cpp CEnemy.h .cpp CPlayer.h .cpp ICharacter.h .cpp |
||
Components | ||
CMontagesComponent.h .cpp CMovementComponent.h .cpp CStateComponent.h .cpp CStatusComponent.h .cpp CWeaponComponent.h .cpp |
||
Notifies | ||
CAnimNotifyState_BeginAction.h .cpp CAnimNotifyState_BowString.h .cpp CAnimNotify_CameraShake.h .cpp CAnimNotifyState_EndAction.h .cpp CAnimNotify_EndState.h .cpp CAnimNotifyState.h .cpp CAnimNotifyState_CameraAnim.h .cpp CAnimNotifyState_Collision.h .cpp CAnimNotifyState_Combo.h .cpp CAnimNotifyState_Equip.h .cpp CAnimNotifyState_SubAction.h .cpp |
||
Parkour | ||
CParkourComponent.h .cpp 생성 | ||
Utilities | ||
CHelper.h CLog.h .cpp |
||
Weapons | ||
CArrow.h .cpp CAura.h .cpp CCamerModifier.h .cpp CGhostTrail.h .cpp CRotate_Object.h .cpp CThornObject.h .cpp CAnimInstance_Bow.h .cpp CAttachment_Bow.h .cpp CDoAction_Around.h .cpp CDoAction_Bow.h .cpp CDoAction_Combo.h .cpp CDoAction_Warp.h .cpp CSubAction_Around.h .cpp CSubAction_Bow.h .cpp CSubAction_Fist.h .cpp CSubAction_Hammer.h .cpp CSubAction_Sword.h .cpp CDoAction_Warp.h .cpp CAttachment.h .cpp CDoAction.h .cpp CEquipment.h .cpp CSubAction.h .cpp CWeaponAsset.h .cpp CWeaponStructures.h .cpp |
||
Global.h CGameMode.h .cpp U2212_06.Build.cs |
||
U2212_06.uproject | ||
파쿠르 구현하기
CParkourComponent 생성
새 C++ 클래스 - ActorComponent - CParkourComponent 생성


CParkourComponent .h
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "Kismet/KismetSystemLibrary.h"//DrawDebug를 포함하는 헤더 #include "Engine/DataTable.h"//DataTable를 다루는 헤더 #include "CParkourComponent.generated.h" //파쿠르를 위해 추적할 화살표 타입 UENUM(BlueprintType) enum class EParkourArrowType : uint8 { Center = 0, Ceil, Floor, Left, Right, Land, Max, }; //파쿠르 동작 타입 UENUM(BlueprintType) enum class EParkourType : uint8 { Climb = 0, Fall, Slide, Short, Normal, Wall, Max, }; USTRUCT(BlueprintType) struct FParkourData : public FTableRowBase { GENERATED_BODY() public: UPROPERTY(EditAnywhere) EParkourType Type; UPROPERTY(EditAnywhere) UAnimMontage* Montage;//파쿠르 타입에 따라 Play할 몽타주 UPROPERTY(EditAnywhere) float PlayRatio = 1;//Play 속도 UPROPERTY(EditAnywhere) FName SectionName;//몽타주에 Section을 주면 해당 Section부터 재생. 이를 위한 변수 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;//파쿠르 DataTable private: UPROPERTY(EditAnywhere, Category = "Trace") float TraceDistance = 600;//파쿠르 Line Trace가 적용되는 거리 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: TMap<EParkourType, TArray<FParkourData>> DataMap;//TMap에 Key와 Key를 넣으면 배열이 리턴된다. private: class ACharacter* OwnerCharacter; class UArrowComponent* Arrows[(int32)EParkourArrowType::Max]; FHitResult HitResults[(int32)EParkourArrowType::Max];//Arrows 마다 충돌 결과를 저장할 배열 변수 private: AActor* HitObstacle; FVector HitObstacleExtent; float HitDistance; float ToFrontYaw; };
CParkourComponent .cpp
#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'");//DataTable 생성. } void UCParkourComponent::BeginPlay() { Super::BeginPlay(); TArray<FParkourData*> datas;//FParkourData 사용하여 파쿠르 관련 변수들을 담는 구조체 사용. DataTable->GetAllRows<FParkourData>("", datas);//DataTable에서 데이터를 가져온다. for (int32 i = 0; i < (int32)EParkourType::Max; i++) { TArray<FParkourData> temp;//FParkourData 배열 temp 생성. for(FParkourData* data : datas)//datas를 순회하여 검색 { if (data->Type == (EParkourType)i)//데이터의 타입 == UENUM인 EParkourType 이라면 temp.Add(*data);//해당 파쿠르 타입을 temp에 담는다. DataMap.Add((EParkourType)i, temp);//DataMap에 Key에 파쿠르 타입을 숫자로, Value에 FParkourData 배열 temp를 담는다. } } OwnerCharacter = Cast<ACharacter>(GetOwner());//해당 클래스를 가지고 있는 GetOwner인 캐릭터(여기서는 Player) OwnerCharacter로 설정. USceneComponent* arrow = CHelpers::GetComponent<USceneComponent>(OwnerCharacter, "ArrowGroup");//Arrows를 가져온다(=OwnerCharacter의 ArrowGroup을 가져온다). TArray<USceneComponent*> components;//components 변수에 SceneComponent들(=Arrows)을 가져와 담는다. arrow->GetChildrenComponents(false, components); for (int32 i = 0; i < (int32)EParkourArrowType::Max; i++) Arrows[i] = Cast<UArrowComponent>(components[i]);//Arrows[] 배열 변수에 components인 Arrows를 담는다. } 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];//Trace할 화살을 가져온다. FLinearColor color = FLinearColor(arrow->ArrowColor); FTransform transform = arrow->GetComponentToWorld();//arrow의 위치를 가져온다. //위치를 활용하여 길이를 만든다. FVector start = transform.GetLocation(); FVector end = start + OwnerCharacter->GetActorForwardVector() * TraceDistance; TArray<AActor*> ignores;//Line Trace 시 무시할 것들을 담는 변수 ignores.Add(OwnerCharacter);//OwnerCharacter는 Line Trace에서 무시되게 한다. UKismetSystemLibrary::LineTraceSingle(GetWorld(), start, end, ETraceTypeQuery::TraceTypeQuery3, false, ignores, DebugType, HitResults[(int32)InType], true, color, FLinearColor::White);//TraceTypeQuery3는 새로 만든 TraceChannel인 Parkour다. } void UCParkourComponent::CheckTrace_Center() { EParkourArrowType type = EParkourArrowType::Center; LineTrace(type); const FHitResult& hitResult = HitResults[(int32)type]; CheckFalse(hitResult.bBlockingHit); //StaticMesh가 충돌체가 되므로 StaticMesh를 변수로 두고 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;//충돌체까지의 거리 //충돌체에 대한 Normal을 뒤집는다. Player는 밖에서 안을 바라보기 때문에 -로 값을 뒤집는다. 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 }



CPlayer
CPlayer.h
#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(VisibleAnywhere) class USpringArmComponent* SpringArm; UPROPERTY(VisibleAnywhere) class UCameraComponent* Camera; private: UPROPERTY(VisibleAnywhere) class UCWeaponComponent* Weapon; UPROPERTY(VisibleAnywhere) class UCMontagesComponent* Montages; UPROPERTY(VisibleAnywhere) class UCMovementComponent* Movement; UPROPERTY(VisibleAnywhere) class UCStateComponent* State; /** 파쿠르 */ private: UPROPERTY(VisibleDefaultsOnly) class USceneComponent* ArrowGroup;//파쿠르를 위한 ArrowGroup UPROPERTY(VisibleDefaultsOnly) class UArrowComponent* Arrows[(int32)EParkourArrowType::Max]; UPROPERTY(VisibleDefaultsOnly) class UCParkourComponent* Parkour; /** 파쿠르 */ public: ACPlayer(); protected: virtual void BeginPlay() override; public: virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; private: UFUNCTION() void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType); private: void OnAvoid(); private: void BackStep(); public: void End_BackStep() override;//ICharacter의 함수 오버라이드 };
변수 추가
- UPROPERTY(VisibleDefaultsOnly)
class USceneComponent* ArrowGroup; - PROPERTY(VisibleDefaultsOnly)
class UArrowComponent* Arrows[(int32)EParkourArrowType::Max]; - UPROPERTY(VisibleDefaultsOnly)
class UCParkourComponent* Parkour;
CPlayer.cpp
#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/CMontagesComponent.h" #include "Components/CMovementComponent.h" #include "Components/CWeaponComponent.h" #include "Components/ArrowComponent.h" ACPlayer::ACPlayer() { CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetMesh()); CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm); CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon"); CHelpers::CreateActorComponent<UCMontagesComponent>(this, &Montages, "Montages"); CHelpers::CreateActorComponent<UCMovementComponent>(this, &Movement, "Movement"); CHelpers::CreateActorComponent<UCStateComponent>(this, &State, "State"); CHelpers::CreateActorComponent<UCParkourComponent>(this, &Parkour, "Parkour"); GetMesh()->SetRelativeLocation(FVector(0, 0, -90)); GetMesh()->SetRelativeRotation(FRotator(0, -90, 0)); USkeletalMesh* mesh; CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'"); GetMesh()->SetSkeletalMesh(mesh); TSubclassOf<UCAnimInstance> animInstance; CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/ABP_Character.ABP_Character_C'"); GetMesh()->SetAnimClass(animInstance); SpringArm->SetRelativeLocation(FVector(0, 0, 140)); SpringArm->SetRelativeRotation(FRotator(0, 90, 0)); SpringArm->TargetArmLength = 200; SpringArm->bDoCollisionTest = false; SpringArm->bUsePawnControlRotation = true; SpringArm->bEnableCameraLag = true; GetCharacterMovement()->RotationRate = FRotator(0, 720, 0); CHelpers::CreateComponent<USceneComponent>(this, &ArrowGroup, "ArrowGroup", GetCapsuleComponent());//ArrowGroup을 생성하고 GetCapsuleComponent 아래로 붙여준다. //파쿠르에 활용한 Arrows들을 생성한다. for (int32 i = 0; i < (int32)EParkourArrowType::Max; i++) { FString name = StaticEnum<EParkourArrowType>()->GetNameStringByIndex(i); 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; } } } void ACPlayer::BeginPlay() { Super::BeginPlay(); Movement->OnRun(); //Movement의 기본을 Run으로 설정 Movement->DisableControlRotation();//Movement의 기본을 DisableControlRotation으로 설정 State->OnStateTypeChanged.AddDynamic(this, &ACPlayer::OnStateTypeChanged); } void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis("MoveForward", Movement, &UCMovementComponent::OnMoveForward); PlayerInputComponent->BindAxis("MoveRight", Movement, &UCMovementComponent::OnMoveRight); PlayerInputComponent->BindAxis("VerticalLook", Movement, &UCMovementComponent::OnVerticalLook); PlayerInputComponent->BindAxis("HorizontalLook", Movement, &UCMovementComponent::OnHorizontalLook); PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Pressed, Movement, &UCMovementComponent::OnSprint); PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Released, Movement, &UCMovementComponent::OnRun); PlayerInputComponent->BindAction("Avoid", EInputEvent::IE_Pressed, this, &ACPlayer::OnAvoid); PlayerInputComponent->BindAction("Fist", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetFistMode); PlayerInputComponent->BindAction("Sword", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetSwordMode); PlayerInputComponent->BindAction("Hammer", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetHammerMode); PlayerInputComponent->BindAction("Warp", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetWarpMode); PlayerInputComponent->BindAction("Around", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetAroundMode); PlayerInputComponent->BindAction("Bow", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetBowMode); PlayerInputComponent->BindAction("Action", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::DoAction); PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SubAction_Pressed); PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Released, Weapon, &UCWeaponComponent::SubAction_Released); } void ACPlayer::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType) { switch (InNewType) { case EStateType::BackStep: BackStep(); break; } } void ACPlayer::OnAvoid() { CheckFalse(State->IsIdleMode()); CheckFalse(Movement->CanMove()); CheckTrue(InputComponent->GetAxisValue("MoveForward") >= 0.0f);//뒷방향을 입력했다면 State->SetBackStepMode();//State을 BackStepMode로 변경한다. } void ACPlayer::BackStep() { Movement->EnableControlRotation();//정면을 바라본 상태로 뒤로 뛰어야하기 때문에 EnableControlRotation으로 만들어준다. Montages->PlayBackStepMode();//PlayBackStepMode()를 통해 몽타주 재생. } void ACPlayer::End_BackStep() { Movement->DisableControlRotation();//Backstep이 끝나면 원래대로 돌려준다. State->SetIdleMode();//Idle상태로 돌려줌. }
헤더 추가
- #include "Components/ArrowComponent.h"
ACPlayer::ACPlayer()
- ParkourComponent 생성
- CHelpers::CreateActorComponent<UCParkourComponent>(this, &Parkour, "Parkour");
- 파쿠르를 위해 앞쪽의 오브젝트를 검사할 Arrow들 생성
- CHelpers::CreateComponent<USceneComponent>(this, &ArrowGroup, "ArrowGroup", GetCapsuleComponent());
- for (int32 i = 0; i < (int32)EParkourArrowType::Max; i++)
- FString name = StaticEnum<EParkourArrowType>()->GetNameStringByIndex(i);
- CHelpers::CreateComponent<UArrowComponent>(this, &Arrows[i], FName(name), ArrowGroup);
- switch ((EParkourArrowType)i) { }
- for (int32 i = 0; i < (int32)EParkourArrowType::Max; i++)
- CHelpers::CreateComponent<USceneComponent>(this, &ArrowGroup, "ArrowGroup", GetCapsuleComponent());
BP_CPlayer

DT_Parkour




CSV 파일 임포트 - 위에서 코드로 만든 ParkourData - DT_Parkour 생성
프로젝트 세팅 - 콜리전


프로젝트 세팅 - 콜리전
- 새 트레이스 채널
- Parkour: 기본반응 Ignore로 생성
- 새 프로파일
- Parkour: 콜리전 켜짐 Collision Enabled(Query and Physics), 모두 블록
- Parkour Preset은 모두 블록 처리하고 나머지 Preset들은 Parkour Trace Channel을 무시하게 한다. 따라서 Parkour Preset으로 설정된 물체들만 블록 처리로 인식하게 된다. 이를 이용한 Trace한 결과값을 바탕으로 Parkour 할 오브젝트인지 아닌지를 판단한다.
파쿠르 장애물 StaticMesh의 콜리션 프리셋을 Parkour로 설정하기

실행화면

파쿠르로 설정한 물체들은 Block(블록) 처리하고 그 외의 물체들은 Ignore(무시) 처리한다.
Blueprint 구현 - 이전 글
2023.02.09 - [Unreal Engine/Unreal RPG Blueprint] - [Unreal] (2023.02.09) 29강 - Targeting, Parkour
2023.02.10 - [Unreal Engine/Unreal RPG Blueprint] - [Unreal] 30강 - Parkour
'⭐ Unreal Engine > UE RPG Skill' 카테고리의 다른 글
[UE] 파쿠르, Feet IK (0) | 2023.07.20 |
---|---|
[UE] 파쿠르: 벽 오르기 (0) | 2023.07.19 |
[UE] 화살 (0) | 2023.07.13 |
[UE] 활 시위에 손 맞추기 (0) | 2023.07.10 |
[UE] 활 시위 당기기 (0) | 2023.07.07 |
댓글
이 글 공유하기
다른 글
-
[UE] 파쿠르, Feet IK
[UE] 파쿠르, Feet IK
2023.07.20 -
[UE] 파쿠르: 벽 오르기
[UE] 파쿠르: 벽 오르기
2023.07.19 -
[UE] 화살
[UE] 화살
2023.07.13 -
[UE] 활 시위에 손 맞추기
[UE] 활 시위에 손 맞추기
2023.07.10
댓글을 사용할 수 없습니다.