개인프로젝트/IronBird

[IronBird #1] - 플레이어 이동 + 터치 입력 구현 - UE5 C++ 탑뷰 슈팅게임 - Claude Code 활용기

Client Side 2026. 4. 20. 00:21

IronBird 개발일지 #1

플레이어 이동 + 터치 입력 구현

C++ · APawn · APlayerController · 탑뷰 카메라

🎯 목표

Blueprint 없이 C++만으로 플레이어 기체의 터치 드래그 이동을 구현한다. 카메라는 위에서 내려다보는 탑뷰로 고정하고, 화면 좌표를 월드 좌표로 변환해 기체가 손가락을 따라오게 만든다.

AI로 클래스 구조 생성 → 내가 빌드 에러 원인 분석 → 내가 트러블슈팅 → 동작 확인

🔧 구현 내용

Blueprint 없이 C++ 3개 클래스로 구성했다.

클래스 역할
AIronBirdPawn 기체 메시 + 탑뷰 카메라 + 목표 위치 보간 이동
AIronBirdPlayerController 터치/마우스 입력 → 월드 좌표 변환 → Pawn 전달
AIronBirdGameMode DefaultPawnClass, PlayerControllerClass 등록

1. AIronBirdPawn

탑뷰 카메라는 SpringArm을 -90도 회전시켜 구현했다. Pitch/Yaw/Roll Inherit를 전부 false로 설정해 카메라가 회전하지 않고 고정되게 했다. 이동은 Tick에서 VInterpTo로 TargetLocation을 향해 부드럽게 보간한다.

// 탑뷰 카메라 설정
SpringArm->SetRelativeRotation(FRotator(-90.0f, 0.0f, 0.0f));
SpringArm->bInheritPitch = false;
SpringArm->bInheritYaw = false;
SpringArm->bInheritRoll = false;

// Tick에서 목표 위치로 보간 이동
FVector Next = FMath::VInterpTo(Current, TargetLocation, DeltaTime, MoveInterpSpeed);
SetActorLocation(Next);

// 이동 범위 Clamp (화면 밖 이탈 방지)
float ClampedX = FMath::Clamp(NewLocation.X, -BoundX, BoundX);
float ClampedY = FMath::Clamp(NewLocation.Y, -BoundY, BoundY);

모바일 관점 메모: Tick에서 매 프레임 SetActorLocation을 호출하는 구조다. 지금은 동작 우선이지만 나중에 입력이 없을 때 Tick을 비활성화하는 최적화가 필요하다.

2. AIronBirdPlayerController

핵심은 화면 좌표(픽셀)를 월드 좌표로 변환하는 것이다. DeprojectScreenPositionToWorld로 스크린 → 월드 레이를 생성한 뒤, Z=0 평면과의 교점을 구해 기체의 목표 위치로 사용한다.

// 스크린 → 월드 레이 생성
DeprojectScreenPositionToWorld(ScreenX, ScreenY, WorldLocation, WorldDirection);

// Z=0 평면과의 교점 계산
float T = -WorldLocation.Z / WorldDirection.Z;
FVector HitPoint = WorldLocation + WorldDirection * T;

// Pawn에 목표 위치 전달
IronBirdPawn->SetTargetLocation(HitPoint);

입력은 두 가지 경로로 처리한다. Android 실기기에서는 BindTouch가 처리하고, PC 에디터에서는 PlayerTick에서 마우스 입력을 직접 폴링한다. 둘 다 동일한 ApplyTouchToWorld를 호출한다.

 

IronBirdPawn을 마우스입력(터치)로 조작할 수 있게 구현하였다

🔥 트러블슈팅

문제 1. Build.cs include 경로 에러

Environment variable 'ModuleDirectory' is not defined

원인: Build.cs에서 MSBuild 문법 $(ModuleDirectory)를 사용. UBT는 C# 환경이라 이 문법을 모름.

해결: UBT C# 프로퍼티인 ModuleDirectory로 직접 변경.

- PublicIncludePaths.AddRange(new string[] { "$(ModuleDirectory)" });
+ PublicIncludePaths.AddRange(new string[] { ModuleDirectory });

문제 2. 마우스 클릭해도 기체가 안 움직임

원인: BindTouch는 실제 터치 이벤트만 수신. PC 에디터의 마우스 클릭은 터치 이벤트가 아님.

시도: bUseMouseForTouch = true → UE 5.7에서 직접 멤버 접근 불가. 컴파일 에러.

해결: PlayerTick에서 IsInputKeyDown + GetMousePosition으로 마우스 상태를 직접 폴링.

if (IsInputKeyDown(EKeys::LeftMouseButton))
{
    float MouseX, MouseY;
    if (GetMousePosition(MouseX, MouseY))
        ApplyTouchToWorld(MouseX, MouseY);
}

💡 배운 것

• Build.cs는 MSBuild가 아닌 UBT(C#) 환경. 문법이 다르다.

• BindTouch는 실제 터치 전용. PC 테스트는 별도 마우스 폴링이 필요하다.

• DeprojectScreenPositionToWorld로 스크린 → 월드 레이 변환 후 Z=0 평면 교점을 구하면 탑뷰 좌표를 얻을 수 있다.

• bUseMouseForTouch는 UE 5.7에서 변경됨. 버전별 API 확인 필수.

📌 다음 작업 / 개선할 것

지금 당장 개선 대상: Tick에서 매 프레임 SetActorLocation 호출 → 입력 없을 때 Tick 비활성화

다음 구현: 총알 발사 (Spawn/Destroy → 나중에 Object Pool로 교체 예정)

다음 구현: 배경 스크롤 (Panner 머티리얼)

나중에: 이동 범위 하드코딩 수치 → DataAsset으로 분리