# FirstRising 코드·시스템 최적화 — 인수인계 (2026-09-22) BT18-FirstRising · 이전 세션: 로비 UI 2차(㉝~㊸) + steam 2회 병합 + 문서 구조 정비. **이 문서만 읽고 시작하면 된다.** 다음 세션은 **Fable 모델로 코드·시스템 최적화**를 진행한다(PD 2026-09-22). ## ⚠ 전제 — 이 게임은 PC(Windows) 게임이고 **위젯 모드가 핵심 기능**이다 바탕화면에 투명 창으로 캐릭터만 띄우는 **데스크톱 위젯 모드**가 제품의 핵심이다. 모바일은 쓰지 않는다(SDK 전부 제거됨). 따라서 **모든 기능은 빌드에서 동작해야 하고, 위젯 모드에서도 동작해야 한다.** 에디터에서만 되는 구현은 결함이다. --- ## §0 다음 세션 첫 프롬프트 (그대로 복사해 쓰세요) ``` FirstRising(FR) 코드·시스템 최적화 작업을 진행한다. 인계서: E:\BurningTimes\공유\대화로그\FirstRising\2026-09-22_코드최적화_인수인계.md — 이것부터 읽어라. 인계서 §0-1 에 이번 세션에서 열 문서가 정리돼 있다. 그 밖의 문서는 열지 마라. 전제: 이 게임은 PC 게임이고 위젯 모드가 핵심이다. 에디터에서만 되는 구현은 결함이다. 이번 세션 기본 방침 (PD 2026-09-22 지시): 1. 최적화를 고려한 객체지향 설계가 기본이다. 매 프레임 씬 전체 탐색·할당 금지. 2. 인간 개발자 정섭 님(bluecell2) 코드를 기준선으로, 설계와 스타일을 최대한 맞춘다. (매우 중요한 협업 규칙) 참고 파일: Assets/Scripts/UI/Lobby/ViewportLayoutController.cs · Assets/Scripts/Entity/DynamicSpawnEntitySystem.cs 3. 게임 원본 소스 수정이 허용됐다. 단 수정 지점에 '날짜 + 이유' 한글 주석을 반드시 남긴다. 1순위: §3-1 "위젯 모드에서 플레이어가 공격하지 못하는 버그" 를 먼저 잡는다. 2순위: §4 기술 부채 정리. 먼저 원인을 실측으로 확인하고 수정안·비용을 보고해라. 승인 후 진행한다. ``` --- ## §0-1 이번 세션에서 열 문서 (이것만 열면 된다) 문서 구조가 2026-09-22 에 **3층**으로 정비됐다 — ① `CLAUDE.md` = 규칙만(자동 주입) ② `Docs/INDEX.md` = 색인 ③ 인계서 = 이번에 볼 것. | 언제 | 문서 | |---|---| | 항상 (자동으로 읽힘) | `CLAUDE.md` — 항상 지키는 규칙만 있다(4.1 KB). 설명은 없다 | | 무엇을 볼지 모를 때 | `Docs/INDEX.md` — "하려는 일 / 증상" 두 표로 찾아 들어간다 | | §3-1 공격 불가 버그 | `Docs/ECS_Mono_브리지.md` · `Docs/ECS_전투루프.md` · `Assets/Scripts/System/Windows/README_위젯모드.md` | | §4 기술 부채 정리 | `Docs/싱글턴_규약.md` · `Docs/ECS_전투루프.md` + 정섭 님 코드 2개(§5) | | 빌드가 필요할 때 | `Docs/빌드_실행.md` | 그 밖의 문서는 **열지 않는다.** 필요해지면 색인에서 찾는다. > 위젯 관련 43줄은 원래 `CLAUDE.md` 안에 있었으나 **`Assets/Scripts/System/Windows/README_위젯모드.md` 로 옮겼다**(매 세션 자동으로 읽혀 낭비라는 PD 지적). 내용은 그대로다. --- ## §1 현재 상태 - 브랜치 `bt/work`, 최신 **`20640c4f15`**(= origin/steam `67ccbe71f9 "위젯 작업"` 병합본). origin/bt/work 는 `737b565cf7` 까지 올라가 있다 — **병합 2건이 아직 푸시 전**이다. - 컴파일 에러 0. 모바일 SDK 전부 제거(2026-09-21 PD "모바일 플랫폼 미사용" 결정), 잔여 폴더·`FacebookSDK` 안에 있던 FantasyAxeAndShield 45개도 원위치 복구 완료. - 로비 구조가 정섭 님 손에 두 번 바뀌었다. **경로를 박아 두면 계속 깨진다**: - `Lobby/PlayScene` → **`Lobby/LobbyPlayScene`**(프리팹 `Assets/GameAssets/LobbyPlayScene.prefab` 로 분리) - `Canvas` · `GameCanvas` 아래에 **`CenterHolder` 래퍼 추가** → 내 숨김 경로 17개가 전부 깨졌다 - → `TVLobbySetup` 이 **계층에서 찾아 절대경로를 써 넣도록** 고쳤다("BattlePage 아래의 ChapterInfo" 식으로 지정). 래퍼가 더 끼어도 Apply 한 번이면 따라간다. - 우리 루트는 `Lobby/LobbyPlayScene/BT_Visual`. 로비 UI 재적용은 에디터 메뉴 **`BurningTimes/Lobby 메뉴/Apply`** 한 방(멱등). 정섭 님이 로비를 다시 올리면 **그 버전을 받고(`--theirs`) Apply 를 다시 실행**한다. 내 버전으로 덮으면 그의 새 UI 가 사라진다. ### 미커밋 상태로 남겨 둔 것 (PD 확인 대기) | 파일 | 내용 | 왜 안 올렸나 | |---|---|---| | `CLAUDE.md` · `Docs/*` · `Assets/Scripts/System/Windows/README_위젯모드.md` | 문서 3층 구조 정비(15.7 KB → 4.1 KB) | 정섭 님과 공유하는 파일이라 PD 승인 대기 | | `ProjectSettings/ProjectSettings.asset` | `gpuSkinning 1→0`, `meshDeformation 1→0`, `useFlipModelSwapchain 0→1` | 성능·렌더링 영향. 유니티가 플랫폼 전환 중 바꾼 것으로 보임 | | `Packages/manifest.json`·`packages-lock.json` | `com.unity.postprocessing`·`com.unity.shadergraph` 자동 추가 | 유니티 자동 해석 | | `Assets/Plugins/Android/settingsTemplate.gradle` · `Assets/Scenes/TestVisual.unity` | 리임포트 부산물 | 내 작업 아님 | > `README_위젯모드.md` 에 `useFlipModelSwapchain` 은 **false 여야 창 투명화가 된다**고 적혀 있는데 지금 로컬은 1(켬)이다. §6-1 참고. --- ## §2 이전 세션에서 한 로비 UI 작업 (반영·일부 푸시 완료) | 기능 | 파일 | |---|---| | 하단 메뉴 6개(성장·장비·특성·컨텐츠·길드·포탈) + 재터치 시 닫기 | `Visual/Scripts/TVBottomMenu.cs` | | 스킬 슬롯 6 + 자동 버튼 + 상자(인벤토리) 버튼 | `Visual/Scripts/TVSkillBar.cs` | | 닉네임 옆 전투력(임시값 9,110) | `Visual/Scripts/TVPlayerPower.cs` | | 로비 UI 숨김(CanvasGroup 방식) | `Visual/Scripts/TVLobbyUIHider.cs` | | 이동 중 공격 시 이동 속도 65% | `Visual/Scripts/TVAttackMoveSpeed.cs` | | 지도(포탈로 열기) | `Visual/Scripts/TVMapButton.cs` | | 위 전부를 씬에 쓰는 에디터 도구 | `Editor/TVLobbySetup.cs` | 핵심 교훈 3가지(다시 밟지 말 것): 1. **씬에 한 번 저장된 값은 C# 기본값을 바꿔도 안 바뀐다.** 배치·크기 값은 `TVLobbySetup` 이 `SerializedProperty` 로 직접 써야 한다. 이것 때문에 "고쳤는데 그대로"가 여러 차례 반복됐다. 2. **로비 UI 는 오브젝트를 끄면 안 된다.** `MainPage`(현재 `Lobby/Canvas/CenterHolder/MainPage`)에 로비 전체를 초기화하는 `MainPageUI` 싱글톤이 있어, 끄면 로비 기능이 통째로 죽는다. CanvasGroup 투명+클릭불가로만 가린다. 3. **계층 경로를 박아 두지 않는다.** 정섭 님이 래퍼를 넣을 때마다 깨진다. 에디터에서 찾아 절대경로로 기록하고, 실행 중에는 탐색하지 않는다. --- ## §3 정섭 님이 내 코드를 고친 이유 (반드시 이해하고 갈 것) 정섭 님 커밋 `afdcdfba82` "위젯 투명화" 가 내 `TVMonsterZones.cs` 를 고쳤다. ``` -#if UNITY_EDITOR void Update() { ... } - Fx.WorldManager.Instance.SpawnMonsterEntityInEditor(...) + Fx.WorldManager.Instance.SpawnMonsterEntity(...) -#endif ``` **내 몬스터 배치 코드가 통째로 `#if UNITY_EDITOR` 안에 있었고, 에디터 전용 API(`SpawnMonsterEntityInEditor`)를 쓰고 있었다 → 빌드에서는 몬스터가 한 마리도 안 나온다.** PD 지적: "네 코드가 위젯 상태(빌드에서 동작하지 않는 임시 더미 코드 기반)로 만들어졌다." 정섭 님이 함께 한 일: - `WorldManager.SpawnMonsterEntityInEditor` → **`SpawnMonsterEntity`** 로 바꾸고 `#endif` 밖으로 빼 빌드에 포함 - `WorldManager.InitPlayer` 에 `UIManager.HasInstance()` 가드 추가(위젯 모드처럼 UI 없이 도는 경우 대비) + 조이스틱 세로 잠금 연결 - 신규 **`Assets/Scripts/Entity/DynamicSpawnEntitySystem.cs`** — 런타임 동적 스폰을 ECS `SystemBase`(Burst, `TR_UpdateSystemGroup`)로 처리. `TestSystem.cs` 의 에디터 테스트 경로 60줄 제거 - 신규 `Assets/Scripts/UI/Lobby/ViewportLayoutController.cs` — 위젯 모드 뷰포트 레이아웃 - 위젯 투명화 지식 43줄 문서화 — 지금은 `Assets/Scripts/System/Windows/README_위젯모드.md` 에 있다(§0-1 참고) 이어서 `67ccbe71f9 "위젯 작업"`(내 파일은 건드리지 않음): - 신규 `GamePlayCanvas.prefab` · `CharacterPageUI` · `GamePlayUI` · `ItemPage2UI` · `SkillPageUI` - `DesktopWidgetManager` · `HideOnWidgetMode` · `PageUI` 수정, `Canvas`/`GameCanvas` 아래 `CenterHolder` 추가 → 앞으로 **에디터 전용 경로에 기대는 구현을 하지 않는다.** `#if UNITY_EDITOR`, `...InEditor` API, `[ExecuteInEditMode]` 에 의존한 게임 동작은 빌드에서 죽는다. ### §3-1 [1순위] 위젯 모드에서 플레이어가 공격하지 못한다 (빌드 확인 · PD 2026-09-22) **가장 유력한 원인** — `Assets/BurningTimes/Visual/Scripts/TVPlayerAttackRange.cs` ```csharp // 84행 player.suppressCastSkill = !EnemyInRange; // 사거리 안에 적이 없으면 자동 발사를 막는다 ``` 이 구성 요소는 "사거리 안에 살아 있는 적이 없으면 무기 스킬 자동 발사를 멈춰 이동 모션만 나오게" 하려고 만든 **TestVisual 연출용**이다. `EnemyInRange` 가 한 번도 true 가 되지 않으면 `suppressCastSkill` 이 계속 true 로 남아 **플레이어가 영영 공격하지 않는다.** 확인할 경로(전부): 1. 몬스터가 스폰되지 않음 — `TVMonsterZones` 가 같은 `#if UNITY_EDITOR` 함정이었다(정섭 님이 고침). **다른 곳에 같은 패턴이 더 있는지 먼저 훑을 것.** 2. 사거리 계산이 스킬 버퍼(`skillBufferElement.targetingRange`)에 의존한다 — 위젯 모드 초기화 순서에서 비어 있을 수 있다. 3. `rangeScale = 0.7f` 를 곱해 사거리를 줄여 둔다. 원본 복원은 `OnDisable` 경로(269행)에만 있다. 4. `TVSpawnFilter` 가 스폰을 거르고 있을 수 있다. **수정 방향(제안)** — 이 기능은 연출 목적이므로 위젯/빌드에서는 기본으로 **끄거나**, `suppressCastSkill` 을 건드리지 않는 모드를 두는 것이 안전하다. 고칠 때 `Character.suppressCastSkill` 을 누가 켜고 끄는지 전부 추적하고(`TVPlayerAttackRange` 외에 `TVAutoPilot` 도 조이스틱 입력을 만진다), 어떤 경로로도 true 로 굳지 않게 보장할 것. --- ## §4 기술 부채 — 2순위 (PD 2026-09-22 지적) PD: "업데이트마다 캐릭터하고 몬스터를 모든 노드를 돌면서 찾고 있는데, 모바일에서는 퍼포먼스가 엄청나게 떨어진다. PC라도 최적화가 엉망이다. **최적화까지 고려한 객체지향 설계가 기본**이어야 한다." `Assets/BurningTimes/Visual/Scripts/` 실측(호출 지점 수): | 유형 | 건수 | 대표 파일 | |---|---|---| | `ToComponentDataArray` / `ToEntityArray` (프레임마다 Temp 배열 할당) | 17 | `TVMonsterZones`(4) · `TVPlayerAttackRange`(3) · `TVMonsterLocomotion`(3) · `TVBossLights`(3) · `HitFeelDirector`(3) · `TVCameraProfile`(2) | | `GetComponentsInChildren` (계층 전체 순회) | 14 | `TVCharacterSwap`(6) · `TVXRayGate`(2) · `TVMonsterLocomotion`(2) · `TVAutoPilot`(2) · `TVArcherGear`(2) | | `CreateEntityQuery` (재사용 안 하고 반복 생성) | 9 | `HitFeelDirector`(3) · `TVPlayerAttackRange`(2) · `TVMonsterZones`(2) | | `GameObject.Find` / `FindObjectsByType` | 다수 | `TVXRayGate` · `TVSpawnFilter` · `TVMapButton` · `TVPlayerPower` | 특히 확인된 것: - `TVCameraProfile.BossNearby()` — **매 프레임 `ToComponentDataArray` 2개 할당 + 전체 몬스터 순회** - `TVPlayerPower.LateUpdate()` — **매 프레임 `ForceMeshUpdate()` + `GetPreferredValues()`**(TMP 메시 재생성) - `TVLobbyUIHider.Find()` — 대상을 못 찾으면 **열린 씬 전체 Transform 순회**(0.25초 주기). 경로는 이제 Apply 가 절대경로로 써 주므로 평소엔 타지 않지만, 대비책 자체가 비싸다 - `TVAttackMoveSpeed.LateUpdate()` — 매 프레임 `CalcFinalSpeed()` + ECS `SetComponentData` **정리 방침** - 질의·참조는 필드로 한 번 만들고 월드/씬이 바뀔 때만 재생성한다. - 폴링 대신 이벤트·더티 플래그. 불가피하면 주기(0.2~0.5초)로. - 값이 바뀔 때만 계산한다(TMP 갱신, 속도 쓰기 등). - 한 MonoBehaviour 가 생성·배치·상태·게임연동을 다 하지 않게 나눈다. - **원본 코드를 고쳐도 된다(§5). 우회보다 제자리 수정이 낫다.** --- ## §5 지켜야 할 규칙 **코드** - **정섭 님 코딩 스타일에 맞춘다**(2026-09-22 PD "매우 중요한 협업 규칙"). 실측한 스타일: - `namespace Fx` · 비공개 필드 `m_` · 상수 `k` 접두 · `[SerializeField]` 는 필드 윗줄에 단독 - 클래스 위에 한글 `` 로 **무엇을·왜**. 주석은 동작 설명이 아니라 판단 근거를 남긴다 - **`Update()` 를 쓰지 않는다** — 실제로 주석 처리해 두고 Start·버튼 콜백·상태 전이에서만 호출한다 - 참조는 `Start` 에서 한 번 캐싱, 버튼은 `onClick.AddListener` - ECS: `[BurstCompile]` + `SystemBase` + `RequireForUpdate`, `NativeList` 는 `OnCreate` 에서 `Persistent` 로 한 번, 요청은 버퍼에 모아 일괄 처리 - 새 코드를 쓰기 전에 **같은 영역의 정섭 님 파일을 먼저 읽고** 그 형태를 따른다 - **원본 게임 소스 수정 허용**(2026-09-22 PD). 단 고친 자리에 **한글로 날짜 + 이유 주석** 필수. 예) `// 2026-09-22 이동 중 공격 시 발이 미끄러져 보여 이동 속도를 낮춘다(PD 지시 BT18)` **문서 (2026-09-22 PD 지시)** - `CLAUDE.md` 에는 **항상 지켜야 하는 규칙만** 넣는다. 매 세션 자동으로 읽히므로 설명·절차를 넣으면 계속 비용이 된다. - 설명·구조·절차는 **주제별 문서 + `Docs/INDEX.md` 색인 한 줄**로 넣는다. - 세션 인계서에는 **"이번 세션에서 열 문서"(§0-1) 절을 반드시** 둔다. 다음 세션은 인계서가 가리키는 문서만 연다. **작업 방식** - **커밋·푸시는 PD 지시가 있을 때만.** 수정·검증 후 작업 트리에 두고 보고한다. - **내가 Play 를 켜지 않는다.** 대기(폴링·백그라운드 감시)도 걸지 않는다. 검증이 필요하면 무엇을 볼지 한 줄로 말하고 PD 판단에 맡긴다. - PD Play 중에는 `AssetDatabase.Refresh()` 를 부르지 않는다 — 도메인이 다시 로드되며 런타임 UI가 파괴된다. - `AssetDatabase.SaveAssets()` 금지 — `SaveAssetIfDirty` 를 쓴다. - Unity Auto Refresh 는 꺼져 있다(`kAutoRefresh=False`). 파일을 써 두고 내가 원할 때 Refresh 한다. - 긴 에디터 작업은 `--detach` 로 돌린다(5초 넘기면 콘솔에 빨간 타임아웃 로그가 남는다). - 정섭 님이 로비를 올리면 **그의 씬을 받고 Apply 를 다시 실행**한다(§1). - 보고는 한국어·간결·경어, 호칭 **PD님**. 진행중·보류만 보고한다. --- ## §6 남은 이슈 1. **`useFlipModelSwapchain` 이 1(켬)로 바뀌어 있다** — `README_위젯모드.md` 요구값은 false(켜면 알파가 버려져 투명화가 깨진다). 되돌릴지 확인 필요. 2. `gpuSkinning` · `meshDeformation` 이 0으로 꺼졌다 — 캐릭터 많은 화면 성능에 영향. 의도인지 확인 필요. 3. 문서 3층 구조 정비분이 **미커밋**이다(`CLAUDE.md`·`Docs/*`·`README_위젯모드.md`). 정섭 님과 공유하는 파일이라 PD 승인 후 올린다. 4. 병합 2건(`c452c9aae4`·`20640c4f15`)이 **미푸시**다. 5. 전투력 숫자는 임시값 9,110 이다. 실제 수식이 정해지면 `TVPlayerPower.Power()` 만 바꾸면 된다. 6. 자동 버튼은 켜짐/꺼짐 표시만 하고 실제 자동 전투에 연결돼 있지 않다. 7. 스킬 슬롯 6개는 배치·쿨타임 표현만이고 실제 스킬에 연결돼 있지 않다. 8. TestVisual2 의 안개·주변광(RenderSettings)이 로비에는 없다(로비 기본값). 적용 여부 PD 결정 대기.