2026. 4. 22. 18:09ㆍUnreal
https://tjdgus123.tistory.com/51
< 언리얼 C++ > Pawn 클래스 3D캐릭터 만들기 ( 필수 기능 )
필수 구현 기능C++ Pawn 클래스 및 컴포넌트 구성[ ] Pawn 클래스 생성: Pawn을 상속받는 새로운 C++ 클래스를 생성합니다.[ ] 컴포넌트 추가: 아래 컴포넌트들을 Pawn 클래스에 추가합니다.CapsuleComponent (
tjdgus123.tistory.com
위 내용에서 추가 기능들을 추가해서 구현해보았다.
목차
- 상태 설계 - bIsFlying, bIsGrounded
- Roll 회전 & 상하 이동
- 모드 전환 시 회전 리셋
- LineTrace로 지면 감지
- Velocity 기반 중력 구현
- 에어컨트롤
- 겪었던 버그 모음
1. 상태 설계 - bIsFlying , bIsGrounded
이 부분에서 깨달은 점은 비행모드와 공중상태는 엄연히 다른 개념이라는 것이다.
bIsFlying으로 모든 함수를 처리하려 했으나, 걷기 모드에서 점프하거나 절벽에서 떨어지는 상황을 구분하려면 bIsGrounded가 별도로 필요했다.
| bIsFlying | bIsGrounded | 이동속도 | 중력 | Z입력 | Pitch |
| True | x | 40 % | 없음 | space / 좌측 Ctrl | Pawn 전체 |
| false | True | 100 % | Velocity = 0 | 무시 | SpringArm만 |
| false | false | 40 % | 누적 중 | 무시 | SpringArm만 |
핵심
- 에어 컨트롤 조건은 !bIsGrounded 이다. 공중에 있을 때는 항상 공기의 저항을 받아 속도가 느려져야 한다.
2. Roll 회전 & 상하 이동
6방향의 자유도를 위해 기존 X축, Y축 이동에서 두 가지 입력을 추가했다.
Roll은 Q, E로, 상하는 Space, 왼쪽 Ctrl로 이동하도록 구현했다.
IA는 Axis1D(float)로 구현했으며 IMC에서 음수 방향에 Negate를 붙여 +1 / -1값을 만든다.

// Yaw: 기존
float YawDelta = LookInput.X * LookSensitivity * DeltaTime;
AddActorLocalRotation(FRotator(0, YawDelta, 0));
// Roll: 추가 — FRotator(Pitch, Yaw, Roll) 순서 주의
if (bIsFlying)
{
float RollDelta = (RollInput * LookSensitivity * DeltaTime);
AddActorLocalRotation(FRotator(0.f, 0.f, RollDelta));
}
// 상하 이동: UpVector를 이용해 로컬 기준 수직 이동
float zInput = bIsFlying ? UpDownInput : 0.0f;
FVector MoveDir = (RightDir * MoveInput.Y)
+ (ForwardDir * MoveInput.X)
+ (UpDir * zInput);
zInput은 비행모드일 때만 상하 입력을 반영한다. 점프가 구현되지 않아서 걷기 모드에서는 space를 누르면 반응이 없어야 정상이다.
3. 모드 전환 시 회전 상태 리셋
걷기 모드에서 Pitch는 SpringArm에만 적용했지만, 비행모드에서는 Pawn 전체로 적용해서 실제 비행체처럼 구현했다.
모드를 전환할 때, Pawn이 돌아가 있는 상태로 있으면 카메라가 이중으로 기울어지거나, 걷기 모드로 돌아왔을 때 캐릭터가 돌아가있는 상태로 걸어 의도했던 내용대로 구현되지 않는다.
구현한 부분은 다음과 같다.
void AMyPawn::SwitchMode(const FInputActionValue& value)
{
bIsFlying = !bIsFlying;
if (bIsFlying)
{
// 비행 모드 진입: SpringArm 리셋
CurrentPitch = 0.0f;
SpringArm->SetRelativeRotation(FRotator::ZeroRotator);
}
else
{
// 걷기 모드 복귀: Yaw만 유지, Pitch/Roll 리셋
SetActorRotation(FRotator(0, GetActorRotation().Yaw, 0));
}
}
추가적으로 BindAction을 하면서 토글 액션을 지정할 때 Triggered로 지정했다가 살짝만 눌렀는데 10번이 넘게 입력되는 문제가 있었다.
그래서 토글 액션을 선언할 땐 ETriggerEvent::Started를 이용해서 선언해야 한다는 것을 알았다.
if (PlayerController->SwitchAction)
{
EnhancedInput->BindAction(
PlayerController->SwitchAction,
ETriggerEvent::Started,
this,
&AMyPawn::SwitchMode
);
}
4. LineTrace로 지면 감지
LineTrace는 투명한 레이저를 쏴서 무언가에 맞는지 확인하는 기능이다. Capsule 중심에서 아래로 쏴, 발 밑 GroundCheckDistance 이내에 무언가 있으면 지면으로 판단한다.


다음과 같이 구현했다.
여기서 꼭 들어가야 하는 부분은 두 가지가 있다.
첫째는 AddIgnoredActor(this)이다. 이걸 빠뜨리면 자기 Capsule에 먼저 맞아 항상 true가 반환된다.
두번째로는 DrawDebugLine은 개발 중 필수이다. 레이저가 제대로 닿는지 눈으로 확인하기 위해서 색상을 구분해서 계속 화면에서 보이도록 한다.
5. Velocity 기반 중력 구현
기존 이동은 AddActorWorldOffset으로 "즉시 이동"하는 방식이다.
중력은 시간이 지날수록 빨라지는 가속운동이기 때문에, 속도를 누적하는 FVector Velocity를 별도로 관리해야 한다.
// Tick 내부
bIsGrounded = CheckGrounded();
// 수평 이동 (기존) ...
if (!bIsFlying)
{
if (!bIsGrounded)
{
// 공중: 중력 누적 — 매 프레임 Velocity.Z 감소
Velocity.Z += Gravity * DeltaTime;
}
else if (Velocity.Z < 0)
{
// 착지 순간: 낙하 속도만 리셋 (점프 대비 조건 필요)
Velocity.Z = 0;
}
AddActorWorldOffset(FVector(0, 0, Velocity.Z * DeltaTime), true);
}
else
{
// 비행 모드 진입 시 누적 속도 초기화
Velocity = FVector::ZeroVector;
}
여기서 중간에 else if(Velocity.Z < 0)으로 구현한 부분이 있다.
왜 Velocity.Z < 0 으로 조건을 설정했냐면, 나중에 점프를 추가했을 때 점프하는 순간 양의 z속도까지 리셋되어 점프가 동작하지 않기 때문이다.
따라서 내려가는 중에만 리셋되는 것이 안전하고 꼬이지 않는다.
중력 구현
매 프레임마다 Velocity.z += -980 * DeltaTime이 누적되므로, 1초후 Velocity.Z = -980, 2초후 -1960 이런식으로 가속된다.
이 부분이 자유낙하의 수학적 정의와 일치한다.
6. 에어 컨트롤
이 부분은 지상일 때와 아닐 때의 변수를 다르게 입력하여 속도 배율을 적용하면 되는 간단한 부분이다.
float CurrentSpeed = bIsGrounded ? MoveSpeed : MoveSpeed * AirControlRatio;
AddActorWorldOffset(MoveDir * CurrentSpeed * DeltaTime, true);
7. 겪었던 버그 모음
1. 비행모드에서 마우스를 위로 올리니 아래를 봄
의도했던 바와 반대로 진행되어 이 부분을 수정했다.
원인은 PitchDelta = LookInput.Y에서 마이너스 부호를 누락해서 이러한 상황이 발생한 것이었다.
이 문제는 부호를 간단하게 마이너스를 붙여 -LookInput.Y로 변환하면 해결되는 간단한 문제였다. 언리얼은 Pitch가 음수이기 때문에 이 반전이 필요하다.
2. 모드 전환 후 카메라가 이중으로 기울어짐
원인 : 걷기 모드에서 누적된 CurrentPitch가 비행 모드 전환 후에도 SpringArm에 남아있었다.
해결 : SwitchMode에서 CurrentPitch = 0, SpringArm을 ZeroRotator로 리셋해서 모드가 변환되면 다시 기본 세팅으로 돌아오도록 했다.
3. 공중인지 판단이 되지 않고 계속 빨간 선
원인 : AddIgnoreActor(this)를 빼서 자기 자신에게 닿아 true가 반환되었다.
해결 : AddIgnoreActor(this)를 추가해 자기 자신에게 닿는 것은 무시하도록 파라미터를 추가했다.
4. F키를 눌러도 반응이 없음
원인 : ETriggerEvent::Triggered 사용으로 키를 짧게 눌러도 굉장히 많은 키가 입력되어 모드가 수시로 변환됨
해결 : ETriggerEvent::Started를 사용해서 토글모드로 변환해 한 번의 입력으로 여러 번 입력되지 않도록 수정함
시연영상
https://youtu.be/aZXFV0VQp8U?si=s7lyX0BjQgWPS8QW
'Unreal' 카테고리의 다른 글
| <언리얼 C++> RandRange와 VRandCone의 차이 (0) | 2026.05.07 |
|---|---|
| < UE5 > 레벨 진행 시스템 - GameStateClass (1) | 2026.04.28 |
| < 언리얼 C++ > Pawn 클래스 3D캐릭터 만들기 ( 필수 기능 ) (0) | 2026.04.20 |
| 언리얼 엔진 애니메이션 블루프린트 정리 (0) | 2026.04.16 |
| 리플렉션 / UBT / UHT / CDO 정리 (0) | 2026.04.15 |