
프로젝트 개요
| 프로젝트명 | SagoMagic (Co-op 탑다운 타워디펜스) |
| 엔진 | Unreal Engine 5.6, C++ / Blueprint |
| 네트워크 | Dedicated Server, Seamless Travel |
| 핵심 기술 | GAS, GMS, 그리드 건축, 격자형 인벤토리, Fragment 패턴 |
| 팀 규모 | 7인 ( 곽은서, 김현, 박원종, 이준로, 임영택, 주철민, 허태린 ) |
| 개발 기간 | 4 / 1 ~ 4 / 24 |
게임 루프는 로비 → 정비 단계(BuildState) → 웨이브 전투(CombatState) → 결과 화면(ResultState)의 사이클을 반복하며 베이스캠프(ASMBaseCampActor)의 HP가 0이 되면 패배, 마지막 웨이브를 클리어하면 승리한다.4인 Dedicated Server 구조로 운영되며 맵 전환은 Seamless Travel(L_Transition 경유)로 처리했다.
Keep - 잘 된 것, 유지할 것
1. GAS 기반 전투·건축 시스템의 통합 설계

이번 프로젝트에서 가장 큰 기술적 성과는 GAS를 단순히 "스킬 시스템"으로 국한하지 않고, 전투·건축·쿨타임·상태 관리를 아우르는 프로젝트 전체의 공통 인프라로 정착시킨 것이다.
어빌리티 시스템 컴포넌트(SMAbilitySystemComponent)는 플레이어, 몬스터, 건물, 베이스캠프 각각에 독립적인 AttributeSet을 붙여 관리한다. USMPlayerAttributeSet은 플레이어의 HP와 이동 속도를, USMMonsterAttributeSet은 몬스터 스탯을, USMBuildingAttributeSet은 건물 내구도를, USMBaseCampAttributeSet은 베이스캠프 HP를 전담한다. 이처럼 어트리뷰트를 객체 타입별로 분리함으로써 각 객체의 상태 변화가 다른 객체에 간섭하지 않으며, GE(GameplayEffect)의 적용 대상을 태그 기반으로 명확히 구분할 수 있었다.
스킬 실행 흐름은 다음과 같이 정형화되어 있다. 마우스 클릭이 TryActivateAbilitiesByTag()를 호출하면GA_SkillBase::ActivateAbility()가 실행되고, 먼저 인벤토리에서 FSMCompiledSkillSummary를 로드해 최종 수치(피해량, 쿨타임, 범위, 지속시간, 업그레이드 태그)를 확보한다. 이후 CommitAbility()로 GE_SkillCooldown을 적용하고, 몽타주를 재생하여 AN_SendEvent AnimNotify가 발동되는 타이밍에 GameplayEvent를 전달한다. 클라이언트는 이 시점에 마우스 좌표를 수집해 ServerSetReplicatedTargetData로 서버에 전송하며, 서버는 OnTargetDataReadyCallback()에서 실제 피해를 적용한다.
건물 배치(GA_BuildPlace) 역시 동일한 어빌리티 파이프라인으로 처리된다. ServerRPC_RequestPlaceBuilding() 호출 후 GA_BuildPlace::ActivateAbility()가 서버에서만 실행되어 셀 점유 검증 → GE로 골드 차감 → 건물 스폰 + GridData 등록 순서로 원자적으로 처리된다. 스킬 발동과 건물 배치가 동일한 어빌리티 인터페이스를 공유하기 때문에, 어빌리티 등록/해제, 쿨타임 처리, 태그 기반 발동 차단 등의 공통 로직을 중복 없이 재사용할 수 있었다.
쿨타임 관리는 GE_SkillCooldown(HasDuration)과 SetByCaller(Data.Cooldown) 조합으로 동적 주입 방식을 채택했다. GA의 ActivationBlockedTags에 Cooldown.Skill.* 태그를 등록해두면, 해당 GE가 활성화된 동안에는 어빌리티 발동이 자동으로 차단된다. GE의 GrantedTags가 부여/제거되는 시점에 USMSkillCooldownWidget이 실시간으로 갱신되어, 별도의 UI 타이머 로직 없이도 정확한 쿨타임 표시가 가능하다.
2. GMS 기반 UI 디커플링의 구조적 효과
멀티플레이어 게임에서 UI 갱신을 서버 → 클라이언트 복제에 의존하면 불필요한 네트워크 트래픽이 발생하고, 게임 로직과 UI 간의 결합도가 높아진다. 이를 해결하기 위해 GameplayMessageRouter 플러그인 기반의 GameplayMessageSubsystem(GMS)을 UI 갱신의 단일 채널로 채택했다.
구체적인 채널 구조는 다음과 같다. 웨이브 진행 정보는 UI.Event.Wave 채널로 방송되어 USMWaveTimeWidget이 수신하고, 베이스캠프 HP 변화는 USMBaseCampAttributeSet::OnRep_Health()에서 UI.Event.BaseCamp로 방송되어 USMBaseCampHPBarWidget이 수신한다. 알림 메시지는 ClientRPC_ShowNotification()에서 UI.Event.Notification으로 방송되고, 건축 모드 진입/해제는 UI.Event.BuildMode, 편집 모드는 UI.Event.EditMode, 인벤토리 관련 조작은 SM.Message.Inventory.* 채널을 통해 처리된다.
이 구조의 핵심 이점은 게임 로직 클래스가 어떤 위젯도 직접 참조하지 않는다는 점이다. 예를 들어 ASMBaseCampActor는 HP가 변할 때 GMS에 메시지를 방송하는 것 외에 UI에 대해 아무것도 알지 못한다. 위젯을 교체하거나 추가하더라도 로직 코드를 건드릴 필요가 없다. 실제 개발 과정에서 HUD 구조가 여러 차례 변경되었음에도 게임 로직 측에서의 수정이 전혀 필요 없었던 것이 이를 잘 보여준다.
3. Fragment 패턴 기반 데이터 주도 아키텍처

인벤토리와 스킬 시스템의 데이터 설계에서 Fragment 패턴을 채택한 것은 이번 프로젝트의 아키텍처적 하이라이트 중 하나다. USMItemDefinition이 필요한 Fragment를 조합하는 방식으로, 아이템 종류마다 클래스를 새로 만들 필요 없이 Fragment 구성만으로 다양한 아이템을 정의할 수 있다.
각 Fragment의 역할은 명확하게 분리되어 있다. USMGridShapeFragment는 비트마스크(FSMGridMaskData)로 격자 점유 형태를 정의하며 0°/90°/180°/270° 회전을 지원하고, 배치 충돌 감지도 비트마스크 AND 연산으로 처리되어 루프 기반 충돌 검사 대비 성능이 우수하다. USMAbilityFragment는 아이템과 어빌리티 클래스를 연결하며 관련 태그를 보유한다. USMGemModifierFragment는 젬 효과의 종류·수치·장착 조건 태그(RequiredAllTargetTags, RequiredAnyTargetTags, BlockedTargetTags)를 포함해 태그 기반 호환성 검사를 가능하게 한다. USMInternalInventoryFragment와 USMSkillProgressionFragment는 스킬 아이템의 내부 인벤토리 구조와 레벨업 규칙을 담당한다.
스킬 발동 시마다 젬 효과를 재순회하는 대신 FSMCompiledSkillSummary에 수치를 미리 캐싱해두는 설계도 중요한 최적화다. BuildSkillSummary()는 DT_Skill의 베이스 수치에 레벨 증가량을 합산하고, 내부 젬을 순회하며 FinalDamage *= (1 + %), FinalCooldown *= (1 - %) 등의 승산 합성을 적용한 뒤, GrantedBehaviorTags를 BehaviorTags에 병합하여 동작 분기 플래그로 활용한다. 이 캐시는 인벤토리 구성이 바뀔 때만 재계산되므로, 매 어빌리티 발동마다 순회 비용이 발생하지 않는다.
4. 그리드 건축 시스템의 완성도

ASMGridManager는 1D 배열(Index = Y × Width + X) 기반으로 전체 그리드 상태를 관리한다. 각 셀은 bIsOccupied, BuildingType, OwnerId를 클라이언트에 DOREPLIFETIME으로 복제하고, PlacedActor는 서버 전용으로 유지한다. 이는 건물 액터 자체가 bReplicates = true로 별도 복제되기 때문에 이중 복제를 피하면서도 클라이언트가 그리드 상태를 정확히 인식할 수 있게 하는 효율적인 설계다.
BakeGridHeights()는 StartPlay 시 1회 Landscape의 Z값을 사전 베이크하여, 건물 배치 시마다 지형 높이를 실시간으로 계산하는 비용을 제거한다.
경로 기반 펜스 배치에서의 A* 구현은 방향 전환 패널티를 포함하여 직선 경로를 우선시하도록 설계했다. 이를 통해 플레이어가 두 점을 찍으면 꺾이는 경로보다 직선에 가까운 자연스러운 펜스 배치가 이루어진다. 경로의 방향 전환 지점은 GetEffectiveCornerInfo()로 자동 감지되어 ConvertToCornerPreview()를 통해 코너 메시로 교체되고 적절한 Yaw가 계산된다. 경로 끝점에서도 인접 점유 셀을 검사해 접합부 코너를 자동 처리하므로 플레이어가 코너를 수동으로 배치할 필요가 없다.
편집 모드(SMEditModeComponent)에서 건물 이동 시 클라이언트는 로컬에서 선제적으로 이동을 표시하고, ServerRPC_PreviewMove → MulticastRPC_PreviewMove로 다른 클라이언트에 동기화한다. 이동 확정 시 ServerRPC_MoveBuildings가 원자적으로 검증하여 전체 성공 또는 전체 원위치 처리(all-or-nothing)를 보장한다. 이동 중 Pawn 충돌 채널을 MulticastRPC_SetBuildCollision으로 임시 비활성화해 플레이어가 이동 중인 건물에 막히지 않도록 처리한 것도 세심한 UX 고려였다.
5. 웨이브 시스템의 서버-클라이언트 동기화 설계
웨이브 전환 시 가장 까다로운 문제는 서버와 모든 클라이언트의 DataAsset 로드가 완료된 시점에만 전투를 시작해야 한다는 것이었다. 이를 위해 BuildState::Enter() 시점에 USMWaveManagerSubsystem::PreSpawnForWave()가 DT_Monster에서 MonsterDataAsset 경로를 수집하고, AssetManager로 PrimaryAssetId를 조회한 뒤 GameState::SetAssetsToLoad()를 통해 클라이언트에 로드 목록을 복제한다. 서버는 LoadAssetsByIDWithBundles({"Server"}), 클라이언트는 OnRep_AssetsToLoad() 콜백에서 LoadAssetsByIDWithBundles({"Client"})를 비동기로 실행하며, 각 클라이언트가 로드 완료 시 ServerNotifyClientLoadComplete()를 호출해 ReadyClientCount를 증가시킨다. 전원이 준비되면 OnReadyForCombat이 발동되어 CombatState로 전환된다.
CombatState에서 몬스터는 SpawnInterval 간격으로 순차 활성화된다. PreSpawn 단계에서 이미 Hidden 상태로 스폰해 두었기 때문에 전투 시작 시점의 스폰 부하가 없으며, 시각적으로도 몬스터가 갑자기 대량으로 나타나는 현상을 방지할 수 있다. 타임오버 패널티로 잔존 몬스터가 SelfKill하여 베이스캠프에 고정 데미지를 입히는 메커니즘은, 플레이어가 웨이브를 질질 끌어 시간을 낭비하는 전략을 방지하는 게임 디자인적 의도와 서버 측 리소스 정리를 동시에 달성한다.
6. 네트워크 보안 및 서버 권한 설계
보안 설계에서 세 가지 핵심 원칙을 일관되게 유지했다. 첫째, 이속 핵 방어를 위해 서버가 1초마다 MaxWalkSpeed를 검증하고 허용 오차(110%) 초과 시 강제 복구한다. 둘째, 건축 배치는 클라이언트의 고스트 프리뷰가 순수 로컬 표현에 그치고 실제 배치는 GA_BuildPlace [ServerOnly]에서 이중 검증 후 처리된다. 셋째, 인벤토리의 모든 Add/Move/Drop 조작이 서버에서 Authority 체크를 통과해야만 반영된다.
USMSyncDataManager를 서버 전용으로 제한한 것도 의도적인 설계 제약이다. 클라이언트에서 DataTable 원본에 직접 접근할 수 없으므로 치트 도구를 통한 스탯 조작 시도를 구조적으로 차단한다. 스킬 어빌리티를 ASMPlayerCharacter의 ASC가 아닌 ASMPlayerState의 ASC에 등록한 것도 중요한 설계 결정이다. 캐릭터가 리스폰으로 재생성되더라도 PlayerState는 유지되기 때문에, 부활 후에도 어빌리티 등록 상태와 쿨타임 정보가 그대로 보존된다.
Problem - 아쉬웠던 점, 겪었던 문제
1. GAS 초기 설계 비용과 구조 재설계

GAS는 초기 아키텍처 결정이 이후 전체 개발 흐름에 미치는 영향이 매우 크다. 이번 프로젝트에서도 AttributeSet 계층 구조와 GameplayEffect 연산 파이프라인을 초반에 확정하는 데 예상보다 많은 시간이 소요되었고, 프로젝트 중반에 일부 Effect 구조를 재설계하는 일이 발생했다.
특히 피해 계산 흐름에서 GE_InstantDamage의 Execution을 통한 계산과 SetByCaller를 통한 직접 수치 주입 방식 사이의 선택을 두고 초반 설계가 흔들렸다. 최종적으로 SetByCaller(Data.Damage.Amount) 방식을 채택했지만, 이 결정이 늦어지면서 일부 어빌리티에서 두 방식이 혼재하는 시기가 있었고 이를 정리하는 데 추가 공수가 들었다.
ActivationBlockedTags와 ActivationRequiredTags의 설계도 초반에는 충분히 체계화되지 않아, 전투 중 건축 모드 진입 차단(CombatState에서 State.Build.Place 태그 차단) 같은 요구사항이 생길 때마다 태그 구조를 추가하는 방식으로 대응했다. 처음부터 게임 상태 전환과 연동되는 태그 계층을 설계 문서로 정리해두었다면 이런 추가 작업을 줄일 수 있었을 것이다.
2. 멀티플레이어 복제 및 RPC 부하

Dedicated Server 환경에서 액터 수가 늘어남에 따라 복제 부하가 누적되는 문제가 있었다. 웨이브 후반부에 ASMMonsterBase 객체가 다수 활성화된 상태에서 ASMMonsterProjectile 스폰, ASMBaseCampActor HP 변화, ASMGridManager 상태 갱신이 동시에 발생하면 클라이언트 측에서 리플리케이션 지연이 체감될 수 있었다.
몬스터 ASC를 Minimal 복제 모드로 설정한 것은 클라이언트 태그 동기화만을 위한 최소한의 복제라는 의도였으나, 실제로는 UGA_MonsterAttackBase 실행 시 GameplayCue의 네트워크 발동이 예상보다 많은 트래픽을 발생시켰다. 원거리 몬스터(Squid)의 투사체는 서버에서S 스폰되어 bReplicates = true로 모든 클라이언트에 복제되는데, 웨이브 후반에 다수의 투사체가 동시에 활성화되면 복제 대역폭 소비가 컸다.
SMEditModeComponent의 건물 이동 미리보기에서 ServerRPC_PreviewMove → MulticastRPC_PreviewMove 체인이 건물 수만큼 반복 호출될 수 있어, 다수의 건물을 동시에 이동할 때 RPC 호출 빈도가 높아지는 문제도 있었다. 이 부분은 건물 이동 데이터를 배열로 묶어 단일 RPC 호출로 처리하도록 배칭(batching)하지 못한 것이 아쉽다.
3. 몬스터 AI 연산 부하

ASMMonsterAIController는 BehaviorTree와 타이머 기반 UpdateTargetAndTryAttack()을 함께 운영한다. 웨이브 후반에 수십 개의 몬스터가 동시에 활성화되면 각 AIController의 타이머가 거의 같은 프레임에 집중될 수 있으며, 여기에 FindBuildingInRange(), FindNearestPlayerInRange() 같은 오버랩 탐색이 겹치면 CPU 스파이크가 발생했다.
ASMGridManager::FindPath()의 A* 연산도 그리드 크기와 건물 배치 밀도에 따라 비용이 증가한다. 현재 구현에서는 몬스터의 이동 목표(TargetActor)가 항상 가장 가까운 베이스캠프로 고정되어 있고, 장애물(건물)이 변경될 때마다 경로를 재계산하는 구조인데, 이 재계산이 전투 중에도 빈번하게 발생할 수 있었다. 특히 편집 모드에서 건물 이동이 일어날 때 다수의 몬스터가 동시에 경로를 재계산하는 구간이 문제였다.
USMWaveManagerSubsystem의 틱 처리도 최적화가 부족했다. 서브시스템이 매 틱마다 웨이브 상태를 검사하는 대신, 이벤트 기반으로 전환하거나 틱 간격을 늘렸다면 불필요한 연산을 줄일 수 있었다.
4. GAS Prediction 구현의 미완성
현재 구현에서 스킬 발동 시 클라이언트는 GameplayCue의 코스메틱 이펙트만 선예측하고, 실제 피해 처리는 서버에서 OnTargetDataReadyCallback() 이후에 이루어진다. 이 구조는 서버 권한을 보장한다는 측면에서 올바르지만, 네트워크 레이턴시가 높을 때 스킬 이펙트와 실제 피해 판정 사이의 시각적 불일치가 발생할 수 있다.
건축 배치는 ServerOnly 어빌리티로 처리되어 클라이언트 예측이 전혀 없다. 클라이언트가 배치 요청을 보낸 후 서버 응답을 받기까지 건물이 나타나지 않는 딜레이가 존재하며, 이것이 빠른 연속 배치 시에는 체감될 수 있다.
FPredictionKey 기반의 예측 롤백을 건축과 인벤토리 조작에까지 확장하지 못한 것이 UX 측면에서 아쉬운 부분으로 남았다.
5. 인벤토리 복제와 클라이언트 반응성

USMInventoryComponent는 COND_OwnerOnly로 복제되어 보안과 트래픽 측면에서는 이상적이지만, 인벤토리 조작의 모든 경로가 서버를 경유해야 한다는 제약이 생긴다. 아이템 이동, 젬 장착, 스킬 레벨업 등 인벤토리 내부 조작 시 서버 왕복 레이턴시만큼 반응이 늦게 느껴질 수 있으며, 이를 보완하는 로컬 예측 레이어가 구현되지 않았다.
비트마스크 기반 충돌 감지는 성능 측면에서는 효율적이지만, FSMGridMaskData의 회전 변환 로직이 복잡해 일부 엣지 케이스(아이템 회전 후 격자 경계 근처 배치)에서 잘못된 충돌 판정이 발생하는 버그가 간헐적으로 재현되었다. 이 문제는 회전 변환 행렬 계산의 부호 처리 오류에서 기인했으나, 프로젝트 기간 내에 완전히 수정하지 못했다.
Try - 앞으로 시도할 것, 개선 방향
1. 대규모 환경 최적화 체계 도입
다음 장기 프로젝트에서는 Unreal Engine 5의 대규모 최적화 도구를 본격적으로 활용할 계획이다.
Significance Manager를 도입하여 카메라 거리, 화면 점유율, 플레이어와의 상호작용 빈도에 따라 몬스터 AI 틱 빈도를 동적으로 조절한다. 예를 들어 화면 밖 원거리 몬스터는 Significance 값이 낮게 설정되어 AI 틱이 0.3~0.5초 간격으로 줄어들고, 플레이어 근처의 몬스터는 정상 빈도로 유지된다. 이 접근만으로도 웨이브 후반 AI 연산 부하의 상당 부분을 줄일 수 있을 것으로 기대한다.
A* 경로 계산은 온디맨드 캐싱 전략으로 전환한다. 전투 중에는 그리드 변화(건물 배치/삭제/이동)가 발생할 때만 영향 받는 몬스터 그룹의 경로를 재계산하고, 그 외에는 기존 경로를 재사용한다. 이를 위해 그리드 셀 변경 이벤트를 발행하고, 해당 셀을 경로의 일부로 포함하는 몬스터만 선택적으로 재계산하는 구조가 필요하다. ASMGridManager에 구독 패턴을 추가하면 구현 가능하다.
USMWaveManagerSubsystem의 상태 검사는 틱 기반에서 이벤트 기반으로 전환한다. 몬스터가 사망할 때마다 CheckWaveCleared()를 호출하는 현재 방식은 이미 이벤트 기반에 가깝지만, 타임오버 처리 등 일부 흐름이 틱에 의존하고 있어 이를 타이머 델리게이트로 대체할 것이다.
렌더링 측면에서는 World Partition과 HLOD를 도입해 대규모 맵에서 원거리 오브젝트의 렌더링 비용을 줄이고, Occlusion Culling과 Distance Culling을 병행하여 화면에 보이지 않는 건물과 몬스터의 렌더링 스레드 부하를 제거한다.
2. GAS Prediction 고도화
FPredictionKey 기반의 클라이언트 예측을 스킬 피해 판정을 넘어 건축 배치까지 확장한다. 건물 배치의 경우, 클라이언트가 서버 검증 전에 고스트 프리뷰가 아닌 실제 건물 메시를 로컬에서 즉시 표시하고, 서버 검증 실패 시 롤백하는 방식으로 반응성을 개선할 수 있다. 이를 위해 GA_BuildPlace에 LocalPredicted 활성화 정책을 적용하고, 서버 검증 실패 시 OnAbilityCancelled 콜백에서 클라이언트 측 임시 건물 액터를 제거하는 롤백 로직을 구현해야 한다.
인벤토리 조작의 경우 서버 왕복 없이 로컬에서 선예측 상태를 표시하고, 서버 응답이 오면 결과를 확정하거나 원상 복귀하는 Optimistic UI 패턴을 도입한다. GAS의 LocalPredicted 정책을 인벤토리 어빌리티에도 적용하거나, 별도의 클라이언트 측 임시 상태 레이어를 두는 방식으로 구현할 수 있다.
몬스터 투사체의 경우, 서버에서만 스폰하고 복제하는 현재 방식 대신 클라이언트에서 발사 시점을 예측하여 로컬 코스메틱 투사체를 생성하고, 피해 판정은 서버에서만 처리하는 방식으로 체감 반응성을 높인다.
3. 복제 전략 세분화
ASMGridManager의 GridData 배열은 현재 DOREPLIFETIME 단일 조건으로 전체가 복제된다. 실제로는 변경된 셀만 클라이언트에 전달하면 충분한데, 배열 전체를 복제하면 웨이브 시작 시처럼 다수의 셀이 동시에 변경되는 구간에서 불필요한 트래픽이 발생한다. DOREPLIFETIME_CONDITION과 ReplicatedUsing 콜백을 조합하거나, FastArraySerializer를 활용해 변경된 요소만 델타 전송하도록 개선할 계획이다.
몬스터 투사체의 복제도 재검토가 필요하다. 수명이 짧고 수가 많은 투사체 액터를 전통적인 액터 복제로 처리하는 것은 연결/해제 오버헤드가 크다. UReplicationGraph의 UReplicationGraphNode_ActorList를 활용하거나, 투사체 상태를 별도의 경량 구조체 배열로 압축해 GameState를 통해 복제하는 커스텀 복제 전략이 더 효율적일 수 있다.
RPC 배칭도 개선 과제다. 편집 모드의 다중 건물 이동처럼 여러 엔티티의 상태를 동시에 변경하는 작업은 단일 RPC에 배열을 담아 전송하도록 통일한다. ServerRPC_MoveBuildings는 이미 이 방식을 사용하고 있으나, 미리보기 단계의 ServerRPC_PreviewMove도 배칭으로 통일해야 한다.
4. 프로파일링 기반 개발 문화 정착
이번 프로젝트에서 성능 문제는 주로 증상이 나타난 후 사후 대응 방식으로 처리했다. 다음 프로젝트에서는 Unreal Insights와 stat unit, stat game, stat net 명령을 개발 초기부터 주기적으로 모니터링하여 병목을 선제적으로 발견하는 습관을 팀 전체에 정착시킨다.
특히 네트워크 프로파일링에서 NetworkProfiler를 활용해 복제 대역폭을 액터 단위로 분석하고, RPC 호출 빈도와 페이로드 크기를 정량적으로 파악하는 과정을 스프린트마다 반복한다. 이를 통해 주관적인 "느린 것 같다"는 판단 대신 수치 기반으로 최적화 우선순위를 결정할 수 있다.
GAS 측면에서는 AbilitySystem.DebugAbilities 콘솔 명령과 ShowDebug AbilitySystem 활성화를 통해 어트리뷰트 수치, 활성 Effect, 태그 상태를 실시간으로 확인하는 디버깅 워크플로를 팀 표준으로 수립한다.
5. 게임 기획과 기술의 통합적 시각 확보

Fragment 패턴과 DataTable 기반의 데이터 주도 아키텍처를 구축해두었기 때문에, 다음 단계는 이 구조를 플레이테스트 피드백 사이클에 실제로 활용하는 것이다. 젬 조합별 스킬 성능 데이터를 로깅하고, 어떤 조합이 실제 플레이에서 지배적인지 분석하는 인게임 통계 시스템을 USMSyncDataManager 위에 구축할 계획이다.
밸런스 조정을 위해 코드를 재컴파일할 필요 없이 DT_Skill, DT_Wave, DT_Building 수치를 런타임에 핫리로드하는 기능도 다음 프로젝트에서 구현한다. Unreal Engine의 FTableRowBase 기반 DataTable은 에디터에서 이미 핫리로드를 지원하므로, 이를 패키징된 빌드에서도 외부 JSON으로 오버라이드할 수 있는 레이어를 추가하면 된다.
궁극적으로는 기술 아키텍처가 게임 기획의 실험 속도를 뒷받침하는 구조, 즉 프로그래머의 개입 없이 기획자가 새로운 스킬 조합이나 웨이브 구성을 빠르게 시험하고 검증할 수 있는 파이프라인을 완성하는 것이 목표다.

'프로그래밍 > 개발일지' 카테고리의 다른 글
| [개발전반] Ureal Engine 5를 이용해 한 달동안 진행했던 팀프로젝트 TPS게임 개발 KRP (0) | 2026.03.05 |
|---|---|
| [개발전반] Best Practice란? (0) | 2026.02.03 |
| [개발전반] Text기반 RPG게임 제작이후 KRP 및 후기 (0) | 2026.01.07 |