diff --git a/docs/CREDITS.md b/docs/CREDITS.md new file mode 100644 index 0000000..2f1e3a0 --- /dev/null +++ b/docs/CREDITS.md @@ -0,0 +1,42 @@ +# Credits + +`Magic Exam Hall`이 외부 자산을 사용할 때 이 파일에 출처를 남깁니다. 자산을 추가하기 전에 반드시 `docs/SPRITE_GUIDE.md`의 라이선스 규칙을 확인합니다. + +원본 LICENSE, README, attribution 파일은 `docs/asset-licenses/` 아래에 보관합니다. Unity `Resources/` 아래에는 런타임에 불러올 실제 PNG만 둡니다. + +## 비주얼 + +| 자산 | 작가 | 출처 | 라이선스 | 사용 위치 | +| --- | --- | --- | --- | --- | +| _아직 외부 자산 없음. 모든 sprite는 `PixelArtFactory.cs`의 procedural 생성_ | — | — | — | — | + +## 사운드 + +| 자산 | 작가 | 출처 | 라이선스 | 사용 위치 | +| --- | --- | --- | --- | --- | +| _아직 외부 자산 없음_ | — | — | — | — | + +## 폰트 + +| 자산 | 작가 | 출처 | 라이선스 | 사용 위치 | +| --- | --- | --- | --- | --- | +| Unity 기본 Arial | — | Unity 번들 | — | HUD, 룬 라벨, 노트 | + +## 자산 추가 시 작성 규칙 + +- **자산** 이름은 PNG 파일명 또는 자산 팩 이름. +- **작가** 이름과 가능하면 핸들 함께. 익명 자산은 "Anonymous". +- **출처**는 다운로드 페이지 URL. 팩으로 받은 경우 팩의 메인 페이지. +- **라이선스**는 CC0, CC-BY 4.0 등 정확히. 출처 페이지에서 확인. +- **사용 위치**는 어떤 `PixelSpriteKind` 또는 어떤 화면에 쓰이는지 짧게. + +CC-BY 자산은 작가 표기가 의무이므로 이 표 외에 게임 내 옵션 화면 또는 엔딩 리포트 푸터에도 짧게 노출하는 것을 권장합니다. + +## 도구 + +| 도구 | 용도 | +| --- | --- | +| Unity 6000.3.14f1 | 게임 엔진 | +| TypeScript 6.x + Vite 8.x | Web prototype | +| Vitest | Web 테스트 | +| Aseprite (권장) | sprite 작업 | diff --git a/docs/SPRITE_GUIDE.md b/docs/SPRITE_GUIDE.md new file mode 100644 index 0000000..ed35c05 --- /dev/null +++ b/docs/SPRITE_GUIDE.md @@ -0,0 +1,108 @@ +# Sprite Guide + +`Magic Exam Hall`의 비주얼 자산을 외부 픽셀 아트로 교체할 때 따르는 규칙입니다. 자산만 정해진 자리에 두면 코드 수정 없이 바로 게임에 반영됩니다. + +## 경로와 명명 규칙 + +PNG 파일을 다음 위치에 둡니다. + +``` +unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites/ +``` + +파일 이름은 `PixelArtFactory.cs`의 `PixelSpriteKind` enum 값과 정확히 일치해야 합니다. 대소문자도 같게 둡니다. + +| Kind | 파일명 | 용도 | +| --- | --- | --- | +| `Player` | `Player.png` | 입학생 마법사 | +| `Station` | `Station.png` | 보조 설치물 | +| `Target` | `Target.png` | 일반 base 목표 (fallback) | +| `Pulse` | `Pulse.png` | 시전 펄스·위험 잔향 | +| `FloorTile` | `FloorTile.png` | 바닥 타일 (반복) | +| `WallTrim` | `WallTrim.png` | 벽 장식 (반복) | +| `Rug` | `Rug.png` | 중앙 카펫 (반복) | +| `Bookshelf` | `Bookshelf.png` | 양쪽 책장 | +| `Candle` | `Candle.png` | 모서리 촛불 | +| `RuneCircle` | `RuneCircle.png` | overlay·combo 목표 룬 | + +family별 base 룬은 시각 정합성 PR에서 enum이 확장된 뒤에 사용 가능합니다. + +| Kind | 파일명 | 형태 권장 | +| --- | --- | --- | +| `FireRune` | `FireRune.png` | 위로 솟은 닫힌 삼각형 | +| `WaterRune` | `WaterRune.png` | 닫힌 원형 루프 | +| `WindRune` | `WindRune.png` | 평행한 열린 세 줄 | +| `EarthRune` | `EarthRune.png` | 아래가 넓은 사다리꼴 | +| `LifeRune` | `LifeRune.png` | 줄기와 위로 갈라지는 두 가지 | + +## 권장 스펙 + +- **해상도**: 32×32 픽셀 권장. 16×16 또는 64×64도 동작하지만 다른 sprite와 같은 단위로 그려야 화면에서 크기가 일관됨. +- **Pixels per Unit**: 16 (코드 상수와 일치). 다른 PPU로 그렸다면 Unity 임포트 설정에서 PPU를 같이 16으로 맞춰 import. +- **Filter Mode**: Point (no filter). Unity 임포트 시 자동으로 잡지만 확인. +- **Wrap Mode**: Clamp. 단 반복 타일 sprite는 Repeat로 설정 가능. +- **Pivot**: Center. +- **Transparency**: 알파 채널 포함 PNG. +- **Palette**: 일관된 16~24색 팔레트 권장. 외부 팩을 섞을 때는 [Lospec](https://lospec.com/palette-list) 같은 사이트의 팔레트로 통일 처리하면 톤이 안 깨짐. + +## 로더 동작 + +`PixelArtFactory.CreateSprite` 호출 시: + +1. `Resources/Sprites/` 경로에서 PNG 검색 +2. 있으면 그대로 반환 (원본 색 유지) +3. 없으면 기존 코드 기반 procedural 도형으로 fallback + +따라서 PNG가 일부만 준비돼도 그 sprite만 교체되고 나머지는 그대로 동작합니다. 점진적 교체가 가능합니다. + +캐싱이 있으므로 자산을 새로 넣거나 바꾼 직후엔 `PixelArtFactory.ResetExternalSpriteCache()`를 한 번 호출하거나 Editor 재시작. + +## 색 처리 + +외부 sprite는 원본 색 그대로 표시됩니다. `PixelArtFactory.CreateSprite`의 `primary`·`secondary` 인자는 procedural fallback에만 적용됩니다. + +층별로 같은 sprite를 다른 색조로 보이고 싶다면 호출 측에서 `SpriteRenderer.color`를 곱셈 tint로 설정. 예를 들어 1층의 따뜻한 톤과 4층의 위험한 톤은 같은 `FloorTile.png` 위에 `Color` 곱으로 처리합니다. + +## 라이선스 규칙 + +저장소는 MIT 라이선스로 배포되므로 외부 자산도 호환되는 라이선스만 받아들입니다. + +| 라이선스 | 가능 여부 | 의무 | +| --- | --- | --- | +| CC0 | 가능 | 없음 (선택적 출처 명시) | +| CC-BY 4.0 | 가능 | `docs/CREDITS.md`에 출처·작가 명시 | +| CC-BY-SA | 가능하지만 검토 필요 | 동일 조건 배포 의무. 코드 라이선스와 충돌 가능 | +| CC-BY-NC | 사용 금지 | 비상업 한정 | +| 독점·재배포 금지 | 사용 금지 | — | +| 명시 라이선스 없음 | 사용 금지 | 작가에게 확인 필요 | + +자산을 받기 전 라이선스를 먼저 확인하고, 받은 후 즉시 `docs/CREDITS.md`에 한 줄 추가합니다. + +## 후보 팩 탐색 방향 + +검증된 자유 자산 출처: + +- **[Kenney](https://kenney.nl/)** — CC0. 톱다운 RPG·던전 타일·캐릭터 sprite 다수. 일관된 톤, 안전. +- **[OpenGameArt.org](https://opengameart.org/)** — CC0~CC-BY 혼재. 마법·룬·이펙트 검색이 풍부. +- **[itch.io](https://itch.io/game-assets/free/tag-pixel-art)** — 무료 픽셀 아트 태그. Mystic Woods 같은 환경 팩, Penzilla의 캐릭터 팩 등. +- **[Lospec](https://lospec.com/)** — 팔레트 라이브러리. 톤 통일에 사용. + +검색 키워드 권장: `top-down pixel rpg`, `magic spell pixel`, `wizard pixel character`, `dungeon tile 16x16`. + +자산을 후보로 정하면 다음을 평가: + +1. 톤이 어두운 돌·마법탑 분위기와 맞는가 +2. 같은 해상도(16 또는 32)에 정렬되는가 +3. 라이선스가 안전한가 +4. 캐릭터·환경·이펙트 중 어디까지 커버하는가 + +한 팩으로 다 못 채울 가능성이 큽니다. 환경은 A 팩, 캐릭터는 B 팩, 룬은 C 팩처럼 묶어 쓰되 팔레트만 같게 맞추면 인상이 통일됩니다. + +## 작업 흐름 + +1. 후보 팩 다운로드 후 라이선스 파일 보관 (`docs/asset-licenses/`) +2. PNG를 위 표의 이름 규칙대로 저장 +3. Unity Editor에서 import 설정 점검 (PPU 16, Point filter, Clamp) +4. Play로 실행해 자동 교체 확인 +5. `docs/CREDITS.md`에 출처 추가 +6. 새 sprite kind를 enum에 추가했다면 본 문서 표도 같이 갱신 diff --git a/docs/asset-licenses/README.md b/docs/asset-licenses/README.md new file mode 100644 index 0000000..6b1c874 --- /dev/null +++ b/docs/asset-licenses/README.md @@ -0,0 +1,5 @@ +# Asset Licenses + +외부 비주얼, 사운드, 폰트 자산을 추가하면 원본 LICENSE, README, attribution 파일을 이 폴더에 보관합니다. + +런타임에 불러올 PNG 파일은 `unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites/`에 두고, 라이선스 원본은 이 폴더에 둡니다. Unity `Resources/` 아래에 라이선스 문서를 넣으면 빌드에 포함될 수 있습니다. diff --git a/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites.meta b/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites.meta new file mode 100644 index 0000000..2e9aba7 --- /dev/null +++ b/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3236a56b731c4633bcfb1185f8954f2e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites/README.md b/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites/README.md new file mode 100644 index 0000000..fa7bf0d --- /dev/null +++ b/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites/README.md @@ -0,0 +1,16 @@ +# Sprites + +이 폴더에 PNG 파일을 두면 `PixelArtFactory`가 자동으로 procedural 도형 대신 사용합니다. + +자세한 규칙은 `docs/SPRITE_GUIDE.md` 참조. + +## 빠른 시작 + +1. PNG 파일 이름을 `PixelSpriteKind` enum 값과 동일하게 둡니다 (예: `Player.png`, `FireRune.png`). +2. Unity Editor에서 import 설정 확인: PPU 16, Filter Point, Wrap Clamp, Pivot Center. +3. Play 모드 실행 시 자동 교체. +4. `docs/CREDITS.md`에 출처를 한 줄 추가합니다. + +## 라이선스 보관 + +받은 자산의 LICENSE 또는 README 파일은 `docs/asset-licenses/` 아래에 보관합니다. 이 `Resources/Sprites` 폴더에는 런타임에 불러올 PNG만 둡니다. diff --git a/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites/README.md.meta b/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites/README.md.meta new file mode 100644 index 0000000..965870f --- /dev/null +++ b/unity/MagicExamHall/Assets/MagicExamHall/Resources/Sprites/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c60ed3016a3e4cc2a63c966eabd03395 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/MagicExamHall/Assets/MagicExamHall/Scripts/Runtime/PixelArtFactory.cs b/unity/MagicExamHall/Assets/MagicExamHall/Scripts/Runtime/PixelArtFactory.cs index 339a2fc..45ae839 100644 --- a/unity/MagicExamHall/Assets/MagicExamHall/Scripts/Runtime/PixelArtFactory.cs +++ b/unity/MagicExamHall/Assets/MagicExamHall/Scripts/Runtime/PixelArtFactory.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using UnityEngine; namespace MagicExamHall @@ -6,9 +7,29 @@ public static class PixelArtFactory { private const int Size = 32; private const float PixelsPerUnit = 16f; + private const string ExternalSpriteRoot = "Sprites/"; + private static readonly Dictionary ExternalCache = new(); + private static readonly HashSet ExternalMissCache = new(); + + /// + /// Creates or loads a sprite for the given kind. If a PNG exists at + /// Assets/MagicExamHall/Resources/Sprites/<Kind>.png, it is + /// loaded as-is. Otherwise the legacy procedural drawer is used. + /// + /// External art is rendered with its own colors as authored, so the + /// and values + /// only apply to the procedural fallback. Per-floor tinting can still + /// be applied via SpriteRenderer.color on the call site. + /// public static Sprite CreateSprite(string name, Color primary, Color secondary, PixelSpriteKind kind) { + var external = LoadExternalSprite(kind); + if (external != null) + { + return external; + } + var texture = new Texture2D(Size, Size, TextureFormat.RGBA32, false) { name = $"{name} Texture", @@ -55,6 +76,46 @@ public static Sprite CreateSprite(string name, Color primary, Color secondary, P return Sprite.Create(texture, new Rect(0, 0, Size, Size), new Vector2(0.5f, 0.5f), PixelsPerUnit); } + /// + /// Looks up a sprite under Resources/Sprites/<Kind>. + /// Results are cached so a missing PNG only hits disk once per session. + /// + /// Call from editor reload hooks + /// or tests when the art needs to be re-discovered. + /// + private static Sprite LoadExternalSprite(PixelSpriteKind kind) + { + if (ExternalCache.TryGetValue(kind, out var cached)) + { + return cached; + } + + if (ExternalMissCache.Contains(kind)) + { + return null; + } + + var sprite = Resources.Load(ExternalSpriteRoot + kind); + if (sprite == null) + { + ExternalMissCache.Add(kind); + return null; + } + + ExternalCache[kind] = sprite; + return sprite; + } + + /// + /// Drops the external sprite caches. Useful for editor reload hooks + /// or PlayMode tests that want a clean lookup. + /// + public static void ResetExternalSpriteCache() + { + ExternalCache.Clear(); + ExternalMissCache.Clear(); + } + private static void DrawPlayer(Texture2D texture, Color skin, Color robe) { var outline = new Color(0.035f, 0.032f, 0.045f, 1f);