IronBird 개발일지 #7
BGM + 이펙트 + 하트 아이템 + 틸팅 + UMG 메인화면
UAudioComponent · SpawnEmitter · UMG · 카메라 고정 · 난이도 조정
🎯 목표
게임 완성도를 높인다. BGM과 효과음으로 타격감을 살리고, 하트 아이템과 난이도 조정으로 게임성을 추가한다. 카메라를 레벨에 고정해 갤러그 스타일 시점을 완성하고, UMG로 메인화면을 교체한다.
이번 작업의 핵심은 C++ 코드로 UPROPERTY 슬롯을 노출하고, 에디터에서 에셋을 할당하는 방식으로 분리한 것이다. 코드 수정 없이 에셋만 교체할 수 있어 유지보수가 편해진다.
🔧 구현 내용
https://youtube.com/shorts/lW6pr6lhA-8?feature=share IronBird 시연영상

1. BGM 시스템 설계
UAudioComponent로 루프 재생하고 상황에 따라 전환하는 방식이다. UPROPERTY로 슬롯을 노출해 코드 수정 없이 에셋만 교체할 수 있다.
BeginPlay → GameplayBGM 재생
SpawnBoss() → BossBGM 전환 (null이면 유지)
게임오버 → GameOverBGM 전환
보스 처치 → GameClearBGM 전환
// 발사음은 볼륨 30%로 BGM과 균형
UGameplayStatics::PlaySoundAtLocation(
this, ShootSound, GetActorLocation(), 0.3f);
설계 결정: 적 발사음은 의도적으로 제외했다. 적이 많아질수록 발사음이 겹쳐 BGM과 피격음이 묻히기 때문이다. 갤러그, 1945도 동일한 방식이다.
2. 카메라 고정 (갤러그 스타일)
기존 SpringArm + Camera 구조는 플레이어를 따라다녀서 화면이 같이 이동하는 문제가 있었다. 레벨에 CameraActor를 고정 배치하고 PlayerController에서 뷰를 전환하는 방식으로 해결했다.
SpringArm->TargetArmLength = 2000.0f;
// After: 레벨 고정 카메라로 뷰 전환
ACameraActor* FixedCam = Cast<ACameraActor>(
UGameplayStatics::GetActorOfClass(...));
SetViewTargetWithBlend(FixedCam, 0.0f);
3. 플레이어 틸팅
스켈레탈 메시 애니메이션 없이 코드로 구현했다. Y축 이동 방향에 따라 메시의 Roll 값을 조정해 기체가 자연스럽게 기우는 효과를 낸다.
float TiltAngle = FMath::Clamp(
(TargetLocation.Y - GetActorLocation().Y) * -0.05f,
-25.0f, 25.0f);
MeshComponent->SetRelativeRotation(
FRotator(0.0f, -90.0f, 90.0f + TiltAngle));
모바일 관점: StarSparrow는 스태틱 메시라 스켈레탈 애니메이션이 불가능하다. 코드 기반 틸팅은 별도 애니메이션 에셋 없이 구현 가능하고 모바일 성능 부담도 없다.
4. 나이아가라 전환
기존 Cascade 파티클 시스템(UParticleSystem)을 나이아가라(UNiagaraSystem)로 교체했다. UE5에서 Cascade는 레거시이며 나이아가라가 공식 파티클 시스템이다.
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(), ExplosionEffect, GetActorLocation());
// After: Niagara
UNiagaraFunctionLibrary::SpawnSystemAtLocation(
GetWorld(), ExplosionEffect, GetActorLocation());
Build.cs 주의: Niagara 사용 시 IronBird.Build.cs에 "Niagara" 모듈을 반드시 추가해야 한다. 누락 시 링크 에러 발생.
5. 점수 기반 하트 스폰
기존 타이머 기반 하트 스폰은 게임 진행과 무관하게 일정 시간마다 나왔다. 점수 기반으로 변경해 게임 흐름과 연동했다.
SetTimer(HeartTimerHandle, ..., FMath::RandRange(20.f, 30.f));
// After: 1000점마다 스폰
if (GS->GetScore() >= NextHeartScoreThreshold)
{
SpawnHeartItem();
NextHeartScoreThreshold += 1000;
}
6. BGM 루프 재생
UAudioComponent에 직접 루프 설정이 불안정해 OnAudioFinished 델리게이트로 반복 재생을 구현했다.
BGMComponent->OnAudioFinished.AddDynamic(
this, &AIronBirdGameMode::RestartBGM);
// RestartBGM: 처음부터 다시 재생
void AIronBirdGameMode::RestartBGM()
{
BGMComponent->Play(0.0f);
}
7. 타입별 점수 조정
🔥 트러블슈팅
문제 1. 메인메뉴 → 게임 진입 후 마우스 이동 불가
원인: 메인메뉴에서 설정한 FInputModeGameAndUI가 레벨 전환 후에도 유지됐다. Slate가 마우스 입력을 가로채 PlayerTick의 IsInputKeyDown이 false를 반환했다.
해결: AIronBirdPlayerController BeginPlay에서 FInputModeGameOnly로 명시적 초기화.
SetInputMode(GameInputMode);
문제 2. WBP_MainMenu MCP 자동 생성 실패
원인: UE5.7 Python API에서 Widget Blueprint의 WidgetTree와 이벤트 그래프 편집이 차단돼 있다. MCP로 레이아웃 자동 구성이 불가능했다.
해결: C++ 코드로 UMG 위젯을 뷰포트에 추가하는 부분만 자동화하고, 위젯 내부 디자인은 에디터에서 직접 구성했다.
AI 도구의 한계를 파악하고 수동 작업으로 전환한 것도 포트폴리오 소재가 된다. "AI가 할 수 있는 것과 없는 것을 판단할 수 있는 사람"이 목표이기 때문이다.
💡 배운 것
• UPROPERTY로 사운드/이펙트 슬롯을 노출하면 코드 수정 없이 에디터에서 에셋만 교체할 수 있다. 기획 변경에 유연한 구조다.
• FInputMode는 레벨 전환 후에도 유지된다. 레벨 시작 시 항상 명시적으로 초기화해야 한다.
• 스태틱 메시도 코드로 Roll 값을 조정하면 애니메이션 느낌을 낼 수 있다. 모바일에서 애니메이션 에셋 없이 구현하는 좋은 대안이다.
• 사운드 설계는 "무엇을 넣느냐"보다 "무엇을 뺄 것인가"가 중요하다. 적 발사음을 제외한 것이 전체 사운드 균형을 유지하는 데 핵심이었다.
• UE5.7 Python MCP API는 Widget Blueprint 내부 편집을 지원하지 않는다. AI 도구의 한계를 파악하고 빠르게 수동 작업으로 전환하는 판단이 중요하다.
📌 다음 작업
[IronBird #8] 드로우콜 최적화 (Before/After 핵심)
머티리얼 인스턴싱, 스태틱 메시 병합으로 드로우콜을 줄이고 수치를 기록한다. 이 시리즈에서 가장 임팩트 있는 포스팅이 될 예정이다.
'개인프로젝트 > IronBird' 카테고리의 다른 글
| [IronBird #9] Android 빌드 + 실기기 프로파일링 (Galaxy S23+) (0) | 2026.06.08 |
|---|---|
| [IronBird #8] 드로우콜 최적화 시도 - PC 프로파일링 결과 분석 (0) | 2026.06.05 |
| [IronBird #6] 메인화면 + 점수 시스템 + 게임오버/클리어 (0) | 2026.05.28 |
| [IronBird #5] 보스 페이즈 + 적 3종류 + 충돌 시스템 구현 (0) | 2026.05.28 |
| [IronBird #4] 적 스폰 시스템 구현 (0) | 2026.05.28 |