[UE] 파쿠르, Feet IK

Feet IK(Inverse Kinematics)는 캐릭터의 발이 지면에 정확하게 위치하도록 하는 기술이다. 캐릭터 발의 위치를 조정할 수 있는 가상 본(virtual bone)을 생성한 후 사용한다. Animation Blueprint에 Two Bone IK을 사용하여 발의 위치를 정확하게 조정한다. 이를 위해 Foot IK 노드에는 목표 위치(Target Location)와 목표 회전(Target Rotation)을 입력하여 이 목표 위치와 회전은 발이 지면에 어떻게 위치해야 하는지를 정의한다.
목차
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 | ||
CFeetComponent.h .cpp 생성 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 CAnimNotify_End_Parkour 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.h
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: FORCEINLINE bool IsExecuting() { return Type != EParkourType::Max; } public: UCParkourComponent(); protected: virtual void BeginPlay() override; public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; private: void LineTrace(EParkourArrowType InType); private: //Arrow로 쏜 LineTrace를 체크하는 함수들 void CheckTrace_Center(); void CheckTrace_Ceil(); void CheckTrace_Floor(); void CheckTrace_LeftRight(); void CheckTrace_Land(); private: bool Check_Obstacle(); public: //파쿠르 수행 함수 void DoParkour(bool bLanded = false); void End_DoParkour(); private: bool Check_ClimbMode(); void DoParkour_Climb(); void End_DoParkour_Climb(); bool Check_FallMode(); void DoParkour_Fall(); void End_DoParkour_Fall(); bool Check_SlideMode(); void DoParkour_Slide(); void End_DoParkour_Slide(); //Short, Normal, Wall bool Check_ObstacleMode(EParkourType InType, FParkourData& OutData); void DoParkour_Obstacle(EParkourType InType, FParkourData& InData); void End_DoParkour_Obstacle(); 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; private: EParkourType Type = EParkourType::Max;//현재 수행중인 파쿠프 타입. 기본값을 Max로 설정하여 아무것도 하지 않는 타입을 기본값으로 만들어준다. private: bool bFalling; private: AActor* BackupObstacle;//장애물을 정보를 잠시 담는 변수 };
인라인 함수 추가
- FORCEINLINE bool IsExecuting() { return Type != EParkourType::Max; }
함수 추가
- void CheckTrace_Land();
내려오기 파쿠르 함수 추가
- bool Check_FallMode();
- void DoParkour_Fall();
- void End_DoParkour_Fall();
Slide 파쿠르 함수 추가
- bool Check_SlideMode();
- void DoParkour_Slide();
- void End_DoParkour_Slide();
Obstacle 파쿠르 함수 추가
- bool Check_ObstacleMode(EParkourType InType, FParkourData& OutData);
- void DoParkour_Obstacle(EParkourType InType, FParkourData& InData);
- void End_DoParkour_Obstacle();
변수 추가
- bool bFalling;
- 공중에서 떨어지는지 여부를 판단하는 bool 변수
- AActor* BackupObstacle;
- 장애물을 정보를 잠시 담는 변수
CParkourComponent.cpp
#include "Parkour/CParkourComponent.h" #include "Global.h" #include "GameFramework/Character.h" #include "GameFramework/CharacterMovementComponent.h" #include "Components/ArrowComponent.h" #include "Components/CMovementComponent.h" //#define LOG_UCParkourComponent void FParkourData::PlayMontage(class ACharacter* InCharacter) { //파쿠르 동작 중에는 FixedCamera를 켜서 카메라가 움직이지 않게 해준다. if (bFixedCamera) { UCMovementComponent* movement = CHelpers::GetComponent<UCMovementComponent>(InCharacter); if (!!movement) movement->EnableFixedCamera(); } 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); CheckTrace_Land();//아래로 쏘는 Land Arrow의 LineTrace는 매 Tick 검사해야 한다. } 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 } void UCParkourComponent::CheckTrace_Ceil() { LineTrace(EParkourArrowType::Ceil); } void UCParkourComponent::CheckTrace_Floor() { LineTrace(EParkourArrowType::Floor); } void UCParkourComponent::CheckTrace_LeftRight() { LineTrace(EParkourArrowType::Left); LineTrace(EParkourArrowType::Right); } void UCParkourComponent::CheckTrace_Land() { CheckFalse(OwnerCharacter->GetCharacterMovement()->IsFalling()); //최초 추락 CheckTrue(bFalling); bFalling = true; UArrowComponent* arrow = Arrows[(int32)EParkourArrowType::Land];//Land Arrow FLinearColor color = FLinearColor(arrow->ArrowColor); FTransform transform = arrow->GetComponentToWorld(); FVector start = transform.GetLocation();//Land Arrow 위치 const TArray<FParkourData>* datas = DataMap.Find(EParkourType::Fall); FVector end = start + transform.GetRotation().GetForwardVector() * (*datas)[0].Extent;//Land Arrow 위치에서 Extent만큼 늘어난 거리를 end 지점으로 설정. TArray<AActor*> ignores;//LineTrace를 무시할 Actor들을 위한 변수 ignores.Add(OwnerCharacter);//OwnerCharacter인 Player를 무시하도록 설정. UKismetSystemLibrary::LineTraceSingle(GetWorld(), start, end, ETraceTypeQuery::TraceTypeQuery1, false, ignores, DebugType, HitResults[(int32)EParkourArrowType::Land], true, color, FLinearColor::White); } bool UCParkourComponent::Check_Obstacle() { CheckNullResult(HitObstacle, false);//HitObstacle이 null이 아닌지 체크 //Arrow Center, Left, Right 모두 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); //Arrow Center, Left, Right의 Normal이 모두 같은 방향인지 체크. 모서리인지 아닌지 판단 FVector center = HitResults[(int32)EParkourArrowType::Center].Normal; FVector left = HitResults[(int32)EParkourArrowType::Left].Normal; FVector right = HitResults[(int32)EParkourArrowType::Right].Normal; CheckFalseResult(center.Equals(left), false);//Arrow center와 left값이 같은지 체크 CheckFalseResult(center.Equals(right), false);//Arrow center와 right값이 같은지 체크 //impactNormal과 player가 바라보는 사이의 각도를 구하여 AvailableFrontAngle 각도보다 작을 때 수행하게 만든다. FVector start = HitResults[(int32)EParkourArrowType::Center].ImpactPoint;//Hit된 지점 FVector end = OwnerCharacter->GetActorLocation();//Player의 위치 float lookAt = UKismetMathLibrary::FindLookAtRotation(start, end).Yaw;//Player가 Hit된 위치를 바라보는 방향의 Yaw값을 구한다. FVector impactNormal = HitResults[(int32)EParkourArrowType::Center].ImpactNormal;//Hit된 지점의 Normal float impactAt = UKismetMathLibrary::MakeRotFromX(impactNormal).Yaw;//Normal 방향벡터의 Yaw값 float yaw = abs(abs(lookAt) - abs(impactAt));//두 개의 Yaw값의 차이(=impactNormal과 player가 바라보는 사이의 각도) CheckFalseResult(yaw <= AvailableFrontAngle, false);//AvailableFrontAngle로 설정한 각 15도 이하라면 return true;//수행한다. } void UCParkourComponent::DoParkour(bool bLanded) { CheckFalse(Type == EParkourType::Max);//Max는 파쿠르가 수행중이 아닌 상황 if (bLanded && Check_FallMode()) { DoParkour_Fall(); return; } //값 초기화 HitObstacle = NULL; HitObstacleExtent = FVector::ZeroVector; HitDistance = 0; ToFrontYaw = 0; CheckTrace_Center(); if (!!HitObstacle)//HitObstacle이 있다면(=CheckTrace_Center의 hitResult가 있다면) { //나머지 Arrow들로도 LineTrace 검사한다. CheckTrace_Ceil(); CheckTrace_Floor(); CheckTrace_LeftRight(); } CheckFalse(Check_Obstacle());//장애물이 있는지 체크 if (Check_ClimbMode())//올라가기 파쿠르를 수행할 조건이 된다면 { DoParkour_Climb();//올라가기 파쿠르 수행 return; } if (Check_SlideMode())//슬라이드 파쿠르를 수행할 조건이 된다면 { DoParkour_Slide();//슬라이드 파쿠르 수행 return; } FParkourData data; if (Check_ObstacleMode(EParkourType::Normal, data))//Normal { DoParkour_Obstacle(EParkourType::Normal, data); return; } if (Check_ObstacleMode(EParkourType::Short, data))//Short { DoParkour_Obstacle(EParkourType::Short, data); return; } if (Check_ObstacleMode(EParkourType::Wall, data))//Wall { DoParkour_Obstacle(EParkourType::Wall, data); return; } } void UCParkourComponent::End_DoParkour() { switch (Type)//현재 수행중인 EParkourType { case EParkourType::Climb: End_DoParkour_Climb(); break; case EParkourType::Fall: End_DoParkour_Fall(); break; case EParkourType::Slide: End_DoParkour_Slide(); break; case EParkourType::Short: case EParkourType::Normal: case EParkourType::Wall: End_DoParkour_Obstacle(); break; } Type = EParkourType::Max;//EParkourType을 원래대로 돌려준다. //파쿠르가 끝난 후에 FixedCamera를 꺼준다. UCMovementComponent* movement = CHelpers::GetComponent<UCMovementComponent>(OwnerCharacter); if (!!movement) movement->DisableFixedCamera(); } bool UCParkourComponent::Check_ClimbMode() { CheckFalseResult(HitResults[(int32)EParkourArrowType::Ceil].bBlockingHit, false); const TArray<FParkourData>* datas = DataMap.Find(EParkourType::Climb);//Find는 값이 아닌 포인터를 리턴한다. const를 사용해서 고칠 수 없도록 만든다. CheckFalseResult((*datas)[0].MinDistance < HitDistance, false);//datas접근해서 0번의 MinDistance가 HitDistance보다 작은지 체크. CheckFalseResult((*datas)[0].MaxDistance > HitDistance, false);//datas접근해서 0번의 MaxDistance가 HitDistance보다 큰지 체크. CheckFalseResult(FMath::IsNearlyEqual((*datas)[0].Extent, HitObstacleExtent.Z, 10), false);//datas접근해서 0번의 Extent가 HitObstacleExtent.Z값과 같은지 체크. return true; } void UCParkourComponent::DoParkour_Climb() { Type = EParkourType::Climb;//EParkourType을 Climb으로 설정. OwnerCharacter->SetActorLocation(HitResults[(int32)EParkourArrowType::Center].ImpactPoint);//Player를 Arrow Center가 쏜 LineTrace에 Hit된 위치로 이동시킨다. OwnerCharacter->SetActorRotation(FRotator(0, ToFrontYaw, 0));//Player가 Hit된 방향을 바라보게 한다. (*DataMap.Find(EParkourType::Climb))[0].PlayMontage(OwnerCharacter);//Climb 몽타주를 재생 OwnerCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Flying);//기어올라가야 하므로 중력을 꺼줘야한다. 그래서 MovementMode를 MOVE_Flying로 변경한다. } void UCParkourComponent::End_DoParkour_Climb() { OwnerCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking); } bool UCParkourComponent::Check_FallMode() { CheckFalseResult(bFalling, false);//bFalling=false면 바로 return false; bFalling = false; float distance = HitResults[(int32)EParkourArrowType::Land].Distance; const TArray<FParkourData>* datas = DataMap.Find(EParkourType::Fall); //거리 판단 CheckFalseResult((*datas)[0].MinDistance < distance, false);//파쿠르가 수행될 최소거리<distance면 바로 return false; CheckFalseResult((*datas)[0].MaxDistance > distance, false);//파쿠르가 수행될 최대거리>distance면 바로 return false; return true; } void UCParkourComponent::DoParkour_Fall() { Type = EParkourType::Fall; (*DataMap.Find(EParkourType::Fall))[0].PlayMontage(OwnerCharacter); } void UCParkourComponent::End_DoParkour_Fall() { } bool UCParkourComponent::Check_SlideMode() { //충돌한게 슬라이드 거리 안에 들어와있는지 판단 CheckTrueResult(HitResults[(int32)EParkourArrowType::Floor].bBlockingHit, false); const TArray<FParkourData>* datas = DataMap.Find(EParkourType::Slide); CheckFalseResult((*datas)[0].MinDistance < HitDistance, false); CheckFalseResult((*datas)[0].MaxDistance > HitDistance, false); UArrowComponent* arrow = Arrows[(int32)EParkourArrowType::Floor]; FLinearColor color = FLinearColor(arrow->ArrowColor); FTransform transform = arrow->GetComponentToWorld(); FVector arrowLocation = transform.GetLocation();//Floor Arrow 위치 FVector ownerLocation = OwnerCharacter->GetActorLocation();//캐릭터의 위치 float extent = (*datas)[0].Extent;//DataTable의 Extent 정보로 넣어준다. float z = arrowLocation.Z + extent;//z = Floor Arrow의 z값 + extent 값 FVector forward = OwnerCharacter->GetActorForwardVector();//캐릭터의 전방벡터 forward = FVector(forward.X, forward.Y, 0);//캐릭터의 전방벡터에서 z값만 0으로 만든다. FVector start = FVector(arrowLocation.X, arrowLocation.Y, z);//start위치 = (Floor Arrow의 X, Y 위치값)과 (Floor Arrow의 z값 + extent 값) FVector end = start + forward * TraceDistance;//end위치 = start위치 + forward * TraceDistance TArray<AActor*> ignores; FHitResult hitResult; UKismetSystemLibrary::BoxTraceSingle(GetWorld(), start, end, FVector(0, extent, extent), OwnerCharacter->GetActorRotation(), ETraceTypeQuery::TraceTypeQuery1, false, ignores, DebugType, hitResult, true); CheckTrueResult(hitResult.bBlockingHit, false); return true; } void UCParkourComponent::DoParkour_Slide() { Type = EParkourType::Slide; OwnerCharacter->SetActorRotation(FRotator(0, ToFrontYaw, 0)); (*DataMap.Find(EParkourType::Slide))[0].PlayMontage(OwnerCharacter); BackupObstacle = HitObstacle; BackupObstacle->SetActorEnableCollision(false);//장애물의 Collision을 꺼준다. } void UCParkourComponent::End_DoParkour_Slide() { BackupObstacle->SetActorEnableCollision(true);//장애물의 Collision을 다시 켜준다. BackupObstacle = NULL; } bool UCParkourComponent::Check_ObstacleMode(EParkourType InType, FParkourData& OutData) { CheckTrueResult(HitResults[(int32)EParkourArrowType::Ceil].bBlockingHit, false);//Ceil Arrow의 LineTrace에 충돌된게 없다면 return false로 끝낸다. const TArray<FParkourData>* datas = DataMap.Find(InType);//InType의 데이터를 넣어준다. InType은 Short, Normal, Wall 중 하나일 것이다. for (int32 i = 0; i < (*datas).Num(); i++) { bool b = true; b &= (*datas)[i].MinDistance < HitDistance; b &= (*datas)[i].MaxDistance > HitDistance; b &= FMath::IsNearlyEqual((*datas)[i].Extent, HitObstacleExtent.Y, 10); OutData = (*datas)[i]; CheckTrueResult(b, true); } return false; } void UCParkourComponent::DoParkour_Obstacle(EParkourType InType, FParkourData& InData) { Type = InType;//Short, Normal, Wall OwnerCharacter->SetActorRotation(FRotator(0, ToFrontYaw, 0)); InData.PlayMontage(OwnerCharacter); BackupObstacle = HitObstacle; BackupObstacle->SetActorEnableCollision(false); } void UCParkourComponent::End_DoParkour_Obstacle() { BackupObstacle->SetActorEnableCollision(true); BackupObstacle = NULL; }
헤더 추가
- #include "Components/CMovementComponent.h"
함수 정의
- void UCParkourComponent::CheckTrace_Land();
내려오기 파쿠르 함수 정의
- bool UCParkourComponent::Check_FallMode();
- void UCParkourComponent::DoParkour_Fall();
- void UCParkourComponent::End_DoParkour_Fall();
Slide 파쿠르 함수 정의
- bool UCParkourComponent::Check_SlideMode();
- void UCParkourComponent::DoParkour_Slide();
- void UCParkourComponent::End_DoParkour_Slide();
Obstacle 파쿠르 함수 정
- bool UCParkourComponent:: Check_ObstacleMode(EParkourType InType, FParkourData& OutData);
- void UCParkourComponent::DoParkour_Obstacle(EParkourType InType, FParkourData& InData);
- void UCParkourComponent::End_DoParkour_Obstacle();
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의 함수 오버라이드 public: void Click_RightButton(); public: void Landed(const FHitResult& Hit) override;//BP의 OnLanded C++버젼. };
함수 추가
- void Landed(const FHitResult& Hit) override;
- 블루프린트의 OnLanded의 C++버젼이다. ACharacter에 virtual void Landed(const FHitResult& Hit)가 있다.
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_Pressed, this, &ACPlayer::Click_RightButton); 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상태로 돌려줌. } void ACPlayer::Click_RightButton() { if (Weapon->IsUnarmedMode()) { Parkour->DoParkour(); return; } Weapon->SubAction_Pressed(); } void ACPlayer::Landed(const FHitResult& Hit) { Parkour->DoParkour(true); }
void ACPlayer::Landed(const FHitResult& Hit)
- Parkour->DoParkour(true);
※ 참고: Character.h에 있는 Landed()와 OnLanded() 함수



Parkour 몽타주에 CAnimNotify_End_DoParkour 삽입


모든 파쿠르 몽타주 끝부분에 CAnimNotify_End_DoParkour를 삽입한다.
실행화면

Feet IK
참고
CFeetComponent 생성
새 C++ 클래스 - ActorComponent - CFeetComponent 생성


CFeetComponent.h
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "Kismet/KismetSystemLibrary.h" #include "CFeetComponent.generated.h" USTRUCT(BlueprintType) struct FFeetData { GENERATED_BODY() public: UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Feet") FVector LeftDistance; //X UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Feet") FVector RightDistance; //X UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Feet") FVector PelvisDistance; //Z UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Feet") FRotator LeftRotation; UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Feet") FRotator RightRotation; }; UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class U2212_06_API UCFeetComponent : public UActorComponent { GENERATED_BODY() private: UPROPERTY(EditAnywhere, Category = "Trace") TEnumAsByte<EDrawDebugTrace::Type> DrawDebug; UPROPERTY(EditAnywhere, Category = "Trace") float InterpSpeed = 50;//발이 움직일 때 보간될 때까지의 속도 UPROPERTY(EditAnywhere, Category = "Trace") float TraceDistance = 50;//땅까지의 Trace 간격 UPROPERTY(EditAnywhere, Category = "Trace") float OffsetDistance = 5;//땅에서 살짝 떠있는 값 UPROPERTY(EditAnywhere, Category = "Socket") FName LeftSocket = "Foot_L"; UPROPERTY(EditAnywhere, Category = "Socket") FName RightSocket = "Foot_R"; public: FORCEINLINE FFeetData GetData() { return Data; } public: UCFeetComponent(); protected: virtual void BeginPlay() override; public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; private: void Trace(FName InName, float& OutDistance, FRotator& OutRotation); private: class ACharacter* OwnerCharacter; FFeetData Data; };
CFeetComponent.cpp
#include "Components/CFeetComponent.h" #include "Global.h" #include "GameFramework/Character.h" #include "Components/SkeletalMeshComponent.h" #include "Components/CapsuleComponent.h" #define LOG_UCFeetComponent UCFeetComponent::UCFeetComponent() { PrimaryComponentTick.bCanEverTick = true; } void UCFeetComponent::BeginPlay() { Super::BeginPlay(); OwnerCharacter = Cast<ACharacter>(GetOwner()); } void UCFeetComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); float leftDistance, rightDistance; FRotator leftRotation, rightRotation; Trace(LeftSocket, leftDistance, leftRotation); Trace(RightSocket, rightDistance, rightRotation); float offset = FMath::Min(leftDistance, rightDistance); Data.PelvisDistance.Z = UKismetMathLibrary::FInterpTo(Data.PelvisDistance.Z, offset, DeltaTime, InterpSpeed); Data.LeftDistance.X = UKismetMathLibrary::FInterpTo(Data.LeftDistance.X, (leftDistance - offset), DeltaTime, InterpSpeed); Data.RightDistance.X = UKismetMathLibrary::FInterpTo(Data.RightDistance.X, -(rightDistance - offset), DeltaTime, InterpSpeed); Data.LeftRotation = UKismetMathLibrary::RInterpTo(Data.LeftRotation, leftRotation, DeltaTime, InterpSpeed); Data.RightRotation = UKismetMathLibrary::RInterpTo(Data.RightRotation, rightRotation, DeltaTime, InterpSpeed); #ifdef LOG_UCFeetComponent CLog::Print(Data.LeftDistance, 11); CLog::Print(Data.RightDistance, 12); CLog::Print(Data.PelvisDistance, 13); CLog::Print(Data.LeftRotation, 14); CLog::Print(Data.RightRotation, 15); #endif } void UCFeetComponent::Trace(FName InName, float& OutDistance, FRotator& OutRotation) { FVector socket = OwnerCharacter->GetMesh()->GetSocketLocation(InName);//socket 위치(World 좌표) float z = OwnerCharacter->GetActorLocation().Z;//캐릭터의 위치 z값 FVector start = FVector(socket.X, socket.Y, z);//추적을 시작할 start 지점 z = start.Z - OwnerCharacter->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() - TraceDistance; FVector end = FVector(socket.X, socket.Y, z); TArray<AActor*> ignores; ignores.Add(OwnerCharacter); FHitResult hitResult; UKismetSystemLibrary::LineTraceSingle(GetWorld(), start, end, ETraceTypeQuery::TraceTypeQuery1, true, ignores, DrawDebug, hitResult, true, FLinearColor::Green, FLinearColor::Red);//TraceTypeQuery1은 Visibility(=보이는 모두 다 충돌). //값 초기화 OutDistance = 0; OutRotation = FRotator::ZeroRotator; CheckFalse(hitResult.bBlockingHit);//충돌이 안 일어나면 return false; float length = (hitResult.ImpactPoint - hitResult.TraceEnd).Size(); OutDistance = length + OffsetDistance - TraceDistance; float roll = UKismetMathLibrary::DegAtan2(hitResult.Normal.Y, hitResult.Normal.Z); float pitch = -UKismetMathLibrary::DegAtan2(hitResult.Normal.X, hitResult.Normal.Z); OutRotation = FRotator(pitch, 0, roll); }
BP_CPlayer


CAnimInstance
CAnimInstance.h
#pragma once #include "CoreMinimal.h" #include "Animation/AnimInstance.h" #include "Components/CWeaponComponent.h" #include "Components/CFeetComponent.h" #include "CAnimInstance.generated.h" UCLASS() class U2212_06_API UCAnimInstance : public UAnimInstance { GENERATED_BODY() protected: UPROPERTY(BlueprintReadOnly, Category = "Animation") float Speed; UPROPERTY(BlueprintReadOnly, Category = "Animation") float Pitch; UPROPERTY(BlueprintReadOnly, Category = "Animation") float Direction; UPROPERTY(BlueprintReadOnly, Category = "Animation") bool bBow_Aiming; //Feet IK UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "InverseKinemetics") bool bFeet; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "InverseKinemetics") FFeetData FeetData; protected: UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation") EWeaponType WeaponType = EWeaponType::Max; public: void NativeBeginPlay() override; void NativeUpdateAnimation(float DeltaSeconds) override; private: UFUNCTION() void OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType); private: class ACharacter* OwnerCharacter; class UCWeaponComponent* Weapon; private: FRotator PrevRotation; };
헤더 추가
- #include "Components/CFeetComponent.h"
변수 추가
- UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "InverseKinemetics")
bool bFeet; - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "InverseKinemetics")
FFeetData FeetData;
CAnimInstance.cpp
#include "Characters/CAnimInstance.h" #include "Global.h" #include "GameFramework/Character.h" #include "Weapons/CSubAction.h" #include "Parkour/CParkourComponent.h" #include "Components/CFeetComponent.h" void UCAnimInstance::NativeBeginPlay() { Super::NativeBeginPlay(); OwnerCharacter = Cast<ACharacter>(TryGetPawnOwner()); CheckNull(OwnerCharacter); Weapon = CHelpers::GetComponent<UCWeaponComponent>(OwnerCharacter); if (!!Weapon) Weapon->OnWeaponTypeChange.AddDynamic(this, &UCAnimInstance::OnWeaponTypeChanged); } void UCAnimInstance::NativeUpdateAnimation(float DeltaSeconds) { Super::NativeUpdateAnimation(DeltaSeconds); CheckNull(OwnerCharacter); Speed = OwnerCharacter->GetVelocity().Size2D(); FRotator rotator = OwnerCharacter->GetVelocity().ToOrientationRotator(); FRotator rotator2 = OwnerCharacter->GetControlRotation(); FRotator delta = UKismetMathLibrary::NormalizedDeltaRotator(rotator, rotator2); PrevRotation = UKismetMathLibrary::RInterpTo(PrevRotation, delta, DeltaSeconds, 25); Direction = PrevRotation.Yaw; Pitch = UKismetMathLibrary::FInterpTo(Pitch, OwnerCharacter->GetBaseAimRotation().Pitch, DeltaSeconds, 25); CheckNull(Weapon);//무기가 있는지 확인 /** 파쿠르*/ UCParkourComponent* parkour = CHelpers::GetComponent<UCParkourComponent>(OwnerCharacter); UCFeetComponent* feet = CHelpers::GetComponent<UCFeetComponent>(OwnerCharacter); bFeet = false; if(Weapon->IsUnarmedMode()) { if (!!parkour && !!feet) { bFeet = parkour->IsExecuting() == false;//EParkourType::Max라면 false==false가 되어 bFeet은 true, EParkourType::Max가 아닌 상황이라면 true==false가 되어 bFeet은 false. FeetData = feet->GetData();//FFeetData를 넣어준다. } else if (!!feet) { bFeet = true; FeetData = feet->GetData(); } } /** 파쿠르*/ /** 활*/ if (!!Weapon->GetSubAction()) { bBow_Aiming = true; bBow_Aiming &= WeaponType == EWeaponType::Bow; bBow_Aiming &= Weapon->GetSubAction()->GetInAction(); } /** 활*/ } void UCAnimInstance::OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType) { WeaponType = InNewType; }
헤더 추가
- #include "Parkour/CParkourComponent.h"
- #include "Components/CFeetComponent.h"
void UCAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
- UCParkourComponent* parkour = CHelpers::GetComponent<UCParkourComponent>(OwnerCharacter);
UCFeetComponent* feet = CHelpers::GetComponent<UCFeetComponent>(OwnerCharacter);
bFeet = false;
if (!!parkour && !!feet)
{
bFeet = parkour->IsExecuting() == false;
FeetData = feet->GetData();
}
else if (!!feet)
{
bFeet = true;
FeetData = feet->GetData();
}
실행화면

'⭐ Unreal Engine > UE RPG Skill' 카테고리의 다른 글
[UE] Feet IK, 마우스 Zoom, Enemy AI (0) | 2023.07.21 |
---|---|
[UE] 파쿠르: 벽 오르기 (0) | 2023.07.19 |
[UE] 파쿠르 구현하기 (0) | 2023.07.18 |
[UE] 화살 (0) | 2023.07.13 |
[UE] 활 시위에 손 맞추기 (0) | 2023.07.10 |
댓글
이 글 공유하기
다른 글
-
[UE] Feet IK, 마우스 Zoom, Enemy AI
[UE] Feet IK, 마우스 Zoom, Enemy AI
2023.07.21 -
[UE] 파쿠르: 벽 오르기
[UE] 파쿠르: 벽 오르기
2023.07.19 -
[UE] 파쿠르 구현하기
[UE] 파쿠르 구현하기
2023.07.18 -
[UE] 화살
[UE] 화살
2023.07.13
댓글을 사용할 수 없습니다.