Unreal/C++ 과제 7 - 필수 구현
Unreal/C++ 과제 7 - 회전 발판과 움직이는 장애물 퍼즐 스테이지
과제 소개 및 목표
- Pawn 클래스 구조 이해
- 언리얼 엔진에서 Pawn은 PlayerController가 조종할 수 있는 최소 단위
- CharacterMovementComponent 없이 직접 이동 로직을 구현
- 언리얼 엔진에서 Pawn은 PlayerController가 조종할 수 있는 최소 단위
- Enhanced Input & 3인칭 카메라
- Enhanced Input 액션을 생성하여 키보드와 마우스 입력을 처리
- SpringArmComponent 및 CameraComponent를 사용해 3인칭 시점을 구현, 마우스 움직임으로 카메라를 회전
- Enhanced Input 액션을 생성하여 키보드와 마우스 입력을 처리
- 직접 이동 로직 구현
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 을 구현하던 중 다음과 같은 문제가 발생하였다
카메라는 회전하지 않고,
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);
}
결과
원하는대로 구현되었다!
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 로 입력 누적 가능
- 네트워크 예측/ 입력 누적 처리 등의 기능 포함
- 여기서 파생시켜 탑다운 이동을 만들 수 있다 함
- Pawn 전용 이동 컴포넌트
- UCharacterMovementComponent
- PawnMovementComponent를 크게 확장하여 캐릭터 게임 플레이 전용 기능 제공
- 걷기,달리기, 점프, 중력, 궁중 제어 기능, 충돌, 경사/슬라이딩
- 네트워크 이동 보정 (서버 - 클라 예측)
- 땅과의 접속 여부, 수영 , 비행 등 MovementMode 존재
- 걷기,달리기, 점프, 중력, 궁중 제어 기능, 충돌, 경사/슬라이딩
- Character 클래스가 기본적으로 해당 기능을 생성하여 사용함
- PawnMovementComponent를 크게 확장하여 캐릭터 게임 플레이 전용 기능 제공
동작 흐름
[입력 단계]
┌───────────────────────────────────────┐
│ 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()로 속도 참조 │
└────────────────────────────────────────────┘
댓글남기기