파쿠르 시스템은 캐릭터가 벽이나 장애물과 부딪힐 때의 처리를 통해 구현할 수 있다. 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 USceneComponentArrowGroup;
    • PROPERTY(VisibleDefaultsOnly)
      class UArrowComponentArrows[(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) {  }

     

     

     

    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