diff --git a/공유/PD_지시_트래킹/개발팀_PD_지시_로그.md b/공유/PD_지시_트래킹/개발팀_PD_지시_로그.md index b1e9ead..745c2dd 100644 --- a/공유/PD_지시_트래킹/개발팀_PD_지시_로그.md +++ b/공유/PD_지시_트래킹/개발팀_PD_지시_로그.md @@ -33,7 +33,8 @@ C3·C13 위반에 해당. **즉시 자진 보고 후 소급 등록**. | # | 일시 | 지시 요지 | 처리 상태 | 산출물 경로 | 중단 사유 | 사후 조치 | |---|------|----------|----------|-----------|----------|----------| -| BT37-FirstRising | 2026-09-28 14:53 | **남은 백그라운드 작업 확인 · 작업물 푸시** — PD "백그라운드 작업은 왜 남아있지? 작업이 끝났으면 푸시하도록 해" | **완료(09-28 14:57)** — ① 남아 있던 백그라운드 작업 = 12:03 에 에디터를 다시 띄운 셸(`unity open … | head`). 띄우는 명령은 끝났는데 출력 통로를 에디터가 물고 있어 `head` 가 끝나지 않았다(하는 일 없음) → `head` 만 종료, 에디터는 그대로 [실측] ② BT34~BT36 작업물을 `bt/work` 에 커밋 `1ce5df5b2e`(507개 파일) → push 완료, 원격 = 로컬 [실측]. 에디터가 덧붙인 변경 7개는 넣지 않음 | FR `bt/work` `1ce5df5b2e` | — | 조직 레포 문서 · 도구는 커밋하지 않음(지시 범위 밖으로 판단) | +| BT38-FirstRising | 2026-09-28 15:09 | **오염된 던전 바닥 텍스처 개선** — PD "오염된 던전의 바닥 텍스쳐가 너무 크고 퀄리티가 다른 맵에 비해 떨어지는 느낌이야. 체크해보고 좀 더 자연스러운 던전 타일처럼 수정해줘." | **완료(09-28 15:37) · PD 확인 대기** — 원인 [실측]: ① 안쪽 장식 띠의 바닥 판 46장이 높이 기준 맞춤 탓에 2~4.9 배(폭 21~69 m)로 커져 바닥 전체를 덮음(BT31 PM 실수) ② 지형 층 텍스처가 512 픽셀(1 m 당 64~109) ③ 돌판 다리도 같은 큰 판. 조치: 띠 제거 · 지형 층을 던전 타일 1024 픽셀 4 m(1 m 당 256 · 돌 한 장 0.5~1 m) + 흙 얼룩 · 다리 판 교체 · 뼈 무더기 네모 판 오려내기. 빌드 OK · 보스 자리 막힘 0 ㎡ · Play 와 전용 필드 R4 에디터 로그 오류 0. PD 답변(09-28 16:17)으로 FR `bt/work` `5f9c8dd297` · 조직 레포 `main` 커밋 · push, 쓰이지 않는 에셋은 지우지 않고 목록으로 정리(맵 폴더 35개 · 10.0 MB), 커밋 규칙은 '지시할 때만' 유지 | FR `Assets/BurningTimes/Maps/PlagueCatacomb/`(정의 · Textures · Terrain · Deck) · `Editor/Maps/MapPropMaterials.cs` · 빌드결과 문서 §9 · `맵/tools/texderive_dungeon.py` · `map_tune5.py` · `맵/미사용_에셋_목록.md` | — | 쓰이지 않는 에셋 삭제는 PD 결정 대기(목록 문서) · 항구 널빤지(1.5~2 배)는 손대지 않음 | +| BT37-FirstRising | 2026-09-28 14:53 | **남은 백그라운드 작업 확인 · 작업물 푸시(FR · 조직 레포)** — PD "백그라운드 작업은 왜 남아있지? 작업이 끝났으면 푸시하도록 해" · (후속) "조직 레포도 커밋해야하지 않아?" | **완료(09-28 14:57)** — ① 남아 있던 백그라운드 작업 = 12:03 에 에디터를 다시 띄운 셸(`unity open … | head`). 띄우는 명령은 끝났는데 출력 통로를 에디터가 물고 있어 `head` 가 끝나지 않았다(하는 일 없음) → `head` 만 종료, 에디터는 그대로 [실측] ② BT34~BT36 작업물을 `bt/work` 에 커밋 `1ce5df5b2e`(507개 파일) → push 완료, 원격 = 로컬 [실측]. 에디터가 덧붙인 변경 7개는 넣지 않음 ③ 조직 레포: 커밋 3건(`705986b` 조직 규칙 BT20-Org · `89505fa` 이번 세션 문서/도구/로그 · `c693d2e` 앞선 세션 기록 BT16~BT22) → `main` push 완료(`5698f2c..c693d2e`), 원격 = 로컬 [실측]. 첫 push 는 인증 실패, 재시도에서 성공 | FR `bt/work` `1ce5df5b2e` · 조직 레포 `main` `c693d2e` | — | FR 에셋 백업 폴더(1.2 GB)는 레포에 올리지 않음 — 무시 목록에 넣을지 PD 판단 | | BT36-FirstRising | 2026-09-28 12:5x | **블렌더로 테마별 소품 제작 · 전 맵 꾸미기** — PD "지금까지 만들어진 맵에 오브젝트나 프랍들이 너무 반복적으로 재활용된 느낌이야. 나는 블렌더를 활용해 네가 맵 테마나 컨셉에 맞게 오브젝트나 프랍들을 제작하면 좋겠어. 이번에 추가한 사막맵을 포함해 비주얼적인 완성도를 높이기 위해 자연스럽게 꾸미기 좋은 프랍이나 오브젝트를 블랜더로 추가 제작한 후 배치해서 맵을 꾸며줘. 그리고 모든 맵에 꾸미기 작업이 완성되면 보고해. (나는 상업적인 성공을 위해 비주얼과 완성도를 신경쓰고 싶어.) * 각 맵의 특징과 컨셉을 고려해 자연스럽고 비주얼적인 완성도를 높이는 것이 목표야"(BT35 진행 중 추가 지시) | **완료(09-28) · PD 확인 대기** — 블렌더로 7 테마 67종 제작, 맵 6종에 28~67개 + 숲(로비) 42개 배치. 화면 확인 뒤 6건 수정(검은 풀 · 성채 돌 밝기 · 줄무늬 · 알 발광 · 사막 분위기/바위 · 항구 보스 자리). Play 순회 에디터 로그 오류 0 · 물리 점검 통과 [실측]. **모델 품질은 구매 에셋보다 단순하다(PM 판단)**. `bt/work` `1ce5df5b2e` push(BT37) | FR `Assets/BurningTimes/Maps/_Shared/Props/` · `Editor/Maps/MapPropImporter.cs` · `공유/기획/FirstRising/맵/2026-09-28_FR_신규맵5종_빌드결과.md` §8 · `맵/tools/blender_props/` | — | 소품 품질 PD 판단 대기 · 기기 빌드 미검증 | | BT35-FirstRising | 2026-09-28 12:4x | **이동 가능 영역 점검 · 거미 동굴 바닥 품질 · 사막 맵 1종 추가** — PD "자잘한 오브젝트로 인해 이동이 방해되는 부분이 있는거 같아. 체크해보고 비주얼적으로 확실히 막혀야할 지역과 충분히 이동이 가능할 것으로 보이는 지형이 막히지 않도록 이동 가능한 영역을 체크해줘." · "거미여왕의 동굴의 바닥 텍스쳐가 너무 퀄리티가 떨어진 느낌이야. 다른 맵에 비해 크게 확대되어서 퀄리티가 떨어진 느낌인데, 수정해줘." · "추가로 황무지나 사막에 적합한 지형의 텍스쳐를 활용해 사막 지역의 맵도 1종 추가해줘." | **완료(09-28) · PD 확인 대기** — ① 이동 방해 원인 = 소품 자체 충돌체 → 끔. Play 물리 점검 걸리는 칸 0(묘지 교회 옆 1.3 ㎡ 제외) · 길 폭 6.6 m 이상 · 보스 자리 막힘 0 ㎡ [실측] ② 동굴 바닥 타일 8 · 8 · 6 m → 4 · 3.5 · 4 m ③ 사막 맵 `ScorchedDesert` 추가(전용 필드 · 필드 이동 목록 포함 · 전용 필드의 보스 짝 C1 은 PM 선택). `bt/work` `1ce5df5b2e` push(BT37) | FR `Assets/BurningTimes/Maps/ScorchedDesert/` · `Assets/Scenes/Maps/ScorchedDesert*` · `Editor/Maps/` · 빌드결과 문서 §7 | — | 사막 분위기 · 보스 짝 PD 확인 대기 | | BT34-FirstRising | 2026-09-28 11:2x | **종료 시 에러 수정 · 보스전 필드 바닥 반복 패턴 완화 · 필드 이동 버튼** — PD "게임을 나갈 때 왜 자꾸 에러가 발생되는거지? 수정해줘." · "보스 전 맵의 바닥패턴이 너무 반복적이야. 특히 자연 지형(초원 형태의 맵)의 경우, 자연스러운 지형처럼 보일 수 있도록 텍스쳐를 구성하거나 예를들어 패턴 텍스쳐 위에 흙이나 풀밭 같은 레이어를 1~2개 더 추가해서 반복 패턴이 너무 드러나지 않게 바닥 텍스쳐를 구성해줘." · "보스전 버튼 하단에, 필드 이동 버튼을 추가해서 새로 제작 된 필드를 테스트할 수 있도록 해줘.(드랍다운 형태로 맵 변경할 수 있도록)" | **완료(09-28 12:30) · PD 확인 대기** — ① 종료 시 에러: 원인 = 전용 필드를 닫을 때의 에셋 해제(BT32 PM 코드) → 빼고 모든 종료 경로 에디터 로그 오류 0 [실측]. 앞선 '콘솔 오류 0' 보고는 확인 방법 결함으로 틀렸음(정정 기록) ② 바닥: 층 수는 그대로 두고 자연물 층을 얼룩으로 덧칠 — 보스 자리 한 층 100% → 네 층 20~57% ③ 필드 이동: "보스전" 아래 드롭다운(로비 + 5종) · 8~12초 · 오류 0. **에디터 Play 전용**(테마 맵 씬이 빌드 목록에 없음) · `bt/work` `1ce5df5b2e` push(BT37) | FR `Assets/BurningTimes/Visual/Scripts/TVFieldTravel.cs` · `Boss/Scripts/TVBossField.cs` · `TVBossArenaUI.cs` · `Editor/Maps/MapTerrainBuilder.cs` · 구현 노트 v2 §12 · 맵 빌드결과 §6 · 대화로그 §3 | — | 바닥 자연스러움 PD 확인 · 빌드 목록 반영 여부 | diff --git a/공유/기획/FirstRising/맵/2026-09-28_FR_신규맵5종_빌드결과.md b/공유/기획/FirstRising/맵/2026-09-28_FR_신규맵5종_빌드결과.md index 770fa72..de0011f 100644 --- a/공유/기획/FirstRising/맵/2026-09-28_FR_신규맵5종_빌드결과.md +++ b/공유/기획/FirstRising/맵/2026-09-28_FR_신규맵5종_빌드결과.md @@ -210,3 +210,43 @@ PD 지시: "지금까지 만들어진 맵에 오브젝트나 프랍들이 너무 - 새 소품에는 충돌체가 없다 — 막는 것은 보이지 않는 벽이 맡는다. 숲 맵의 새 소품은 걸을 수 없는 곳에만 있어 벽을 다시 만들지 않았다. - 기기 빌드에서의 모습 · 성능은 확인하지 못했다. 아틀라스 7장(색 · 노멀 · 발광 각 2048)이 늘었다. - 필드 이동은 에디터 Play 전용이다(맵 씬이 빌드 목록에 없다). + +## 9. 오염된 던전 바닥 — 던전 타일로 (BT38 · 2026-09-28) + +PD 지시: "오염된 던전의 바닥 텍스쳐가 너무 크고 퀄리티가 다른 맵에 비해 떨어지는 느낌이야. 체크해보고 좀 더 자연스러운 던전 타일처럼 수정해줘." + +### 9-1. 원인 [실측] +| 원인 | 실측 | +|---|---| +| **바닥을 덮은 큰 바닥돌(주된 원인)** | 길 안쪽 장식 띠 '안쪽 깨진 바닥돌'이 팩의 10 m 바닥 판(GroundTileCATAM)을 '높이 0.04~0.1 m'에 맞춰 놓았다. 판의 높이가 0.02 m 라 **2~4.9 배로 커져 폭 21~69 m 짜리 판 46장**이 서로 겹쳐 바닥 전체를 덮었다(덮은 넓이 합 9.7만 ㎡ · 맵의 걸을 수 있는 면적 8,199 ㎡). 무늬도 그만큼 커지고 흐려졌다 | +| 지형 층의 해상도 | 팩 텍스처가 PC 에서 512 픽셀로 들어온다 — 바탕(무늬 없는 바위) 8 m 에 1 m 당 64 픽셀 · 공터(깨진 타일) 4.7 m 에 1 m 당 109 픽셀. 다른 맵의 주 바닥은 223~256 | +| 돌판 다리 | 독액 수로 위 상판 2곳도 같은 10 m 바닥 판 — 돌 한 장이 2~3 m | + +- BT31 에서 안쪽 장식 띠를 만들 때 '높이에 맞추기'를 납작한 판에 쓴 PM 의 실수다. 같은 실수가 다른 맵에 있는지 전 맵의 소품 기록을 훑었다 — 넓적하고 배율 1.5 배를 넘는 것은 던전의 바닥돌 46개와 항구의 널빤지 19개(1.5~2 배 · 5.5~8 m)뿐. 항구 널빤지는 이번에 손대지 않았다. + +### 9-2. 한 일 +- **큰 바닥돌 띠를 걷어냈다**(정의의 띠 · 풀 삭제). +- **지형 층 넷을 던전 타일로**: 팩의 던전 타일(FloorA 온전 · FloorA3 얼룩)을 1024 픽셀 사본으로 만들어 4 m 로 깐다(1 m 당 256 픽셀 · 돌 한 장 0.5~1 m). 길 = 온전한 타일 · 방과 보스 자리 = 얼룩진 타일 · 오염 저수조와 감옥 = 줄눈에 점액이 고인 타일(얼룩진 타일에 그려 넣음) · 바탕 = 흙과 자갈(5 m). + - 타일 세 가지는 줄눈 배치가 같다(높이 맵 상관 0.74~0.82) → 층이 바뀌어도 줄눈이 이어진다. + - 게임 에셋의 들여오기 설정은 건드리지 않았다(사본을 맵 폴더에 둔다). +- **돌판 다리**: 바닥과 같은 타일을 같은 크기로 입힌 판(`Maps/PlagueCatacomb/Deck/FRMap_dun_deck`)으로 교체. +- **뼈 무더기의 네모난 판**: 가장자리 판의 재질(CATA_BonePileAlfa)이 게임 전용 '불투명' 셰이더라 네모가 그대로 보였다. 재질 파일에 남은 바뀌기 전 값(_AlphaClip 1)을 읽어 맵 재질 사본에서는 오려 낸다 — 바뀐 사본은 이것 하나(사본 50개 대조) [실측]. 이 사본은 맵 6종이 함께 쓴다. + +### 9-3. 조정 과정 +- 1차(타일 세 가지만): 또렷해졌지만 넓은 보스 자리에서 같은 무늬가 4 m 마다 격자처럼 보였다(세 타일의 줄눈 배치가 같아 덧칠로는 되풀이가 가려지지 않는다). +- 2차: 깨진 타일 대신 무늬가 다른 흙 층을 넣어 얼룩으로 덮고, 텍스처 안의 밝기 차(눈에 띄는 밝은 돌)를 폈다 → 격자가 약해졌다. 점액은 선명한 녹색이 페인트처럼 보여 탁한 올리브빛으로 낮췄다. + +### 9-4. 결과 [실측] +- 빌드 RESULT OK · 길 폭 최소 7.5 m · 보스 자리 안 막힌 칸 0 ㎡ · 걸을 수 있는 면적 8,199 ㎡(바뀌지 않음). +- 지형 층 넷 모두 1024 픽셀 · 1 m 당 256 픽셀(흙 층은 205). +- 보스 자리 층 몫: 흙 19 · 온전한 타일 25 · 얼룩진 타일 47 · 점액 타일 9(%). +- Play(로비 → 필드 이동 → 던전, 보스전 전용 필드 R4 입장 · 퇴장): 에디터 로그 오류 0. +- 그림: `img/bt38_room_before_after.jpg` · `bt38_corridor_before_after.jpg` · `bt38_deck_before_after.jpg` · `bt38_bossfield_after.jpg`(왼쪽이 앞 · 오른쪽이 뒤). + +### 9-5. 남은 것 · 미검증 +- 4 m 마다의 되풀이는 약해졌지만 없어지지 않았다(넓은 바닥을 멀리서 보면 보인다). +- 쓰이지 않게 된 에셋은 **지우지 않고 남겼다(PD 결정 2026-09-28)** — 던전의 옛 지형 층 5개 · 텍스처 6개를 포함해 맵 폴더 전체에서 35개(10.0 MB). 목록과 까닭은 `미사용_에셋_목록.md`(같은 폴더). +- 화면이 전체로 어둡고 녹색 한 가지인 것은 맵 분위기 값 때문이다(이번 지시 범위 밖이라 손대지 않았다). +- 기기 빌드에서의 모습 · 성능은 확인하지 못했다. +- FR `bt/work` `5f9c8dd297` 로 push 완료(PD 지시 2026-09-28). + diff --git a/공유/기획/FirstRising/맵/img/bt38_bossfield_after.jpg b/공유/기획/FirstRising/맵/img/bt38_bossfield_after.jpg new file mode 100644 index 0000000..d99e04b Binary files /dev/null and b/공유/기획/FirstRising/맵/img/bt38_bossfield_after.jpg differ diff --git a/공유/기획/FirstRising/맵/img/bt38_corridor_before_after.jpg b/공유/기획/FirstRising/맵/img/bt38_corridor_before_after.jpg new file mode 100644 index 0000000..ba9f178 Binary files /dev/null and b/공유/기획/FirstRising/맵/img/bt38_corridor_before_after.jpg differ diff --git a/공유/기획/FirstRising/맵/img/bt38_deck_before_after.jpg b/공유/기획/FirstRising/맵/img/bt38_deck_before_after.jpg new file mode 100644 index 0000000..83c6bb2 Binary files /dev/null and b/공유/기획/FirstRising/맵/img/bt38_deck_before_after.jpg differ diff --git a/공유/기획/FirstRising/맵/img/bt38_room_before_after.jpg b/공유/기획/FirstRising/맵/img/bt38_room_before_after.jpg new file mode 100644 index 0000000..e744c47 Binary files /dev/null and b/공유/기획/FirstRising/맵/img/bt38_room_before_after.jpg differ diff --git a/공유/기획/FirstRising/맵/tools/floor_tour.sh b/공유/기획/FirstRising/맵/tools/floor_tour.sh new file mode 100644 index 0000000..cee79b4 --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/floor_tour.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# 맵 하나의 바닥을 자리마다 찍는다. bash floor_tour.sh <꼬리표> <맵 이름> " " [" " ...] → cap/floor_<맵>_<꼬리표>_<번호>.png +# Entry 에서 Play → 로비 → 필드 이동 → 준비 기다림 → 자리마다 촬영(임시 카메라 · 플레이어는 옮기지 않는다) → 에디터 로그 점검 → Play 종료 +# BOSS=<보스 열쇠> 를 주면 끝에 그 보스의 전용 필드에도 들어가 찍는다(예: BOSS=R4) +SP="C:/Users/sw/AppData/Local/Temp/claude/E--BurningTimes/3562a2c1-e88a-4143-b7ec-e74c317778d8/scratchpad" +source "$SP/bt.sh" +export PYTHONIOENCODING=utf-8 +TAG="$1"; MAP="$2"; shift; shift +st +python "$SP/logscan.py" mark +unity command clear_console --project-path "$P" --format json --no-banner >/dev/null 2>&1 +unity command editor_play --project-path "$P" --format json --no-banner 2>&1 | grep -E '"success"' | head -1 +for i in $(seq 1 40); do + sleep 3 + r=$(ev lobby_ready 2>&1 | head -1) + case "$r" in *"scene=Lobby"*"player=True"*"config=True"*) echo "ready $i"; break;; esac +done +sleep 6 +ev travel go "$MAP" | grep -o "go=[A-Za-z]*" +ok="" +for k in $(seq 1 60); do + sleep 2 + s=$(ev travel state 2>&1 | head -1) + case "$s" in *"traveling=False"*"current=$MAP/"*"player=True"*) ok="1"; break;; esac +done +sleep 8 +echo "[$MAP] ${ok:+OK} $(ev travel state | grep -o 'characters=[0-9]*\|pos=([^)]*)' | tr '\n' ' ')" +ev shot_cam "floor_${MAP}_${TAG}_0start" >/dev/null +n=1 +for xz in "$@"; do + set -- $xz + ev shot_at "floor_${MAP}_${TAG}_${n}" "$1" "$2" >/dev/null + echo " shot $n ($1, $2)" + n=$((n+1)) +done +python "$SP/logscan.py" scan "floor-$MAP" +if [ -n "$BOSS" ]; then + ev arena enter $BOSS | head -1 + for k in $(seq 1 10); do + sleep 2 + s=$(ev floor_probe 2>&1 | head -1) + case "$s" in "field="*) break;; esac + done + sleep 3 + echo "$BOSS $s" + ev shot_cam "floor_${MAP}_${TAG}_boss" >/dev/null + ev arena exit | head -1 + sleep 3 +fi +unity command editor_stop --project-path "$P" --format json --no-banner 2>&1 | grep -E '"success"' | head -1 +sleep 6 +st +python "$SP/logscan.py" scan "floor-tour-$TAG" diff --git a/공유/기획/FirstRising/맵/tools/map_tune5.py b/공유/기획/FirstRising/맵/tools/map_tune5.py new file mode 100644 index 0000000..1e6bc75 --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/map_tune5.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# 맵 정의 조정(2026-09-28 BT38): 오염된 던전(PlagueCatacomb)의 바닥을 던전 타일로 바꾼다. +# PD "오염된 던전의 바닥 텍스쳐가 너무 크고 퀄리티가 다른 맵에 비해 떨어지는 느낌이야 … 좀 더 자연스러운 던전 타일처럼 수정해줘" +# 원인(실측) 셋: +# ① 바닥을 덮은 큰 바닥돌 — 안쪽 장식 띠 '안쪽 깨진 바닥돌'이 10 m 짜리 바닥 판(GroundTileCATAM)을 '높이 0.04~0.1 m'에 맞춰 놓았는데, +# 판의 높이가 0.02 m 라 2~4.9 배로 커져(폭 21~69 m · 46장) 바닥 전체를 덮었다. 무늬도 그만큼 커지고 흐려졌다 → 띠와 풀을 걷어낸다 +# ② 지형 층 — 바탕 = 무늬 없는 바위 8 m(512 픽셀 · 1 m 에 64 픽셀) · 공터 = 깨진 타일 4.7 m(512 픽셀 · 1 m 에 109 픽셀) +# → 팩의 던전 타일을 1024 픽셀 사본으로 만들어 4 m 로 깐다(1 m 에 256 픽셀 · 돌 한 장 0.5~1 m). 길 = 온전한 타일 · 공터 = 얼룩진 타일 · 강조 = 점액이 고인 타일. +# 세 타일은 줄눈 배치가 같아 층이 바뀌어도 줄눈이 이어진다. 바탕은 무늬가 다른 흙 · 자갈(5 m)로 두고 얼룩으로 덧칠해 4 m 마다의 되풀이를 깬다 +# ③ 돌판 다리(독액 수로 위 상판) — 같은 10 m 바닥 판이라 돌 한 장이 2~3 m → 바닥과 같은 타일을 입힌 판(FRMap_dun_deck)으로 바꾼다 +# 텍스처는 texderive_dungeon.py, 다리 판은 tools/unity_eval/make_dun_deck.cs 가 만든다. 여러 번 돌려도 같다. +# 실행: python map_tune5.py [--no-backup] → 그 뒤 유니티에서 맵을 다시 짓는다(소품이 바뀌므로 Repaint 만으로는 안 된다) +import io, json, os, shutil, sys, time + +ROOT = r"C:\BurningTimes\FirstRising\Assets\BurningTimes\Maps" +NAME = "PlagueCatacomb" +TEX = "Assets/BurningTimes/Maps/PlagueCatacomb/Textures/" +DECK = "Assets/BurningTimes/Maps/PlagueCatacomb/Deck/FRMap_dun_deck.prefab" +BACKUP = r"E:\BurningTimes\공유\개발팀_백업\FirstRising\maps_json_" + time.strftime("%Y%m%d_%H%M") + + +def layer(name, role, tex, tile, tint, nscale=0.6): + return {"name": name, "role": role, "albedo": TEX + tex + ".png", "normal": TEX + tex + "_N.png", "tile": tile, "normalScale": nscale, "tint": list(tint), "smooth": 0.0} + + +def overlay(name, scale, cover, soft, open_, route, area, boss, seed): + return {"layer": name, "scale": scale, "cover": cover, "soft": soft, "open": open_, "route": route, "area": area, "boss": boss, "seed": seed} + + +def main(): + path = os.path.join(ROOT, NAME, NAME + ".map.json") + if "--no-backup" not in sys.argv: + os.makedirs(BACKUP, exist_ok=True) + shutil.copy2(path, os.path.join(BACKUP, NAME + ".map.json")) + print("backup", BACKUP) + raw = io.open(path, "rb").read() + bom = raw.startswith(b"\xef\xbb\xbf") + d = json.loads(raw.decode("utf-8-sig")) + g = d["ground"] + before = [(l["name"], l["tile"], os.path.basename(l["albedo"])) for l in g["layers"]] + # 층의 차례 = 바탕 · 길 · 공터 · 강조(길 · 공터의 floor 번호가 이 차례를 가리킨다 — 차례는 바꾸지 않는다) + g["layers"] = [ + layer("Catacomb_Dirt", "base", "FRMap_dun_dirt", 5.0, (0.86, 0.90, 0.78)), # 방 밖 · 벽 밑 + 타일 위의 흙 얼룩 + layer("Catacomb_Tile", "path", "FRMap_dun_tile", 4.0, (1.12, 1.12, 1.02)), # 통로: 온전한 타일(동선이 읽히게 가장 밝게) + layer("Catacomb_TileWorn", "plaza", "FRMap_dun_tile_worn", 4.0, (0.94, 0.98, 0.86)), # 방 · 보스 자리: 얼룩진 타일 + layer("Catacomb_TileSlime", "accent", "FRMap_dun_tile_slime", 4.0, (1.00, 1.00, 0.95)), # 오염 저수조 · 감옥 · 독액 가: 줄눈에 점액이 고인 타일 + ] + g["overlays"] = [ + overlay("Catacomb_Dirt", 8.0, 0.30, 0.07, 0.60, 0.35, 0.85, 0.75, 19), # 흙 얼룩 — 무늬가 달라 되풀이를 깬다(가장자리는 좁게: 타일 위에 흙이 '쌓인' 모습) + overlay("Catacomb_Tile", 6.0, 0.30, 0.12, 0.50, 0.00, 0.85, 0.85, 33), # 덜 더러운 타일 — 줄눈이 이어지므로 가장자리를 넓게 푼다 + overlay("Catacomb_TileSlime", 4.4, 0.12, 0.10, 0.50, 0.22, 0.75, 0.60, 47), + ] + # ① 큰 바닥돌 걷어내기 + gone_band = [b["name"] for b in d["bands"] if b["pool"] == "inner_slab"] + d["bands"] = [b for b in d["bands"] if b["pool"] != "inner_slab"] + gone_pool = [p["name"] for p in d["pools"] if p["name"] == "inner_slab"] + d["pools"] = [p for p in d["pools"] if p["name"] != "inner_slab"] + print("걷어낸 띠:", gone_band, "· 풀:", gone_pool) + # ③ 돌판 다리의 판 — 풀 'slab' 을 새 판 하나로 + for p in d["pools"]: + if p["name"] != "slab": + continue + old = [os.path.basename(i["path"]) for i in p["items"]] + p["items"] = [{"path": DECK, "fit": "l", "a": 10.0, "b": 10.0, "occ": 0.0, "w": 1, "fade": False, "anchor": "c", "sink": 0.0, "block": "pass", "tilt": 0.0, "yoff": 0.0}] + print("돌판 다리의 판:", old, "→", os.path.basename(DECK)) + text = json.dumps(d, ensure_ascii=False, indent=2) + io.open(path, "w", encoding="utf-8-sig" if bom else "utf-8", newline="\n").write(text + "\n") + print("앞:", before) + print("뒤:", [(l["name"], l["tile"], os.path.basename(l["albedo"])) for l in g["layers"]]) + print("덧칠:", [(o["layer"], o["cover"], o["soft"]) for o in g["overlays"]]) + + +main() diff --git a/공유/기획/FirstRising/맵/tools/texderive_dungeon.py b/공유/기획/FirstRising/맵/tools/texderive_dungeon.py new file mode 100644 index 0000000..d3341ad --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/texderive_dungeon.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +# 2026-09-28 BT38 — 오염된 던전(PlagueCatacomb) 바닥을 '던전 타일'로 다시 짠다. +# PD "오염된 던전의 바닥 텍스쳐가 너무 크고 퀄리티가 다른 맵에 비해 떨어지는 느낌 … 좀 더 자연스러운 던전 타일처럼" +# 원인(실측): 팩 텍스처가 PC 에서 512 픽셀로 들어오고(1 m 에 64~109 픽셀 — 다른 맵의 주 바닥은 223~256), +# 바탕 층은 무늬 없는 바위를 8 m 로, 공터 층은 깨진 타일을 4.7 m 로 깔아 돌 한 장이 1~2 m 였다. +# 방법: 같은 팩의 던전 타일 세 가지(FloorA 온전 · FloorA3 얼룩 · FloorA2 깨짐)는 줄눈 배치가 같다(높이 맵 상관 0.74~0.82). +# → 네 층을 모두 같은 배치 · 같은 크기로 깔면 층이 바뀌어도 줄눈이 이어져 '한 바닥이 닳고 더러워진' 모습이 된다. +# 게임 에셋의 들여오기 설정은 건드리지 않고, 1024 픽셀 사본을 맵 폴더에 만든다(빌더가 1024 · 고화질 압축으로 들여온다). +# 만드는 것(맵 폴더 Textures): FRMap_dun_tile(온전) · FRMap_dun_tile_worn(얼룩) · FRMap_dun_tile_slime(점액) · FRMap_dun_dirt(흙 · 자갈) + 노멀맵(_N) +# 1차 화면 확인: 타일 세 가지는 줄눈 배치가 같아, 덧칠해도 4 m 마다 같은 무늬가 격자처럼 보였다(넓은 보스 자리에서 뚜렷) +# → 깨진 타일 대신 무늬가 다른 흙 층을 넣어 얼룩으로 덮고, 타일 안의 밝기 차(눈에 띄는 밝은 돌)를 더 편다 +# 실행: python texderive_dungeon.py [--project] (--project 를 주면 FR 프로젝트 맵 폴더로 복사한다) +import os, sys, io, json, shutil +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") +import numpy as np +from PIL import Image, ImageFilter + +Image.MAX_IMAGE_PIXELS = None +PROJECT = r"C:\BurningTimes\FirstRising" +PACK = os.path.join(PROJECT, "Assets", "LevelAssets", "Toby Fredson", "Modular Dungeon Catacombs - Mobile", "Textures", "Textures_Terrain") +OUT = r"C:\BurningTimes\FR_maps_staging\_work\dungeon_floor" +DST = os.path.join(PROJECT, "Assets", "BurningTimes", "Maps", "PlagueCatacomb", "Textures") +SIZE = 1024 + + +def load(name, mode="RGB"): + return Image.open(os.path.join(PACK, name)).convert(mode) + + +def down(img): + """2048 → 1024. 줄인 뒤 조금 또렷하게(줄눈 · 돌결이 뭉개지지 않게).""" + small = img.resize((SIZE, SIZE), Image.LANCZOS) + return small.filter(ImageFilter.UnsharpMask(radius=1.2, percent=70, threshold=2)) + + +def down_normal(img): + """노멀맵은 줄인 뒤 길이를 1 로 다시 맞춘다.""" + a = np.asarray(img.resize((SIZE, SIZE), Image.LANCZOS)).astype(np.float64) / 255.0 * 2.0 - 1.0 + n = np.linalg.norm(a, axis=2, keepdims=True) + a = a / np.maximum(n, 1e-6) + return a + + +def save_normal(a, path): + n = np.linalg.norm(a, axis=2, keepdims=True) + a = a / np.maximum(n, 1e-6) + Image.fromarray(np.clip((a * 0.5 + 0.5) * 255.0 + 0.5, 0, 255).astype(np.uint8)).save(path) + + +def wrap_blur(x, radius): + """가장자리가 이어지는 흐림 — 둘레를 되풀이해 붙인 뒤 흐리고 가운데만 잘라 낸다.""" + pad = int(radius * 3) + 2 + p = np.pad(x, pad, mode="wrap") + im = Image.fromarray(np.clip(p * 255.0, 0, 255).astype(np.uint8)) + b = np.asarray(im.filter(ImageFilter.GaussianBlur(radius))).astype(np.float64) / 255.0 + return b[pad:-pad, pad:-pad] + + +def pnoise(size, scale, octaves=4, seed=0): + """주기 노이즈(0~1) — 가장자리가 정확히 이어진다.""" + r = np.random.default_rng(seed) + out = np.zeros((size, size), np.float64) + amp = 1.0 + tot = 0.0 + fy = np.fft.fftfreq(size)[:, None] * size + fx = np.fft.fftfreq(size)[None, :] * size + f = np.hypot(fx, fy) + for o in range(octaves): + w = r.standard_normal((size, size)) + k = scale * (2 ** o) + n = np.real(np.fft.ifft2(np.fft.fft2(w) * np.exp(-(f / k) ** 2))) + n = (n - n.min()) / (n.max() - n.min() + 1e-9) + out += n * amp + tot += amp + amp *= 0.5 + out /= tot + return (out - out.min()) / (out.max() - out.min() + 1e-9) + + +def smooth(x, a, b): + t = np.clip((x - a) / (b - a + 1e-9), 0, 1) + return t * t * (3 - 2 * t) + + +def seam(img): + a = np.asarray(img).astype(np.float32) + inner = (np.abs(a[:, 1:] - a[:, :-1]).mean() + np.abs(a[1:, :] - a[:-1, :]).mean()) * 0.5 + s = (np.abs(a[:, 0] - a[:, -1]).mean() + np.abs(a[0, :] - a[-1, :]).mean()) * 0.5 + return float(s / max(1e-3, inner)) + + +def flatten(img, radius=64.0, amount=0.65): + """큰 밝기 얼룩을 편다 — 텍스처 안의 어두운 돌 무리 · 밝은 자리가 4 m 마다 되풀이되어 눈에 띄는 것을 줄인다(돌결 · 줄눈은 그대로).""" + a = np.asarray(img).astype(np.float64) / 255.0 + l = (a * np.array([0.2126, 0.7152, 0.0722])).sum(axis=2) + low = wrap_blur(l, radius) + k = (low.mean() / np.maximum(low, 1e-3)) ** amount + a = np.clip(a * k[..., None], 0, 1) + return Image.fromarray((a * 255.0 + 0.5).astype(np.uint8)) + + +def grade(img, gain=1.0, gamma=1.0, sat=1.0): + """밝기 · 감마 · 채도를 맞춘다(세 타일의 밝기 차가 너무 크면 층 경계가 얼룩처럼 보인다).""" + a = np.asarray(img).astype(np.float64) / 255.0 + a = np.clip(a * gain, 0, 1) ** gamma + l = (a * np.array([0.2126, 0.7152, 0.0722])).sum(axis=2, keepdims=True) + a = np.clip(l + (a - l) * sat, 0, 1) + return Image.fromarray((a * 255.0 + 0.5).astype(np.uint8)) + + +def main(): + os.makedirs(OUT, exist_ok=True) + rec = [] + + # ---------- 온전한 타일 · 얼룩진 타일 · 깨진 타일 + src = { + "tile": ("CatacombsFloorA_Albedo.png", "CatacombsFloorA_Normal.png", dict(gain=1.00, gamma=1.0, sat=0.95)), + "tile_worn": ("CatacombsFloorA3_Albedo.png", "CatacombsFloorA3_Normal.png", dict(gain=1.22, gamma=0.96, sat=0.95)), + } + made = {} + normals = {} + for key, (alb, nrm, g) in src.items(): + img = grade(flatten(flatten(down(load(alb)), 64.0, 0.8), 18.0, 0.45), **g) # 큰 얼룩(64) + 돌 한 장 크기(18)의 밝기 차를 편다 + n = down_normal(load(nrm)) + img.save(os.path.join(OUT, "FRMap_dun_%s.png" % key)) + save_normal(n, os.path.join(OUT, "FRMap_dun_%s_N.png" % key)) + made[key] = img + normals[key] = n + mean = np.asarray(img).reshape(-1, 3).mean(axis=0) + nmean = np.asarray(load(nrm).resize((64, 64))).reshape(-1, 3).mean(axis=0) + rec.append({"name": key, "from": alb, "mean": [round(float(v), 1) for v in mean], "seam": round(seam(img), 2), "normal_mean": [round(float(v)) for v in nmean]}) + + # ---------- 흙 · 자갈(무늬가 다른 층 — 타일 위를 얼룩으로 덮어 되풀이를 깬다) + dirt = grade(flatten(down(load("PebblesCATA_Albedo.png")), 64.0, 0.7), gain=1.05, gamma=1.0, sat=0.9) + dirt.save(os.path.join(OUT, "FRMap_dun_dirt.png")) + save_normal(down_normal(load("PebblesCATA_Normal.png")), os.path.join(OUT, "FRMap_dun_dirt_N.png")) + rec.append({"name": "dirt", "from": "PebblesCATA_Albedo.png", "mean": [round(float(v), 1) for v in np.asarray(dirt).reshape(-1, 3).mean(axis=0)], "seam": round(seam(dirt), 2)}) + + # ---------- 점액이 고인 타일(얼룩진 타일 + 줄눈에 고인 녹색 점액 + 드문 웅덩이) + base = np.asarray(made["tile_worn"]).astype(np.float64) / 255.0 + h = np.asarray(load("CatacombsFloorA3_Height.png", "L").resize((SIZE, SIZE), Image.LANCZOS)).astype(np.float64) / 255.0 + low = wrap_blur(h, 7.0) - h # 둘레보다 낮은 곳(줄눈 · 패인 곳) + joints = smooth(low, 0.012, 0.05) + joints = np.clip(wrap_blur(joints, 1.6) * 1.5, 0, 1) # 줄눈에서 조금 번지게 + patch = smooth(pnoise(SIZE, 3.0, 4, 381), 0.42, 0.66) # 점액이 도는 구역(큰 얼룩) + pool = smooth(pnoise(SIZE, 5.0, 4, 977), 0.70, 0.82) * smooth(pnoise(SIZE, 2.0, 3, 55), 0.40, 0.65) # 드문 웅덩이 + slime = np.clip(joints * patch * 0.95 + pool * 0.9, 0, 1) + fine = pnoise(SIZE, 24.0, 3, 412) + lum = (base * np.array([0.2126, 0.7152, 0.0722])).sum(axis=2) + green = np.stack([0.20 + 0.08 * fine, 0.34 + 0.12 * fine, 0.09 + 0.04 * fine], axis=2) # 탁한 올리브빛(선명한 녹색은 페인트처럼 보였다) + body = green * (0.55 + 0.9 * lum[..., None]) + rim = smooth(slime, 0.15, 0.5) - smooth(slime, 0.5, 0.95) # 점액 가장자리는 조금 밝게(젖은 테) + out = base * (1 - slime[..., None] * 0.78) + body * (slime[..., None] * 0.78) + out = np.clip(out + rim[..., None] * np.array([0.02, 0.045, 0.008]), 0, 1) + img = Image.fromarray((out * 255.0 + 0.5).astype(np.uint8)) + img.save(os.path.join(OUT, "FRMap_dun_tile_slime.png")) + n = normals["tile_worn"].copy() + flat = np.zeros_like(n) + flat[..., 2] = 1.0 + k = (pool * 0.85 + joints * patch * 0.4)[..., None] + k = np.clip(k, 0, 0.9) + n = n * (1 - k) + flat * k # 웅덩이는 매끈하게 + save_normal(n, os.path.join(OUT, "FRMap_dun_tile_slime_N.png")) + made["tile_slime"] = img + rec.append({"name": "tile_slime", "from": "tile_worn + 점액", "mean": [round(float(v), 1) for v in np.asarray(img).reshape(-1, 3).mean(axis=0)], + "seam": round(seam(img), 2), "slime_share": round(float((slime > 0.5).mean()), 3)}) + + # ---------- 미리 보기: 16 m × 12 m 바닥을 1 m = 64 픽셀로(타일 한 장 = TILE m) — 사람 키(1.8 m) 막대와 함께 + TILE = float(os.environ.get("BT_TILE", "4.0")) + ppm = 64 + W, H = 16, 12 + rep = int(round(TILE * ppm)) + def tiled(key): + t = np.asarray(made[key].resize((rep, rep), Image.LANCZOS)).astype(np.float64) + ny, nx = H * ppm // rep + 2, W * ppm // rep + 2 + return np.tile(t, (ny, nx, 1))[:H * ppm, :W * ppm] + yy, xx = np.mgrid[0:H * ppm, 0:W * ppm] + r = np.random.default_rng(7) + def blot(scale, seed): + n = pnoise(1024, scale, 4, seed) + return np.tile(n, (2, 2))[:H * ppm, :W * ppm] + canvas = tiled("tile_worn") # 방 = 얼룩진 타일 + path = smooth(np.abs(yy / ppm - 6.0), 3.4, 2.2) # 가운데 가로 통로 = 온전한 타일 + canvas = canvas * (1 - path[..., None]) + tiled("tile") * path[..., None] + dt = np.asarray(dirt.resize((int(round(5.0 * ppm)), int(round(5.0 * ppm))), Image.LANCZOS)).astype(np.float64) + dt = np.tile(dt, (H * ppm // dt.shape[0] + 2, W * ppm // dt.shape[1] + 2, 1))[:H * ppm, :W * ppm] + m = smooth(blot(2.0, 3), 0.55, 0.66)[..., None] * 0.92 # 흙 얼룩(5 m 로 깐다) + canvas = canvas * (1 - m) + dt * m + m = smooth(blot(3.0, 9), 0.62, 0.74)[..., None] * 0.9 # 점액 얼룩 + canvas = canvas * (1 - m) + tiled("tile_slime") * m + prev = Image.fromarray(np.clip(canvas, 0, 255).astype(np.uint8)) + from PIL import ImageDraw + d = ImageDraw.Draw(prev) + d.rectangle([40, 40, 40 + int(0.6 * ppm), 40 + int(1.8 * ppm)], outline=(255, 60, 60), width=3) # 사람(0.6 × 1.8 m) + d.line([40, 20, 40 + ppm, 20], fill=(255, 255, 0), width=4) # 1 m + prev.save(os.path.join(OUT, "preview_floor_%sm.png" % ("%.1f" % TILE).replace(".", "_"))) + + for x in rec: + print(json.dumps(x, ensure_ascii=False)) + if "--project" in sys.argv: + os.makedirs(DST, exist_ok=True) + n = 0 + for f in sorted(os.listdir(OUT)): + # 1차에 만들었던 깨진 타일(tile_broken)은 쓰지 않는다 — 폴더에 남아 있어도 옮기지 않는다 + if (f.startswith("FRMap_dun_tile") or f.startswith("FRMap_dun_dirt")) and f.endswith(".png") and "broken" not in f: + shutil.copy2(os.path.join(OUT, f), os.path.join(DST, f)) + n += 1 + print("프로젝트로 복사", n, "→", DST) + + +main() diff --git a/공유/기획/FirstRising/맵/tools/unity_eval/clip_impact.cs b/공유/기획/FirstRising/맵/tools/unity_eval/clip_impact.cs new file mode 100644 index 0000000..9986d35 --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/unity_eval/clip_impact.cs @@ -0,0 +1,45 @@ +// 재질 파일에 남은 _AlphaClip 값으로 오려내기를 켜면 어떤 사본이 바뀌는지 미리 본다(편집 모드 · 읽기 전용) +var sb = new System.Text.StringBuilder(); +var byKey = new System.Collections.Generic.Dictionary(); +foreach (var g in UnityEditor.AssetDatabase.FindAssets("t:Material", new[] { "Assets" })) +{ + var p = UnityEditor.AssetDatabase.GUIDToAssetPath(g); + if (p.StartsWith("Assets/BurningTimes/Maps/_Shared/Materials/")) continue; + string safe = ""; + foreach (var c in System.IO.Path.GetFileNameWithoutExtension(p)) + safe += char.IsLetterOrDigit(c) ? c : '_'; + byKey[safe + "_" + g.Substring(0, 6)] = p; +} +int change = 0, total = 0; +foreach (var g in UnityEditor.AssetDatabase.FindAssets("t:Material", new[] { "Assets/BurningTimes/Maps/_Shared/Materials/Lit" })) +{ + var cp = UnityEditor.AssetDatabase.GUIDToAssetPath(g); + var name = System.IO.Path.GetFileNameWithoutExtension(cp); + if (!name.EndsWith("_Lit")) continue; // 게임 전용 셰이더에서 옮긴 사본만 + string key = name.Substring(0, name.LastIndexOf('_')); + if (!byKey.TryGetValue(key, out var sp)) continue; + var src = UnityEditor.AssetDatabase.LoadAssetAtPath(sp); + var copy = UnityEditor.AssetDatabase.LoadAssetAtPath(cp); + if (src == null || copy == null) continue; + total++; + float saved = -1f; + var floats = new UnityEditor.SerializedObject(src).FindProperty("m_SavedProperties.m_Floats"); + for (int i = 0; floats != null && i < floats.arraySize; i++) + { + var e = floats.GetArrayElementAtIndex(i); + if (e.FindPropertyRelative("first").stringValue == "_AlphaClip") + saved = e.FindPropertyRelative("second").floatValue; + } + var tex = copy.HasProperty("_BaseMap") ? copy.GetTexture("_BaseMap") as UnityEngine.Texture2D : null; + bool fmtAlpha = tex != null && UnityEngine.Experimental.Rendering.GraphicsFormatUtility.HasAlphaChannel(tex.graphicsFormat); + var ti = tex != null ? UnityEditor.AssetImporter.GetAtPath(UnityEditor.AssetDatabase.GetAssetPath(tex)) as UnityEditor.TextureImporter : null; + bool srcAlpha = ti != null && ti.DoesSourceTextureHaveAlpha(); + bool copyClip = copy.HasProperty("_AlphaClip") && copy.GetFloat("_AlphaClip") > 0.5f; + bool will = saved > 0.5f && (fmtAlpha || srcAlpha); + if (saved > 0.5f || copyClip) + sb.Append(will && !copyClip ? "[켜짐으로 바뀜] " : (copyClip ? "[이미 오려냄] " : "[알파 없음 · 그대로] ")).Append(name).Append(" ← ").Append(src.name).Append(" [").Append(src.shader.name) + .Append("] 남은 _AlphaClip ").Append(saved.ToString("0")).Append(" · 텍스처 ").Append(tex != null ? tex.name : "없음").Append(" 형식 알파 ").Append(fmtAlpha).Append(" 원본 알파 ").Append(srcAlpha).Append('\n'); + if (will && !copyClip) change++; +} +sb.Append("게임 셰이더에서 옮긴 사본 ").Append(total).Append("개 · 오려내기가 새로 켜질 것 ").Append(change).Append('\n'); +return sb.ToString(); diff --git a/공유/기획/FirstRising/맵/tools/unity_eval/lit_sources.cs b/공유/기획/FirstRising/맵/tools/unity_eval/lit_sources.cs new file mode 100644 index 0000000..d128fd2 --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/unity_eval/lit_sources.cs @@ -0,0 +1,51 @@ +// 맵 빌더가 만든 재질 사본(_Shared/Materials/Lit)마다 원본 재질을 찾아, 원본이 알파를 쓰는 재질인지와 사본이 오려 내는지를 견준다(편집 모드 · 읽기 전용) +var sb = new System.Text.StringBuilder(); +var byGuid = new System.Collections.Generic.Dictionary(); // 'guid 앞 6자 + 이름' → 경로 +foreach (var g in UnityEditor.AssetDatabase.FindAssets("t:Material", new[] { "Assets" })) +{ + var p = UnityEditor.AssetDatabase.GUIDToAssetPath(g); + if (p.StartsWith("Assets/BurningTimes/Maps/_Shared/Materials/")) continue; + string safe = ""; + foreach (var c in System.IO.Path.GetFileNameWithoutExtension(p)) + safe += char.IsLetterOrDigit(c) ? c : '_'; + byGuid[safe + "_" + g.Substring(0, 6)] = p; +} +int n = 0, suspect = 0; +foreach (var g in UnityEditor.AssetDatabase.FindAssets("t:Material", new[] { "Assets/BurningTimes/Maps/_Shared/Materials/Lit" })) +{ + var cp = UnityEditor.AssetDatabase.GUIDToAssetPath(g); + var name = System.IO.Path.GetFileNameWithoutExtension(cp); + int cut = name.LastIndexOf('_'); + if (cut <= 0) continue; + string key = name.Substring(0, cut); + if (!byGuid.TryGetValue(key, out var sp)) continue; + var src = UnityEditor.AssetDatabase.LoadAssetAtPath(sp); + var copy = UnityEditor.AssetDatabase.LoadAssetAtPath(cp); + if (src == null || copy == null) continue; + n++; + bool srcTest = src.IsKeywordEnabled("_ALPHATEST_ON") || (src.HasProperty("_AlphaClip") && src.GetFloat("_AlphaClip") > 0.5f) || (src.HasProperty("_AlphaCutoffEnable") && src.GetFloat("_AlphaCutoffEnable") > 0.5f); + bool srcTransparent = src.renderQueue >= 2450 || src.shader.name.Contains("Transparent") || src.shader.name.Contains("Cutout") || src.shader.name.Contains("AlphaTest") || (src.HasProperty("_Surface") && src.GetFloat("_Surface") > 0.5f); + bool copyClip = copy.HasProperty("_AlphaClip") && copy.GetFloat("_AlphaClip") > 0.5f; + if ((srcTest || srcTransparent) && !copyClip) + { + suspect++; + sb.Append("[원본은 알파 · 사본은 안 오려냄] ").Append(name).Append(" ← ").Append(sp).Append(" [").Append(src.shader.name).Append("] 큐 ").Append(src.renderQueue) + .Append(" 오려내기 키워드 ").Append(src.IsKeywordEnabled("_ALPHATEST_ON")).Append(" _AlphaClip ").Append(src.HasProperty("_AlphaClip") ? src.GetFloat("_AlphaClip").ToString("0") : "-") + .Append(" _Cutoff ").Append(src.HasProperty("_Cutoff") ? src.GetFloat("_Cutoff").ToString("0.00") : "-").Append('\n'); + } +} +sb.Append("사본 ").Append(n).Append("개 가운데 의심 ").Append(suspect).Append('\n'); +// 던전 바닥돌 · 뼈 무더기 프리팹의 재질 +foreach (var p in new[] { "GroundTileCATAM_A", "CATA_BonePileA", "CATA_BonePileB" }) + foreach (var g in UnityEditor.AssetDatabase.FindAssets(p + " t:Prefab")) + { + var path = UnityEditor.AssetDatabase.GUIDToAssetPath(g); + if (System.IO.Path.GetFileNameWithoutExtension(path) != p) continue; + var go = UnityEditor.AssetDatabase.LoadAssetAtPath(path); + foreach (var r in go.GetComponentsInChildren(true)) + foreach (var m in r.sharedMaterials) + if (m != null) + sb.Append(p).Append(" / ").Append(r.name).Append(" → ").Append(m.name).Append(" [").Append(m.shader.name).Append("] 큐 ").Append(m.renderQueue) + .Append(" ").Append(UnityEditor.AssetDatabase.GetAssetPath(m)).Append('\n'); + } +return sb.ToString(); diff --git a/공유/기획/FirstRising/맵/tools/unity_eval/make_dun_deck.cs b/공유/기획/FirstRising/맵/tools/unity_eval/make_dun_deck.cs new file mode 100644 index 0000000..545f083 --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/unity_eval/make_dun_deck.cs @@ -0,0 +1,88 @@ +// 오염된 던전의 돌판 다리(독액 수로 위 상판)에 쓸 판을 만든다(편집 모드) — 2026-09-28 BT38 +// 앞서 쓰던 팩의 바닥 판(GroundTileCATAM · 10 m)은 돌 한 장이 2~3 m 로 커서 바닥 타일(0.5~1 m)과 어긋났다. +// → 바닥과 같은 타일 텍스처(FRMap_dun_tile)를 같은 크기(4 m 에 한 장)로 입힌 10 × 0.3 × 10 m 판을 만든다. 충돌체는 달지 않는다(막는 것은 보이지 않는 벽). +// 만드는 것: Assets/BurningTimes/Maps/PlagueCatacomb/Deck/FRMap_dun_deck.asset(메시) · .mat(재질) · .prefab. 여러 번 돌려도 같은 에셋을 고쳐 쓴다. +if (UnityEditor.EditorApplication.isPlaying) + return "Play 중이라 하지 않음"; +string dir = "Assets/BurningTimes/Maps/PlagueCatacomb/Deck"; +string tex = "Assets/BurningTimes/Maps/PlagueCatacomb/Textures/FRMap_dun_tile.png"; +string nrm = "Assets/BurningTimes/Maps/PlagueCatacomb/Textures/FRMap_dun_tile_N.png"; +float size = 10f, thick = 0.3f, tile = 4f; +System.IO.Directory.CreateDirectory(dir); +UnityEditor.AssetDatabase.Refresh(); + +// ---------- 메시: 여섯 면 · 면마다 꼭짓점 넷. UV 는 1 m = 1/tile(윗면은 x · z, 옆면은 길이 · 높이) +var v = new System.Collections.Generic.List(); +var n = new System.Collections.Generic.List(); +var uv = new System.Collections.Generic.List(); +var tri = new System.Collections.Generic.List(); +float h = size * 0.5f, t = thick * 0.5f; +System.Action quad = (a, b, c, d, normal, w, hgt) => +{ + int i0 = v.Count; + v.Add(a); v.Add(b); v.Add(c); v.Add(d); + for (int k = 0; k < 4; k++) n.Add(normal); + uv.Add(new UnityEngine.Vector2(0f, 0f)); uv.Add(new UnityEngine.Vector2(w / tile, 0f)); uv.Add(new UnityEngine.Vector2(w / tile, hgt / tile)); uv.Add(new UnityEngine.Vector2(0f, hgt / tile)); + tri.Add(i0); tri.Add(i0 + 2); tri.Add(i0 + 1); tri.Add(i0); tri.Add(i0 + 3); tri.Add(i0 + 2); +}; +// 윗면(+y) · 아랫면(-y) +quad(new UnityEngine.Vector3(-h, t, -h), new UnityEngine.Vector3(h, t, -h), new UnityEngine.Vector3(h, t, h), new UnityEngine.Vector3(-h, t, h), UnityEngine.Vector3.up, size, size); +quad(new UnityEngine.Vector3(-h, -t, h), new UnityEngine.Vector3(h, -t, h), new UnityEngine.Vector3(h, -t, -h), new UnityEngine.Vector3(-h, -t, -h), UnityEngine.Vector3.down, size, size); +// 옆면 넷(-z · +x · +z · -x) +quad(new UnityEngine.Vector3(-h, -t, -h), new UnityEngine.Vector3(h, -t, -h), new UnityEngine.Vector3(h, t, -h), new UnityEngine.Vector3(-h, t, -h), UnityEngine.Vector3.back, size, thick); +quad(new UnityEngine.Vector3(h, -t, -h), new UnityEngine.Vector3(h, -t, h), new UnityEngine.Vector3(h, t, h), new UnityEngine.Vector3(h, t, -h), UnityEngine.Vector3.right, size, thick); +quad(new UnityEngine.Vector3(h, -t, h), new UnityEngine.Vector3(-h, -t, h), new UnityEngine.Vector3(-h, t, h), new UnityEngine.Vector3(h, t, h), UnityEngine.Vector3.forward, size, thick); +quad(new UnityEngine.Vector3(-h, -t, h), new UnityEngine.Vector3(-h, -t, -h), new UnityEngine.Vector3(-h, t, -h), new UnityEngine.Vector3(-h, t, h), UnityEngine.Vector3.left, size, thick); +string meshPath = dir + "/FRMap_dun_deck.asset"; +var mesh = UnityEditor.AssetDatabase.LoadAssetAtPath(meshPath); +bool newMesh = mesh == null; +if (newMesh) mesh = new UnityEngine.Mesh { name = "FRMap_dun_deck" }; +mesh.Clear(); +mesh.SetVertices(v); +mesh.SetNormals(n); +mesh.SetUVs(0, uv); +mesh.SetTriangles(tri, 0); +mesh.RecalculateTangents(); +mesh.RecalculateBounds(); +if (newMesh) UnityEditor.AssetDatabase.CreateAsset(mesh, meshPath); +else UnityEditor.EditorUtility.SetDirty(mesh); + +// ---------- 재질: URP Simple Lit(맵의 다른 소품 사본과 같은 셰이더) · 무광 +var shader = UnityEngine.Shader.Find("Universal Render Pipeline/Simple Lit"); +if (shader == null) + return "Simple Lit 셰이더 없음"; +string matPath = dir + "/FRMap_dun_deck.mat"; +var mat = UnityEditor.AssetDatabase.LoadAssetAtPath(matPath); +bool newMat = mat == null; +if (newMat) mat = new UnityEngine.Material(shader) { name = "FRMap_dun_deck" }; +mat.shader = shader; +var albedo = UnityEditor.AssetDatabase.LoadAssetAtPath(tex); +var normalTex = UnityEditor.AssetDatabase.LoadAssetAtPath(nrm); +mat.SetTexture("_BaseMap", albedo); +mat.SetTexture("_MainTex", albedo); +mat.SetColor("_BaseColor", new UnityEngine.Color(1f, 1f, 0.92f, 1f)); +mat.SetColor("_Color", new UnityEngine.Color(1f, 1f, 0.92f, 1f)); +mat.SetTexture("_BumpMap", normalTex); +if (normalTex != null) mat.EnableKeyword("_NORMALMAP"); +mat.SetFloat("_Surface", 0f); +mat.SetFloat("_AlphaClip", 0f); +mat.SetFloat("_Cull", 2f); +mat.SetFloat("_SpecularHighlights", 0f); +mat.EnableKeyword("_SPECULARHIGHLIGHTS_OFF"); +mat.SetFloat("_Smoothness", 0.1f); +mat.SetColor("_SpecColor", new UnityEngine.Color(0.1f, 0.1f, 0.1f, 1f)); +mat.enableInstancing = true; +if (newMat) UnityEditor.AssetDatabase.CreateAsset(mat, matPath); +else UnityEditor.EditorUtility.SetDirty(mat); + +// ---------- 프리팹 +string prefabPath = dir + "/FRMap_dun_deck.prefab"; +var go = new UnityEngine.GameObject("FRMap_dun_deck"); +go.AddComponent().sharedMesh = mesh; +var mr = go.AddComponent(); +mr.sharedMaterial = mat; +mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On; +var saved = UnityEditor.PrefabUtility.SaveAsPrefabAsset(go, prefabPath); +UnityEngine.Object.DestroyImmediate(go); +UnityEditor.AssetDatabase.SaveAssets(); +return "메시 " + (newMesh ? "새로 만듦" : "고쳐 씀") + " · 꼭짓점 " + mesh.vertexCount + " · 크기 " + mesh.bounds.size + " · 재질 " + (newMat ? "새로 만듦" : "고쳐 씀") + " · 텍스처 " + (albedo != null) + "/" + (normalTex != null) + " · 프리팹 " + (saved != null ? prefabPath : "실패"); diff --git a/공유/기획/FirstRising/맵/tools/unity_eval/repaint_one.cs b/공유/기획/FirstRising/맵/tools/unity_eval/repaint_one.cs new file mode 100644 index 0000000..3593192 --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/unity_eval/repaint_one.cs @@ -0,0 +1,17 @@ +// 맵 하나의 바닥만 다시 칠한다(편집 모드 · 씬을 열지 않는다). args.txt = 맵 이름. 결과는 Logs/bt_map_<이름>_ground.txt +var name = System.IO.File.ReadAllText(@"C:\Users\sw\AppData\Local\Temp\claude\E--BurningTimes\3562a2c1-e88a-4143-b7ec-e74c317778d8\scratchpad\eval\args.txt").Trim(); +if (UnityEditor.EditorApplication.isPlaying) + return "Play 중이라 하지 않음"; +var result = BurningTimes.EditorTools.Maps.MapBuilder.Repaint(name); +var sb = new System.Text.StringBuilder(); +foreach (var line in result.Split('\n')) + if (line.StartsWith("[BT Map") || line.StartsWith("RESULT") || line.StartsWith("보스 자리") || line.StartsWith("바닥") || line.Contains("덧칠") || line.Contains("없음")) + sb.Append(line).Append('\n'); +// 지형 층이 쓰는 텍스처의 실제 크기(들여오기 결과) +var td = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/BurningTimes/Maps/" + name + "/Terrain/" + name + "_Terrain.asset"); +if (td != null) + foreach (var l in td.terrainLayers) + sb.Append(" 층 ").Append(l.name).Append(" tile=").Append(l.tileSize.x.ToString("0.0")).Append(" m · 색 ").Append(l.diffuseTexture != null ? l.diffuseTexture.name + " " + l.diffuseTexture.width + "px" : "없음") + .Append(" · 노멀 ").Append(l.normalMapTexture != null ? l.normalMapTexture.name + " " + l.normalMapTexture.width + "px" : "없음") + .Append(" · 1 m 에 ").Append(l.diffuseTexture != null ? (l.diffuseTexture.width / l.tileSize.x).ToString("0") : "-").Append(" 픽셀\n"); +return sb.ToString(); diff --git a/공유/기획/FirstRising/맵/tools/unity_eval/save_all.cs b/공유/기획/FirstRising/맵/tools/unity_eval/save_all.cs new file mode 100644 index 0000000..f7353bb --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/unity_eval/save_all.cs @@ -0,0 +1,12 @@ +// 커밋 전에 에디터가 들고 있는 변경을 디스크에 쓴다(편집 모드) — 저장하지 않은 에셋 · 열린 씬의 변경 여부를 함께 돌려준다 +if (UnityEditor.EditorApplication.isPlaying) + return "Play 중이라 하지 않음"; +var sb = new System.Text.StringBuilder(); +for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) +{ + var s = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); + sb.Append("씬 ").Append(s.path).Append(" 변경됨=").Append(s.isDirty).Append(" | "); +} +UnityEditor.AssetDatabase.SaveAssets(); +sb.Append("에셋 저장 완료"); +return sb.ToString(); diff --git a/공유/기획/FirstRising/맵/tools/unity_eval/unused_scan.cs b/공유/기획/FirstRising/맵/tools/unity_eval/unused_scan.cs new file mode 100644 index 0000000..c8b78f9 --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/unity_eval/unused_scan.cs @@ -0,0 +1,73 @@ +// 맵 폴더(Assets/BurningTimes/Maps)에서 아무 데서도 쓰이지 않는 에셋을 찾는다(편집 모드 · 읽기 전용). 결과는 Logs/bt_unused_maps_assets.json +// '쓰인다'의 기준: ① 씬(Assets/Scenes 아래 전부) · 로비 프리팹 · 보스전 전용 필드 프리팹 · Resources 폴더의 에셋이 (건너 건너) 가리키는 것 +// ② 맵 정의(<맵>.map.json) · 소품 목록(props.json)에 경로가 적힌 것과 그것이 가리키는 것 — 지금 놓이지 않았어도 빌더의 재료다 +// 그 밖은 '쓰이지 않음'. 정의 파일(.json) · 폴더는 후보에서 뺀다. +if (UnityEditor.EditorApplication.isPlaying) + return "Play 중이라 하지 않음"; +string scope = "Assets/BurningTimes/Maps"; +var roots = new System.Collections.Generic.HashSet(); +foreach (var g in UnityEditor.AssetDatabase.FindAssets("t:Scene", new[] { "Assets/Scenes" })) + roots.Add(UnityEditor.AssetDatabase.GUIDToAssetPath(g)); +foreach (var g in UnityEditor.AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/BurningTimes" })) +{ + var p = UnityEditor.AssetDatabase.GUIDToAssetPath(g); + if (p.Contains("/Resources/")) roots.Add(p); +} +foreach (var g in UnityEditor.AssetDatabase.FindAssets("", new[] { "Assets/BurningTimes" })) +{ + var p = UnityEditor.AssetDatabase.GUIDToAssetPath(g); + if (p.Contains("/Resources/") && !UnityEditor.AssetDatabase.IsValidFolder(p)) roots.Add(p); +} +roots.Add("Assets/GameAssets/LobbyPlayScene.prefab"); +// 정의 파일에 적힌 경로 +int fromJson = 0; +var rx = new System.Text.RegularExpressions.Regex("\"(Assets/[^\"]+)\""); +foreach (var file in System.IO.Directory.GetFiles(scope, "*.json", System.IO.SearchOption.AllDirectories)) +{ + string text = System.IO.File.ReadAllText(file); + foreach (System.Text.RegularExpressions.Match m in rx.Matches(text)) + { + string p = m.Groups[1].Value; + if (System.IO.File.Exists(p) && roots.Add(p)) fromJson++; + } + // 소품 목록(props.json)은 파일 이름만 적는다 — 같은 폴더의 그 파일을 재료로 본다 + if (System.IO.Path.GetFileName(file) == "props.json") + { + string dir = System.IO.Path.GetDirectoryName(file).Replace('\\', '/'); + foreach (System.Text.RegularExpressions.Match m in new System.Text.RegularExpressions.Regex("\"([^\"/]+\\.(fbx|png))\"").Matches(text)) + { + string p = dir + "/" + m.Groups[1].Value; + if (System.IO.File.Exists(p) && roots.Add(p)) fromJson++; + } + } +} +var rootList = new System.Collections.Generic.List(roots); +var used = new System.Collections.Generic.HashSet(UnityEditor.AssetDatabase.GetDependencies(rootList.ToArray(), true)); +foreach (var r in rootList) used.Add(r); + +var sb = new System.Text.StringBuilder(); +sb.Append("{\n \"scope\": \"").Append(scope).Append("\",\n \"roots\": ").Append(rootList.Count).Append(",\n \"rootsFromJson\": ").Append(fromJson).Append(",\n \"unused\": [\n"); +int total = 0, unused = 0; +long bytes = 0; +bool first = true; +var all = UnityEditor.AssetDatabase.FindAssets("", new[] { scope }); +var seen = new System.Collections.Generic.HashSet(); +foreach (var g in all) +{ + var p = UnityEditor.AssetDatabase.GUIDToAssetPath(g); + if (!seen.Add(p)) continue; + if (UnityEditor.AssetDatabase.IsValidFolder(p)) continue; + if (p.EndsWith(".json")) continue; + total++; + if (used.Contains(p)) continue; + var type = UnityEditor.AssetDatabase.GetMainAssetTypeAtPath(p); + long size = new System.IO.FileInfo(p).Length; + bytes += size; + unused++; + if (!first) sb.Append(",\n"); + first = false; + sb.Append(" {\"path\": \"").Append(p).Append("\", \"type\": \"").Append(type != null ? type.Name : "?").Append("\", \"bytes\": ").Append(size).Append(", \"guid\": \"").Append(g).Append("\"}"); +} +sb.Append("\n ],\n \"total\": ").Append(total).Append(",\n \"unusedCount\": ").Append(unused).Append(",\n \"unusedBytes\": ").Append(bytes).Append("\n}\n"); +System.IO.File.WriteAllText("Logs/bt_unused_maps_assets.json", sb.ToString()); +return "기준 에셋 " + rootList.Count + "(정의 파일에서 " + fromJson + ") · 맵 폴더 에셋 " + total + " · 쓰이지 않음 " + unused + " · " + (bytes / 1048576f).ToString("0.0") + " MB"; diff --git a/공유/기획/FirstRising/맵/tools/unused_report.py b/공유/기획/FirstRising/맵/tools/unused_report.py new file mode 100644 index 0000000..c78d891 --- /dev/null +++ b/공유/기획/FirstRising/맵/tools/unused_report.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# FR 맵 폴더의 '쓰이지 않는 에셋' 목록 문서를 만든다 — 2026-09-28 PD 지시 +# "남겨두되, 어떤 에셋이 안쓰이는지 기록해서 추후 삭제 유무를 결정할 수 있도록 정리해둘 것. (내가 나중에 물어보면 바로 알 수 있게 정리 필요)" +# 순서: ① 유니티(편집 모드)에서 tools/unity_eval/unused_scan.cs 실행 → FR/Logs/bt_unused_maps_assets.json +# ② python unused_report.py → 공유/기획/FirstRising/맵/미사용_에셋_목록.md +# 묶음 · 까닭은 아래 RULES 에 적는다(새 묶음이 생기면 여기에 더한다). 어느 묶음에도 안 맞는 것은 '분류 안 됨'으로 나온다. +import io, json, os, re, subprocess, sys, time + +FR = r"C:\BurningTimes\FirstRising" +SRC = os.path.join(FR, "Logs", "bt_unused_maps_assets.json") +OUT = r"E:\BurningTimes\공유\기획\FirstRising\맵\미사용_에셋_목록.md" + +# (묶음 열쇠, 묶음 이름, 경로 정규식, 안 쓰이게 된 까닭, 지워도 되는가(PM 의견), 되살리는 법) +RULES = [ + ("A", "던전 — 옛 지형 층", r"PlagueCatacomb/Terrain/Catacomb_(Rock|Paver|Floor|Slime)\.terrainlayer$", + "BT38(2026-09-28)에서 던전 바닥 층을 던전 타일로 바꿨다. 옛 층 넷은 지형 데이터가 더는 가리키지 않는다", + "지워도 된다", "git 에 있다(bt/work `159f17f6c0` 부터) — `git checkout <커밋> -- <경로>`"), + ("B", "던전 — 옛 바닥 텍스처", r"PlagueCatacomb/Textures/FRMap_dun_(paver|slime)(_N)?\.png$", + "옛 지형 층(A)만 쓰던 텍스처다 — 그려 만든 포석 · 점액 포석과 그 노멀맵", + "지워도 된다(A 와 함께)", "git 에 있다(bt/work `159f17f6c0` 부터). 만든 도구는 준비 폴더 `texderive.py`"), + ("C", "던전 — 1차 시도(깨진 타일)", r"PlagueCatacomb/(Terrain/Catacomb_TileBroken\.terrainlayer|Textures/FRMap_dun_tile_broken(_N)?\.png)$", + "BT38 1차 시도에서 만든 깨진 타일 층. 화면 확인 뒤 흙 층으로 바꿔 쓰지 않게 됐다", + "지워도 된다", "git 에 있다(bt/work `5f9c8dd297`). 팩의 `CatacombsFloorA2` 에서 다시 만들 수도 있다"), + ("D", "큰 바닥돌의 재질 사본", r"_Shared/Materials/Lit/CATA_Floor[AB]_[0-9a-f]+_Lit\.mat$", + "팩의 10 m 바닥 판(GroundTileCATAM)에 끼우던 재질 사본. BT38 에서 바닥돌 띠를 걷어내고 다리 판을 바꿔 쓰는 곳이 없다", + "지워도 된다", "git 에 있다. 그 바닥 판을 다시 쓰면 빌더가 다시 만든다"), + ("E", "옛 이름의 가림 반투명 재질 사본", r"/Materials/Fade/(?!.*_(Tuned|Lit)_Fade)(?!FRProp_).*_Fade\.mat$", + "재질 사본의 이름 규칙이 바뀌기 전(BT31 초기 빌드)에 만든 것. 지금 씬은 `<이름>_<번호>_Tuned_Fade` 를 쓴다", + "지워도 된다", "git 에 있다. 쓸 일은 없다"), + ("F", "빌더의 중간 산출물(밝기 맞춘 재질 사본)", r"_Shared/Materials/Lit/.*_Tuned\.mat$", + "씬은 이 사본에서 다시 만든 가림 반투명 사본(`_Tuned_Fade`)을 쓴다. 이 사본 자체는 씬이 가리키지 않지만 빌더가 맵을 지을 때 재료로 쓴다", + "지울 실익이 없다(다음 빌드 때 다시 생긴다)", "맵을 다시 지으면 생긴다"), +] + + +def tracked(paths): + out = subprocess.run(["git", "-c", "core.quotepath=false", "ls-files", "--"] + paths, cwd=FR, capture_output=True).stdout.decode("utf-8", "replace") + return set(l.strip() for l in out.splitlines() if l.strip()) + + +def addressable(items): + """쓰이지 않는 에셋이 어드레서블 설정에 등록돼 있는지 본다(등록돼 있으면 씬이 가리키지 않아도 빌드에 들어간다). (설정 파일 수, 걸린 에셋 수)""" + import glob + files = glob.glob(os.path.join(FR, "Assets", "AddressableAssetsData", "**", "*.asset"), recursive=True) + hit = set() + for f in files: + text = io.open(f, encoding="utf-8", errors="replace").read() + for i in items: + if i["guid"] in text: + hit.add(i["path"]) + return len(files), len(hit) + + +def kb(n): + return "%.1f MB" % (n / 1048576.0) if n >= 1048576 else "%.1f KB" % (n / 1024.0) + + +def main(): + d = json.loads(io.open(SRC, encoding="utf-8").read()) + items = d["unused"] + paths = [i["path"] for i in items] + tr = tracked(paths) + stamp = time.strftime("%Y-%m-%d %H:%M", time.localtime(os.path.getmtime(SRC))) + groups = {} + for i in items: + i["group"] = "?" + for key, name, rx, why, ok, back in RULES: + if re.search(rx, i["path"]): + i["group"] = key + break + groups.setdefault(i["group"], []).append(i) + L = [] + L.append("# FR 맵 폴더 — 쓰이지 않는 에셋 목록") + L.append("") + L.append("> PD 지시(2026-09-28): \"남겨두되, 어떤 에셋이 안쓰이는지 기록해서 추후 삭제 유무를 결정할 수 있도록 정리해둘 것. (내가 나중에 물어보면 바로 알 수 있게 정리 필요)\"") + L.append("> **지운 것은 없다.** 아래 에셋은 모두 프로젝트에 그대로 있다. 지울지는 PD 결정.") + L.append("") + L.append("## 한눈에") + L.append("") + L.append("| 항목 | 값 |") + L.append("|---|---|") + L.append("| 조사한 때 | %s [실측] |" % stamp) + L.append("| 조사 범위 | FR `%s` (맵 작업으로 만든 에셋) — 에셋 %d개 |" % (d["scope"], d["total"])) + L.append("| **쓰이지 않는 에셋** | **%d개 · %s** |" % (d["unusedCount"], kb(d["unusedBytes"]))) + L.append("| 그 가운데 지워도 되는 것(PM 의견) | %d개 · %s |" % (sum(len(v) for k, v in groups.items() if k in "ABCDE"), kb(sum(i["bytes"] for k, v in groups.items() if k in "ABCDE" for i in v)))) + addr_files, addr_hits = addressable(items) + L.append("| 게임에 주는 영향 | 없다 — 씬 · 프리팹 어디에서도 가리키지 않는다. 어드레서블 설정 파일 %d개에 등록된 것 %d개 [실측]. 기기 빌드에 들어가는지는 빌드로 확인하지 않았다 |" % (addr_files, addr_hits)) + L.append("") + L.append("## 묶음별") + L.append("") + L.append("| 묶음 | 개수 | 용량 | 왜 안 쓰이게 됐나 | 지워도 되는가(PM 의견) | 되살리는 법 |") + L.append("|---|---|---|---|---|---|") + for key, name, rx, why, ok, back in RULES: + v = groups.get(key, []) + if not v: + continue + L.append("| **%s. %s** | %d | %s | %s | %s | %s |" % (key, name, len(v), kb(sum(i["bytes"] for i in v)), why, ok, back)) + if "?" in groups: + v = groups["?"] + L.append("| **?. 분류 안 됨** | %d | %s | 까닭을 아직 적지 못했다 — 확인 필요 | 판단 보류 | — |" % (len(v), kb(sum(i["bytes"] for i in v)))) + L.append("") + L.append("## 전체 목록") + L.append("") + L.append("경로는 `%s/` 아래. 'git' = FR 저장소가 추적하는가." % d["scope"]) + L.append("") + L.append("| 묶음 | 경로 | 종류 | 크기 | git |") + L.append("|---|---|---|---|---|") + for i in sorted(items, key=lambda x: (x["group"], x["path"])): + rel = i["path"][len(d["scope"]) + 1:] + L.append("| %s | `%s` | %s | %s | %s |" % (i["group"], rel, i["type"], kb(i["bytes"]), "추적" if i["path"] in tr else "미추적")) + L.append("") + L.append("## '쓰이지 않는다'의 기준") + L.append("") + L.append("- **쓰인다**: ① 씬(`Assets/Scenes` 아래 전부) · 로비 프리팹(`LobbyPlayScene`) · 보스전 전용 필드 프리팹 · `Resources` 폴더의 에셋이 직접 또는 건너 건너 가리키는 것 " + "② 맵 정의(`<맵>.map.json`) · 소품 목록(`props.json`)에 경로가 적힌 것과 그것이 가리키는 것 — 지금 맵에 놓이지 않았어도 빌더의 재료이므로 쓰이는 것으로 본다.") + L.append("- **쓰이지 않는다**: 위 어디에도 걸리지 않는 것. 유니티의 의존 관계 조회(`AssetDatabase.GetDependencies`)로 판정했다 [실측].") + L.append("- 기준이 된 에셋 %d개(정의 파일에서 %d개). 정의 파일(.json)과 폴더는 후보에서 뺐다." % (d["roots"], d["rootsFromJson"])) + L.append("- **조사하지 않은 곳**: 맵 폴더 밖(`Assets/BurningTimes/Boss` · `Visual` 등)과 게임 원본 에셋. 코드가 경로 글자로 불러오는 에셋은 이 방법으로 잡히지 않는다 — 맵 폴더 안에는 그런 것이 없음을 확인했다(빌더는 정의 파일의 경로만 읽는다).") + L.append("") + L.append("## 지우기로 정하면") + L.append("") + L.append("1. 유니티 에디터의 Project 창에서 지운다(메타 파일이 함께 지워진다). 탐색기에서 지우면 메타 파일이 남는다.") + L.append("2. 지운 뒤 확인: 컴파일 오류 0 → 해당 맵 다시 짓기(`BurningTimes/Maps/<맵>/Build`) RESULT OK → Play 에서 그 맵으로 이동해 에디터 로그 오류 0.") + L.append("3. 묶음 A · B 는 함께 지운다(A 가 B 를 가리킨다 — A 만 남기면 빈 텍스처를 가리키는 층이 된다).") + L.append("4. 묶음 F 는 지워도 다음 빌드 때 다시 생긴다.") + L.append("") + L.append("## 목록을 다시 만드는 법") + L.append("") + L.append("1. 유니티 편집 모드에서 `공유/기획/FirstRising/맵/tools/unity_eval/unused_scan.cs` 실행 → FR `Logs/bt_unused_maps_assets.json`") + L.append("2. `python 공유/기획/FirstRising/맵/tools/unused_report.py` → 이 문서를 다시 쓴다(묶음 · 까닭은 스크립트의 RULES)") + L.append("") + L.append("## 그 밖의 정리 후보(에셋이 아닌 것)") + L.append("") + L.append("| 대상 | 크기 | 상태 | 비고 |") + L.append("|---|---|---|---|") + L.append("| 조직 레포 `공유/개발팀_백업/FirstRising/` | 약 1.2 GB | 미추적(레포에 올리지 않음) | FR 에셋을 바꾸기 전에 떠 둔 안전 사본. 91 MB 짜리 지형 데이터 10개 포함. 무시 목록(.gitignore)에 넣을지 PD 판단 |") + L.append("| 조직 레포 `공유/기획/FirstRising/리뉴얼/~$…기획서_v1.pptx` | 165 B | 미추적 | 오피스 잠금 파일(파워포인트가 열어 둔 흔적) |") + L.append("| 준비 폴더 `C:\\BurningTimes\\FR_maps_staging\\_work\\` | — | 저장소 밖 | 블렌더 굽기 · 텍스처 파생의 중간 결과. 다시 만들 수 있다 |") + L.append("") + io.open(OUT, "w", encoding="utf-8", newline="\n").write("\n".join(L)) + print("쓴 곳", OUT) + print("묶음", {k: len(v) for k, v in sorted(groups.items())}, "· 추적", len(tr), "· 미추적", len(items) - len(tr)) + + +main() diff --git a/공유/기획/FirstRising/맵/미사용_에셋_목록.md b/공유/기획/FirstRising/맵/미사용_에셋_목록.md new file mode 100644 index 0000000..0a71b47 --- /dev/null +++ b/공유/기획/FirstRising/맵/미사용_에셋_목록.md @@ -0,0 +1,94 @@ +# FR 맵 폴더 — 쓰이지 않는 에셋 목록 + +> PD 지시(2026-09-28): "남겨두되, 어떤 에셋이 안쓰이는지 기록해서 추후 삭제 유무를 결정할 수 있도록 정리해둘 것. (내가 나중에 물어보면 바로 알 수 있게 정리 필요)" +> **지운 것은 없다.** 아래 에셋은 모두 프로젝트에 그대로 있다. 지울지는 PD 결정. + +## 한눈에 + +| 항목 | 값 | +|---|---| +| 조사한 때 | 2026-09-28 16:13 [실측] | +| 조사 범위 | FR `Assets/BurningTimes/Maps` (맵 작업으로 만든 에셋) — 에셋 397개 | +| **쓰이지 않는 에셋** | **35개 · 10.0 MB** | +| 그 가운데 지워도 되는 것(PM 의견) | 27개 · 10.0 MB | +| 게임에 주는 영향 | 없다 — 씬 · 프리팹 어디에서도 가리키지 않는다. 어드레서블 설정 파일 71개에 등록된 것 0개 [실측]. 기기 빌드에 들어가는지는 빌드로 확인하지 않았다 | + +## 묶음별 + +| 묶음 | 개수 | 용량 | 왜 안 쓰이게 됐나 | 지워도 되는가(PM 의견) | 되살리는 법 | +|---|---|---|---|---|---| +| **A. 던전 — 옛 지형 층** | 4 | 3.2 KB | BT38(2026-09-28)에서 던전 바닥 층을 던전 타일로 바꿨다. 옛 층 넷은 지형 데이터가 더는 가리키지 않는다 | 지워도 된다 | git 에 있다(bt/work `159f17f6c0` 부터) — `git checkout <커밋> -- <경로>` | +| **B. 던전 — 옛 바닥 텍스처** | 4 | 5.4 MB | 옛 지형 층(A)만 쓰던 텍스처다 — 그려 만든 포석 · 점액 포석과 그 노멀맵 | 지워도 된다(A 와 함께) | git 에 있다(bt/work `159f17f6c0` 부터). 만든 도구는 준비 폴더 `texderive.py` | +| **C. 던전 — 1차 시도(깨진 타일)** | 3 | 4.5 MB | BT38 1차 시도에서 만든 깨진 타일 층. 화면 확인 뒤 흙 층으로 바꿔 쓰지 않게 됐다 | 지워도 된다 | git 에 있다(bt/work `5f9c8dd297`). 팩의 `CatacombsFloorA2` 에서 다시 만들 수도 있다 | +| **D. 큰 바닥돌의 재질 사본** | 2 | 6.1 KB | 팩의 10 m 바닥 판(GroundTileCATAM)에 끼우던 재질 사본. BT38 에서 바닥돌 띠를 걷어내고 다리 판을 바꿔 쓰는 곳이 없다 | 지워도 된다 | git 에 있다. 그 바닥 판을 다시 쓰면 빌더가 다시 만든다 | +| **E. 옛 이름의 가림 반투명 재질 사본** | 14 | 55.1 KB | 재질 사본의 이름 규칙이 바뀌기 전(BT31 초기 빌드)에 만든 것. 지금 씬은 `<이름>_<번호>_Tuned_Fade` 를 쓴다 | 지워도 된다 | git 에 있다. 쓸 일은 없다 | +| **F. 빌더의 중간 산출물(밝기 맞춘 재질 사본)** | 8 | 32.1 KB | 씬은 이 사본에서 다시 만든 가림 반투명 사본(`_Tuned_Fade`)을 쓴다. 이 사본 자체는 씬이 가리키지 않지만 빌더가 맵을 지을 때 재료로 쓴다 | 지울 실익이 없다(다음 빌드 때 다시 생긴다) | 맵을 다시 지으면 생긴다 | + +## 전체 목록 + +경로는 `Assets/BurningTimes/Maps/` 아래. 'git' = FR 저장소가 추적하는가. + +| 묶음 | 경로 | 종류 | 크기 | git | +|---|---|---|---|---| +| A | `PlagueCatacomb/Terrain/Catacomb_Floor.terrainlayer` | TerrainLayer | 0.8 KB | 추적 | +| A | `PlagueCatacomb/Terrain/Catacomb_Paver.terrainlayer` | TerrainLayer | 0.8 KB | 추적 | +| A | `PlagueCatacomb/Terrain/Catacomb_Rock.terrainlayer` | TerrainLayer | 0.8 KB | 추적 | +| A | `PlagueCatacomb/Terrain/Catacomb_Slime.terrainlayer` | TerrainLayer | 0.8 KB | 추적 | +| B | `PlagueCatacomb/Textures/FRMap_dun_paver.png` | Texture2D | 1.6 MB | 추적 | +| B | `PlagueCatacomb/Textures/FRMap_dun_paver_N.png` | Texture2D | 1.1 MB | 추적 | +| B | `PlagueCatacomb/Textures/FRMap_dun_slime.png` | Texture2D | 1.7 MB | 추적 | +| B | `PlagueCatacomb/Textures/FRMap_dun_slime_N.png` | Texture2D | 1017.4 KB | 추적 | +| C | `PlagueCatacomb/Terrain/Catacomb_TileBroken.terrainlayer` | TerrainLayer | 0.8 KB | 추적 | +| C | `PlagueCatacomb/Textures/FRMap_dun_tile_broken.png` | Texture2D | 2.4 MB | 추적 | +| C | `PlagueCatacomb/Textures/FRMap_dun_tile_broken_N.png` | Texture2D | 2.2 MB | 추적 | +| D | `_Shared/Materials/Lit/CATA_FloorA_62ba55_Lit.mat` | Material | 3.0 KB | 추적 | +| D | `_Shared/Materials/Lit/CATA_FloorB_3309c2_Lit.mat` | Material | 3.0 KB | 추적 | +| E | `Graveyard/Materials/Fade/Cliff_02_Fade.mat` | Material | 4.0 KB | 추적 | +| E | `Graveyard/Materials/Fade/MEP_Brush_02_Fade.mat` | Material | 3.9 KB | 추적 | +| E | `Graveyard/Materials/Fade/MEP_Cliff_Dif_Fade.mat` | Material | 3.9 KB | 추적 | +| E | `Graveyard/Materials/Fade/MEP_DTreeBranch_Fade.mat` | Material | 3.9 KB | 추적 | +| E | `Graveyard/Materials/Fade/MEP_DTree_Bark_Fade.mat` | Material | 3.9 KB | 추적 | +| E | `Graveyard/Materials/Fade/MEP_Deadtree_01_Fade.mat` | Material | 3.9 KB | 추적 | +| E | `HarborExecution/Materials/Fade/Cliff_02_Fade.mat` | Material | 4.0 KB | 추적 | +| E | `HarborExecution/Materials/Fade/MEP_StoneWall_Dif_Fade.mat` | Material | 3.9 KB | 추적 | +| E | `HarborExecution/Materials/Fade/MEP_Wall_Roof_Dif_Fade.mat` | Material | 4.0 KB | 추적 | +| E | `RuinedKeep/Materials/Fade/Cliff_02_Fade.mat` | Material | 4.0 KB | 추적 | +| E | `RuinedKeep/Materials/Fade/MEP_Brush_02_Fade.mat` | Material | 3.9 KB | 추적 | +| E | `RuinedKeep/Materials/Fade/MEP_Cliff_Dif_Fade.mat` | Material | 4.0 KB | 추적 | +| E | `RuinedKeep/Materials/Fade/MEP_Deadtree_01_Fade.mat` | Material | 3.9 KB | 추적 | +| E | `SpiderCave/Materials/Fade/MEP_CaveWall_Dif_Fade.mat` | Material | 4.0 KB | 추적 | +| F | `_Shared/Materials/Lit/Cliff_02_cb4591_Tuned.mat` | Material | 4.0 KB | 추적 | +| F | `_Shared/Materials/Lit/MEP_Brush_02_e55995_Tuned.mat` | Material | 3.9 KB | 추적 | +| F | `_Shared/Materials/Lit/MEP_Cliff_Dif_9d1521_Tuned.mat` | Material | 4.0 KB | 추적 | +| F | `_Shared/Materials/Lit/MEP_DTree_Bark_8c2ba9_Tuned.mat` | Material | 3.9 KB | 추적 | +| F | `_Shared/Materials/Lit/MEP_Deadtree_01_62065c_Tuned.mat` | Material | 3.9 KB | 추적 | +| F | `_Shared/Materials/Lit/MEP_DesertCliff_438ce1_Tuned.mat` | Material | 4.0 KB | 추적 | +| F | `_Shared/Materials/Lit/MEP_Rock_02_03_Dif_Desert_af9427_Tuned.mat` | Material | 3.9 KB | 추적 | +| F | `_Shared/Materials/Lit/MEP_Rock_Dif_Desert_e13ae8_Tuned.mat` | Material | 4.3 KB | 추적 | + +## '쓰이지 않는다'의 기준 + +- **쓰인다**: ① 씬(`Assets/Scenes` 아래 전부) · 로비 프리팹(`LobbyPlayScene`) · 보스전 전용 필드 프리팹 · `Resources` 폴더의 에셋이 직접 또는 건너 건너 가리키는 것 ② 맵 정의(`<맵>.map.json`) · 소품 목록(`props.json`)에 경로가 적힌 것과 그것이 가리키는 것 — 지금 맵에 놓이지 않았어도 빌더의 재료이므로 쓰이는 것으로 본다. +- **쓰이지 않는다**: 위 어디에도 걸리지 않는 것. 유니티의 의존 관계 조회(`AssetDatabase.GetDependencies`)로 판정했다 [실측]. +- 기준이 된 에셋 596개(정의 파일에서 550개). 정의 파일(.json)과 폴더는 후보에서 뺐다. +- **조사하지 않은 곳**: 맵 폴더 밖(`Assets/BurningTimes/Boss` · `Visual` 등)과 게임 원본 에셋. 코드가 경로 글자로 불러오는 에셋은 이 방법으로 잡히지 않는다 — 맵 폴더 안에는 그런 것이 없음을 확인했다(빌더는 정의 파일의 경로만 읽는다). + +## 지우기로 정하면 + +1. 유니티 에디터의 Project 창에서 지운다(메타 파일이 함께 지워진다). 탐색기에서 지우면 메타 파일이 남는다. +2. 지운 뒤 확인: 컴파일 오류 0 → 해당 맵 다시 짓기(`BurningTimes/Maps/<맵>/Build`) RESULT OK → Play 에서 그 맵으로 이동해 에디터 로그 오류 0. +3. 묶음 A · B 는 함께 지운다(A 가 B 를 가리킨다 — A 만 남기면 빈 텍스처를 가리키는 층이 된다). +4. 묶음 F 는 지워도 다음 빌드 때 다시 생긴다. + +## 목록을 다시 만드는 법 + +1. 유니티 편집 모드에서 `공유/기획/FirstRising/맵/tools/unity_eval/unused_scan.cs` 실행 → FR `Logs/bt_unused_maps_assets.json` +2. `python 공유/기획/FirstRising/맵/tools/unused_report.py` → 이 문서를 다시 쓴다(묶음 · 까닭은 스크립트의 RULES) + +## 그 밖의 정리 후보(에셋이 아닌 것) + +| 대상 | 크기 | 상태 | 비고 | +|---|---|---|---| +| 조직 레포 `공유/개발팀_백업/FirstRising/` | 약 1.2 GB | 미추적(레포에 올리지 않음) | FR 에셋을 바꾸기 전에 떠 둔 안전 사본. 91 MB 짜리 지형 데이터 10개 포함. 무시 목록(.gitignore)에 넣을지 PD 판단 | +| 조직 레포 `공유/기획/FirstRising/리뉴얼/~$…기획서_v1.pptx` | 165 B | 미추적 | 오피스 잠금 파일(파워포인트가 열어 둔 흔적) | +| 준비 폴더 `C:\BurningTimes\FR_maps_staging\_work\` | — | 저장소 밖 | 블렌더 굽기 · 텍스처 파생의 중간 결과. 다시 만들 수 있다 | diff --git a/공유/대화로그/FirstRising/2026-09-28.md b/공유/대화로그/FirstRising/2026-09-28.md index 0d8578b..e0a648b 100644 --- a/공유/대화로그/FirstRising/2026-09-28.md +++ b/공유/대화로그/FirstRising/2026-09-28.md @@ -150,21 +150,56 @@ ### PD 원문 - "백그라운드 작업은 왜 남아있지? 작업이 끝났으면 푸시하도록 해" +- (후속) "조직 레포도 커밋해야하지 않아?" ### 결정 - 남은 백그라운드 작업은 에디터를 닫지 않고 정리한다 — 작업 전체를 멈추는 대신 기다리던 프로세스(`head`) 하나만 끝낸다. - 푸시 대상은 FR 작업물(BT34~BT36)로 본다 — `bt/work` 에 커밋하고 push. 에디터가 덧붙인 변경 7개(재질 4 · 글자체 2 · 의존성 에셋 1)는 넣지 않는다. -- 조직 레포(문서 · 도구 · 로그)는 커밋하지 않았다 — 지난 BT33 과 같게 처리했고, 다른 안건의 변경(CLAUDE.md · scripts)이 섞여 있다(`main` · 원격과 같음 · 바뀐 항목 92). +- 조직 레포는 처음에 커밋하지 않았다(지난 BT33 과 같게 · 다른 안건의 변경이 섞여 있어 지시 범위 밖으로 판단) → **PD 후속 지시로 커밋 · push.** PM 의 범위 판단이 좁았다. +- 조직 레포는 안건별로 나눠 커밋한다(조직 규칙 · 앞선 세션 기록 · 이번 세션). FR 에셋 백업(1.2 GB · 91 MB 파일 10개)과 오피스 잠금 파일은 넣지 않는다. +- 커밋 게이트(C35-9)는 앞선 세션들과 같게 매니페스트를 등록해 푼다(감사 호출은 BT19-Org 로 생략 — 매니페스트에 적음). - 이번 지시는 이번 작업물에 대한 것으로 본다 — '커밋은 PD 지시 시만' 규칙은 그대로다. ### 결과 - **백그라운드 작업**: 12:03 에 에디터를 다시 띄울 때 쓴 셸(`unity open … | head -30`)이 남아 있었다. 띄우는 명령은 이미 끝났지만, 출력 통로를 에디터 프로세스가 물려받아 `head` 가 끝을 기다리고 있었다(출력 0줄 · 하는 일 없음). `head` 만 종료 → 셸이 스스로 끝남 · 에디터는 그대로 · 상태 ready. -- **푸시**: `bt/work` `159f17f6c0..1ce5df5b2e`(507개 파일 — 새 파일 446 · 고친 파일 61 · 약 140 MB). 원격과 로컬이 같음을 확인. +- **푸시(FR)**: `bt/work` `159f17f6c0..1ce5df5b2e`(507개 파일 — 새 파일 446 · 고친 파일 61 · 약 140 MB). 원격과 로컬이 같음을 확인. +- **푸시(조직 레포)**: `main` `5698f2c..c693d2e` — `705986b`(조직 규칙 BT20-Org 4개 파일) · `89505fa`(이번 세션 문서 · 도구 · 로그 168개 파일) · `c693d2e`(앞선 세션 기록 BT16~BT22 89개 파일). 비밀 값 패턴 검사 205개 파일 · 걸린 곳 0. ### 사고·조치 - 에셋 저장 뒤 글자체 에셋(NanumBarunGothic SDF)이 다시 바뀌어 있었다(에디터가 글자를 쓸 때 덧붙이는 변경) → 커밋에서 뺐다. +- 조직 레포 두 번째 커밋이 한 번 비었다(제외 경로 표기가 잘못돼 아무것도 스테이징되지 않음) → 다시 스테이징해 커밋. +- 조직 레포 첫 push 가 인증 실패(`remote: Verify`)로 떨어졌다 → 같은 명령 재시도에서 성공. 자격 증명 값은 건드리지 않았다. ### 남은 이슈 - §3 · §4 의 'FR 미커밋'은 이 푸시로 해소. FR 작업 트리에는 에디터가 덧붙인 변경 7개만 남아 있다. -- 조직 레포 문서 · 도구는 미커밋 — 반영할지 PD 지시 필요. +- 조직 레포에 남은 것: FR 에셋 백업 폴더(1.2 GB) · 오피스 잠금 파일 1개 — 백업 폴더를 무시 목록에 넣을지 PD 판단 필요. + +## 6. BT38-FirstRising 오염된 던전 바닥 개선 (PM 직접 · 같은 세션) + +### PD 원문 +- "오염된 던전의 바닥 텍스쳐가 너무 크고 퀄리티가 다른 맵에 비해 떨어지는 느낌이야. 체크해보고 좀 더 자연스러운 던전 타일처럼 수정해줘." +- (작업 뒤 질문에 대한 답) 커밋 · 푸시 = "FR·조직 레포 모두" / 안 쓰는 에셋 = "남겨두되, 어떤 에셋이 안쓰이는지 기록해서 추후 삭제 유무를 결정할 수 있도록 정리해둘 것. (내가 나중에 물어보면 바로 알 수 있게 정리 필요)" / 앞으로의 규칙 = "지금처럼 지시할 때만" + +### 결정 +- 텍스처만 바꾸지 않고 화면에서 원인을 확인한 뒤 고친다 — 지형 층을 바꾼 1차 화면에서 바닥을 덮은 큰 판이 따로 있음을 찾았다. +- 게임 에셋(팩 텍스처의 들여오기 설정 · 팩 재질)은 고치지 않고 맵 폴더에 사본을 만든다. +- 지형 층은 넷 그대로(온전한 타일 · 얼룩진 타일 · 점액 타일 · 흙). +- 쓰이지 않게 된 에셋은 지우지 않는다(PD 결정). 던전의 11개만 적지 않고 맵 폴더 전체를 에디터의 의존 관계 조회로 조사해 목록 문서로 남긴다. +- 커밋 · 푸시는 작업을 마친 뒤 PD 에게 물어 답을 받고 했다. '커밋은 PD 지시 시만' 규칙은 그대로다(PD 답). + +### 결과 +- 원인: 안쪽 장식 띠의 바닥 판 46장이 2~4.9 배로 커져 바닥을 덮음(주된 원인 · BT31 PM 실수) · 지형 층 512 픽셀 · 돌판 다리의 큰 판. +- 조치: 띠 제거 · 던전 타일 1024 픽셀 4 m + 흙 얼룩 · 다리 판 교체 · 뼈 무더기 네모 판 오려내기(맵 6종 공통 재질). +- 검증: 빌드 OK · 보스 자리 막힘 0 ㎡ · 길 폭 7.5 m · Play 와 전용 필드 R4 에디터 로그 오류 0. 문서 §9 · 그림 `img/bt38_*.jpg`. +- 쓰이지 않는 에셋: 맵 폴더 397개 가운데 35개(10.0 MB) — `공유/기획/FirstRising/맵/미사용_에셋_목록.md`(묶음 · 까닭 · 되살리는 법 · 다시 조사하는 법). 어드레서블 등록 0. +- 푸시: FR `bt/work` `1ce5df5b2e..5f9c8dd297`(49개 파일) · 조직 레포 `main`. + +### 사고·조치 +- 1차에는 지형 층만 보고 고쳤다가 화면에서 큰 판을 발견했다 — 빌드 기록(소품 크기 · 배율)을 먼저 봤어야 했다. 전 맵의 소품 기록을 훑어 같은 실수가 던전뿐임을 확인(항구 널빤지 1.5~2 배는 별건). +- 파일을 지우는 명령이 섞인 실행이 거부됨 → 지우지 않고 진행. + +### 남은 이슈 +- **PD 확인 필요**: 바닥 모습 · 쓰이지 않는 에셋을 지울지(목록 문서 참고). +- 4 m 마다의 무늬 되풀이는 약해졌지만 남아 있다. 기기 빌드 미검증. +- FR 작업 트리: 에디터가 덧붙인 변경 6개만 남아 있다.