4 분 소요

Unreal/C++ 과제 7 - 회전 발판과 움직이는 장애물 퍼즐 스테이지

과제 소개 및 목표

  • Pawn 클래스 구조 이해
    • 언리얼 엔진에서 Pawn은 PlayerController가 조종할 수 있는 최소 단위
    • CharacterMovementComponent 없이 직접 이동 로직을 구현
  • Enhanced Input & 3인칭 카메라
    • Enhanced Input 액션을 생성하여 키보드와 마우스 입력을 처리
    • SpringArmComponent 및 CameraComponent를 사용해 3인칭 시점을 구현, 마우스 움직임으로 카메라를 회전
  • 직접 이동 로직 구현
    • AddActorLocalOffset, AddActorLocalRotation 등을 활용하여 WASD마우스 입력에 따라 Pawn을 조작

이 과제 중 필수 과제인 다음을 구현하였다

구현 기능


  • Pawn 클래스 생성 및 CapsuleComponent를 Root로
  • Skeltal Mesh, SpringArm, Camera 컴포넌트 생성 후 부착
  • GameMode에서 DefaultPawnClass 지정
  • Physics 설정 false
  • Enhanced Input 매핑 & 바인딩 (Move & Look)
  • 입력에 따른 ‘이동’ 과 ‘회전’ 구현
    (Controller 기반 함수 대신 AddActorLocalOffset, SetRelativeRotation 사용)

클래스 설명

  • 클래스
TaskCharacter (APawn 상속)
- SetupPlayerInputComponent를 통해 
  Move, Look 에 대한 Input Bind & Function 구현
- 매핑에 따른 값을 통해 이동과 회전 구현
- AddActorLocalOffset, AddActorLocalRotation , SetRelativeRotation 사용

TaskPlayerController
- InputAction을 매개변수로 가지고,
  Player에게 IMC를 연결

TaskGameMode
- DefaultPawn 및 PlayerController 세팅용 게임 모드

트러블 슈팅 - 회전이 제대로 먹히지 않는 문제

Look 을 구현하던 중 다음과 같은 문제가 발생하였다

Image

카메라는 회전하지 않고,
Mesh만 자기 멋대로 회전하는 상황이였다

  • 나는 Look에서 Rotator 값을 받고
    Tick에서 Rotate에 해당하는 Rotate를 적용하는 방식을 사용중이었다
// Tick()
AddActorLocalRotation(FRotator(RotateR.Pitch * RotateSpeed * DeltaTime,RotateR.Yaw * RotateSpeed * DeltaTime,0.0));

그런데 생각해보니
SpringArm에 bUsePawnControlRotation = true를 걸어두고
‘액터 전체’를 회전시키는 방식은

내가 원하던 방식의 완벽한 반대였다…

  • CapsuleComponent가 Root 이므로 전체적인 회전을 걸어
    Mesh도 회전됨
    그런데 SpringArm은 Controller의 회전에만 영향을 받으므로
    해당 회전은 적용되지 않음

그렇기에 곰곰히 생각해본 결과
다음과 같은 생각을 할 수 있었다

  • 먼저 우리는 지금 컨트롤러를 사용하지 않으니
    해당 옵션은 끄기

  • Yaw 회전(좌우)은 ‘전체적’으로 적용하고
    Pitch 회전(상하)은 ‘Spring Arm’에만 적용하기

  • 또한 Pitch 회전의 경우 카메라가 일정 범위에 도달할 경우
    ‘제한’하기
    (상하가 반대로 되버리는 것은 의도치 않았기에)

  • 마지막으로 이전 과제처럼 내가 바라보는 방향으로 이동하기를 원하였다

// 생성자
SpringArmComp->bUsePawnControlRotation = false;

...

// Tick
void ATaskCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (MoveVec.Size() > 0)
	{
		FVector TempMoveVec = MoveVec.GetSafeNormal() * MoveSpeed * DeltaTime;

		AddActorLocalOffset(TempMoveVec);
		MoveVec = FVector::Zero();
	}

	if (FMath::IsNearlyZero(RotateR.Yaw) == false)
	{
		AddActorLocalRotation(FRotator(0.0,RotateR.Yaw * RotateSpeed * DeltaTime,0.0));
	}

	if (FMath::IsNearlyZero(RotateR.Pitch) == false)
	{
		FRotator SpringRot = SpringArmComp->GetRelativeRotation() + FRotator(RotateR.Pitch * RotateSpeed * DeltaTime,0.0 , 0.0);
		SpringRot.Pitch = FMath::Clamp(SpringRot.Pitch, -40.0, 60.0);

		SpringArmComp->SetRelativeRotation(SpringRot);
	}

	RotateR = FRotator::ZeroRotator;
}

// Move & Look
void ATaskCharacter::Move(const FInputActionValue& value)
{
	if (Controller == nullptr)
		return;

	const FVector2D& MoveValue = value.Get<FVector2D>();
	const float YValue = SpringArmComp->GetRelativeRotation().Yaw;
	const FRotator YawRot(0.f, YValue, 0.f);

	if (FMath::IsNearlyZero(MoveValue.X) == false)
	{
		const FVector Forward = FRotationMatrix(YawRot).GetUnitAxis(EAxis::X);
		MoveVec += (Forward * MoveValue.X);
	}

	if (FMath::IsNearlyZero(MoveValue.Y) == false)
	{
		const FVector Right = FRotationMatrix(YawRot).GetUnitAxis(EAxis::Y);
		MoveVec += (Right * MoveValue.Y);
	}
}

void ATaskCharacter::Look(const FInputActionValue& value)
{
	const FVector2D& LookValue = value.Get<FVector2D>();
	RotateR = FRotator(-LookValue.Y, LookValue.X, 0.0);
}

결과

Image

원하는대로 구현되었다!

Animation BP 쪽에서 GetVelocity로 속도를 받고 있었기에
애니메이션이 정확히 나오지는 않지만
그래도 꽤 만족스러운 결과이다

TMI : Movement Component

특정한 ‘Scene Component’를 매 프레임 이동시켜주는 것들의 ‘기반 컴포넌트’

이것을 Pawn/Character에 특화시킨 것이

  • Pawn/Character MovementComponent

계층도

UActorComponent
└─ USceneComponent
   └─ UMovementComponent             // 모든 이동 컴포넌트의 베이스
      ├─ UNavMovementComponent       // AI 네비게이션 추가 지원
      │   └─ UPawnMovementComponent  // Pawn 전용 입력 처리, 네트워크 예측
      │       └─ UCharacterMovementComponent // 걷기, 점프, 중력 등 고급 기능
  • UMovementComponent
항목 설명
UpdatedComponent 실제로 움직일 SceneComponent(예: 캡슐 콜리전).
Velocity 이동 속도 벡터 (cm/s).
MoveUpdatedComponent() / SafeMoveUpdatedComponent() 위치·회전을 갱신, 충돌 검사 및 슬라이딩 처리.
TickComponent() 매 프레임 Velocity 기반으로 UpdatedComponent를 이동.
AddInputVector() (PawnMovement에서 확장) 입력 벡터 누적은 지원하지 않음—기본 베이스는 단순 이동만.

기본적인 ‘물체 이동’ 기능을 담당

  • UNavMovementComponent
    • 확장 컴포넌트로서, 네비게이션의 데이터와 연동됨
    • AI 네비게이션 관련 플래그 등이 추가
    • AI Controller 가 경로를 따라 이동시킬 때도 사용
  • UPawnMovementComponent
    • Pawn 전용 이동 컴포넌트
    • PawnOwner 포인터를 가지며, AddInputVector 로 입력 누적 가능
    • 네트워크 예측/ 입력 누적 처리 등의 기능 포함
    • 여기서 파생시켜 탑다운 이동을 만들 수 있다 함
  • UCharacterMovementComponent
    • PawnMovementComponent를 크게 확장하여 캐릭터 게임 플레이 전용 기능 제공
      • 걷기,달리기, 점프, 중력, 궁중 제어 기능, 충돌, 경사/슬라이딩
      • 네트워크 이동 보정 (서버 - 클라 예측)
      • 땅과의 접속 여부, 수영 , 비행 등 MovementMode 존재
    • Character 클래스가 기본적으로 해당 기능을 생성하여 사용함

동작 흐름

[입력 단계]
┌───────────────────────────────────────┐
│  PlayerController / AIController      │
│   ├─ AddControllerYaw/PitchInput()    │  → ControlRotation 갱신 (시점)
│   └─ AddMovementInput(Direction,Scale)│  → 입력 벡터 누적
└───────────────────────────────────────┘
                    │
                    ▼
[Pawn / Character]
┌───────────────────────────────────────┐
│ APawn / ACharacter                    │
│   ├─ bUseControllerRotationYaw?       │ → 몸체 회전 여부 결정
│   └─ MovementComponent (참조)          │
└───────────────────────────────────────┘
                    │
                    ▼
[Movement Component 계층]
┌──────────────────────────────────────────────────────────┐
│ UMovementComponent (베이스)                               │
│   ├─ UNavMovementComponent (AI 네비게이션 지원)            │
│   │   └─ UPawnMovementComponent (Pawn 전용 입력/예측)      │
│   │       └─ UCharacterMovementComponent (걷기/점프/중력)  │
└──────────────────────────────────────────────────────────┘
                    │
  ConsumeInputVector │ (입력 소모)
          계산       ▼
      ┌─────────────────────────────┐
      │ Velocity = f(입력, 가속/감속) │
      └─────────────────────────────┘
                    │
                    ▼
[위치/충돌 처리]
┌────────────────────────────────────────────┐
│ SafeMoveUpdatedComponent()                 │
│  ├─ 충돌 검사 & 슬라이드(SlideAlongSurface)   │
│  └─ UpdatedComponent 위치/회전 갱신          │
└────────────────────────────────────────────┘
                    │
                    ▼
[Actor Transform 업데이트]
┌────────────────────────────────────────────┐
│ Pawn/Character Transform 변경              │
│   ├─ Mesh · SpringArm 등 자식도 이동        │
│   └─ AnimBP: GetVelocity()로 속도 참조      │
└────────────────────────────────────────────┘

댓글남기기