From ce65d3d2bf2e78dce0958ce52322152092577bc2 Mon Sep 17 00:00:00 2001 From: BotTony329 <75003862+BotTony329@users.noreply.github.com> Date: Sun, 2 Aug 2026 13:01:11 +1000 Subject: [PATCH] =?UTF-8?q?[Art][WorkBuddy]=20Prototype=20art=20asset=20pi?= =?UTF-8?q?peline=20=E2=80=94=2063=20placeholder=20sprites,=20concept=20do?= =?UTF-8?q?cs,=20specs,=20Godot=20import=20guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Player: 6 sprite sheets (idle/run/jump/attack/hurt/death, 48x48 frames) - Enemies: 15 sprite sheets (Ghost Soldier, Ghost Archer, Corpse Beast) - Boss: 5 sprite sheets (Gate Guardian Ghost General, 96x96 frames) - Icons: 17 total (6 weapon + 11 UI stat, all 32x32) - Tiles: 7 tiles (floor/brick/wall/bridge/brazier/tombstone/spike, 32x32) - Effects: 4 VFX sprites (hit slash, blood splash, ghost fire, death fade, 48x48) - Background: 4 parallax elements (tree, flag, chains, deep background) - UI: 4 elements (health bar, stamina bar, frame, minimap frame) Documentation: - docs/ART_SPEC.md: Master art specification (palette, sizes, naming, standards) - docs/art/CONCEPT_PLAYER.md: Player character concept (Yue Family Army Soldier) - docs/art/CONCEPT_ENEMIES.md: 3 enemy concepts (Ghost Soldier, Archer, Corpse Beast) - docs/art/CONCEPT_BOSS.md: Boss concept (Gate Guardian Ghost General) - docs/art/CONCEPT_ICONS.md: Weapon + UI icon designs - docs/art/CONCEPT_TILES.md: Tile + environment designs - docs/art/CONCEPT_EFFECTS.md: VFX designs - docs/art/CONCEPT_BACKGROUND.md: Parallax background designs - docs/art/CONCEPT_UI.md: UI element designs - docs/art/GODOT_IMPORT_GUIDE.md: Step-by-step Godot 4 import instructions - docs/art/NAMING_CONVENTION.md: Asset naming rules - godot/import_presets.md: Godot import settings + SpriteFrames reference - Each directory has README.md + metadata.md with frame counts, sizes, FPS All PNGs are placeholders (magenta border, checkerboard pattern) with correct dimensions as horizontal sprite sheets. Replace by saving new PNG with same filename and dimensions. Updated AI_HANDOFF.md with art pipeline status. --- assets/README.md | 31 ++ assets/background/README.md | 17 + assets/background/bg_broken_flag.png | Bin 0 -> 431 bytes assets/background/bg_chains.png | Bin 0 -> 240 bytes assets/background/bg_deep.png | Bin 0 -> 1110 bytes assets/background/bg_tree.png | Bin 0 -> 531 bytes assets/background/metadata.md | 40 ++ assets/boss/README.md | 25 ++ assets/boss/boss_attack.png | Bin 0 -> 1652 bytes assets/boss/boss_death.png | Bin 0 -> 2227 bytes assets/boss/boss_hurt.png | Bin 0 -> 1078 bytes assets/boss/boss_idle.png | Bin 0 -> 1485 bytes assets/boss/boss_run.png | Bin 0 -> 1857 bytes assets/boss/metadata.md | 54 +++ assets/effects/README.md | 19 + assets/effects/effect_blood_splash.png | Bin 0 -> 377 bytes assets/effects/effect_death_fade.png | Bin 0 -> 162 bytes assets/effects/effect_ghost_fire.png | Bin 0 -> 514 bytes assets/effects/effect_hit_slash.png | Bin 0 -> 387 bytes assets/effects/metadata.md | 29 ++ assets/enemy/README.md | 39 ++ assets/enemy/corpse_beast_attack.png | Bin 0 -> 694 bytes assets/enemy/corpse_beast_death.png | Bin 0 -> 694 bytes assets/enemy/corpse_beast_hurt.png | Bin 0 -> 550 bytes assets/enemy/corpse_beast_idle.png | Bin 0 -> 694 bytes assets/enemy/corpse_beast_run.png | Bin 0 -> 840 bytes assets/enemy/ghost_archer_attack.png | Bin 0 -> 550 bytes assets/enemy/ghost_archer_death.png | Bin 0 -> 550 bytes assets/enemy/ghost_archer_hurt.png | Bin 0 -> 471 bytes assets/enemy/ghost_archer_idle.png | Bin 0 -> 550 bytes assets/enemy/ghost_archer_run.png | Bin 0 -> 653 bytes assets/enemy/ghost_melee_attack.png | Bin 0 -> 550 bytes assets/enemy/ghost_melee_death.png | Bin 0 -> 550 bytes assets/enemy/ghost_melee_hurt.png | Bin 0 -> 470 bytes assets/enemy/ghost_melee_idle.png | Bin 0 -> 550 bytes assets/enemy/ghost_melee_run.png | Bin 0 -> 654 bytes assets/enemy/metadata.md | 79 ++++ assets/icons/README.md | 10 + assets/icons/ui/README.md | 21 ++ assets/icons/ui/icon_armor.png | Bin 0 -> 350 bytes assets/icons/ui/icon_attack.png | Bin 0 -> 302 bytes assets/icons/ui/icon_boss.png | Bin 0 -> 373 bytes assets/icons/ui/icon_coin.png | Bin 0 -> 364 bytes assets/icons/ui/icon_crit.png | Bin 0 -> 382 bytes assets/icons/ui/icon_ghostfire.png | Bin 0 -> 331 bytes assets/icons/ui/icon_hp.png | Bin 0 -> 317 bytes assets/icons/ui/icon_key.png | Bin 0 -> 297 bytes assets/icons/ui/icon_sacrifice.png | Bin 0 -> 405 bytes assets/icons/ui/icon_speed.png | Bin 0 -> 323 bytes assets/icons/ui/icon_stamina.png | Bin 0 -> 362 bytes assets/icons/weapons/README.md | 16 + assets/icons/weapons/weapon_flag.png | Bin 0 -> 282 bytes assets/icons/weapons/weapon_ghostfire.png | Bin 0 -> 331 bytes assets/icons/weapons/weapon_gourd.png | Bin 0 -> 274 bytes assets/icons/weapons/weapon_songdao.png | Bin 0 -> 355 bytes assets/icons/weapons/weapon_spear.png | Bin 0 -> 314 bytes assets/icons/weapons/weapon_talisman.png | Bin 0 -> 243 bytes assets/player/README.md | 29 ++ assets/player/metadata.md | 63 ++++ assets/player/player_attack.png | Bin 0 -> 557 bytes assets/player/player_death.png | Bin 0 -> 662 bytes assets/player/player_hurt.png | Bin 0 -> 480 bytes assets/player/player_idle.png | Bin 0 -> 557 bytes assets/player/player_jump.png | Bin 0 -> 480 bytes assets/player/player_run.png | Bin 0 -> 662 bytes assets/tiles/README.md | 22 ++ assets/tiles/metadata.md | 36 ++ assets/tiles/tile_brazier.png | Bin 0 -> 317 bytes assets/tiles/tile_floor.png | Bin 0 -> 169 bytes assets/tiles/tile_ground_spike.png | Bin 0 -> 261 bytes assets/tiles/tile_stone_brick.png | Bin 0 -> 154 bytes assets/tiles/tile_tombstone.png | Bin 0 -> 313 bytes assets/tiles/tile_wall.png | Bin 0 -> 135 bytes assets/tiles/tile_wood_bridge.png | Bin 0 -> 144 bytes assets/ui/README.md | 17 + assets/ui/metadata.md | 41 ++ assets/ui/ui_frame.png | Bin 0 -> 175 bytes assets/ui/ui_health_bar.png | Bin 0 -> 145 bytes assets/ui/ui_minimap_frame.png | Bin 0 -> 239 bytes assets/ui/ui_stamina_bar.png | Bin 0 -> 149 bytes docs/AI_HANDOFF.md | 13 +- docs/ART_SPEC.md | 297 +++++++++++++++ docs/art/CONCEPT_BACKGROUND.md | 81 ++++ docs/art/CONCEPT_BOSS.md | 116 ++++++ docs/art/CONCEPT_EFFECTS.md | 70 ++++ docs/art/CONCEPT_ENEMIES.md | 120 ++++++ docs/art/CONCEPT_ICONS.md | 116 ++++++ docs/art/CONCEPT_PLAYER.md | 99 +++++ docs/art/CONCEPT_TILES.md | 77 ++++ docs/art/CONCEPT_UI.md | 92 +++++ docs/art/GODOT_IMPORT_GUIDE.md | 189 ++++++++++ docs/art/NAMING_CONVENTION.md | 47 +++ godot/import_presets.md | 107 ++++++ tools/.gdignore | 1 + tools/generate_placeholders.py | 431 ++++++++++++++++++++++ 95 files changed, 2438 insertions(+), 6 deletions(-) create mode 100644 assets/README.md create mode 100644 assets/background/README.md create mode 100644 assets/background/bg_broken_flag.png create mode 100644 assets/background/bg_chains.png create mode 100644 assets/background/bg_deep.png create mode 100644 assets/background/bg_tree.png create mode 100644 assets/background/metadata.md create mode 100644 assets/boss/README.md create mode 100644 assets/boss/boss_attack.png create mode 100644 assets/boss/boss_death.png create mode 100644 assets/boss/boss_hurt.png create mode 100644 assets/boss/boss_idle.png create mode 100644 assets/boss/boss_run.png create mode 100644 assets/boss/metadata.md create mode 100644 assets/effects/README.md create mode 100644 assets/effects/effect_blood_splash.png create mode 100644 assets/effects/effect_death_fade.png create mode 100644 assets/effects/effect_ghost_fire.png create mode 100644 assets/effects/effect_hit_slash.png create mode 100644 assets/effects/metadata.md create mode 100644 assets/enemy/README.md create mode 100644 assets/enemy/corpse_beast_attack.png create mode 100644 assets/enemy/corpse_beast_death.png create mode 100644 assets/enemy/corpse_beast_hurt.png create mode 100644 assets/enemy/corpse_beast_idle.png create mode 100644 assets/enemy/corpse_beast_run.png create mode 100644 assets/enemy/ghost_archer_attack.png create mode 100644 assets/enemy/ghost_archer_death.png create mode 100644 assets/enemy/ghost_archer_hurt.png create mode 100644 assets/enemy/ghost_archer_idle.png create mode 100644 assets/enemy/ghost_archer_run.png create mode 100644 assets/enemy/ghost_melee_attack.png create mode 100644 assets/enemy/ghost_melee_death.png create mode 100644 assets/enemy/ghost_melee_hurt.png create mode 100644 assets/enemy/ghost_melee_idle.png create mode 100644 assets/enemy/ghost_melee_run.png create mode 100644 assets/enemy/metadata.md create mode 100644 assets/icons/README.md create mode 100644 assets/icons/ui/README.md create mode 100644 assets/icons/ui/icon_armor.png create mode 100644 assets/icons/ui/icon_attack.png create mode 100644 assets/icons/ui/icon_boss.png create mode 100644 assets/icons/ui/icon_coin.png create mode 100644 assets/icons/ui/icon_crit.png create mode 100644 assets/icons/ui/icon_ghostfire.png create mode 100644 assets/icons/ui/icon_hp.png create mode 100644 assets/icons/ui/icon_key.png create mode 100644 assets/icons/ui/icon_sacrifice.png create mode 100644 assets/icons/ui/icon_speed.png create mode 100644 assets/icons/ui/icon_stamina.png create mode 100644 assets/icons/weapons/README.md create mode 100644 assets/icons/weapons/weapon_flag.png create mode 100644 assets/icons/weapons/weapon_ghostfire.png create mode 100644 assets/icons/weapons/weapon_gourd.png create mode 100644 assets/icons/weapons/weapon_songdao.png create mode 100644 assets/icons/weapons/weapon_spear.png create mode 100644 assets/icons/weapons/weapon_talisman.png create mode 100644 assets/player/README.md create mode 100644 assets/player/metadata.md create mode 100644 assets/player/player_attack.png create mode 100644 assets/player/player_death.png create mode 100644 assets/player/player_hurt.png create mode 100644 assets/player/player_idle.png create mode 100644 assets/player/player_jump.png create mode 100644 assets/player/player_run.png create mode 100644 assets/tiles/README.md create mode 100644 assets/tiles/metadata.md create mode 100644 assets/tiles/tile_brazier.png create mode 100644 assets/tiles/tile_floor.png create mode 100644 assets/tiles/tile_ground_spike.png create mode 100644 assets/tiles/tile_stone_brick.png create mode 100644 assets/tiles/tile_tombstone.png create mode 100644 assets/tiles/tile_wall.png create mode 100644 assets/tiles/tile_wood_bridge.png create mode 100644 assets/ui/README.md create mode 100644 assets/ui/metadata.md create mode 100644 assets/ui/ui_frame.png create mode 100644 assets/ui/ui_health_bar.png create mode 100644 assets/ui/ui_minimap_frame.png create mode 100644 assets/ui/ui_stamina_bar.png create mode 100644 docs/ART_SPEC.md create mode 100644 docs/art/CONCEPT_BACKGROUND.md create mode 100644 docs/art/CONCEPT_BOSS.md create mode 100644 docs/art/CONCEPT_EFFECTS.md create mode 100644 docs/art/CONCEPT_ENEMIES.md create mode 100644 docs/art/CONCEPT_ICONS.md create mode 100644 docs/art/CONCEPT_PLAYER.md create mode 100644 docs/art/CONCEPT_TILES.md create mode 100644 docs/art/CONCEPT_UI.md create mode 100644 docs/art/GODOT_IMPORT_GUIDE.md create mode 100644 docs/art/NAMING_CONVENTION.md create mode 100644 godot/import_presets.md create mode 100644 tools/.gdignore create mode 100644 tools/generate_placeholders.py diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..ca2943c --- /dev/null +++ b/assets/README.md @@ -0,0 +1,31 @@ +# assets/ — Art Resource Directory + +This directory contains all visual art assets for the 九幽 (Nine Nether) prototype. + +## Structure + +| Directory | Contents | Count | +|-----------|----------|-------| +| `player/` | Player character sprite sheets | 6 sheets | +| `enemy/` | Enemy sprite sheets (3 enemy types) | 15 sheets | +| `boss/` | Boss sprite sheets | 5 sheets | +| `icons/weapons/` | 32×32 weapon icons | 6 icons | +| `icons/ui/` | 32×32 UI stat icons | 11 icons | +| `tiles/` | 32×32 tilemap tiles | 7 tiles | +| `effects/` | 48×48 VFX sprites | 4 effects | +| `background/` | Parallax background elements | 4 elements | +| `ui/` | UI elements (bars, frames) | 4 elements | + +## Key Documents +- **Master spec:** `docs/ART_SPEC.md` +- **Concept docs:** `docs/art/CONCEPT_*.md` +- **Import guide:** `docs/art/GODOT_IMPORT_GUIDE.md` +- **Naming rules:** `docs/art/NAMING_CONVENTION.md` + +## Placeholder Status +All PNGs are currently **placeholders** (marked with magenta border). Replace each with final pixel art using the same filename and dimensions. See `docs/ART_SPEC.md` Section 5 for replacement workflow. + +## Regenerating Placeholders +```bash +python3 tools/generate_placeholders.py +``` diff --git a/assets/background/README.md b/assets/background/README.md new file mode 100644 index 0000000..14fc488 --- /dev/null +++ b/assets/background/README.md @@ -0,0 +1,17 @@ +# assets/background/ — Parallax Background Elements + +## Files +| File | Name | Size | Parallax | Purpose | +|------|------|------|----------|---------| +| bg_deep.png | 深景背景 | 320×180 | 0.2x | Underworld vista — distant mountains, mist | +| bg_tree.png | 背景树 | 64×128 | 0.5x | Dead skeletal tree — mid decoration | +| bg_broken_flag.png | 残破旗帜 | 48×96 | 0.5x | Broken flag pole — mid decoration | +| bg_chains.png | 锁链 | 32×96 | 0.7x | Hanging chains — close decoration | + +## Notes +- `bg_deep.png` is the only full-frame background — tiles horizontally +- Other elements are sprite props placed at intervals +- All have transparent backgrounds except `bg_deep.png` (full opaque) + +## Concept Reference +See `docs/art/CONCEPT_BACKGROUND.md` for full design descriptions. diff --git a/assets/background/bg_broken_flag.png b/assets/background/bg_broken_flag.png new file mode 100644 index 0000000000000000000000000000000000000000..9a0086b019c7b302262c0139764d03af9b8e720d GIT binary patch literal 431 zcmeAS@N?(olHy`uVBq!ia0vp^20)y^!3HFwdp;I0Fff*Rx;TbZ%z1mu){ogx#P#9h z&5Tm7Os<$@-f#Dq`M?&2wP`6c7d$O;V$0quHmR{{jaTY~IPWaU1<&gotX}Um-kknX@U~aAsN7cd zZSEH*-sOxIm$@6YF?FM4tzxw3tg8z*yH1?>*0o=3#$9RC$`9>KYYcweW%N7mU;j=# zcklhXe>ViLJ~%JE<%iwItzI0JCe!89pM_O&&Hf|*E87R%rzqYgf^+UBv&Ad!riSLM Uwsq;Dz=&Y*boFyt=akR{08eHt-t%J>#xVG;lH1s zo4(_1`i-BwDNiqae_H=j{Eb%00%jH26?{G#G-JGAethRhL!{%pnLsZvc)I$ztaD0e F0sxlqS#SUV literal 0 HcmV?d00001 diff --git a/assets/background/bg_deep.png b/assets/background/bg_deep.png new file mode 100644 index 0000000000000000000000000000000000000000..799f1088a7dffccdd77485cb2e92895153ef6a6c GIT binary patch literal 1110 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Yow{Wll$)``g3NkRT$a=ashE&XXduMN!w4;PW zU}d1-vl4T zB5*>;LBX4$QHPOb(hT=K6E7IX@7-gLn*X=>-<^K%{QY(R z>i&P(&%oGymNBmS(e2AN3%=g8e}4Y&jwiBUZP%0-_k3+V9=DHUBgBg96-+QEot9CU zU|P-hVWPg(wmIxuAoAOPaPXvXLiDa-u(Nux=c>aNsF}y1PO)@Q$o?erV9NKZ^>dCx zogV*DPy#99)mRH&C464ZwrK77f0p&{jw{yXrk#@54OhUdhe@bpXiEhsEL()_isCfH*;7{)=v)?XOo_9#EZs=h_UItIvNq zY7KPMjaPf;&QJe8NBn#8YdM5RL5Z=r!Qq;7aYOb$&ef{V7@@A3vJdLpuc-`wS<~wo ztNH2|R?Gd~{_bBTN-&j{^A)_+cX)OV=1R+2gn!p0^1r{n?2jEJ!VI^I&x0iXGe9q! zKWB*l+xPZnqytP=|8U%+PiNk5g#`EcUDtTu!}E&Nw@-C%FW%3u{dWFmvHh}LZ`#!z z6hP7RHIuL4b^na`voQOY{(7WrFv0ZA+n2BIS*?MDam;hsk1VjTyS8Eb_w4Q0k|9P- z`*cwl6axP(5A3x~j6L@{0+NE3{(b~WC)N$se<~{X=|Nn%aZ7zH!l-GW5PzX4VBzwY Zb;D?gwN3lQzt~<+$(h)a+8tVN@?*QGp|RlUiD9+-!^{7$&{u=J|73&I|lGqvA0qJ@Rj4=!2O}^Cz-&e$H8Kzl%%dobkGUC$uJ5 zoZt0p@hXQ$K>qaq9k~jHLeFoWu77Jb!J>1rh5ey_@t@_uOc3vs_=B7JjYmA^U`79{ zAAe1Mdi(#+{#O^}SF8FxQURiK?s;~aRXV#|s`MJ$2;IVUl6%TUdo05A3)js}Sg$2^p$40lw()EV6>|G7 zJki44&ulkWTFK;z2h&s&!?a~*CX@)JeG`nDp>fyqRoJ%aH`bPz@7(_G@A-P)Gw0*& t&1#qVzj@%nt`Bk&h3MQ~`Goxc3`a7bU)pg*&;l6Q44$rjF6*2UngE>}{h9y( literal 0 HcmV?d00001 diff --git a/assets/background/metadata.md b/assets/background/metadata.md new file mode 100644 index 0000000..fba6142 --- /dev/null +++ b/assets/background/metadata.md @@ -0,0 +1,40 @@ +# assets/background/ — Metadata + +## Background Element Specifications + +### bg_deep.png +- Size: 320 × 180 px +- Type: Full-frame parallax background +- Parallax: 0.2x (deepest layer) +- Tiling: Horizontal (left/right edges match) +- Purpose: Underworld vista — jagged mountains, low mist, distant ghost fires + +### bg_tree.png +- Size: 64 × 128 px +- Type: Sprite prop (transparent background) +- Parallax: 0.5x (mid-background) +- Tiling: Non-tileable, placed at intervals +- Purpose: Dead, skeletal tree — bare twisted branches + +### bg_broken_flag.png +- Size: 48 × 96 px +- Type: Sprite prop (transparent background) +- Parallax: 0.5x (mid-background) +- Tiling: Non-tileable, placed at intervals +- Purpose: Broken flag pole with tattered battle flag + +### bg_chains.png +- Size: 32 × 96 px +- Type: Sprite prop (transparent background) +- Parallax: 0.7x (close background) +- Tiling: Non-tileable, placed at intervals +- Purpose: Hanging rusted iron chains from above + +## Godot Import Settings +- Filter: Off (Nearest) +- Mipmaps: Off +- Compression: Lossless +- Format: RGBA + +## Parallax Layer Setup +See `docs/art/CONCEPT_BACKGROUND.md` — Parallax Layer Structure section. diff --git a/assets/boss/README.md b/assets/boss/README.md new file mode 100644 index 0000000..0f027f4 --- /dev/null +++ b/assets/boss/README.md @@ -0,0 +1,25 @@ +# assets/boss/ — Boss Sprites + +## Boss +**镇关鬼将** (Gate Guardian Ghost General) — a towering underworld general with ghost fire and a massive blade. + +## Files +| File | Animation | Frames | Sheet Size | Frame Size | FPS | +|------|-----------|--------|------------|------------|-----| +| boss_idle.png | Idle | 4 | 384×96 | 96×96 | 6 | +| boss_run.png | Run | 6 | 576×96 | 96×96 | 8 | +| boss_attack.png | Attack | 5 | 480×96 | 96×96 | 10 | +| boss_hurt.png | Hurt | 2 | 192×96 | 96×96 | 8 | +| boss_death.png | Death | 8 | 768×96 | 96×96 | 6 | + +## Notes +- Boss is **3× the player's size** (96×96 vs 48×48) +- Ghost fire (blue-green) appears in every animation +- Death animation is the longest (8 frames) for dramatic effect +- Attack is 5 frames — wind-up → strike → impact → follow-through → recovery + +## Concept Reference +See `docs/art/CONCEPT_BOSS.md` for full visual design description. + +## Color Palette +Underworld purple `#321E37`, ghost fire `#1EB464` / `#4AE68A`, bone `#A0987A`, void `#0F0C12` diff --git a/assets/boss/boss_attack.png b/assets/boss/boss_attack.png new file mode 100644 index 0000000000000000000000000000000000000000..46720bc364cb6177151480ec12ae647920b42c23 GIT binary patch literal 1652 zcmdT^TToM16up54sEAZ6LNPQf7wJHLk zRx64&u}Xtr32H?Qk4sS!l}A9h!(fD@hDit^LQDwBO>$2!41Tm<{q6ZUb7syyd#`=g zTKnw!_%&YB{HH+>(1Sa3l8S_pjR^$ZdICOw`8gtA@VX!xZLhH`R}y zN6xK1xPZJdO7mE}Dkk{bGeLm^!PzsSKZ;&cR7TN}Vq>37U%DP*#sqr!?f>R8|AO&f z>)Vts@P?40uh|^U)geRwYqWRe{*vl6h!=P5C^&8zFl*q}{*u0jk5J_%Hm)$#R>(_Q zdU_ZrAn`~i9-y`!!pXx-k zo=jGS(kQ$&HKh)w3`daMC@a`t}C`M&7fOj~)JK9ALu^##+3!cVO7L~vXWZ)=L~ z@Dak~h>PjQ5%#MkiyBrBiSYon(y!`@Bl3~vne;v?FCnccBY6xkz1FcBq- z(^z`{&*|((=h_FfhGO4((aSK2X@@}sd)42-hZq#96t>uI5hv8adORb$cj5DNBPF!S z`IB19;2zgznhGx%vQcn794Aux`m_ONXk&e4kvd?wu8E(q)iQ0)7`z}K9uxO6$tJkd zbF|}bQ610!Rh z1aF3CC9|(qwO7cmzs}t7fEM6Cwh3JM01P1s&0&qup)+|qc(pVot8Tq13YR2J$7N*D|j&g8U$O$XJJgJm61Bu+J9Pd2+uhfbGU z$u)}lbctuRJqes{w-P9~Q>+eFE_JkeOwws#zm|GmRaEzB5hn72+}cChR%faaeE&>wHcIS^fClv=fftxO`CKu zqHI@-J9O&oIO$U0HFz0!%QA4(@yAhIXWgfBbQ!zO+Y}_%RXoBUE+02S>(&1)?meM* z4Ub;)k4(>PBDF~8>c)Gv^rRaPnqYR&HB{&w8o*feugVyYB#bB&k^&nE`bZehbMU*h zvV^r-L80L1cE(iv6?zI(mOpPVC%Z$h!Y$)ePL-t@SbD_EPdLi;3Yh@`IXTV^ zPE=_X;5J&_b|=aR2flX*3SLZjYT|R3SV+p3_+IC^5xnx+N-jEP&{-x&9nBw+f@5+j zp%T$$E`FbQG)WopP$(TCCc?d_TI4_vUu>{1MJFaBOA7k76vKPgwV4pYx|6PbOJBA; ziCTr}SGTPIW>N|KgPD*NnG!XnD>F&&S}F0)$Q8`u2B{i|Ia>gF;{}Wf6}U{OKvkCZ fB66l5bS2N}uE^Z?CZwPv86s93Gd`v=I_2cwcpi&g literal 0 HcmV?d00001 diff --git a/assets/boss/boss_death.png b/assets/boss/boss_death.png new file mode 100644 index 0000000000000000000000000000000000000000..886266c50e2cbf5d17b7f5044d84ec3bc5d690b3 GIT binary patch literal 2227 zcmeHIX;4#F6n@C2h|;PkiZ*qoYO6AU3kqVMwQfNLmA0rU2G_V@Tu=cemtw7L*{leR z6%4f^YLqBgzy%)GN>tFGkxdIhgM>gJ#Dpa8CGXwdQ0g+%U;WwXpEGaf-Sd6td}sT9 zS@6O^E~8v93>(B*G;all*`rS^qQ4We7qZb(cD4(Q(EmFsrPT zx;Z;8juBIQYB61?74(F``yy|OM^9qa!L(>vdvpIV46ENZ9EG~9b4U&;@8=cF84Zjt zNo`0|ptkk+6ZtQE?V8X^vr>AFpo%p(EwK7B9*`?!JU|u5==qM6>Cb8jz2ls1g&gDM z_o8E{b;tacFbAT?TtXf)Ib78)VZ27d!Y4fXz(}YybT9C5@v%6SrLWcb&KkOW%PTyD zt^qkP8LtqxhruV!f=O9+9w*%+8aB>+HVu%EPKSQvex9Jh&cYgE3N2yMOD6}y zKo6r`Tc_V~z4*$;x)$%%SG+7I5>`DLajEEW@!C-~dead`R)*K`Xs$t|Q>Y|5sda)s z)Vy#lzu?~B6C>g|RI#&ow64-w#*GTfEq1Sq%wgJjCm5uZ0nr%?s;z~Uj^$O$?&@Zk z6J2n2QM+g06IfIY-@M9PgH>^->v+a`shddC?Ot>G9f++VD$$Q|n4f=He$yJ@X2G`a zJ{VqFDn!*-O7X+hOf76D9x$uIt`j{3B`5^7>0MfJ%6?*K_fCIVfLIc9TfbV-v_*ju zdXxX`Mm@9o|ce)z%NR~h`*eG>u744VSIp#_@PirEN zZQ2!EWmEeLu>BB2kd{z7vrcLjSZ6SrE_$u?o=j#W^~D`i5o-CqRmjd6+uIw9OepHg z$-${4s)3|EHL@Dc0N?_AMR~wvSRraG#glN7Y+wUsUz}5O%L9G#{!q_T`1bS3l)}%} zaPmIA!aHK^5biK8=Bj8?7$(OHU=1w#`>##&%qHgu`L=@C19WjD3qvA>8|%!zy(%dR zh>BE>sTfr!gPty2a-DEXd;lJfzy>JdbXrKT%xl_{Pe3Ab0maR`kc!R%SNl^Hq9-p6l}X745H zT72F@Sw5MUb%1!Mbd?O zQCoO?Mcr!Qf!#3!RxDPQDCORkpx#OG_UL5>+zOU5B!QIW-cS?YB!+rl6F^`lOIjAJ zMLl5OH>sgXIC>^athY!i!kSP;Sr*LjVKxX5t`2}v)~ivB+Pp<=mFwh>$vkCVu0BK> zvREZb5oJ^qU$ z#v^ugl3}G~;iDlflm9h>z+zHZccJZ2OY^NJa+}o`a#HZ{EZcHv;Oc;Jzs<_7~djVe1KKpvewbcX3Z6~YEb4IFV&NJuR zY>%!(><@;vHaJ(XeL4;b>yJoP+VRRGI@R_|)XcYHc9!3Z`bqdc3f~jjrT6#GEN@c^ P5H`k{A3QIAZbZVLEWoq= literal 0 HcmV?d00001 diff --git a/assets/boss/boss_hurt.png b/assets/boss/boss_hurt.png new file mode 100644 index 0000000000000000000000000000000000000000..36df7d80d905ad31266fee4d12877729c9d5960c GIT binary patch literal 1078 zcmeAS@N?(olHy`uVBq!ia0vp^2Y@(%gAGV}AE!@8_y4EPnMtp{y!g1o-*;PlE$>JBY?FVB zSu&#bod{Cm(Yq&}_3}~wh5ls%{}sF!{Bc~uZP0(f*Yo7`bfx9p``7sYF*SAw-N&$8 z*V%04oQQA=2r>`CLfA#Uh;Z2u|(q>*c_`^LGBu?29$tUzDbvv`Nx6 z`n9L7%-~;Loc*nqAKddfX0z%lYZ*R=7W+KA|H>Lx# z{!}(_TzMaVci*f}e~pmkPrtbWES%YB*@3z-6vJb z;LxHf0M;+7aKLWQu3FPq3=JIK3K7bT3B6Vh4Yd`ezq76|G6+s{09heb+Q9Jp!>eys zS1>au0Rym?r2(jc@!Lb7Qv%r;Tq2kbf^5CQ$n@s$>f7Oq`4~Djur!$h<6{Lg%Z>K% z?d#oS83YnJS+F_GN#(%DhbK=xe`@c$?Y7zRV;}EJiyeO0yx4tH?TeZ}_x}A|YW@G~ z_36EH?bp}ew)pq=`Oz2JAFm&^Kf=SXVge)t_V5|l?b~1T{POu*H-3KpwSDR1tJeko z=Pmx2&ei~O1<>!4?fEyH+pGC9J@;45_UDYdo*~JB!sV>5tb=%c8Ur{4z}~yU*cA%& oT$v0oSfp)*R=#5i==j6zYcb1LDyY;RSROEVy85}Sb4q9e05Qt{EC2ui literal 0 HcmV?d00001 diff --git a/assets/boss/boss_idle.png b/assets/boss/boss_idle.png new file mode 100644 index 0000000000000000000000000000000000000000..d839894a30bc6cd60c8b026f9b9e3b3bab4a0ae9 GIT binary patch literal 1485 zcmd5+{ZCU@7(PWSrpP)YD2URT2`WTjhWQD-odW>{iw^A~)|#LelvTba7Nxi9bVHpw z0ZSCPC?iCx)PYr2Q_4&f4Pwojf<@b+P;0$F?I*Nc?%gfRGWQ?shxgBIf z;=(^>l?d##mKB23hjTLRK?(j5!4bjn;ZYC>%vjq&n-`ZAbjLPbIPhY9jeZDY#L*83xjp0y?pxQsIEq`@{~GT%cHfvB2z zid`gz(QG`$hA|B5Xsl2gV40RTSh|ldib;!MmI)m$liIklt*J!hedDe@brzZk@koe8 zX2b)<)Dc(w;_iqpa2K^Ja8XrngzBUt$(+Ds;qL-yv;hAH%xg zy_}Q1NMry@wrMDzS|=IT<(!FHZ6~s72s=*!(zEY;fSfPd`0CQb8|05w7{|Sz-G`(y zIwQL{+J4Wy=l-{!*tW?J;bdV~o+8_t%ww&CJ*9r7FV{%3jCp#;sSf10q`7>C<0s5D#^ZPUU5>@YC>IEBd zld84b4`pje+6UAIX+66AbuUT$8TmM!wq6GHj8XCxOZxgfkemrf$GFdAx~^Z%W9=`n z^UAPy5(JwdI7=GU9Ui`A*G}Tn^YfOcXtX=$hLwZVzY-~j6WkS~h+P=8Oo=BkXAcW( zp~^4E_o{i$pKB!J$>vqo1STYLVP&@v!2cUl(mH%m@Qr~zn#A3A8_Y53FV}KxYgM#kh~0lWXrVN z0p<|NJ9RY0qVWfv1Njunl<)uR0GvQUxA{jSm<*n5m78PNskjS>Ni}+fXX4>K#A_a0_a0@>;gFKCDPZYFGNKIr1xK*M zu1YK3aw`$mc}Cui59g5oL*Hf9TVkIK8NBQ%rIOP7EwbAXk?gXc!A{ceX;$xx63 za>3D!9Xv2u_)(hADfcTEOktd2&MF6kw>m^Vqh|Vy j(jQ&C$GaGW1dc)jp^G~u@$gCVn+9pgo0D4B?>_z)Mo0Z% literal 0 HcmV?d00001 diff --git a/assets/boss/boss_run.png b/assets/boss/boss_run.png new file mode 100644 index 0000000000000000000000000000000000000000..7658b7cf01d46e39913d8b7b52042b5b86db7ea8 GIT binary patch literal 1857 zcmds2`BPI@6n+UP2x9y}S%fCqDq@u>D2kvYRHPO~jJP1Ogei_lQKLYpB!ri0#adY` z473;_QBgn!B1?${A#M#cm&hTX1eypxuw)*tD2 zjGR6%bO8!lfA3wL?8+qql`hL_x#`-V2tC%H+kIWKASamim+<+daL8dAO?*bpW5d@) zMxir23?fbFK-;{oriI9j6Oqx@IR(A-l+5%I#>3nOX4G*ae!BB)SSuJ$yfj7s$)4X$ zeSpk+Tz7W|DN5(N{t2KuW5~s70eaOb@Z3|+jJ*N6?o(pct;_o{td(ofK2cqQa1wPz ztQsE4xeBF`H9C{^(ab^@3U_NBT~0H95p!Aj|kS=>xfz@yVM zn$3e^>5?FQkTXiI6tP(u`Hr$k$=hAUT6!l17p=449bUhQnm|1Smu7#wUh1 zu$CLDVnuHVUquc{V`Oz>dWBebp*oOON)hR=qYAo7g_?g+9M|L-65>$NZU%Z*32o0g z-BQEg**BpQqThhW@R4iiKav)fIJST+BNTI#mD6axl++x4#nMA^J6wdd^05Y}V`Zct zRd{Zxmd#E}x0d$!yh)u>Hila`3X0)Qij@X#F^q&6-A&9fPvMm2xukrV>oRQn2-z^k zLTsWj(8}7^IH+qEiBBxk>=5tRHp0v06+NbWMDZ+v{WJIbgH%()0W^&RagtZP3!B&+ zWBvyprJ{&F5i#|ud-Dwgn7|#i&`jvIsW?TRJ9FG)D3_OCeFt`pfaw>MdwT|85~tu3 zf@YxK-)bpCtgNA~^PhOV-5fVI$A9$R`TsJulo=r${&Nc?$5ud;t* zzi;#c8GHU5{P0$9dH;_oH|>K0n1AZq1+E~`Mfcv}#D%SZ_$WbR*s`2=-#9-z4?d>5 zm-87a6vd8@Ya?Q0WFjz2H0K1E<=f#Yo$RF?%c{jZ0|>S}WSEZI90Q?1KTA-P1L;Ij zv!RV3l0J@w`?N|#jig`>r~72)0Obn`W}`fVXwVdIgNb8NXgX9FT=;D%wUfnz?pd-V zseoPrCjBs+fo$1!i}kRYi4STl(vmf0m_f#Z3eo{$bpTCV-MV)pnVqYK2OBJMO(bc-TSk_e+36%p)Mo{ZYU!*znGJUkg+D==n{bbiM+OA{Fv3hjA=69`c6t@VCZ@0 v&W~~yC5scQ79^jo<(O>INjdsl{@^NWrXQ2=1Ziniprs6+u6{1-oD!Mb>!45SIc@m!!oh6d=CN8|_H`T~!7lE=Ovn}5(pcq`Bj847fEGsJ~E6t-$eqR7}` z+8ctzlC6_?lt?;R2VDn>Vfv5ToAu9yVpWVfw4d$h_a(Kz9bN!IIS`b?xMsOz$xOAY zw*Z22AY6kFv;$p9XtsJd4XzBo1K3*DCkzPT=`o)q>|!`MTk{2Ab5%7|p2P#4NcZ0!l zIQ1I}ETQ0~{vgY=*M$sc5Xp(y#5yTULQ+0q8AVN806@GO9J+GDBme*a07*qoM6N<$ Eg8KyR#Q*>R literal 0 HcmV?d00001 diff --git a/assets/effects/effect_hit_slash.png b/assets/effects/effect_hit_slash.png new file mode 100644 index 0000000000000000000000000000000000000000..513fe21beafb5d895c655a294ad9e449ec602680 GIT binary patch literal 387 zcmV-}0et?6P)NklTIuK95=HTEJ>V)Y+fIzUcul?T}NN79!fBUt6 zLMd8mTM}HQd+fIRu0e0~V1WJF!8=m_`|~12!b_3xD#y!$jLcNO_!Uqjyc7Xf{d_Er zDO?U99;4=d>)M*8q09Ly=NL63@l{pLj#U!G9}g!P2I?O3|A>dm0W&l6B67fZVgqHs z1c=XWpkBfJWHi^OhqAQezX?;AZ-RY9Y zB_%gEn|NMkVNj7w-7og}^T7x?>G=AXTd6f1)! z2Lr<^)rOg3Eps+)G0Z#9nOpzj_`lp=?2-%(1}qE=i6Hs;j5A6dubaNydHk*A!ngc? zZ|wK|ufWV8(8j=^z{AMkFk{6phIiqg5C8A?e;?Ps{dvop^QL`uZ{n~1Uwo3m=y`m7 zE}uPrxp}7$(CF7v|Ll8C{`8&F#PDn5|6R-ey9+WfELdvCuz4MisP(Uz-_w8JzwTW6 zi{V25-{f!q11GaOB~)5w*YX0L9r1U*5QBpR6VPer>>4BrUOL-<3;+86UOGc$ZCnWZ z+{d3A*E!mMi#Pl5{~kL7!;13;*UAsRDLXO$-h3gT_Eg{h<=+^;e05FeDu-yTUvJm2 zZ^5U&c$Qxq_or|&Fnm>DVBm0;XQ*ZP;@DIATj2x)LyICvh=D=i*lPX-YgQ_(zq{eo z9jzZW`S+#<=B10D{QSK9tsMi<_|hf+>^&x%#8%AV-@j|tGrfKFNuNL7Y;rB09m8@t zoqwI>x1TR0fg!N!pFbxw+)h1by76SW&gDZpms^}ZzqNgnrSEyxg0;PMZ{~kF@&6>U z7q{BzE#S_YtbhQIAWk#$d;Mf3HN&6T(RZ2R<9YrY#- z@!Q~a{dI5T7yqBk$iNZ2lV4$5VYOezZ(yM1U$%$&Z1ZokgzK{z8kPwEV_<5TwEXw8 RvJz0T^mO%eS?83{1OU!`L+=0p literal 0 HcmV?d00001 diff --git a/assets/enemy/corpse_beast_death.png b/assets/enemy/corpse_beast_death.png new file mode 100644 index 0000000000000000000000000000000000000000..a8275d648f58844e1c6a63e1f4468880a740071a GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K53^>?;AZ-RY9Y zB_%gEn|NMkVNj7w-7og}^T7x?>G=AXTd6f1)! z2Lr<^)rOg3Eps+)G0Z#9nOpzj_`lp=?2-%(1}qE=i6Hs;j5A6dubaNydHk*A!ngc? zZ|wK|ufWV8(8j=^z{AMkFk{6phIiqg5C8A?e;?Ps{dvop^QL`uZ{n~1Uwo3m=y`m7 zE}uPrxp}7$(CF7v|Ll8C{`8&F#PDn5|6R-ey9+WfELdvCuz4MisP(Uz-_w8JzwTW6 zi{V25-{f!q11GaOB~)5w*YX0L9r1U*5QBpR6VPer>>4BrUOL-<3;+86UOGc$ZCnWZ z+{d3A*E!mMi#Pl5{~kL7!;13;*UAsRDLXO$-h3gT_Eg{h<=+^;e05FeDu-yTUvJm2 zZ^5U&c$Qxq_or|&Fnm>DVBm0;XQ*ZP;@DIATj2x)LyICvh=D=i*lPX-YgQ_(zq{eo z9jzZW`S+#<=B10D{QSK9tsMi<_|hf+>^&x%#8%AV-@j|tGrfKFNuNL7Y;rB09m8@t zoqwI>x1TR0fg!N!pFbxw+)h1by76SW&gDZpms^}ZzqNgnrSEyxg0;PMZ{~kF@&6>U z7q{BzE#S_YtbhQIAWk#$d;Mf3HN&6T(RZ2R<9YrY#- z@!Q~a{dI5T7yqBk$iNZ2lV4$5VYOezZ(yM1U$%$&Z1ZokgzK{z8kPwEV_<5TwEXw8 RvJz0T^mO%eS?83{1OU!`L+=0p literal 0 HcmV?d00001 diff --git a/assets/enemy/corpse_beast_hurt.png b/assets/enemy/corpse_beast_hurt.png new file mode 100644 index 0000000000000000000000000000000000000000..b463eb2d94763d36c639336605808d1588c00c9c GIT binary patch literal 550 zcmV+>0@?kEP)Q9*@Xa`O#x^vQh-Z$+r6AYOV9;z1%SG;BhW-Rve!GXMWUOVgcX z_b-!~Fx}l%p^8EgSYrKly}d2JSYNIy2Ki53IhNG^`thRB^+%Of0tX!coVXqq<{Yxd zXh+x22mlTp03131IB`8MOr4X?-HxuG5da)I06262a75JsvUFR1etLd)Iv-uXm+#`EA(46Nk0gmVL2`mDK)(nFAS2cR%)ByJ>fUdDug8DNhdyxh)T#yxvz zZP;2kmoaqP`0o#ph}6#r0FLv7kgiHvowwufD6?!`3MppaL-xNHs-F=698voN__U+z oX9NI;4gk*ozkyQ^f~d*(1BQl94W`I!#{d8T07*qoM6N<$f-;=?a{vGU literal 0 HcmV?d00001 diff --git a/assets/enemy/corpse_beast_idle.png b/assets/enemy/corpse_beast_idle.png new file mode 100644 index 0000000000000000000000000000000000000000..a8275d648f58844e1c6a63e1f4468880a740071a GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K53^>?;AZ-RY9Y zB_%gEn|NMkVNj7w-7og}^T7x?>G=AXTd6f1)! z2Lr<^)rOg3Eps+)G0Z#9nOpzj_`lp=?2-%(1}qE=i6Hs;j5A6dubaNydHk*A!ngc? zZ|wK|ufWV8(8j=^z{AMkFk{6phIiqg5C8A?e;?Ps{dvop^QL`uZ{n~1Uwo3m=y`m7 zE}uPrxp}7$(CF7v|Ll8C{`8&F#PDn5|6R-ey9+WfELdvCuz4MisP(Uz-_w8JzwTW6 zi{V25-{f!q11GaOB~)5w*YX0L9r1U*5QBpR6VPer>>4BrUOL-<3;+86UOGc$ZCnWZ z+{d3A*E!mMi#Pl5{~kL7!;13;*UAsRDLXO$-h3gT_Eg{h<=+^;e05FeDu-yTUvJm2 zZ^5U&c$Qxq_or|&Fnm>DVBm0;XQ*ZP;@DIATj2x)LyICvh=D=i*lPX-YgQ_(zq{eo z9jzZW`S+#<=B10D{QSK9tsMi<_|hf+>^&x%#8%AV-@j|tGrfKFNuNL7Y;rB09m8@t zoqwI>x1TR0fg!N!pFbxw+)h1by76SW&gDZpms^}ZzqNgnrSEyxg0;PMZ{~kF@&6>U z7q{BzE#S_YtbhQIAWk#$d;Mf3HN&6T(RZ2R<9YrY#- z@!Q~a{dI5T7yqBk$iNZ2lV4$5VYOezZ(yM1U$%$&Z1ZokgzK{z8kPwEV_<5TwEXw8 RvJz0T^mO%eS?83{1OU!`L+=0p literal 0 HcmV?d00001 diff --git a/assets/enemy/corpse_beast_run.png b/assets/enemy/corpse_beast_run.png new file mode 100644 index 0000000000000000000000000000000000000000..9e37c3a4e2f65495030b720ed893b6bea5ce5ee4 GIT binary patch literal 840 zcmeAS@N?(olHy`uVBq!ia0y~yU~B-g4LI06S-MmX5mp^~{Z1<0=^<6(LzZCx5 ze`eA4|2OXiG8{5sU~q|J((>mHnzxCka8H!l*4M)Of5{u!uSyL%?f=tW{>w*0W(JN^ z3=9HO7#S2k-Df!U+G%>+Wb5xAvu0jd=l|zb{fR#-rg`exKmA{I;rMYL1_cdf28RGP zhK2=dTlpVcKAiOZzkYoEmS2CfW=?sWZKYTDb-(KWt^WglGjyGM|M7a(_xGQVe~(mo z6#oBH{O|fVH~ybpcID?(S1Fbw>;FC7|Ly+r|Fe%UFdSg`4Yc{x%2js1HSfp8{4cZJ za{l##qwD_#s{;+&_xFAp!;9Gonmg*Njel4Ibq0g%2##Q40y{nAv0Z~^Mi&(d@Rzf4x}R8UOcm{oDQd|IL3fGBnhs1=otj{F(Dm-^n{499M9PHWDG8RGyn=DY2${b4`}rBlZLl7ZGLPyWAphPMMlNjd{C(CmPY ze$%{xKk2&S4fc)amMzoUf0luv$%ui0B^4;Y>Z)DCE??I6ZPMkMw_Rudw*7L_Lq0Dq z^?u#IcYFCA{=A9@+7SHa|GG~)=kvwOcT4xxc6>Oz`*pMW{d%PET2W_UGUVPJ5S1UfM54*Q4yHy)O*)yocZy%`+GcJ-aqtJ5WS*%j)(-naYy zWdApN`x)vhztyJ!HEjLoFk{!;Eur_r>)9*I51L79o_bz)@-xsS;vj`f|D6Y>i52I5 rGb}5^o4l?(yX&A|E69+N{+(al^SYyOJaarShcI}$`njxgN@xNAp}VWp literal 0 HcmV?d00001 diff --git a/assets/enemy/ghost_archer_attack.png b/assets/enemy/ghost_archer_attack.png new file mode 100644 index 0000000000000000000000000000000000000000..18d7a1e747a91854210ad7c5e04330a7d1e08434 GIT binary patch literal 550 zcmeAS@N?(olHy`uVBq!ia0vp^2Y}dsgAGW!`<2QuFfhLLba4!+nDch7q4yC3iMHaP z4`~M%HaGoXo^#~n)wA)hpS@D?)5}wu=iJWxym*z$g(BU+6Q&D2??0DZliUC4)5Pie z@%v7fUY)hglwncGxAQMO@5V>SuVYoXW4pP$@g9?l5Z~y=Q>qn9o^WW3`^;3f?KJRh zz{cEQqWwE|!RB8|udMd!Ce>t~xyJBVDb34w!{0opdnW5J-#Ga1e&xFb@yh=$PIIh1 z+Ufu0->p19??o@J_tampI}m?$cfX7=)32|OSuVYoXW4pP$@g9?l5Z~y=Q>qn9o^WW3`^;3f?KJRh zz{cEQqWwE|!RB8|udMd!Ce>t~xyJBVDb34w!{0opdnW5J-#Ga1e&xFb@yh=$PIIh1 z+Ufu0->p19??o@J_tampI}m?$cfX7=)32|O=y2eiPL&*_v?U6~B+r}6^Bb<0 z^e7L!eg9pO&7g)s5g3*IxHxad30>>i4@_@nV@dbFx(}7R{sE3h&VT7z;D%q4~ZVp0rUs&1JvtXaF$m18VjG zMZ*wuDh61)aY@o*3{pR=4{*;hnXU(f($5eCh6Vsb1C-haxaa6h*8@W7rw4$c0l?4z zVAu!r>;sC1AqY&+y`lTJo#VuR4nbHX*x2^S4>fP^DTXc4MlegHXgSuVYoXW4pP$@g9?l5Z~y=Q>qn9o^WW3`^;3f?KJRh zz{cEQqWwE|!RB8|udMd!Ce>t~xyJBVDb34w!{0opdnW5J-#Ga1e&xFb@yh=$PIIh1 z+Ufu0->p19??o@J_tampI}m?$cfX7=)32|OlqlBLOfj@Ln`LHy=&Ml>?p$U zpp)fJ)DETArXS2}j-0x7EsVQL!`rNj0fy;-^W-gi;)$+q9* z`pplYc~`voW$=CV28P>z9zVPI?vLa54bp!Pef(xob^p@G_wDPKhkrc1|5s*y&Arq= zb`r9zD^9Sm9LYPVH{DfN|K_s3?)ZOK{jb0I@#l7-wt_+#>y|0g^W*0J|NHXp@hxVL zrq_Sn{N~49J<9_A_;1h8&)Z*DSoQbAx7w_^Kc2?_U8424yn3&`_SWaSdFm?mSASdb zV_wjoPwVUYR?F@`x}Ndr%uiN79RJnK@QhZi`x$QkJL~av*9?UoP7aREC;yAT`S>pL z;_p8{mEK)3(|NDBUO(yY>3N@ypFjLE6lBOX%R2FB#-|DYzw9doJLJy~-7@LASC6iQ zT$sMsRQK*A@%?{R&;Rv!dxrdl_wVX_GYef+K-QEV&;Mw*!1B}1sMpN1oWbtN{mRIA zXDZO%WG1G6^Pe-Gop}E0tEwgd+WVF3X*`3ZIl0v%r*zT_UZ zng1LYSks{y4Gjjr8on_0ayiLQv)eSQwjj&_qQ(>!4R#A2 z+GN+tO#p?FO0C0zs-0{sEryJY-`DeJSe%cV&klB`r@X+1IJ|a>{LTCE_j!JI9q+kI zNA&-{TE5z*(^D zx;^GQo9pKO2tQmIxXSYPzKZ&|`LQbNIWPGCIo>n>iD^UWtAY>JZ%W@kiE-Q?_D8eH zQHqD*(waBrjef5`FM0iCTLnNuvhYH~;58=lf;) zXnu~QNywZQm-p-6KHH+wRd@D>`dj8dp?lj;7%DG_*Z+6&H-q7g^rzR@I~e~yJw88F zVFDX-g9*?xp8Mn#*nY3M^NsmP!@p0D{eiwQVDM;WbddVR@P)CbNWKl|lmrH_@-K`p zm?fUt@klZ(Imn>kYs7ryUqPVX>_nXx*L(iw)H3{^6ZD;VLA~V1bS@T{p3Pqw{}g-O z?Iw>f*t{mW-WI6yY_R=Zxhqr`B==MPgg X=Yd`OZ=KEn#y^9ntDnm{r-UW|f!Yb0 literal 0 HcmV?d00001 diff --git a/assets/enemy/ghost_melee_death.png b/assets/enemy/ghost_melee_death.png new file mode 100644 index 0000000000000000000000000000000000000000..d1aae6dbb7a99feaa7d5683b92240ff504997275 GIT binary patch literal 550 zcmeAS@N?(olHy`uVBq!ia0vp^2Y}dsgAGW!`<2QuFfhLLba4!+nDch-M(-mABFBn@ zKBPTt*(^D zx;^GQo9pKO2tQmIxXSYPzKZ&|`LQbNIWPGCIo>n>iD^UWtAY>JZ%W@kiE-Q?_D8eH zQHqD*(waBrjef5`FM0iCTLnNuvhYH~;58=lf;) zXnu~QNywZQm-p-6KHH+wRd@D>`dj8dp?lj;7%DG_*Z+6&H-q7g^rzR@I~e~yJw88F zVFDX-g9*?xp8Mn#*nY3M^NsmP!@p0D{eiwQVDM;WbddVR@P)CbNWKl|lmrH_@-K`p zm?fUt@klZ(Imn>kYs7ryUqPVX>_nXx*L(iw)H3{^6ZD;VLA~V1bS@T{p3Pqw{}g-O z?Iw>f*t{mW-WI6yY_R=Zxhqr`B==MPgg X=Yd`OZ=KEn#y^9ntDnm{r-UW|f!Yb0 literal 0 HcmV?d00001 diff --git a/assets/enemy/ghost_melee_hurt.png b/assets/enemy/ghost_melee_hurt.png new file mode 100644 index 0000000000000000000000000000000000000000..c16d9f5d3626bcd52bee5c9901b07e2e2b56f46d GIT binary patch literal 470 zcmV;{0V)28P)dDQ$^DG0F4h^8AMD zB|XXmZ{M#=vKiDcC<3FhAKUY0oY1wN{lN5QHkNe%vwK&$>pz>_CCQ-yB!r-;TS#_U z-~HV6(*q=UKrGtZyYc(W(}T*5Hi7;srtt3LZJgWP?2p&OP zsbAmS@tU*#XdYn2{xz0PlSqHCGSruxe4?kd^+8Zciqapn5BRNm@FCG7I)MJ*eZU%Y zt;O{Lsr9o80z(6Up#e^9Jzx!{H{#X3TpY{qz7ZGyoVH z01W$po_#>kFa&`KyEpXpIqEi$*dX-toN&!^!bQUzAmNA-T7)W?ALnxA5I6f|s{jB1 M07*qoM6N<$g8B2{H2?qr literal 0 HcmV?d00001 diff --git a/assets/enemy/ghost_melee_idle.png b/assets/enemy/ghost_melee_idle.png new file mode 100644 index 0000000000000000000000000000000000000000..d1aae6dbb7a99feaa7d5683b92240ff504997275 GIT binary patch literal 550 zcmeAS@N?(olHy`uVBq!ia0vp^2Y}dsgAGW!`<2QuFfhLLba4!+nDch-M(-mABFBn@ zKBPTt*(^D zx;^GQo9pKO2tQmIxXSYPzKZ&|`LQbNIWPGCIo>n>iD^UWtAY>JZ%W@kiE-Q?_D8eH zQHqD*(waBrjef5`FM0iCTLnNuvhYH~;58=lf;) zXnu~QNywZQm-p-6KHH+wRd@D>`dj8dp?lj;7%DG_*Z+6&H-q7g^rzR@I~e~yJw88F zVFDX-g9*?xp8Mn#*nY3M^NsmP!@p0D{eiwQVDM;WbddVR@P)CbNWKl|lmrH_@-K`p zm?fUt@klZ(Imn>kYs7ryUqPVX>_nXx*L(iw)H3{^6ZD;VLA~V1bS@T{p3Pqw{}g-O z?Iw>f*t{mW-WI6yY_R=Zxhqr`B==MPgg X=Yd`OZ=KEn#y^9ntDnm{r-UW|f!Yb0 literal 0 HcmV?d00001 diff --git a/assets/enemy/ghost_melee_run.png b/assets/enemy/ghost_melee_run.png new file mode 100644 index 0000000000000000000000000000000000000000..c6788321ee1fb1544f5e40a0ee97bbcc8ee8351f GIT binary patch literal 654 zcmeAS@N?(olHy`uVBq!ia0y~yU{nCI4LI0<lqlBLOop^Ln`LHy=&Ng#X*GO zL1d)C)`X3%>`CdFx#d%r?!R*B)VnNhZfQ#v-E9Kv;#Qx~Nx7*LsBbaT{Nuj$J4(&n z-+inwKBIE}%`*Sm`3y`spZk~lmo4Ytb^PuNaXx$g#ou*h|L^->RU^9p&+7T#jyg*GKgKzfupC+<*IYZRASZ`;{LK|Gs;* z`%$&m$J6_NS*|JjBetJyO8TkO57>WxPE^aC3bN)-nDl-mv2j~#L7iv7E9$HPH}L_Yr!o5MP#`S0c5*E&HC`Sf`EjocqAyMu!r z=YPMprNle#&!_cuulnyVs&&}E`?u-RqoNZ*)?AU@fAn|5vlGv=*79fYf!(v^DmzO- zyqbbS4=2Zu`g!ao?snnX=b2|YgG|^J`S|qbhK95oO^hI?Nz^+0YH(v+$nPy5dDhNC z8?1>VnThGp3+7AgCR!Y|5x-7keLEP*2vUCFm->R8@iEsf9X??MvTCJ$-QiyiZJ*bC zt#o)az5eUuFN}<@pGpY`9Ajaz*<-ihp-pzJ+ysy_Rcaj$RNcg9XYjY(5BJwZeaSAE zrdIbe+&=c|$LKx$E4~$iEP6RhcK$qe7L(Ja3JPa{DM0@F^Tsa?f0@>r%}J@cxWx#V P&KNvh{an^LB{Ts5^!7}L literal 0 HcmV?d00001 diff --git a/assets/enemy/metadata.md b/assets/enemy/metadata.md new file mode 100644 index 0000000..168334b --- /dev/null +++ b/assets/enemy/metadata.md @@ -0,0 +1,79 @@ +# assets/enemy/ — Metadata + +## Ghost Soldier (鬼卒) — Melee + +### ghost_melee_idle.png +- Size: 192×48 | Frames: 4 | Frame: 48×48 | FPS: 8 | Loop: Yes +- Purpose: Ghost soldier idle — swaying, weapon dragging + +### ghost_melee_run.png +- Size: 288×48 | Frames: 6 | Frame: 48×48 | FPS: 10 | Loop: Yes +- Purpose: Lurching forward, uneven gait + +### ghost_melee_attack.png +- Size: 192×48 | Frames: 4 | Frame: 48×48 | FPS: 12 | Loop: No +- Purpose: Wild overhead chop + +### ghost_melee_hurt.png +- Size: 96×48 | Frames: 2 | Frame: 48×48 | FPS: 10 | Loop: No +- Purpose: Recoil, body flickers + +### ghost_melee_death.png +- Size: 192×48 | Frames: 4 | Frame: 48×48 | FPS: 8 | Loop: No +- Purpose: Dissolves into blue mist + +--- + +## Ghost Archer (鬼弓手) — Ranged + +### ghost_archer_idle.png +- Size: 192×48 | Frames: 4 | Frame: 48×48 | FPS: 8 | Loop: Yes +- Purpose: Hovering, bow held loosely + +### ghost_archer_run.png +- Size: 288×48 | Frames: 6 | Frame: 48×48 | FPS: 10 | Loop: Yes +- Purpose: Gliding forward, feet not touching ground + +### ghost_archer_attack.png +- Size: 192×48 | Frames: 4 | Frame: 48×48 | FPS: 12 | Loop: No +- Purpose: Draw bow → release ghost arrow → lower bow + +### ghost_archer_hurt.png +- Size: 96×48 | Frames: 2 | Frame: 48×48 | FPS: 10 | Loop: No +- Purpose: Flickers, body becomes semi-transparent + +### ghost_archer_death.png +- Size: 192×48 | Frames: 4 | Frame: 48×48 | FPS: 8 | Loop: No +- Purpose: Bow shatters, body disperses into green wisps + +--- + +## Corpse Beast (尸兽) — Charge + +### corpse_beast_idle.png +- Size: 256×48 | Frames: 4 | Frame: 64×48 | FPS: 8 | Loop: Yes +- Purpose: Low growl, body heaving, limbs twitching + +### corpse_beast_run.png +- Size: 384×48 | Frames: 6 | Frame: 64×48 | FPS: 14 | Loop: Yes +- Purpose: Fast charge — all four legs, mouth open + +### corpse_beast_attack.png +- Size: 256×48 | Frames: 4 | Frame: 64×48 | FPS: 12 | Loop: No +- Purpose: Lunge forward, jaws snapping + +### corpse_beast_hurt.png +- Size: 128×48 | Frames: 2 | Frame: 64×48 | FPS: 10 | Loop: No +- Purpose: Recoils, limbs spasm + +### corpse_beast_death.png +- Size: 256×48 | Frames: 4 | Frame: 64×48 | FPS: 8 | Loop: No +- Purpose: Collapses, flesh sloughing off bones + +--- + +## Godot Import Settings (all enemy sprites) +- Filter: Off (Nearest) +- Mipmaps: Off +- Compression: Lossless +- Format: RGBA diff --git a/assets/icons/README.md b/assets/icons/README.md new file mode 100644 index 0000000..026dc93 --- /dev/null +++ b/assets/icons/README.md @@ -0,0 +1,10 @@ +# assets/icons/ — Icon Assets + +## Structure +- `weapons/` — 32×32 weapon icons (6 total) +- `ui/` — 32×32 UI stat icons (11 total) + +All icons are unified at 32×32 px, transparent background, nearest-neighbor filtering. + +## Concept Reference +See `docs/art/CONCEPT_ICONS.md` for full design descriptions. diff --git a/assets/icons/ui/README.md b/assets/icons/ui/README.md new file mode 100644 index 0000000..ba00c8d --- /dev/null +++ b/assets/icons/ui/README.md @@ -0,0 +1,21 @@ +# assets/icons/ui/ — UI Stat Icons + +All 32×32 px. Transparent background. 1px dark outline. + +## Files +| File | Name | Description | +|------|------|-------------| +| icon_hp.png | 生命值 | HP — heart shape, dark red | +| icon_stamina.png | 体力 | Stamina — circular gauge, green | +| icon_attack.png | 攻击 | Attack — downward sword, iron | +| icon_armor.png | 护甲 | Armor — shield, dark steel | +| icon_crit.png | 暴击 | Critical — starburst, orange | +| icon_speed.png | 速度 | Speed — chevron, grey | +| icon_sacrifice.png | 献祭 | Sacrifice — chalice/blood, purple | +| icon_ghostfire.png | 鬼火 | Ghost fire — flame orb, blue-green | +| icon_boss.png | Boss | Skull with horns, bone | +| icon_key.png | 钥匙 | Key — skeleton key, bronze | +| icon_coin.png | 阴钱 | Yin coin — round coin with square hole | + +## Concept Reference +See `docs/art/CONCEPT_ICONS.md` — UI Stat Icons section. diff --git a/assets/icons/ui/icon_armor.png b/assets/icons/ui/icon_armor.png new file mode 100644 index 0000000000000000000000000000000000000000..ff85421e5b4976258df40fa7792206aad7a3b23e GIT binary patch literal 350 zcmV-k0iphhP)7gNk!AZtHv^KyVYWP`5u2XD!W wNDGvg0FKu47-`FJr~iM!+US?#)mcY)10)u#;T>Asd;kCd07*qoM6N<$f&{3R?*IS* literal 0 HcmV?d00001 diff --git a/assets/icons/ui/icon_attack.png b/assets/icons/ui/icon_attack.png new file mode 100644 index 0000000000000000000000000000000000000000..5adb31eb19be86d37b58fcfc073dd98bc37dd174 GIT binary patch literal 302 zcmV+}0nz@6P)Nkl?DPM(DWFfee1G6fka6^+`ec0OKsL#5{D=jZ4tZajDo zh9LeN_Q!Fc-L;bi!+V(g=bV4O9)MsBv?f5YEF~qMn@2DPrXJ9rFSZ0yNl;uDNliJh zyF2Jk-MCJAClE4#N!XeoPBA8-m4P#R<{dkqjWL#hHv zd_aKP)zjJjTQ9Gms9_+JfR))ZSyxU-PpfB@E&N%WTyvA-A943{0|FEoAHIM(r-! z=phHWu?yTDxqJ$=Z4fG}QQ*K_6PotG`$N9}8c%E~aD@;!-H(j|=A%VTh$}@%!COE` z8NzE9u}ug(j#7r}mgE4|hg1~xL0%Q%Ixk)U@SrPss0hvT!gCf2hJ)LuK0@E-JdhFw zNej?CFZ??Y6X4otb=tpxzs+*O2+91!mZ zbf`horiFGyKFxulK^Q1aKs(Q8`y-I8*3G>-R914XL=vh4F58d)B%_U?0lcBux2Y zB|sLKl#`ozgpyFUIuHx2!?_OR9bE5|4+z3QX#z@*i(Us}g7oO}5rlz%0`QDG4~i2F zY9^)v;{)ysZTXycC>?@?y5Vd(f4$HvRo8;V)_;`AvZCBJ7MKniu8M;QFuo;KNGVtF>;DBPO`zF^k^~4dbv zYO#EOd>9S-JORb|LKZ3|zJ@;o_Z0SjsE0pg2Um^oB!4`d)!H0Zv? zRtV9&D84~v@jIvzA>)v158!KlkrMY5M?nUfBgnBF1Sm>`AV;7FFQQGY|Gh5lWoR=_CkIm|WgL++yo;@A5? zNT8ji4|snAN>k??nEHXV%>%f}w;F7~!}K;L&jX4X3DbwUMBAgKdA1PN+t?oqBS%1JsGR#u4nhfMXo7{1*T>1zdC^Sxg6>d}Wk%v|&w< P00000NkvXXu0mjf0!4#~ literal 0 HcmV?d00001 diff --git a/assets/icons/ui/icon_key.png b/assets/icons/ui/icon_key.png new file mode 100644 index 0000000000000000000000000000000000000000..a9c2a4e8257930a7e3a91a59b21aeb12abf85b68 GIT binary patch literal 297 zcmV+^0oMMBP)1EmEh-cQBlZ6JvNUDeQD z&I1dN`Y?-#f^r~q3~6zHktzJU;K|2OdpqYqJUQ_T5QKqQ=YZ)tbu*8-dfE`*ZhfU6njgxh&YAO{lc=YSeJF(hzo5QWN#APh{kP^e!NEd4%! vix;5$x}?08uO-OBfi(wEx&+VPv7~?p!Dg3a56#P900000NkvXXu0mjf2ljkl literal 0 HcmV?d00001 diff --git a/assets/icons/ui/icon_sacrifice.png b/assets/icons/ui/icon_sacrifice.png new file mode 100644 index 0000000000000000000000000000000000000000..983081531f9d70d8b7f85cba2b1af1469b8e0af4 GIT binary patch literal 405 zcmV;G0c!q=flzE; zcIEi<_(89;Rr#KN@B1ULPBt|q&yP!b&)#a}e2J*4Uz&V|jj<`@HopoP%*WUj6YRrM zl!pMy#bP#aAIfzSdb|V^SSOp-$^}?&BcZzH0UGXS%76dU^_GJW1)T)?pjC_GH}SQ3a{Xmoi<9>5jEJ{S+K zA7GmhR`4k(HpuB)5eTVjCYW5RAMdH$(@GmqYL+Ru+8a z0JR)~uN+}wpfNDgHG*4Cw90g%k@VxY~IRJO4 VpLY)VtndH;002ovPDHLkV1oE$dw>7{ literal 0 HcmV?d00001 diff --git a/assets/icons/ui/icon_stamina.png b/assets/icons/ui/icon_stamina.png new file mode 100644 index 0000000000000000000000000000000000000000..d70c7fabd0f0df067ddcab318267f9de8590744f GIT binary patch literal 362 zcmV-w0hRuVP)TM!H4}JNECPPBsi6 z(Q*&~sfUFaKEOl^%aI*{FTD_S0LTaAT28DZSO}E^)B-R&h%N^LC8|>;rra4Pq@O%K_LdA=C^f1XvhGdcS~apdA3th6CvU0JUSX)1FheNdN!<07*qo IM6N<$f|rSeU;qFB literal 0 HcmV?d00001 diff --git a/assets/icons/weapons/README.md b/assets/icons/weapons/README.md new file mode 100644 index 0000000..2ce7480 --- /dev/null +++ b/assets/icons/weapons/README.md @@ -0,0 +1,16 @@ +# assets/icons/weapons/ — Weapon Icons + +All 32×32 px. Transparent background. 1px dark outline. + +## Files +| File | Name | Description | +|------|------|-------------| +| weapon_songdao.png | 宋刀 | Song dynasty straight-backed sword | +| weapon_spear.png | 长枪 | Military spear with red tassel | +| weapon_ghostfire.png | 鬼火 | Ghost fire orb (element/power) | +| weapon_talisman.png | 护符 | Paper talisman with red seal | +| weapon_gourd.png | 酒葫芦 | Wine gourd (healing item) | +| weapon_flag.png | 残破军旗 | Broken battle flag on snapped pole | + +## Concept Reference +See `docs/art/CONCEPT_ICONS.md` — Weapon Icons section. diff --git a/assets/icons/weapons/weapon_flag.png b/assets/icons/weapons/weapon_flag.png new file mode 100644 index 0000000000000000000000000000000000000000..dc203efc1eda27cbde1055a11e6bcc31c1226953 GIT binary patch literal 282 zcmV+#0p}BAX3jqnpn{HQR6`N>ao9KeFF9Kcp~)2$q!7hoE+4%o4O4_zF<0wPBH g1p^5f2fBL>01j=26s;SPRsaA107*qoM6N<$f+-JXV*mgE literal 0 HcmV?d00001 diff --git a/assets/icons/weapons/weapon_ghostfire.png b/assets/icons/weapons/weapon_ghostfire.png new file mode 100644 index 0000000000000000000000000000000000000000..5a64bd7bfd0853f7da31953df1cda95a69ba3f4f GIT binary patch literal 331 zcmV-R0kr;!P)3|zJ@;o_Z0SjsE0pg2Um^oB!4`d)!H0Zv? zRtV9&D84~v@jIvzA>)v158!KlkrMY5M?nUfBgnBF1Sm>`AV;7FFYP)9U5Zn#{b1o{V(EtA{K~bTI=Xi=Jq@>ZqZd%!f%%QK{CeztunlzrgCq%mF1t3|B8c_pg=CV!+yB#?~klDKub`-MRk$ZG|iK~ z4^jrICt#-fK$=&F4|LJ|57@5T&-rw@irT^)olD}AeSibWm;wpF{kl*N4nWF4aX!EW z_ul|g28trkGZ!5aGcrZ_07nJs(#(jmlTKp839?IPjmaSp6Uiyj7Xe%Iz5v<;8tv`* Y3umf(L$KA^BLDyZ07*qoM6N<$g2Wz z%?g4*5XaYLA$(oV znVn@Aos4A}vnT<#`E*gfa3^l%gY=bF=9KH_+mUf*S=_b*qP{IAw_LT(nPu@YAkk`F zJ_o)xt^?5r4x^k++39lqJ_9Mr3uVp=cydVc5#V)u!3+t*>rop|LDUg$!~qPyKc3^( zfk+W~o4sH}0ysWwUBxNk;WF$6CvgD7*id$ACL+-g}6;PsPPOG%>!l5JJ$h;qNdDwV;u;5F9*W~Dr!6R zw*PcMC6jQF^{!Z`^72>z1p-f?g=&j{T8{1C^EcXXB86ZbtH=dTE~k0i<@R=pgk&$wWCjK{ z>!9mE5K3Qm+v58o-{gY>?YkX3$DBW!J0J)Hr3EP07Ea5}yrL4$^+m>kippOHxIxF$ z=nN&ujr|(R%`Il;Hn}D>_S_B+@QW)c?FO~?vk3iWfY_OP^#Czs zzPJa7(e_yZl!$y(fXSL8a~GN34fn+%>0V&)d^Pf$5lRc_BajsE0hn2)lAGM1Z~y=R M07*qoM6N<$f(hP;YXATM literal 0 HcmV?d00001 diff --git a/assets/icons/weapons/weapon_talisman.png b/assets/icons/weapons/weapon_talisman.png new file mode 100644 index 0000000000000000000000000000000000000000..5a19d57e5f0087e2aacf1b75709b3903a35f928f GIT binary patch literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJU7jwEAr*6y|D6Bu-~KQIlbC>_ zmI|l%gM{Y}fgc_}=NGXveJXFi|NMa?1_v547swuHlGEA3QESbi9glUFl z^FEmbsgHaKjI9SUEDaV3D{^;8=(4j38St)n%d_|`Bip6fDK}%=40ScN$aoedtVIn1%s!npUXO@geCxjl2f(- literal 0 HcmV?d00001 diff --git a/assets/player/README.md b/assets/player/README.md new file mode 100644 index 0000000..5d067e8 --- /dev/null +++ b/assets/player/README.md @@ -0,0 +1,29 @@ +# assets/player/ — Player Character Sprites + +## Character +**岳家军普通军士** (Yue Family Army Soldier) — a broken, battle-worn foot soldier trapped in the underworld. + +## Files +| File | Animation | Frames | Sheet Size | Frame Size | FPS | +|------|-----------|--------|------------|------------|-----| +| player_idle.png | Idle | 4 | 192×48 | 48×48 | 8 | +| player_run.png | Run | 6 | 288×48 | 48×48 | 12 | +| player_jump.png | Jump | 2 | 96×48 | 48×48 | 10 | +| player_attack.png | Attack | 4 | 192×48 | 48×48 | 14 | +| player_hurt.png | Hurt | 2 | 96×48 | 48×48 | 10 | +| player_death.png | Death | 6 | 288×48 | 48×48 | 8 | + +## Sprite Sheet Format +Horizontal strip — frames arranged left-to-right. Transparent background. Nearest-neighbor filtering. + +## Godot Import +- Filter: Off (Nearest) +- Mipmaps: Off +- Use with `AnimatedSprite2D` + `SpriteFrames` resource +- Set H-Frames per the table above + +## Concept Reference +See `docs/art/CONCEPT_PLAYER.md` for full visual design description. + +## Color Palette +Dried blood red `#5A322B`, ash brown `#3A2E25`, dark iron `#4A4035`, ghost fire eyes `#B4641E` diff --git a/assets/player/metadata.md b/assets/player/metadata.md new file mode 100644 index 0000000..5748700 --- /dev/null +++ b/assets/player/metadata.md @@ -0,0 +1,63 @@ +# assets/player/ — Metadata + +## Sprite Sheet Specifications + +### player_idle.png +- **Size:** 192 × 48 px +- **Frame size:** 48 × 48 px +- **Frame count:** 4 +- **Layout:** Horizontal strip (left-to-right) +- **FPS:** 8 +- **Loop:** Yes +- **Purpose:** Player idle animation — exhausted soldier breathing + +### player_run.png +- **Size:** 288 × 48 px +- **Frame size:** 48 × 48 px +- **Frame count:** 6 +- **Layout:** Horizontal strip +- **FPS:** 12 +- **Loop:** Yes +- **Purpose:** Player run cycle — heavy armored movement + +### player_jump.png +- **Size:** 96 × 48 px +- **Frame size:** 48 × 48 px +- **Frame count:** 2 +- **Layout:** Horizontal strip +- **FPS:** 10 +- **Loop:** No +- **Purpose:** Player jump — launch + apex + +### player_attack.png +- **Size:** 192 × 48 px +- **Frame size:** 48 × 48 px +- **Frame count:** 4 +- **Layout:** Horizontal strip +- **FPS:** 14 +- **Loop:** No +- **Purpose:** Player sword attack — wind-up, strike, follow-through, recovery + +### player_hurt.png +- **Size:** 96 × 48 px +- **Frame size:** 48 × 48 px +- **Frame count:** 2 +- **Layout:** Horizontal strip +- **FPS:** 10 +- **Loop:** No +- **Purpose:** Player hurt reaction — impact + stagger + +### player_death.png +- **Size:** 288 × 48 px +- **Frame size:** 48 × 48 px +- **Frame count:** 6 +- **Layout:** Horizontal strip +- **FPS:** 8 +- **Loop:** No +- **Purpose:** Player death — sword drop, collapse, dissolve + +## Godot Import Settings +- Filter: Off (Nearest) +- Mipmaps: Off +- Compression: Lossless +- Format: RGBA diff --git a/assets/player/player_attack.png b/assets/player/player_attack.png new file mode 100644 index 0000000000000000000000000000000000000000..628c7eafe2745551c72093b7e5ebc2d1ff8a9a3a GIT binary patch literal 557 zcmeAS@N?(olHy`uVBq!ia0vp^2Y}dsgAGW!`<2QuFfe}cba4!+nDch-M!)6&k+yu^ zP942oK3S8OFK#WJeg4S{Ws6@neR+E8`lMv7u2@{>EH)HS$XFtCXyO0wyh;fl&Y%2S z{BGy+?PoSkNM&@GqVmsTw*D#ZmxWQko9|vfg z-%CE+^OoD>e!uAd=Xa015rfC%^oE5ycJ9)t-(USlGh#W*FMj)fCnp@5f6wkz_Vw4P zb$e z{V`j&d7G$u?xNM%yXQ@Fxwu}k{<7VH{HxW{a>h)*zCOyo$Ns{pX4-Z;2k{H`e}8P| zV!4#S0Ji1XZ^m5$^PlG0Lu4j1I!N&_fF#@O7-}09+-X+?Iz@tE$w3AM-dcuV430bb zo!J;N4H!I<(;C*r$2`e2_F?%u>!bNQ<~e_U%Zs1?xK*Cv@5+cjGnpEp`p#z5HaxNn zzRTb6?{J5`B*ZMB884VGuycgIU}oSrD8jMC@NeFQ-`C$Rkgu1O`tthd{O>HE=iOcK sXI|Pe2L)cB>QCV+y}C>aJx9#r)*ft|DR27(m<$*^UHx3vIVCg!0H5pqWdHyG literal 0 HcmV?d00001 diff --git a/assets/player/player_death.png b/assets/player/player_death.png new file mode 100644 index 0000000000000000000000000000000000000000..4b2708fb13aef2bdae492967e03de6c81ebe1448 GIT binary patch literal 662 zcmeAS@N?(olHy`uVBq!ia0y~yU{nCI4LI0<lqlBVm)0PLn`LHy=$0%*+GOM zVR=`}bUjfXS%+6QrY@D<-#KyqffMf&Q<4)?b6nf`*3LMbQsUV6=){(d7Wb_$zn7L0 zTKwhZ%e|A&SC)pwUM}TzXej;l@ztA}?@qrL$^L0SyZ-EhO5fx8@7Hbbe>A=RYjAwM z+~xi1&5IdWBO4lS=-&938F+SiPV(`i`u|@oU%SnIzSTP(jt*PqS*P2}r|+(>|Mly6 z)5l32@&B%#-}86(yv>FC|5n`C`|aPWq~AY2p8me93}otMk(c+sKHR5seO|u(kAFXJ zo9l0WuL&|See;cfXX|)1)=p_Z`2EuBzg|(=A5ZW9RjF}pzn&9wqK<>ZjL=`v8?HYW zkFTQ+_G>?GX6MS7p z;J`+py%O94AO7Fr&*=IRxAiW61{cU#nd)Ds|7K*ox`CDXP((w6!Lx=ljJ#ZjWTshe znp9kH4k#j~pwPq0!I8{&LgvB(g)@mWm9;I|pYs4k7@4N+XYl>|Xw$4bw`ri@^8fko ztPOif)ffGD_m{8x%?NV2|H*$_p=OIIJotH*@$37|SD;eKd>j#a(FQ2yGtOkTYO_>G z_?UCB!60(K^uNz%|E`H|HZ}*lw)>tKAi)DdQLpd1?+?%42{R??2>Q#A!gue-u5WX{JYBa>FNJjKi2BQMT=Et3 z`r>VSb$jJKrlzjndk!#f%2>R`^xHQ{Dx1jqnS{a60AOb3zo47N((63eBr%s8n;1Gy zV(K3;_3*hrT~pQ{%ma+r-`dhyMbhtW8tO_;F46tD^;Xc76s6y59q`la!G%PR*a7r= zuLGu_DkZKCh^?QqATTrl7#iT*)&r)X8!c`otx#TcZ1&vn4%pMTgsM~hkyh_9a^2n-DXh6c#B4zSNriLM9u z(oYWnLj!=J0l=^hXjlhi4MPwZuX{ru-wr{yfrJL3pXY=to)gX*<^Tytl+YrIjQIlb W^W}D6pG2hq0000EH)HS$XFtCXyO0wyh;fl&Y%2S z{BGy+?PoSkNM&@GqVmsTw*D#ZmxWQko9|vfg z-%CE+^OoD>e!uAd=Xa015rfC%^oE5ycJ9)t-(USlGh#W*FMj)fCnp@5f6wkz_Vw4P zb$e z{V`j&d7G$u?xNM%yXQ@Fxwu}k{<7VH{HxW{a>h)*zCOyo$Ns{pX4-Z;2k{H`e}8P| zV!4#S0Ji1XZ^m5$^PlG0Lu4j1I!N&_fF#@O7-}09+-X+?Iz@tE$w3AM-dcuV430bb zo!J;N4H!I<(;C*r$2`e2_F?%u>!bNQ<~e_U%Zs1?xK*Cv@5+cjGnpEp`p#z5HaxNn zzRTb6?{J5`B*ZMB884VGuycgIU}oSrD8jMC@NeFQ-`C$Rkgu1O`tthd{O>HE=iOcK sXI|Pe2L)cB>QCV+y}C>aJx9#r)*ft|DR27(m<$*^UHx3vIVCg!0H5pqWdHyG literal 0 HcmV?d00001 diff --git a/assets/player/player_jump.png b/assets/player/player_jump.png new file mode 100644 index 0000000000000000000000000000000000000000..521de7082bd7ba0ac6d03c16954dbe3cd8f79133 GIT binary patch literal 480 zcmV<60U!Q}P)>tKAi)DdQLpd1?+?%42{R??2>Q#A!gue-u5WX{JYBa>FNJjKi2BQMT=Et3 z`r>VSb$jJKrlzjndk!#f%2>R`^xHQ{Dx1jqnS{a60AOb3zo47N((63eBr%s8n;1Gy zV(K3;_3*hrT~pQ{%ma+r-`dhyMbhtW8tO_;F46tD^;Xc76s6y59q`la!G%PR*a7r= zuLGu_DkZKCh^?QqATTrl7#iT*)&r)X8!c`otx#TcZ1&vn4%pMTgsM~hkyh_9a^2n-DXh6c#B4zSNriLM9u z(oYWnLj!=J0l=^hXjlhi4MPwZuX{ru-wr{yfrJL3pXY=to)gX*<^Tytl+YrIjQIlb W^W}D6pG2hq0000lqlBVm)0PLn`LHy=$0%*+GOM zVR=`}bUjfXS%+6QrY@D<-#KyqffMf&Q<4)?b6nf`*3LMbQsUV6=){(d7Wb_$zn7L0 zTKwhZ%e|A&SC)pwUM}TzXej;l@ztA}?@qrL$^L0SyZ-EhO5fx8@7Hbbe>A=RYjAwM z+~xi1&5IdWBO4lS=-&938F+SiPV(`i`u|@oU%SnIzSTP(jt*PqS*P2}r|+(>|Mly6 z)5l32@&B%#-}86(yv>FC|5n`C`|aPWq~AY2p8me93}otMk(c+sKHR5seO|u(kAFXJ zo9l0WuL&|See;cfXX|)1)=p_Z`2EuBzg|(=A5ZW9RjF}pzn&9wqK<>ZjL=`v8?HYW zkFTQ+_G>?GX6MS7p z;J`+py%O94AO7Fr&*=IRxAiW61{cU#nd)Ds|7K*ox`CDXP((w6!Lx=ljJ#ZjWTshe znp9kH4k#j~pwPq0!I8{&LgvB(g)@mWm9;I|pYs4k7@4N+XYl>|Xw$4bw`ri@^8fko ztPOif)ffGD_m{8x%?NV2|H*$_p=OIIJotH*@$37|SD;eKd>j#a(FQ2yGtOkTYO_>G z_?UCB!60(K^uNz%|E`H|HZ}*lw)Hb-_ zmI|l%gM{Y}fgc_}=NGXveJXFi|NMa?1_v547swuHlGEA3QW?9yrzGu6bZ&}NI5MW?W;$%c`(ldjVs4;i44$rjF6*2UngAC) BH-`WK literal 0 HcmV?d00001 diff --git a/assets/tiles/tile_tombstone.png b/assets/tiles/tile_tombstone.png new file mode 100644 index 0000000000000000000000000000000000000000..7f00d381ab47254d806ce187a19e882b07c641aa GIT binary patch literal 313 zcmV-90mlA`P)VgL(zu;2?a zQUJ^xLQ%>@Y0zT>Aa$V7qIwk20x(hA_oRjb3wpd@k2B&E6*?f4rkEH87%&Zb2j~)! z_bEvSEQHDdO5z^|@R`rVFu;Im#5;h6P&q(u0z}sTC}0}s5MUvcPkFf5 zD7Iv@92nZ=00YB6iY>usJ`=+L1Evx002WXPjFtlf3BbMMfph=>Fko}_kg|S?00000 LNkvXXu0mjfMJah) literal 0 HcmV?d00001 diff --git a/assets/tiles/tile_wall.png b/assets/tiles/tile_wall.png new file mode 100644 index 0000000000000000000000000000000000000000..a26d0299f5cdec3f16385d7d80501877d1e2644c GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzKTj9Okcv5PFD?{paA06QsLY#S zAo5Yl_kU}GK?uXEXoY9jj;21F_F3Y6slCtdZJ(a=rG8{k)97Y5aE!ER;5_w+(I-HR iKcQ(;1%vQ~uZ-NwBv<4e`H==RnZeW5&t;ucLK6VG?yC9;q#`_B978JRyuERdmqC$(!Er}K z&(f%4Q&R=MEZEr9TloCo?3Buh_3yP?GSknj+HrHmsT~(A|MkZ`DrIYsWJqI}!I*%_ dcxi1FARSb|^*{3AmlB{I44$rjF6*2UngFM-Gjad` literal 0 HcmV?d00001 diff --git a/assets/ui/ui_minimap_frame.png b/assets/ui/ui_minimap_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..edb51ece2b0799ba1e760aae4f2420bdb8db2188 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=+dN$yLn`LHy}gl_$&rWk;?9fH z8a*`HTV^nIdTO+@Tx54?zwpw9(R|N|PifiKe=fhQs1sBxOx9bs{j|%8JwG4s-g#eE z==q0=d-jLr_RDS0uuSQkY(ZrtecDacX6G-){@!-A>puU#Smfq=JrybZJ>j_x&<6~j Lu6{1-oD!M<8$x3W literal 0 HcmV?d00001 diff --git a/assets/ui/ui_stamina_bar.png b/assets/ui/ui_stamina_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..98192849eac4c312162c505819bdbe5c4e09bf71 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!3HE>?yC9;q+&c>978JRyuD$_d%%E)!O@y) z#v#oO4jgkDG&qVgL`BxGsBcd9et)vE_cw3iFS)NFbJjdNWpTmopPT;2a<&FZhBSs5 hj0u>Gm)2GR@{XUl)%Tb$n*y|l!PC{xWt~$(696x8FiQXc literal 0 HcmV?d00001 diff --git a/docs/AI_HANDOFF.md b/docs/AI_HANDOFF.md index 10c433b..d26648b 100644 --- a/docs/AI_HANDOFF.md +++ b/docs/AI_HANDOFF.md @@ -1,15 +1,16 @@ # AI Handoff -- **Current phase:** Repository collaboration setup complete; core framework not started. +- **Current phase:** Art asset pipeline complete; core framework not started. - **Current playable state:** No playable build exists. -- **Completed repository work:** Collaboration rules, ownership, templates, task tracking, and repository hygiene validation. -- **In progress:** None. +- **Completed repository work:** Collaboration rules, ownership, templates, task tracking, repository hygiene validation, and **prototype art asset pipeline** (63 placeholder sprites, 8 concept docs, master art spec, Godot import guide, all directory READMEs and metadata). +- **In progress:** Art PR (`workbuddy/prototype-art`) awaiting review/merge. - **Next owner:** Claude. -- **Next task:** Create `claude/core-framework` from the latest `develop` branch. +- **Next task:** Create `claude/core-framework` from the latest `develop` branch. Art assets are ready in `assets/` — read `docs/ART_SPEC.md` and each directory's `metadata.md` for sprite sheet frame counts, sizes, and FPS values. - **Frozen interfaces:** None; no gameplay interfaces are frozen yet. -- **Do not modify:** Product requirements without Game Director approval; `main`/`develop` directly; future frozen interfaces without the decision process. +- **Do not modify:** Product requirements without Game Director approval; `main`/`develop` directly; future frozen interfaces without the decision process; art concept documents (`docs/art/CONCEPT_*.md`) without consulting the Art Director. - **Known blockers:** Approved detailed PRD and Prototype Contract source material is not present in the repository. +- **Art asset notes:** All PNGs in `assets/` are placeholders (magenta border). Replace by saving a new PNG with the same filename and dimensions. See `docs/ART_SPEC.md` Section 5 for replacement workflow. Godot import settings documented in `docs/art/GODOT_IMPORT_GUIDE.md` and `godot/import_presets.md`. - **Last successful test:** Repository validation checks (2026-08-02). - **Last updated:** 2026-08-02 (Australia/Melbourne). -The PRD and Prototype Contract are the current highest-priority sources of truth. +The PRD and Prototype Contract are the current highest-priority sources of truth. `docs/ART_SPEC.md` is the source of truth for all visual art assets. diff --git a/docs/ART_SPEC.md b/docs/ART_SPEC.md new file mode 100644 index 0000000..c2879cb --- /dev/null +++ b/docs/ART_SPEC.md @@ -0,0 +1,297 @@ +# 九幽 — Art Specification (美术资源规范) + +> **STATUS: ACTIVE — Prototype Phase** +> **Owner:** WorkBuddy (Art Director / Asset Pipeline Engineer) +> **Last Updated:** 2026-08-02 + +This document is the **single source of truth** for all visual art assets in the Nine Nether prototype. Claude and Codex should read this before referencing any sprite, icon, tile, or UI element. + +--- + +## 1. Art Direction + +### Style +- **Genre:** 2D Side-scrolling Action Roguelite +- **Visual Style:** Chinese Dark Fantasy Pixel Art (中国黑暗幻想像素风) +- **Era Reference:** Southern Song Dynasty (南宋) — Underworld / Hell (地府) — Ghosts & Demons (鬼怪) — Ancient Warfare (古代战争) +- **DO NOT reference:** Anime style, Chibi/Q-version, Western cartoon, any existing game directly + +### Tone +- Oppressive, somber, decayed +- Muted palette with strategic accents of ghost-fire orange and blood red +- Textures should feel worn, broken, ancient — never clean or pristine + +### Color Philosophy +| Role | Hex | Usage | +|------|-----|-------| +| Deep Void | `#0F0C12` | Backgrounds, shadows | +| Ash Stone | `#28231E` | Tiles, environment | +| Dried Blood | `#78322B` | Player base, blood | +| Ghost Blue | `#2D3746` | Ghost enemies | +| Corpse Flesh | `#372823` | Beast enemies | +| Underworld Purple | `#321E37` | Boss, sacrifice | +| Ghost Fire | `#B4641E` | Accents, fire, energy | +| Pale Bone | `#C4B89A` | Highlights, bone, pale skin | +| Rust Iron | `#5A4632` | Metal, armor | +| Yin Coin Gold | `#8B7332` | Currency, treasure | + +--- + +## 2. Pixel Art Standards + +### Resolution & Scale +| Asset Type | Frame Size | Notes | +|------------|-----------|-------| +| Player sprite | 48 × 48 px | Per animation frame | +| Enemy sprites (humanoid) | 48 × 48 px | Ghost Soldier, Ghost Archer | +| Enemy sprites (beast) | 64 × 48 px | Corpse Beast (wider, quadruped) | +| Boss sprite | 96 × 96 px | Larger than player for intimidation | +| Weapon icons | 32 × 32 px | Unified | +| UI icons | 32 × 32 px | Unified | +| Tiles | 32 × 32 px | Grid-based, tileable | +| Effects | 48 × 48 px | Combat VFX | +| Background elements | Varies | See individual metadata | +| UI elements | Varies | See individual metadata | + +### Sprite Sheet Format +- **Layout:** Horizontal strip (frames arranged left-to-right) +- **Background:** Transparent (alpha channel) +- **Format:** PNG (lossless) +- **Filter:** Nearest-neighbor (no anti-aliasing) +- **Color depth:** RGBA 32-bit + +### Animation Frame Counts +| Animation | Frames | Purpose | +|-----------|--------|---------| +| Idle | 4 | Subtle breathing / hovering | +| Run | 6 | Full run cycle | +| Jump | 2 | Launch + apex | +| Attack | 4-5 | Wind-up → strike → recovery | +| Hurt | 2 | Impact + stagger | +| Death | 4-8 | Collapse / dissolve | + +### Frame Rate +- Default: 10 FPS (0.1s per frame) +- Attack: 12-15 FPS (snappier) +- Death: 6-8 FPS (slower, dramatic) + +--- + +## 3. Naming Convention + +### Pattern +``` +__.png +``` + +### Rules +1. **All lowercase**, words separated by underscores +2. **No spaces, no Chinese characters, no special chars** in filenames +3. **No version numbers** in filenames (use git, not `v2_final.png`) +4. **No generic names** like `image1.png`, `新建文件.png` +5. Sprite sheets use the action name, not frame numbers + +### Examples +``` +player_idle.png ← Player idle animation (4-frame sheet) +player_run.png ← Player run animation (6-frame sheet) +ghost_melee_attack.png ← Ghost Soldier attack animation +boss_death.png ← Boss death animation +weapon_songdao.png ← Song Dynasty sword icon +icon_hp.png ← HP UI icon +tile_floor.png ← Floor tile +effect_blood_splash.png ← Blood splash VFX +bg_tree.png ← Background tree +ui_health_bar.png ← Health bar UI element +``` + +### Category Prefixes +| Prefix | Directory | Description | +|--------|-----------|-------------| +| `player_` | `assets/player/` | Player character sprites | +| `ghost_melee_` | `assets/enemy/` | Ghost Soldier (鬼卒) | +| `ghost_archer_` | `assets/enemy/` | Ghost Archer (鬼弓手) | +| `corpse_beast_` | `assets/enemy/` | Corpse Beast (尸兽) | +| `boss_` | `assets/boss/` | Boss sprites | +| `weapon_` | `assets/icons/weapons/` | Weapon icons | +| `icon_` | `assets/icons/ui/` | UI stat icons | +| `tile_` | `assets/tiles/` | Tilemap tiles | +| `effect_` | `assets/effects/` | Visual effects | +| `bg_` | `assets/background/` | Background elements | +| `ui_` | `assets/ui/` | UI elements (bars, frames) | + +--- + +## 4. Directory Structure + +``` +assets/ +├── ART_SPEC.md ← This file (master spec) +├── generate_placeholders.py ← Placeholder generation script +├── player/ ← Player character sprites +│ ├── README.md +│ ├── metadata.md +│ └── *.png +├── enemy/ ← All enemy sprites +│ ├── README.md +│ ├── metadata.md +│ └── *.png +├── boss/ ← Boss sprites +│ ├── README.md +│ ├── metadata.md +│ └── *.png +├── icons/ +│ ├── weapons/ ← 32×32 weapon icons +│ │ ├── README.md +│ │ └── *.png +│ └── ui/ ← 32×32 UI stat icons +│ ├── README.md +│ └── *.png +├── tiles/ ← 32×32 tilemap tiles +│ ├── README.md +│ ├── metadata.md +│ └── *.png +├── effects/ ← 48×48 VFX sprites +│ ├── README.md +│ ├── metadata.md +│ └── *.png +├── background/ ← Parallax / decorative backgrounds +│ ├── README.md +│ ├── metadata.md +│ └── *.png +└── ui/ ← UI elements (bars, frames) + ├── README.md + ├── metadata.md + └── *.png +``` + +--- + +## 5. Placeholder Policy + +All current PNGs are **placeholders** generated programmatically. They: +- Use the correct dimensions and sprite sheet layout +- Have a magenta border (1px) marking them as placeholders +- Show a checkerboard pattern + simple silhouette +- Can be directly replaced by dropping a new PNG with the same filename + +### Replacement Workflow +1. Generate or commission the final pixel art +2. Save with the **exact same filename** and **exact same dimensions** +3. Run `git add assets/ && git commit -m "[Art] Replace placeholder with final art"` +4. The Godot import settings will remain valid as long as dimensions match + +### How to Identify Placeholders +- Magenta (#FF00FF) 1px border = placeholder +- Checkerboard interior = placeholder +- Once replaced, remove the magenta border from the final art + +--- + +## 6. Godot 4 Import Settings + +### Texture Import (per PNG) +| Setting | Value | +|---------|-------| +| Preset | 2D Pixel | +| Filter | Off (Nearest) | +| Mipmaps | Off | +| Compression | Lossless | +| HDR | Off | + +### Sprite Sheet → AnimatedSprite2D +1. Import the PNG as a texture +2. In the SpriteFrames editor, create a new animation +3. Set "Horizontal frames" to the frame count from metadata +4. Set FPS per the metadata (default 10) + +### Recommended Godot Resource Path +``` +res://assets/player/player_idle.png → SpriteFrames → "idle" animation +res://assets/player/player_run.png → SpriteFrames → "run" animation +``` + +See `docs/art/GODOT_IMPORT_GUIDE.md` for step-by-step instructions. + +--- + +## 7. Asset Inventory + +### Player (6 sheets, 24 total frames) +| File | Frames | Size | FPS | +|------|--------|------|-----| +| player_idle.png | 4 | 192×48 | 8 | +| player_run.png | 6 | 288×48 | 12 | +| player_jump.png | 2 | 96×48 | 10 | +| player_attack.png | 4 | 192×48 | 14 | +| player_hurt.png | 2 | 96×48 | 10 | +| player_death.png | 6 | 288×48 | 8 | + +### Enemies (15 sheets, 70 total frames) +| File | Frames | Size | FPS | +|------|--------|------|-----| +| ghost_melee_idle.png | 4 | 192×48 | 8 | +| ghost_melee_run.png | 6 | 288×48 | 10 | +| ghost_melee_attack.png | 4 | 192×48 | 12 | +| ghost_melee_hurt.png | 2 | 96×48 | 10 | +| ghost_melee_death.png | 4 | 192×48 | 8 | +| ghost_archer_idle.png | 4 | 192×48 | 8 | +| ghost_archer_run.png | 6 | 288×48 | 10 | +| ghost_archer_attack.png | 4 | 192×48 | 12 | +| ghost_archer_hurt.png | 2 | 96×48 | 10 | +| ghost_archer_death.png | 4 | 192×48 | 8 | +| corpse_beast_idle.png | 4 | 256×48 | 8 | +| corpse_beast_run.png | 6 | 384×48 | 14 | +| corpse_beast_attack.png | 4 | 256×48 | 12 | +| corpse_beast_hurt.png | 2 | 128×48 | 10 | +| corpse_beast_death.png | 4 | 256×48 | 8 | + +### Boss (5 sheets, 25 total frames) +| File | Frames | Size | FPS | +|------|--------|------|-----| +| boss_idle.png | 4 | 384×96 | 6 | +| boss_run.png | 6 | 576×96 | 8 | +| boss_attack.png | 5 | 480×96 | 10 | +| boss_hurt.png | 2 | 192×96 | 8 | +| boss_death.png | 8 | 768×96 | 6 | + +### Icons (17 total, all 32×32) +- Weapons: 6 (songdao, spear, ghostfire, talisman, gourd, flag) +- UI: 11 (hp, stamina, attack, armor, crit, speed, sacrifice, ghostfire, boss, key, coin) + +### Tiles (7 total, all 32×32) +floor, stone_brick, wall, wood_bridge, brazier, tombstone, ground_spike + +### Effects (4 total, all 48×48) +hit_slash, blood_splash, ghost_fire, death_fade + +### Background (4 total) +tree (64×128), broken_flag (48×96), chains (32×96), deep (320×180) + +### UI Elements (4 total) +health_bar (96×16), stamina_bar (96×16), frame (32×32), minimap_frame (64×64) + +--- + +## 8. Concept Documents + +Detailed visual concept descriptions for each entity: + +| Document | Contents | +|----------|----------| +| `docs/art/CONCEPT_PLAYER.md` | Player character — Yue Family Army Soldier | +| `docs/art/CONCEPT_ENEMIES.md` | Ghost Soldier, Ghost Archer, Corpse Beast | +| `docs/art/CONCEPT_BOSS.md` | Gate Guardian Ghost General | +| `docs/art/CONCEPT_ICONS.md` | Weapon & UI icon designs | +| `docs/art/CONCEPT_TILES.md` | Tile & environment designs | +| `docs/art/CONCEPT_EFFECTS.md` | VFX designs | +| `docs/art/CONCEPT_BACKGROUND.md` | Background layer designs | +| `docs/art/CONCEPT_UI.md` | UI element designs | + +--- + +## 9. AI Handoff Notes + +- **For Claude:** All sprite sheets are horizontal strips. Frame counts and sizes are in each directory's `metadata.md`. Import with `filter=off, mipmaps=off` in Godot. +- **For art generation AI:** Replace any placeholder PNG with pixel art matching the same dimensions. Remove the magenta border. Follow the concept documents for visual direction. +- **Palette file:** See color table in Section 1. Use these exact hex values for consistency. diff --git a/docs/art/CONCEPT_BACKGROUND.md b/docs/art/CONCEPT_BACKGROUND.md new file mode 100644 index 0000000..0387459 --- /dev/null +++ b/docs/art/CONCEPT_BACKGROUND.md @@ -0,0 +1,81 @@ +# Concept: Background Elements — 背景资源 + +> **Type:** Parallax / Decorative +> **Sizes:** Varies (see each) +> **Style:** Distant, atmospheric, dark + +--- + +## Design Philosophy +- Background elements are **behind the gameplay layer** +- They should be darker and less detailed than foreground elements +- Parallax scrolling: background moves slower than foreground +- All elements have transparent backgrounds (except bg_deep which is full-frame) + +--- + +## Background Designs (4 total) + +### 1. bg_tree.png — 背景树 (Background Tree) +- **Size:** 64 × 128 px +- **Description:** Dead, skeletal tree — bare branches reaching upward +- **Colors:** Trunk `#14100E`, branches `#1E1812`, highlight `#2A2218` +- **Notes:** Leafless, twisted, ancient. Should look like it died centuries ago. Placed at intervals along the background parallax layer. +- **Parallax layer:** Mid-background (0.5x scroll speed) +- **Tiling:** Can be placed at intervals; not seamless but repeatable with variation + +### 2. bg_broken_flag.png — 残破旗帜 (Broken Flag) +- **Size:** 48 × 96 px +- **Description:** Broken flag pole with tattered battle flag still attached +- **Colors:** Pole `#2A2218`, flag `#3A221B` (faded blood red), frayed edges `#1A0E0B` +- **Notes:** Flag should look wind-blasted and old. Pole is broken at the top — flag hangs from the break point. Placed as atmospheric decoration. +- **Parallax layer:** Mid-background (0.5x scroll speed) +- **Notes:** Can have slight sway animation if multi-frame variant is created later + +### 3. bg_chains.png — 锁链 (Chains) +- **Size:** 32 × 96 px +- **Description:** Hanging chains from above — vertical decoration +- **Colors:** Chain `#3A3530`, highlight `#5A5045`, shadow `#1A150F` +- **Notes:** 3-4 chain links visible. Hangs from top of frame. Rusted, heavy iron chains. Atmospheric element suggesting imprisonment/torture. +- **Parallax layer:** Close background (0.7x scroll speed) +- **Notes:** Can have slight sway animation in future + +### 4. bg_deep.png — 深景背景 (Deep Background) +- **Size:** 320 × 180 px +- **Description:** Full-frame deep background — distant underworld landscape +- **Colors:** Sky `#0A080C`, mountains `#15110D`, mist `#1A151F`, distant fire `#1E2820` +- **Notes:** Wide landscape showing the underworld vista — jagged mountains, low-hanging mist, faint distant ghost fires. This is the deepest parallax layer. +- **Parallax layer:** Deep background (0.2x scroll speed) +- **Tiling:** Designed to tile horizontally — left and right edges match + +--- + +## Parallax Layer Structure (for Claude's reference) +``` +Layer 0 (0.2x): bg_deep.png — underworld vista +Layer 1 (0.5x): bg_tree.png, bg_broken_flag.png — mid decoration +Layer 2 (0.7x): bg_chains.png — close decoration +Layer 3 (1.0x): Gameplay (tiles, player, enemies) +Layer 4 (1.0x): effects, UI +``` + +--- + +## Future Background Elements (Not Required for Prototype) +| Element | Description | +|---------|-------------| +| bg_gate.png | Massive underworld gate (boss arena backdrop) | +| bg_river.png | River of blood/souls in deep background | +| bg_tower.png | Distant watchtower silhouette | +| bg_fog.png | Moving fog overlay (semi-transparent) | +| bg_fireflies.png | Floating ghost fire particles (ambient) | + +--- + +## Design Notes for AI Art Generation +1. **Backgrounds are DARKER than foreground** — they recede visually +2. **Less detail, more silhouette** — at parallax distance, details are lost +3. **bg_deep.png is the only full-frame background** — all others are sprite props +4. **Trees should be dead/skeletal** — no leaves, twisted branches +5. **Chains should look heavy and rusted** — not clean metal +6. **The deep background sets the MOOD** — spend the most effort here diff --git a/docs/art/CONCEPT_BOSS.md b/docs/art/CONCEPT_BOSS.md new file mode 100644 index 0000000..88232cf --- /dev/null +++ b/docs/art/CONCEPT_BOSS.md @@ -0,0 +1,116 @@ +# Concept: Boss — 镇关鬼将 (Gate Guardian Ghost General) + +> **Type:** Boss Character +> **Frame Size:** 96 × 96 px +> **Direction:** Face left by default (toward player) + +--- + +## Visual Description + +A towering underworld general who guards the gate between realms. Not a historical figure — a pure creation of the underworld's malice. He radiates oppressive ghost fire and wields a massive blade too heavy for any mortal. + +### Scale +- **3× the player's height** — fills the 96×96 frame +- Player stands ~40px tall; Boss stands ~88px tall +- This size difference must feel threatening in-game + +### Key Characteristics +- **巨大 (Gigantic):** Towers over the player. Body fills most of the frame. +- **压迫感 (Oppressive):** Broad shoulders, heavy posture. Even idle, he looms. +- **鬼火 (Ghost Fire):** Blue-green flames wreath his shoulders, blade, and eyes. This is his signature visual. +- **重刀 (Heavy Blade):** Wields a massive guandao/Heavy saber (重刀), blade alone is ~40px long. +- **Not historical:** No real armor style. This is underworld plate — organic, bone-like, demonic. +- **Horns:** Curved bone horns protrude from helmet/brow +- **Cape:** Tattered cloak of dark energy flows behind him + +### Face +- No visible eyes — just two burning ghost-fire orbs in a shadowed face +- Mouth: Permanent grimace, visible fangs +- Helmet: Bone crown fused to skull + +### Body Proportions (within 96×96) +- Head + horns: ~24px tall +- Torso: ~36px tall, shoulders ~56px wide +- Legs: ~28px tall +- Blade extends ~40px to the left when attacking +- Cape flows ~20px behind +- Ghost fire aura: ~8px radius around shoulders and blade + +--- + +## Color Palette +| Part | Color | Hex | +|------|-------|-----| +| Armor (bone plate) | Dark underworld steel | `#321E37` | +| Armor highlight | Ghostly purple sheen | `#5A3264` | +| Cape | Void black with purple edge | `#1A0E1F` | +| Blade | Dark iron | `#2A2520` | +| Blade edge | Faint cold steel | `#4A4540` | +| Ghost fire (shoulders/eyes/blade) | Blue-green flame | `#1EB464` | +| Ghost fire core | Bright cyan-green | `#4AE68A` | +| Bone (horns/teeth) | Yellowed ivory | `#A0987A` | +| Shadow | Deep void | `#0F0C12` | + +--- + +## Animations + +### Idle (4 frames, 6 FPS) +- Frame 1: Standing, blade resting on ground, ghost fire flickering on shoulders +- Frame 2: Slight rise, fire intensifies +- Frame 3: Neutral, fire dims slightly +- Frame 4: Slight sink, fire returns to baseline +- **Feel:** Slow, heavy breathing. A sleeping volcano. The fire should pulse. + +### Run (6 frames, 8 FPS) +- Frame 1: Left foot forward, blade dragging on ground +- Frame 2: Mid-stride, ground cracks beneath foot +- Frame 3: Right foot forward, blade lifted slightly +- Frame 4: Mid-stride, airborne moment (despite size, he moves fast) +- Frame 5: Left foot forward, blade swings +- Frame 6: Recovery +- **Feel:** Earth-shaking charge. The ground should feel like it trembles. + +### Attack (5 frames, 10 FPS) +- Frame 1: Wind-up — blade raised overhead with both hands, ghost fire channels into blade +- Frame 2: Strike begins — blade swings down in a massive overhead arc +- Frame 3: Impact — blade hits ground, shockwave of ghost fire erupts +- Frame 4: Follow-through — blade drags along ground, creating fire trail +- Frame 5: Recovery — lifts blade back to resting position +- **Feel:** Devastating, slow, telegraphed. Each swing should feel like it could level a wall. + +### Hurt (2 frames, 8 FPS) +- Frame 1: Recoil — staggers back, ghost fire flares defensively +- Frame 2: Recovers — plants feet, glares (fire eyes intensify) +- **Feel:** He barely feels it. The anger is more dangerous than the pain. + +### Death (8 frames, 6 FPS) +- Frame 1: Blade drops from hands, embeds in ground +- Frame 2: Grabs chest — cracks appear in bone armor +- Frame 3: Ghost fire erupts from cracks +- Frame 4: Falls to one knee +- Frame 5: Both knees, head bowed +- Frame 6: Armor shatters — bone fragments fly +- Frame 7: Body dissolves into ghost fire +- Frame 8: Only the blade remains, driven into the ground, fire slowly dying +- **Feel:** Epic, slow, dramatic. The boss's death should feel like a mountain crumbling. + +--- + +## Design Notes for AI Art Generation +1. **Scale is critical** — at 96×96, the Boss must look 3× the player's size +2. **Ghost fire is the signature** — blue-green flames on shoulders, eyes, and blade in EVERY frame +3. **The blade is massive** — it should look too heavy for any normal character to wield +4. **Bone armor, not metal** — the armor should look organic, grown from bone, not forged +5. **No symmetrical design** — the cape flows left, blade rests right — asymmetry adds menace +6. **Color: purple + blue-green fire** — distinct from all other enemies (no red, no brown) +7. **Death animation is the longest** (8 frames) — it should feel rewarding to defeat + +--- + +## Boss Arena Visual Notes +- The boss room should have larger ghost fire braziers +- Ground should crack during boss attacks (tile variation) +- Background should darken when boss appears +- Consider a faint purple fog at screen edges during boss fight diff --git a/docs/art/CONCEPT_EFFECTS.md b/docs/art/CONCEPT_EFFECTS.md new file mode 100644 index 0000000..19688fb --- /dev/null +++ b/docs/art/CONCEPT_EFFECTS.md @@ -0,0 +1,70 @@ +# Concept: Visual Effects — 特效 + +> **Type:** VFX Sprites +> **Size:** 48 × 48 px (all effects) +> **Style:** Quick, impactful, dark fantasy + +--- + +## Design Philosophy +- Effects are **fast** — visible for 2-6 frames (0.1-0.4 seconds) +- Each effect should read clearly even in chaotic combat +- Colors should contrast with the dark environment +- All effects use transparent backgrounds +- Effects are centered in the 48×48 frame + +--- + +## Effect Designs (4 total) + +### 1. effect_hit_slash.png — 斩击特效 (Hit Slash) +- **Type:** Single-frame (or 2-frame) impact effect +- **Description:** A white/silver slash arc — appears when player or enemy lands a melee hit +- **Colors:** Core `#E0E0E0`, edge `#A0A0A0`, outline `#606060` +- **Frame layout:** 2 frames — Frame 1: slash arc expanding, Frame 2: slash dissipating +- **Notes:** Diagonal slash arc, ~36px long. Should feel like a blade cut through air. +- **Engine usage:** Spawn at hit position, play 2 frames at 20 FPS, destroy. + +### 2. effect_blood_splash.png — 鲜血飞溅 (Blood Splash) +- **Type:** Multi-frame particle burst +- **Description:** Dark crimson blood particles exploding outward +- **Colors:** Core `#5A1818`, spray `#8A2828`, droplets `#3A0808` +- **Frame layout:** 4 frames — expanding burst, particles flying, particles falling, fade +- **Notes:** Radial burst from center. 8-12 particles. Dark blood, not bright red. +- **Engine usage:** Spawn at damage position, play 4 frames at 15 FPS, destroy. + +### 3. effect_ghost_fire.png — 鬼火特效 (Ghost Fire) +- **Type:** Looping ambient effect +- **Description:** Floating blue-green flame — used for brazier light, boss aura, projectile trail +- **Colors:** Core `#4AE68A`, mid `#1EB464`, outer `#0A5030` +- **Frame layout:** 4 frames — flame flickering (loop) +- **Notes:** Teardrop shape, pointed top. Flickers organically. Can be scaled for different uses. +- **Engine usage:** Loop at 10 FPS. Can be used as light source texture. + +### 4. effect_death_fade.png — 死亡消散 (Death Fade) +- **Type:** Multi-frame dissolve +- **Description:** Character body dissolving into dark particles — used on enemy death +- **Colors:** Body `#3A2E35` (generic), particles `#1A151F`, glow `#2A1E2F` +- **Frame layout:** 4 frames — body intact → cracking → fragmenting → fully dissolved +- **Notes:** Vertical dissolve — bottom fades first. Particles drift upward (soul ascending). +- **Engine usage:** Play 4 frames at 8 FPS after death animation. Destroy after. + +--- + +## Future Effects (Not Required for Prototype) +| Effect | Description | +|--------|-------------| +| effect_dash_trail.png | Player dash afterimage | +| effect_parry_spark.png | Parry successful spark | +| effect_sacrifice_aura.png | Sacrifice activation glow | +| effect_level_up.png | Power gain burst | +| effect_screen_shake.png | (Not a sprite — engine effect) | + +--- + +## Design Notes for AI Art Generation +1. Effects should be **brighter than the environment** — they need to pop +2. Blood is **dark crimson**, not cartoon red — this is a dark fantasy game +3. Ghost fire is **blue-green**, not orange — this is the underworld's fire +4. All effects are **centered** in the 48×48 frame — origin point is center +5. Keep particle count readable at 48px — 8-12 particles max per effect diff --git a/docs/art/CONCEPT_ENEMIES.md b/docs/art/CONCEPT_ENEMIES.md new file mode 100644 index 0000000..5bf91de --- /dev/null +++ b/docs/art/CONCEPT_ENEMIES.md @@ -0,0 +1,120 @@ +# Concept: Enemies — 鬼卒 / 鬼弓手 / 尸兽 + +> **Type:** Enemy Characters +> **Frame Size:** 48 × 48 px (humanoid), 64 × 48 px (beast) +> **Direction:** Face left by default (toward player) + +--- + +## 1. 鬼卒 (Ghost Soldier) — Melee + +### Visual Description +A reanimated soldier of a defeated ancient army, now serving as cannon fodder in the underworld. It shambles with rusted weapon in hand. + +### Key Characteristics +- **Rusted weapon:** Broken spear or notched axe, held in one hand +- **Tattered robes:** Ancient military uniform, rotted to strips +- **Pale blue-grey skin:** Corpse-like, slightly translucent +- **Empty eye sockets:** Faint ghost-fire glow within +- **Hunched posture:** Not standing straight — decades of death have bent it + +### Color Palette +| Part | Color | Hex | +|------|-------|-----| +| Skin | Corpse blue-grey | `#2D3746` | +| Skin highlight | Pale dead flesh | `#46566B` | +| Robes | Rotten dark teal | `#1E2A2A` | +| Weapon | Rusted iron | `#3A3028` | +| Eye glow | Ghost fire | `#B4641E` | +| Shadow | Deep void | `#0F0C12` | + +### Animations +| Animation | Frames | FPS | Description | +|-----------|--------|-----|-------------| +| idle | 4 | 8 | Swaying slightly, weapon dragging | +| run | 6 | 10 | Lurching forward, uneven gait | +| attack | 4 | 12 | Wild overhead chop with weapon | +| hurt | 2 | 10 | Recoils, parts of body flicker | +| death | 4 | 8 | Dissolves into blue mist | + +### Design Notes +- Should look clearly "dead" — not a living soldier +- Movements are jerky and unnatural (puppet-like) +- The ghost-fire eye glow is the only warm color on the body + +--- + +## 2. 鬼弓手 (Ghost Archer) — Ranged + +### Visual Description +A spectral archer that fires soul arrows from a distance. Thinner and more ethereal than the Ghost Soldier. + +### Key Characteristics +- **Bow:** Longbow made of bone/ghost energy, faintly glowing string +- **Quiver:** Worn at hip, arrows are wisps of ghost fire +- **Thin frame:** Gaunt, almost skeletal beneath tattered archer's garb +- **Hood:** Dark hood obscures most of the face +- **Floating slightly:** Feet barely touch ground — hovering + +### Color Palette +| Part | Color | Hex | +|------|-------|-----| +| Skin | Sickly green-grey | `#323C37` | +| Skin highlight | Pale green | `#4B5F55` | +| Garb | Dark moss green | `#1E2820` | +| Bow | Bone white | `#8A8070` | +| Bowstring/arrow | Ghost fire | `#B4641E` | +| Hood shadow | Deep void | `#0F0C12` | + +### Animations +| Animation | Frames | FPS | Description | +|-----------|--------|-----|-------------| +| idle | 4 | 8 | Hovering, bow held loosely | +| run | 6 | 10 | Gliding forward, feet not touching ground | +| attack | 4 | 12 | Draws bow → releases ghost arrow → lower bow | +| hurt | 2 | 10 | Flickers, body becomes semi-transparent | +| death | 4 | 8 | Bow shatters, body disperses into green wisps | + +### Design Notes +- Clearly distinguishable from Ghost Soldier by color (green vs blue) and build (thin vs hunched) +- The arrow should be visible as a distinct projectile (ghost fire wisp) +- Hovering/gliding movement replaces walking + +--- + +## 3. 尸兽 (Corpse Beast) — Charge + +### Visual Description +A massive quadruped formed from fused corpses of war horses and soldiers. It charges blindly, driven by rage. + +### Key Characteristics +- **Quadruped:** Four legs, low-slung body, wider than humanoid enemies +- **Fused flesh:** Visible human limbs protruding from its mass +- **No eyes:** Just a gaping maw with too many teeth +- **Exposed bone:** Ribs and spine visible through rotted flesh +- **Size:** Visually larger and wider than other enemies (64×48 frame) + +### Color Palette +| Part | Color | Hex | +|------|-------|-----| +| Flesh | Dark rotten brown | `#372823` | +| Flesh highlight | Rancid pink | `#5A4035` | +| Bone | Yellowed ivory | `#A0987A` | +| Mouth interior | Void black | `#0A080C` | +| Teeth | Stained bone | `#8A8060` | +| Blood | Dried crimson | `#5A1818` | + +### Animations +| Animation | Frames | FPS | Description | +|-----------|--------|-----|-------------| +| idle | 4 | 8 | Low growl, body heaving, limbs twitching | +| run | 6 | 14 | Fast charge — all four legs, mouth open | +| attack | 4 | 12 | Lunge forward, jaws snapping | +| hurt | 2 | 10 | Recoils, limbs spasm | +| death | 4 | 8 | Collapses, flesh sloughing off bones | + +### Design Notes +- The charge (run animation) should feel FAST and DANGEROUS — this is the core behavior +- Frame is 64×48 (wider) to accommodate the quadruped body +- Mouth should be the most visually striking element (threat indicator) +- No ghost fire on this enemy — it's pure flesh/horror, not spectral diff --git a/docs/art/CONCEPT_ICONS.md b/docs/art/CONCEPT_ICONS.md new file mode 100644 index 0000000..47fc15c --- /dev/null +++ b/docs/art/CONCEPT_ICONS.md @@ -0,0 +1,116 @@ +# Concept: Icons — Weapons & UI Stats + +> **Type:** Icon Assets +> **Size:** 32 × 32 px (all unified) +> **Style:** Pixel art, dark fantasy, limited palette + +--- + +## Design Philosophy +- Icons should be **instantly recognizable** at 32×32 +- Use 3-4 colors max per icon (silhouette + 1-2 highlights + outline) +- All icons share a unified palette (see ART_SPEC.md) +- Background: transparent +- No text/numbers in icons — those are rendered by UI code + +--- + +## Weapon Icons (6 total) + +### 1. weapon_songdao.png — 宋刀 (Song Sword) +- **Description:** Straight-backed single-edged dao, blade pointing up-right +- **Colors:** Dark iron blade `#4A4035`, steel edge `#6A5A4A`, dark handle `#2A2018` +- **Notes:** Blade takes up ~70% of icon. Handle wraps at bottom-left. Small tassel (red) optional. + +### 2. weapon_spear.png — 长枪 (Spear) +- **Description:** Long spear diagonal bottom-left to top-right +- **Colors:** Wood shaft `#5A4025`, iron tip `#4A4035`, red tassel below tip `#5A1818` +- **Notes:** Tassel is the Song dynasty military identifier. Shaft is thin (2px wide). + +### 3. weapon_ghostfire.png — 鬼火 (Ghost Fire) +- **Description:** Floating orb of blue-green flame with wisps +- **Colors:** Core `#4AE68A`, outer `#1EB464`, wisp `#0A8040` +- **Notes:** Not a weapon per se — represents the ghost fire power/element. Used in skill icons. + +### 4. weapon_talisman.png — 护符 (Talisman) +- **Description:** Rectangular paper talisman with seal script, hanging string +- **Colors:** Paper `#8A7A5A`, seal ink `#5A1818`, string `#3A3028` +- **Notes:** Vertical orientation. Red seal mark in center. Torn bottom edge. + +### 5. weapon_gourd.png — 酒葫芦 (Wine Gourd) +- **Description:** Traditional gourd shape with stopper +- **Colors:** Gourd body `#6A5028`, highlight `#8A6838`, stopper `#3A2818` +- **Notes:** Healing/consumable item icon. Two-bulb gourd shape. + +### 6. weapon_flag.png — 残破军旗 (Broken Battle Flag) +- **Description:** Broken flag pole with tattered flag +- **Colors:** Pole `#4A3828`, flag cloth `#5A322B` (dried blood red), frayed edges `#3A221B` +- **Notes:** Flag should look torn — ragged bottom edge. Pole is snapped at top. + +--- + +## UI Stat Icons (11 total) + +### 1. icon_hp.png — 生命值 (HP) +- **Shape:** Heart (organic, not geometric) +- **Colors:** Dark red `#5A1818`, bright red `#8A2828`, outline `#2A0808` +- **Notes:** Slightly stylized — not a perfect heart, more like a torn flesh shape + +### 2. icon_stamina.png — 体力 (Stamina) +- **Shape:** Circular gauge with lightning bolt +- **Colors:** Dark green `#1A4A2A`, bright green `#2A8A4A`, outline `#0A2A1A` +- **Notes:** Represents physical endurance. Green = vitality. + +### 3. icon_attack.png — 攻击 (Attack) +- **Shape:** Downward-pointing sword/triangle +- **Colors:** Iron `#4A4035`, steel `#6A5A4A`, outline `#2A2520` +- **Notes:** Simple crossed swords or single sword pointing down + +### 4. icon_armor.png — 护甲 (Armor) +- **Shape:** Shield outline +- **Colors:** Dark steel `#3A3530`, highlight `#5A5045`, outline `#2A2520` +- **Notes:** Song dynasty shield shape (rounded top, pointed bottom) + +### 5. icon_crit.png — 暴击 (Critical Hit) +- **Shape:** Starburst / explosion +- **Colors:** Orange `#B4641E`, yellow `#E6A030`, outline `#8A4810` +- **Notes:** 4-point starburst. Should feel "explosive." + +### 6. icon_speed.png — 速度 (Speed) +- **Shape:** Wing / chevron pointing right +- **Colors:** Grey `#5A5A5A`, light grey `#8A8A8A`, outline `#3A3A3A` +- **Notes:** Movement speed indicator. Forward-leaning chevron. + +### 7. icon_sacrifice.png — 献祭 (Sacrifice) +- **Shape:** Circle with dripping blood / chalice +- **Colors:** Purple `#321E37`, dark red `#5A1818`, outline `#1A0E1F` +- **Notes:** Core mechanic icon. Should feel ominous — drop of blood falling into a vessel. + +### 8. icon_ghostfire.png — 鬼火 (Ghost Fire - UI) +- **Shape:** Flame orb (same as weapon_ghostfire but framed for UI) +- **Colors:** Core `#4AE68A`, outer `#1EB464`, outline `#0A5030` +- **Notes:** Resource/element indicator. Same visual language as weapon version. + +### 9. icon_boss.png — Boss +- **Shape:** Skull with horns +- **Colors:** Bone `#A0987A`, shadow `#3A3028`, horns `#5A4A3A` +- **Notes:** Used in boss health bar / boss intro. Should feel threatening even at 32px. + +### 10. icon_key.png — 钥匙 (Key) +- **Shape:** Ancient Chinese key (skeleton key style) +- **Colors:** Bronze `#8A6A32`, highlight `#AA8A4A`, outline `#5A4220` +- **Notes:** Skeleton key — ring at top, teeth at bottom. Used for locked doors/chests. + +### 11. icon_coin.png — 金币/阴钱 (Yin Coin) +- **Shape:** Round coin with square hole (Chinese cash coin) +- **Colors:** Bronze gold `#8B7332`, highlight `#AB9350`, shadow `#5A4A1A` +- **Notes:** Traditional Chinese coin (圆形方孔). Square hole in center. Yin money for the underworld. + +--- + +## Design Notes for AI Art Generation +1. All icons must be readable at 32×32 — no fine details that disappear at this size +2. Use bold silhouettes — if you squint, you should still tell what it is +3. Consistent lighting: top-left light source for all icons +4. 1px dark outline on all icons for readability against any background +5. Maximum 4 colors per icon (outline + 2-3 fills) diff --git a/docs/art/CONCEPT_PLAYER.md b/docs/art/CONCEPT_PLAYER.md new file mode 100644 index 0000000..fbee788 --- /dev/null +++ b/docs/art/CONCEPT_PLAYER.md @@ -0,0 +1,99 @@ +# Concept: Player — 岳家军普通军士 (Yue Family Army Soldier) + +> **Type:** Player Character +> **Frame Size:** 48 × 48 px +> **Direction:** Faces right by default + +--- + +## Visual Description + +A common soldier of the Yue Family Army (岳家军), not Yue Fei himself. He is a grunt — worn down by endless war, now trapped in the underworld. + +### Key Characteristics +- **残破盔甲 (Broken Armor):** Song dynasty lamellar armor (步人甲), cracked and missing plates. Visible chainmail beneath torn cloth. +- **布甲 (Cloth Armor):** Simple padded gambeson (绵甲) underneath, dark brown fabric with frayed edges. +- **朴素 (Plain):** No ornamentation. No insignia. No gleam. This is a foot soldier, not a general. +- **一把宋刀 (Single Song Sword):** Wielded right-handed. Straight-backed dao (直背刀), blade nicked and dark. +- **黑红色调 (Dark Red Tone):** Palette dominated by dried blood red, rust brown, ash grey. No bright colors. + +### Face +- Half-shadowed, eyes glowing faintly (residual life force) +- Simple headband or helmet rim — broken +- No expression — dead-eyed determination + +### Body Proportions (within 48×48) +- Head: ~12px tall, centered +- Torso: ~16px tall, shoulders ~20px wide +- Legs: ~14px tall +- Sword extends ~16px to the right when attacking +- Total silhouette ~40px tall, 24px wide (standing) + +--- + +## Color Palette +| Part | Color | Hex | +|------|-------|-----| +| Armor plates | Dried blood red | `#5A322B` | +| Armor highlight | Faded rust | `#7A4235` | +| Cloth/gambeson | Dark ash brown | `#3A2E25` | +| Skin | Pale corpse grey | `#8A7A6A` | +| Sword blade | Dark iron | `#4A4035` | +| Sword edge | Faint steel | `#6A5A4A` | +| Eyes | Faint ghost glow | `#B4641E` | +| Shadow | Deep void | `#0F0C12` | + +--- + +## Animations + +### Idle (4 frames, 8 FPS) +- Frame 1: Standing, sword at side, slight slouch +- Frame 2: Very slight rise (breathing in) +- Frame 3: Neutral (same as 1, minor variation) +- Frame 4: Slight sink (breathing out) +- **Feel:** Exhausted, haunted, but ready. Not a heroic idle — a tired soldier's. + +### Run (6 frames, 12 FPS) +- Frame 1: Left foot forward, sword arm back +- Frame 2: Mid-stride, weight transitioning +- Frame 3: Right foot forward, sword arm forward +- Frame 4: Mid-stride, airborne moment +- Frame 5: Left foot forward, leaning +- Frame 6: Recovery to frame 1 +- **Feel:** Heavy, armored clank. Not graceful — burdened running. + +### Jump (2 frames, 10 FPS) +- Frame 1: Crouch + launch (knees bent, sword raised) +- Frame 2: Apex (legs tucked, sword overhead) +- **Feel:** Desperate leap, not athletic jump. + +### Attack (4 frames, 14 FPS) +- Frame 1: Wind-up — sword pulled back to right shoulder +- Frame 2: Strike — horizontal slash, sword fully extended left +- Frame 3: Follow-through — sword sweeping down +- Frame 4: Recovery — returning to guard +- **Feel:** Brutal, heavy slash. Military sword technique, not flashy. + +### Hurt (2 frames, 10 FPS) +- Frame 1: Impact — recoil backward, head snapped +- Frame 2: Stagger — off-balance, one knee buckling +- **Feel:** Painful but recovering. Not a knockdown. + +### Death (6 frames, 8 FPS) +- Frame 1: Sword drops from hand +- Frame 2: Knees buckle +- Frame 3: Falling forward +- Frame 4: Hits ground on hands +- Frame 5: Collapses fully +- Frame 6: Dissolves into dark particles (entering the underworld) +- **Feel:** Slow, inevitable. A soldier's final fall. + +--- + +## Design Notes for AI Art Generation +1. Keep the silhouette readable at 48×48 — avoid excessive detail +2. The sword should be clearly visible in all frames (it's the identity) +3. Armor damage should be consistent across all animations +4. The dark red palette must dominate — no blues, no greens on the player +5. Eyes glow faintly orange (ghost fire) — this is the only warm accent diff --git a/docs/art/CONCEPT_TILES.md b/docs/art/CONCEPT_TILES.md new file mode 100644 index 0000000..07a6faf --- /dev/null +++ b/docs/art/CONCEPT_TILES.md @@ -0,0 +1,77 @@ +# Concept: Tiles & Environment — 场景资源 + +> **Type:** Tilemap Tiles +> **Size:** 32 × 32 px (all tiles) +> **Style:** Seamless, tileable, dark stone + +--- + +## Design Philosophy +- Tiles must be **seamless** — edges match perfectly when placed adjacent +- Keep texture simple — detail should come from variation, not per-tile complexity +- All tiles share a base palette (dark stone / ash) +- Decorative tiles (brazier, tombstone, spike) are NOT seamless — they're props + +--- + +## Tile Designs (7 total) + +### 1. tile_floor.png — 地面 (Ground Floor) +- **Type:** Seamless tileable +- **Description:** Cracked stone floor — the ground of the underworld +- **Colors:** Base `#28231E`, cracks `#15110D`, highlights `#3A3328` +- **Notes:** Horizontal crack running through center. Subtle texture noise. Must tile in all directions. + +### 2. tile_stone_brick.png — 石砖 (Stone Brick) +- **Type:** Seamless tileable +- **Description:** Cut stone bricks in running bond pattern +- **Colors:** Base `#352E25`, mortar lines `#1A150F`, highlights `#4A4035` +- **Notes:** 2 rows of bricks per tile. Offset pattern. Mortar lines 1px thick. + +### 3. tile_wall.png — 墙壁 (Wall) +- **Type:** Seamless tileable (vertical) +- **Description:** Dark rough stone wall — background barrier +- **Colors:** Base `#1E1812`, texture `#2A2218`, shadow `#0F0C08` +- **Notes:** Vertical striations (rock texture). Darker at bottom. Must tile horizontally. + +### 4. tile_wood_bridge.png — 木桥 (Wooden Bridge) +- **Type:** Seamless tileable (horizontal) +- **Description:** Wooden plank surface — for bridge/platform sections +- **Colors:** Base `#3A2818`, plank gaps `#1A0E08`, highlights `#5A3E25` +- **Notes:** Horizontal planks. Gap every 8px. Slightly warped/rotted appearance. + +### 5. tile_brazier.png — 鬼火火盆 (Ghost Fire Brazier) +- **Type:** Decorative prop (NOT tileable) +- **Description:** Stone brazier with ghost fire burning +- **Colors:** Base stone `#352E25`, fire `#1EB464` / `#4AE68A`, shadow `#1A150F` +- **Notes:** Light source — should have a glow effect in-engine. Brazier takes ~20px, fire ~12px on top. + +### 6. tile_tombstone.png — 墓碑 (Tombstone) +- **Type:** Decorative prop (NOT tileable) +- **Description:** Worn stone tombstone with illegible inscription +- **Colors:** Base `#3A3530`, inscription `#1A150F`, moss `#2A3520` +- **Notes:** Rounded top, tapered bottom. Cracks visible. Background decoration, not collidable. + +### 7. tile_ground_spike.png — 地刺 (Ground Spike) +- **Type:** Hazard prop (NOT tileable) +- **Description:** Row of bone/iron spikes pointing up — damage on contact +- **Colors:** Spike `#5A4A3A`, tip `#8A7A5A`, shadow `#2A2520` +- **Notes:** 3-4 spikes per tile. Sharp, jagged. Should look painful. Collidable hazard. + +--- + +## Tile Variants (Future) +For prototype, one variant per tile is sufficient. Future versions should include: +- `tile_floor_cracked.png` — damaged variant +- `tile_floor_bloodied.png` — blood-stained variant +- `tile_stone_brick_mossy.png` — overgrown variant +- `tile_wall_crumbled.png` — broken wall section + +--- + +## Design Notes for AI Art Generation +1. **Seamless tiles are critical** — test by placing 4 tiles in a 2×2 grid; seams must be invisible +2. **Keep texture low** — at 32×32, less is more. Suggest material, don't detail it. +3. **Props (brazier, tombstone, spike) have transparent backgrounds** — only the object is drawn +4. **Brazier fire should animate** — if multi-frame, create `tile_brazier_01.png` through `tile_brazier_04.png` +5. **Spikes should look like bone, not metal** — the underworld reuses the dead diff --git a/docs/art/CONCEPT_UI.md b/docs/art/CONCEPT_UI.md new file mode 100644 index 0000000..ca97583 --- /dev/null +++ b/docs/art/CONCEPT_UI.md @@ -0,0 +1,92 @@ +# Concept: UI Elements — 界面元素 + +> **Type:** UI Sprites +> **Sizes:** Varies (see each) +> **Style:** Dark, ornate, Chinese underworld aesthetic + +--- + +## Design Philosophy +- UI elements should feel like they belong in the underworld — dark, rusted, bone-like +- Bars use a frame + fill pattern (frame is static, fill scales with value) +- All UI elements share a base palette (dark steel + accent colors) +- Pixel-perfect edges — no anti-aliasing on UI borders + +--- + +## UI Element Designs (4 total) + +### 1. ui_health_bar.png — 生命条 (Health Bar) +- **Size:** 96 × 16 px +- **Description:** Player health bar — dark frame with red fill +- **Colors:** Frame `#2A2520`, frame highlight `#4A4035`, fill `#5A1818`, fill highlight `#8A2828` +- **Layout:** 1px frame border, 14px interior fill area +- **Notes:** The fill represents 100% HP. In-engine, the fill width is scaled by HP percentage. The frame stays static. +- **Engine usage:** + - Frame: drawn as 9-slice or static sprite + - Fill: separate sprite scaled horizontally by `current_hp / max_hp` +- **Alternative:** Use as a single sprite and let engine handle fill via shader/modulate + +### 2. ui_stamina_bar.png — 体力条 (Stamina Bar) +- **Size:** 96 × 16 px +- **Description:** Player stamina bar — dark frame with green fill +- **Colors:** Frame `#2A2520`, frame highlight `#4A4035`, fill `#1A4A2A`, fill highlight `#2A8A4A` +- **Layout:** Same as health bar +- **Notes:** Placed below or beside the health bar. Green distinguishes it from red HP. +- **Engine usage:** Same as health bar — frame static, fill scales with stamina value. + +### 3. ui_frame.png — 通用边框 (Generic Frame) +- **Size:** 32 × 32 px +- **Description:** Decorative border frame for UI panels, icon backgrounds, tooltips +- **Colors:** Frame `#2A2520`, inner border `#4A4035`, corner accents `#5A4A3A` +- **Layout:** 3px ornate border, 26px transparent interior +- **Notes:** Designed as a 9-slice sprite — corners stay fixed, edges stretch. Used for inventory slots, dialogue boxes, menu panels. +- **Engine usage:** Import as 9-slice texture in Godot. Set margins to 3px on all sides. + +### 4. ui_minimap_frame.png — 小地图边框 (Minimap Frame) +- **Size:** 64 × 64 px +- **Description:** Square frame for minimap display +- **Colors:** Frame `#2A2520`, inner border `#4A4035`, corner spikes `#5A4A3A` +- **Layout:** 4px border, 56px interior (for minimap rendering) +- **Notes:** Larger version of ui_frame with corner decorative spikes. Underworld aesthetic — bone/iron corners. +- **Engine usage:** Static sprite. Minimap renders inside the transparent interior area. + +--- + +## UI Layout Reference (for Claude/Codex) +``` +┌──────────────────────────────────────────┐ +│ [HP████████░░░░] [icon_hp] │ ← Top-left: Health bar + icon +│ [STA██████████] [icon_stamina] │ ← Below HP: Stamina bar + icon +│ │ +│ │ +│ [ GAMEPLAY AREA ] │ +│ │ +│ │ +│ [icon_ghostfire] ×3 │ ← Bottom-right: Resources +│ [icon_coin] 999 │ ← Below: Currency +│ [minimap] │ ← Bottom-right: Minimap +└──────────────────────────────────────────┘ +``` + +--- + +## Future UI Elements (Not Required for Prototype) +| Element | Description | +|---------|-------------| +| ui_boss_bar.png | Boss health bar (wider, more ornate) | +| ui_sacrifice_panel.png | Sacrifice selection panel background | +| ui_inventory_slot.png | Individual inventory slot (32×32) | +| ui_cursor.png | Custom mouse/cursor sprite | +| ui_dialogue_box.png | NPC dialogue text box background | +| ui_death_screen.png | Death overlay texture | +| ui_pause_menu.png | Pause menu background | + +--- + +## Design Notes for AI Art Generation +1. **Bars are 96×16** — wide enough to read, thin enough to not block gameplay +2. **Frames use 9-slice** — corners must be identical, edges must tile seamlessly +3. **Colors must match the stat they represent** — HP=red, Stamina=green, Ghost Fire=blue-green +4. **No text in UI sprites** — all text is rendered by Godot's Label/Font system +5. **UI elements should feel "underworld"** — rusted iron, bone, dark metal, not clean modern UI diff --git a/docs/art/GODOT_IMPORT_GUIDE.md b/docs/art/GODOT_IMPORT_GUIDE.md new file mode 100644 index 0000000..4f0982d --- /dev/null +++ b/docs/art/GODOT_IMPORT_GUIDE.md @@ -0,0 +1,189 @@ +# Godot 4 Import Guide — 九幽 + +> **For:** Claude (framework developer) and any AI/human working in Godot +> **Purpose:** How to import and use the art assets in Godot 4.x + +--- + +## 1. Project Import Settings + +### Global Texture Preset +Create a `.godot/import_settings` or use the Import dock: + +1. Select all PNG files in `assets/` +2. In the Import dock, set: + - **Preset:** 2D Pixel + - **Filter:** Off (Nearest) + - **Mipmaps:** Off + - **Lossy Compression:** Off → use Lossless +3. Click "Reimport" + +### project.godot Settings (recommended) +```ini +[rendering] +textures/canvas_textures/default_texture_filter=0 # Nearest +renderer/rendering_method="gl_compatibility" # For 2D pixel art +``` + +--- + +## 2. Sprite Sheet → AnimatedSprite2D + +### For Player, Enemies, and Boss + +Each animation is a separate horizontal sprite sheet PNG. + +#### Step-by-step (Player example): +1. The file `assets/player/player_idle.png` is a **192×48** image (4 frames × 48px) +2. In Godot, this imports as a `CompressedTexture2D` +3. To use with `AnimatedSprite2D`: + - Create a `SpriteFrames` resource + - Add animation "idle" + - Set the texture to `player_idle.png` + - Set "Horizontal Frames" to **4** + - Set FPS to **8** (from metadata) +4. Repeat for each animation (run, jump, attack, hurt, death) + +#### Quick Reference Table +| File | H-Frames | V-Frames | FPS | +|------|----------|----------|-----| +| player_idle.png | 4 | 1 | 8 | +| player_run.png | 6 | 1 | 12 | +| player_jump.png | 2 | 1 | 10 | +| player_attack.png | 4 | 1 | 14 | +| player_hurt.png | 2 | 1 | 10 | +| player_death.png | 6 | 1 | 8 | +| ghost_melee_idle.png | 4 | 1 | 8 | +| ghost_melee_run.png | 6 | 1 | 10 | +| ghost_melee_attack.png | 4 | 1 | 12 | +| ghost_melee_hurt.png | 2 | 1 | 10 | +| ghost_melee_death.png | 4 | 1 | 8 | +| ghost_archer_idle.png | 4 | 1 | 8 | +| ghost_archer_run.png | 6 | 1 | 10 | +| ghost_archer_attack.png | 4 | 1 | 12 | +| ghost_archer_hurt.png | 2 | 1 | 10 | +| ghost_archer_death.png | 4 | 1 | 8 | +| corpse_beast_idle.png | 4 | 1 | 8 | +| corpse_beast_run.png | 6 | 1 | 14 | +| corpse_beast_attack.png | 4 | 1 | 12 | +| corpse_beast_hurt.png | 2 | 1 | 10 | +| corpse_beast_death.png | 4 | 1 | 8 | +| boss_idle.png | 4 | 1 | 6 | +| boss_run.png | 6 | 1 | 8 | +| boss_attack.png | 5 | 1 | 10 | +| boss_hurt.png | 2 | 1 | 8 | +| boss_death.png | 8 | 1 | 6 | + +### Alternative: SpriteFrames .tres Resource +You can create a `.tres` SpriteFrames resource that references all animation sheets: + +```godot +# Example: player_sprite_frames.tres (generated by Godot) +# Each animation references its sprite sheet with hframes set +``` + +Claude should create these `.tres` files during framework implementation. + +--- + +## 3. Tile Set Import + +### For TileMapLayer (Godot 4.3+) or TileMap (Godot 4.0-4.2) + +1. Tiles are individual 32×32 PNGs in `assets/tiles/` +2. Create a `TileSet` resource in Godot +3. Add each tile PNG as a tile source +4. Set tile size to 32×32 +5. For seamless tiles (floor, brick, wall, bridge): enable "Tile" in collision or just place freely +6. For props (brazier, tombstone, spike): use as individual sprites or TileSet tiles with specific collision shapes + +### Collision Shapes +| Tile | Collision | +|------|-----------| +| tile_floor.png | Full rect (solid ground) | +| tile_stone_brick.png | Full rect (solid platform) | +| tile_wall.png | Full rect (solid wall) | +| tile_wood_bridge.png | Full rect (one-way platform) | +| tile_brazier.png | No collision (decorative) | +| tile_tombstone.png | Optional small rect (obstacle) | +| tile_ground_spike.png | Full rect + damage area (hazard) | + +--- + +## 4. Icon Import + +### Weapon & UI Icons +1. All icons are 32×32 PNGs +2. Import as `CompressedTexture2D` with Nearest filter +3. Use in `TextureRect` nodes for UI +4. Use in `Texture2D` properties for inventory items + +--- + +## 5. Effect Import + +### VFX Sprites +1. Effects are 48×48 PNGs in `assets/effects/` +2. Some are multi-frame (hit_slash, blood_splash, death_fade) — treat as sprite sheets +3. ghost_fire is a 4-frame loop — use AnimatedSprite2D +4. Spawn as `Sprite2D` or `AnimatedSprite2D` at runtime, queue_free after animation + +### Effect Frame Reference +| File | Frames | FPS | Loop | +|------|--------|-----|------| +| effect_hit_slash.png | 2 | 20 | No | +| effect_blood_splash.png | 4 | 15 | No | +| effect_ghost_fire.png | 4 | 10 | Yes | +| effect_death_fade.png | 4 | 8 | No | + +--- + +## 6. Background Import + +### Parallax Background +1. Create a `ParallaxBackground` node +2. Add `ParallaxLayer` children for each depth: + - Layer 0 (0.2x): `bg_deep.png` — set texture, enable mirror (horizontal) + - Layer 1 (0.5x): `bg_tree.png`, `bg_broken_flag.png` — placed as sprites + - Layer 2 (0.7x): `bg_chains.png` — placed as sprites +3. Set `motion_scale` on each ParallaxLayer to control parallax depth + +--- + +## 7. UI Element Import + +### Bars +1. `ui_health_bar.png` and `ui_stamina_bar.png` are 96×16 +2. Use as `TextureProgressBar` nodes: + - Set texture as the bar + - Or split into frame + fill textures for more control +3. `ui_frame.png` and `ui_minimap_frame.png`: + - Import as 9-slice textures + - Set `texture_region` and 9-slice margins in Godot + +--- + +## 8. .gitignore Notes + +The project's `.gitignore` already excludes: +- `.godot/` (Godot cache) +- `.import/` (imported texture cache) + +This means: +- **Commit:** All `.png` source files in `assets/` +- **Don't commit:** `.import/` files, `.godot/` cache +- **Claude should commit:** `.tres` resource files (SpriteFrames, TileSet, etc.) that reference the PNGs + +--- + +## 9. Automated Import Script (Future) + +Claude can create a script to auto-generate SpriteFrames resources: + +```python +# Tool: tools/generate_sprite_frames.gd (EditorScript) +# Reads metadata.md files and generates .tres SpriteFrames resources +# This is Claude's responsibility — not included in art pipeline +``` + +For now, the metadata files in each directory contain all the information needed. diff --git a/docs/art/NAMING_CONVENTION.md b/docs/art/NAMING_CONVENTION.md new file mode 100644 index 0000000..302df63 --- /dev/null +++ b/docs/art/NAMING_CONVENTION.md @@ -0,0 +1,47 @@ +# Naming Convention — 九幽 Art Assets + +> **Rule:** All filenames follow `__.png` +> **All lowercase, underscores only, no spaces, no version numbers** + +--- + +## Prefixes + +| Prefix | Directory | Example | +|--------|-----------|---------| +| `player_` | `assets/player/` | `player_idle.png` | +| `ghost_melee_` | `assets/enemy/` | `ghost_melee_attack.png` | +| `ghost_archer_` | `assets/enemy/` | `ghost_archer_idle.png` | +| `corpse_beast_` | `assets/enemy/` | `corpse_beast_run.png` | +| `boss_` | `assets/boss/` | `boss_death.png` | +| `weapon_` | `assets/icons/weapons/` | `weapon_songdao.png` | +| `icon_` | `assets/icons/ui/` | `icon_hp.png` | +| `tile_` | `assets/tiles/` | `tile_floor.png` | +| `effect_` | `assets/effects/` | `effect_blood_splash.png` | +| `bg_` | `assets/background/` | `bg_tree.png` | +| `ui_` | `assets/ui/` | `ui_health_bar.png` | + +## Actions (for animated sprites) + +| Action | Description | +|--------|-------------| +| `idle` | Default standing/breathing | +| `run` | Lateral movement | +| `jump` | Vertical launch | +| `attack` | Offensive action | +| `hurt` | Taking damage | +| `death` | Dying animation | + +## Forbidden Names + +❌ `新建文件.png` +❌ `image1.png` +❌ `最终版2.png` +❌ `player_idle_v2.png` +❌ `Player Idle.png` +❌ `player-idle.png` +❌ `temp.png` + +## Replacement Rule + +When replacing a placeholder, the new file MUST have the **exact same filename** and **exact same pixel dimensions**. No version numbers — use git for versioning. diff --git a/godot/import_presets.md b/godot/import_presets.md new file mode 100644 index 0000000..62f48bf --- /dev/null +++ b/godot/import_presets.md @@ -0,0 +1,107 @@ +# Godot 4 Import Presets — 九幽 + +> **Purpose:** Default import settings for all texture assets. +> **Usage:** When first opening the project in Godot 4, apply these settings via the Import dock. + +## Default Texture Import Settings (2D Pixel Art) + +Apply to ALL `.png` files in `assets/`: + +```ini +# In Godot Import dock, select all PNGs and set: +preset = "2D Pixel" +compress/mode = 0 # Lossless +compress/lossy_quality = 0.7 +mipmaps/generate = false +process/fix_alpha_border = true +process/premult_alpha = false +process/normal_map_invert_y = false +process/hdr_as_srgb = false +process/hdr_clamp_exposure = false +process/size_limit = 0 +detect_3d/compress_to = 0 +``` + +## project.godot Settings (recommended) + +```ini +[rendering] +textures/canvas_textures/default_texture_filter = 0 +renderer/rendering_method = "gl_compatibility" + +[display] +window/size/viewport_width = 1280 +window/size/viewport_height = 720 +window/stretch/mode = "viewport" +window/stretch/aspect = "keep" +``` + +## SpriteFrames Auto-Setup Reference + +When creating SpriteFrames resources, reference the metadata files in each directory: + +| Directory | Metadata File | Contains | +|-----------|--------------|----------| +| `assets/player/` | `metadata.md` | Frame counts, sizes, FPS for all player animations | +| `assets/enemy/` | `metadata.md` | Frame counts, sizes, FPS for all enemy animations | +| `assets/boss/` | `metadata.md` | Frame counts, sizes, FPS for all boss animations | +| `assets/effects/` | `metadata.md` | Frame counts, sizes, FPS for all effects | +| `assets/tiles/` | `metadata.md` | Tile specs and collision info | +| `assets/background/` | `metadata.md` | Background element specs and parallax info | +| `assets/ui/` | `metadata.md` | UI element specs and layout info | + +## Naming → Animation Mapping + +### Player SpriteFrames +| Animation Name | Source File | H-Frames | +|----------------|-------------|----------| +| idle | `res://assets/player/player_idle.png` | 4 | +| run | `res://assets/player/player_run.png` | 6 | +| jump | `res://assets/player/player_jump.png` | 2 | +| attack | `res://assets/player/player_attack.png` | 4 | +| hurt | `res://assets/player/player_hurt.png` | 2 | +| death | `res://assets/player/player_death.png` | 6 | + +### Ghost Soldier (鬼卒) SpriteFrames +| Animation Name | Source File | H-Frames | +|----------------|-------------|----------| +| idle | `res://assets/enemy/ghost_melee_idle.png` | 4 | +| run | `res://assets/enemy/ghost_melee_run.png` | 6 | +| attack | `res://assets/enemy/ghost_melee_attack.png` | 4 | +| hurt | `res://assets/enemy/ghost_melee_hurt.png` | 2 | +| death | `res://assets/enemy/ghost_melee_death.png` | 4 | + +### Ghost Archer (鬼弓手) SpriteFrames +| Animation Name | Source File | H-Frames | +|----------------|-------------|----------| +| idle | `res://assets/enemy/ghost_archer_idle.png` | 4 | +| run | `res://assets/enemy/ghost_archer_run.png` | 6 | +| attack | `res://assets/enemy/ghost_archer_attack.png` | 4 | +| hurt | `res://assets/enemy/ghost_archer_hurt.png` | 2 | +| death | `res://assets/enemy/ghost_archer_death.png` | 4 | + +### Corpse Beast (尸兽) SpriteFrames +| Animation Name | Source File | H-Frames | Frame Size | +|----------------|-------------|----------|------------| +| idle | `res://assets/enemy/corpse_beast_idle.png` | 4 | 64×48 | +| run | `res://assets/enemy/corpse_beast_run.png` | 6 | 64×48 | +| attack | `res://assets/enemy/corpse_beast_attack.png` | 4 | 64×48 | +| hurt | `res://assets/enemy/corpse_beast_hurt.png` | 2 | 64×48 | +| death | `res://assets/enemy/corpse_beast_death.png` | 4 | 64×48 | + +### Boss (镇关鬼将) SpriteFrames +| Animation Name | Source File | H-Frames | Frame Size | +|----------------|-------------|----------|------------| +| idle | `res://assets/boss/boss_idle.png` | 4 | 96×96 | +| run | `res://assets/boss/boss_run.png` | 6 | 96×96 | +| attack | `res://assets/boss/boss_attack.png` | 5 | 96×96 | +| hurt | `res://assets/boss/boss_hurt.png` | 2 | 96×96 | +| death | `res://assets/boss/boss_death.png` | 8 | 96×96 | + +### Effects SpriteFrames +| Animation Name | Source File | H-Frames | Loop | +|----------------|-------------|----------|------| +| hit_slash | `res://assets/effects/effect_hit_slash.png` | 2 | No | +| blood_splash | `res://assets/effects/effect_blood_splash.png` | 4 | No | +| ghost_fire | `res://assets/effects/effect_ghost_fire.png` | 4 | Yes | +| death_fade | `res://assets/effects/effect_death_fade.png` | 4 | No | diff --git a/tools/.gdignore b/tools/.gdignore new file mode 100644 index 0000000..5e7ffa6 --- /dev/null +++ b/tools/.gdignore @@ -0,0 +1 @@ +# This file tells Godot to ignore this directory during import. diff --git a/tools/generate_placeholders.py b/tools/generate_placeholders.py new file mode 100644 index 0000000..83fdd13 --- /dev/null +++ b/tools/generate_placeholders.py @@ -0,0 +1,431 @@ +#!/usr/bin/env python3 +""" +Nine Nether (九幽) — Placeholder Sprite Generator +Generates all prototype placeholder PNGs with correct dimensions. +Each placeholder uses a colored silhouette + checkerboard to indicate +the intended content, sized as horizontal sprite sheets for Godot import. + +Run: python3 generate_placeholders.py +""" + +from PIL import Image, ImageDraw +import os + +ASSETS = os.path.join(os.path.dirname(__file__), "..", "assets") + +# ── Palette ────────────────────────────────────────────────────────────────── +# Chinese dark fantasy — muted, desaturated +C_BG = (15, 12, 18) # near-black background +C_PLAYER = (90, 50, 45) # dark red-brown (Yue army) +C_PLAYER_LT = (140, 80, 65) # highlight +C_GHOST = (45, 55, 70) # cold blue-grey (ghost soldier) +C_GHOST_LT = (70, 85, 105) +C_ARCHER = (50, 60, 55) # sickly green-grey +C_ARCHER_LT = (75, 95, 85) +C_BEAST = (55, 40, 35) # dark fleshy brown +C_BEAST_LT = (85, 60, 50) +C_BOSS = (50, 30, 55) # dark purple +C_BOSS_LT = (90, 50, 100) +C_FIRE = (180, 100, 30) # ghost fire orange +C_FIRE_LT = (220, 160, 60) +C_TILE = (40, 35, 30) # stone +C_TILE_LT = (65, 55, 45) +C_WOOD = (60, 40, 25) # wood +C_WOOD_LT = (90, 65, 40) +C_ICON = (50, 45, 50) # icon base +C_ICON_LT = (100, 90, 100) +C_UI = (30, 28, 35) # ui element +C_UI_LT = (80, 75, 90) +C_BG_DEEP = (10, 8, 14) # deep background +C_BG_TREE = (20, 18, 22) +C_BG_TREE_LT = (35, 30, 35) +C_CHECKER_A = (20, 18, 24) # checkerboard dark +C_CHECKER_B = (28, 25, 32) # checkerboard light +C_PLACEHOLDER= (255, 0, 255) # magenta = "replace me" + + +def checkerboard(draw, x0, y0, w, h, cell=4): + """Draw a checkerboard pattern to indicate placeholder.""" + for cy in range(0, h, cell): + for cx in range(0, w, cell): + c = C_CHECKER_A if ((cx // cell) + (cy // cell)) % 2 == 0 else C_CHECKER_B + draw.rectangle([x0 + cx, y0 + cy, x0 + cx + cell - 1, y0 + cy + cell - 1], fill=c) + + +def silhouette_rect(draw, x0, y0, w, h, color, lt_color): + """Draw a simple humanoid silhouette within the frame.""" + cx = x0 + w // 2 + # head + hs = max(6, w // 6) + draw.ellipse([cx - hs // 2, y0 + h // 8, cx + hs // 2, y0 + h // 8 + hs], fill=color, outline=lt_color) + # body + bw = max(8, w // 4) + bh = max(12, h // 3) + draw.rectangle([cx - bw // 2, y0 + h // 8 + hs, cx + bw // 2, y0 + h // 8 + hs + bh], fill=color, outline=lt_color) + # legs + lw = max(4, w // 8) + lh = max(8, h // 4) + draw.rectangle([cx - bw // 2 + 1, y0 + h // 8 + hs + bh, cx - bw // 2 + 1 + lw, y0 + h // 8 + hs + bh + lh], fill=color) + draw.rectangle([cx + bw // 2 - 1 - lw, y0 + h // 8 + hs + bh, cx + bw // 2 - 1, y0 + h // 8 + hs + bh + lh], fill=color) + + +def silhouette_beast(draw, x0, y0, w, h, color, lt_color): + """Draw a quadruped beast silhouette.""" + body_h = h // 3 + body_y = y0 + h // 3 + draw.rectangle([x0 + w // 8, body_y, x0 + w - w // 8, body_y + body_h], fill=color, outline=lt_color) + # head + hs = max(6, w // 8) + draw.ellipse([x0 + w - w // 8 - hs, body_y - hs // 2, x0 + w - w // 8, body_y + hs], fill=color, outline=lt_color) + # legs + lw = max(3, w // 12) + lh = max(6, h // 5) + for lx in [x0 + w // 6, x0 + w // 3, x0 + w - w // 3, x0 + w - w // 5]: + draw.rectangle([lx, body_y + body_h, lx + lw, body_y + body_h + lh], fill=color) + + +def silhouette_boss(draw, x0, y0, w, h, color, lt_color): + """Draw a large imposing boss silhouette.""" + cx = x0 + w // 2 + # large head with horns + hs = max(10, w // 5) + draw.ellipse([cx - hs // 2, y0 + h // 12, cx + hs // 2, y0 + h // 12 + hs], fill=color, outline=lt_color) + # horns + draw.polygon([(cx - hs // 2, y0 + h // 12), (cx - hs, y0 + h // 20), (cx - hs // 2 + 2, y0 + h // 12 + 2)], fill=lt_color) + draw.polygon([(cx + hs // 2, y0 + h // 12), (cx + hs, y0 + h // 20), (cx + hs // 2 - 2, y0 + h // 12 + 2)], fill=lt_color) + # massive body + bw = max(16, w // 3) + bh = max(20, h // 2) + draw.rectangle([cx - bw // 2, y0 + h // 12 + hs, cx + bw // 2, y0 + h // 12 + hs + bh], fill=color, outline=lt_color) + # legs + lw = max(6, w // 8) + lh = max(10, h // 5) + draw.rectangle([cx - bw // 2 + 2, y0 + h // 12 + hs + bh, cx - bw // 2 + 2 + lw, y0 + h // 12 + hs + bh + lh], fill=color) + draw.rectangle([cx + bw // 2 - 2 - lw, y0 + h // 12 + hs + bh, cx + bw // 2 - 2, y0 + h // 12 + hs + bh + lh], fill=color) + # ghost fire aura + draw.ellipse([cx - bw // 2 - 3, y0 + h // 12 + hs - 2, cx - bw // 2 + 1, y0 + h // 12 + hs + 2], fill=C_FIRE) + draw.ellipse([cx + bw // 2 - 1, y0 + h // 12 + hs - 2, cx + bw // 2 + 3, y0 + h // 12 + hs + 2], fill=C_FIRE) + + +def draw_icon_simple(draw, x0, y0, size, shape, color, lt_color): + """Draw simple icon shapes.""" + cx = x0 + size // 2 + cy = y0 + size // 2 + s = size // 3 + if shape == "sword": + draw.line([cx - s, cy + s, cx + s, cy - s], fill=lt_color, width=2) + draw.line([cx - s - 2, cy + s, cx + s + 2, cy - s], fill=color, width=1) + draw.rectangle([cx - s - 3, cy + s - 1, cx - s + 1, cy + s + 3], fill=color) + elif shape == "spear": + draw.line([cx - s, cy + s, cx + s, cy - s], fill=lt_color, width=2) + draw.polygon([(cx + s, cy - s), (cx + s + 3, cy - s - 2), (cx + s + 2, cy - s + 3)], fill=lt_color) + elif shape == "fire": + draw.ellipse([cx - s // 2, cy - s, cx + s // 2, cy + s], fill=color) + draw.ellipse([cx - s // 3, cy - s // 2, cx + s // 3, cy + s // 2], fill=lt_color) + elif shape == "talisman": + draw.rectangle([cx - s // 2, cy - s, cx + s // 2, cy + s], fill=color, outline=lt_color) + draw.line([cx, cy - s, cx, cy + s], fill=lt_color, width=1) + elif shape == "gourd": + draw.ellipse([cx - s // 2, cy - s // 3, cx + s // 2, cy + s], fill=color) + draw.rectangle([cx - 1, cy - s, cx + 1, cy - s // 3], fill=color) + elif shape == "flag": + draw.line([cx - s, cy - s, cx - s, cy + s], fill=lt_color, width=1) + draw.polygon([(cx - s, cy - s), (cx + s, cy - s // 2), (cx - s, cy)], fill=color) + elif shape == "heart": + draw.ellipse([cx - s, cy - s // 2, cx, cy + s // 2], fill=color) + draw.ellipse([cx, cy - s // 2, cx + s, cy + s // 2], fill=color) + draw.polygon([(cx - s, cy), (cx + s, cy), (cx, cy + s)], fill=color) + elif shape == "stamina": + draw.ellipse([cx - s, cy - s, cx + s, cy + s], fill=color, outline=lt_color) + draw.line([cx - s // 2, cy, cx + s // 2, cy], fill=lt_color, width=1) + elif shape == "attack": + draw.polygon([(cx, cy - s), (cx + s, cy + s), (cx - s, cy + s)], fill=color, outline=lt_color) + elif shape == "armor": + draw.polygon([(cx, cy - s), (cx + s, cy - s // 2), (cx + s // 2, cy + s), (cx - s // 2, cy + s), (cx - s, cy - s // 2)], fill=color, outline=lt_color) + elif shape == "crit": + draw.line([cx, cy - s, cx, cy + s], fill=lt_color, width=2) + draw.line([cx - s, cy, cx + s, cy], fill=lt_color, width=2) + draw.line([cx - s, cy - s, cx + s, cy + s], fill=color, width=1) + draw.line([cx - s, cy + s, cx + s, cy - s], fill=color, width=1) + elif shape == "speed": + draw.polygon([(cx - s, cy - s), (cx + s, cy), (cx - s, cy + s)], fill=color, outline=lt_color) + elif shape == "sacrifice": + draw.ellipse([cx - s, cy - s, cx + s, cy + s], outline=lt_color, width=1) + draw.line([cx, cy - s, cx, cy + s], fill=color, width=1) + draw.line([cx - s, cy, cx + s, cy], fill=color, width=1) + elif shape == "boss": + draw.ellipse([cx - s, cy - s, cx + s, cy + s], fill=color, outline=lt_color) + draw.line([cx - s, cy - s, cx - s - 2, cy - s - 3], fill=lt_color, width=1) + draw.line([cx + s, cy - s, cx + s + 2, cy - s - 3], fill=lt_color, width=1) + elif shape == "key": + draw.ellipse([cx - s, cy - s, cx, cy], outline=lt_color, width=1) + draw.line([cx, cy, cx + s, cy], fill=lt_color, width=1) + draw.line([cx + s, cy, cx + s, cy + 2], fill=lt_color, width=1) + elif shape == "coin": + draw.ellipse([cx - s, cy - s, cx + s, cy + s], fill=color, outline=lt_color) + draw.rectangle([cx - 1, cy - s // 2, cx + 1, cy + s // 2], fill=lt_color) + + +def draw_tile(draw, x0, y0, size, kind, color, lt_color): + """Draw tile placeholders.""" + if kind == "floor": + draw.rectangle([x0, y0, x0 + size - 1, y0 + size - 1], fill=color) + draw.rectangle([x0 + 2, y0 + 2, x0 + size - 3, y0 + size - 3], outline=lt_color, width=1) + draw.line([x0 + size // 2, y0 + 2, x0 + size // 2, y0 + size - 3], fill=lt_color, width=1) + elif kind == "brick": + draw.rectangle([x0, y0, x0 + size - 1, y0 + size - 1], fill=color) + draw.line([x0, y0 + size // 2, x0 + size, y0 + size // 2], fill=lt_color, width=1) + draw.line([x0 + size // 2, y0, x0 + size // 2, y0 + size // 2], fill=lt_color, width=1) + draw.line([x0 + size // 4, y0 + size // 2, x0 + size // 4, y0 + size], fill=lt_color, width=1) + draw.line([x0 + 3 * size // 4, y0 + size // 2, x0 + 3 * size // 4, y0 + size], fill=lt_color, width=1) + elif kind == "wall": + draw.rectangle([x0, y0, x0 + size - 1, y0 + size - 1], fill=color) + for i in range(0, size, 4): + draw.line([x0 + i, y0, x0 + i, y0 + size - 1], fill=lt_color, width=1) + elif kind == "bridge": + draw.rectangle([x0, y0, x0 + size - 1, y0 + size - 1], fill=color) + for i in range(0, size, 6): + draw.line([x0, y0 + i, x0 + size, y0 + i], fill=lt_color, width=1) + elif kind == "brazier": + draw.rectangle([x0 + size // 4, y0 + size // 3, x0 + 3 * size // 4, y0 + 2 * size // 3], fill=color, outline=lt_color) + draw.ellipse([x0 + size // 3, y0 + size // 6, x0 + 2 * size // 3, y0 + size // 3], fill=C_FIRE) + elif kind == "tombstone": + draw.pieslice([x0 + size // 4, y0, x0 + 3 * size // 4, y0 + size], 180, 360, fill=color, outline=lt_color) + draw.rectangle([x0 + size // 4, y0 + size // 2, x0 + 3 * size // 4, y0 + size - 2], fill=color) + elif kind == "spike": + for i in range(0, size, 8): + draw.polygon([(x0 + i, y0 + size), (x0 + i + 4, y0 + size // 3), (x0 + i + 8, y0 + size)], fill=lt_color) + + +def draw_effect(draw, x0, y0, size, kind, color, lt_color): + """Draw effect placeholders.""" + cx = x0 + size // 2 + cy = y0 + size // 2 + s = size // 3 + if kind == "slash": + draw.line([cx - s, cy + s, cx + s, cy - s], fill=lt_color, width=3) + draw.line([cx - s + 1, cy + s, cx + s + 1, cy - s], fill=color, width=1) + elif kind == "blood": + for dx in range(-s, s, 3): + for dy in range(-s, s, 3): + if (dx * dx + dy * dy) < s * s: + draw.point([cx + dx, cy + dy], fill=color) + elif kind == "ghostfire": + draw.ellipse([cx - s, cy - s, cx + s, cy + s], fill=color) + draw.ellipse([cx - s // 2, cy - s // 2, cx + s // 2, cy + s // 2], fill=lt_color) + elif kind == "deathfade": + for i in range(size): + alpha = max(0, 255 - i * 8) + draw.line([x0 + i, y0, x0 + i, y0 + size - 1], fill=color) + + +def draw_background(draw, x0, y0, w, h, kind, color, lt_color): + """Draw background element placeholders.""" + if kind == "tree": + # trunk + tw = max(4, w // 10) + draw.rectangle([x0 + w // 2 - tw // 2, y0 + h // 2, x0 + w // 2 + tw // 2, y0 + h - 1], fill=color) + # canopy + draw.ellipse([x0 + w // 6, y0, x0 + 5 * w // 6, y0 + h // 2 + h // 8], fill=color, outline=lt_color) + elif kind == "flag": + draw.line([x0 + w // 4, y0, x0 + w // 4, y0 + h - 1], fill=lt_color, width=2) + draw.polygon([(x0 + w // 4, y0 + h // 6), (x0 + 3 * w // 4, y0 + h // 4), (x0 + w // 4, y0 + h // 2)], fill=color) + # ragged edge + for i in range(0, w // 2, 6): + draw.line([x0 + w // 4 + i, y0 + h // 2, x0 + w // 4 + i + 3, y0 + h // 2 + 4], fill=color, width=1) + elif kind == "chains": + for i in range(0, h, 8): + draw.ellipse([x0 + w // 4, y0 + i, x0 + 3 * w // 4, y0 + i + 6], outline=lt_color, width=1) + elif kind == "deep": + draw.rectangle([x0, y0, x0 + w - 1, y0 + h - 1], fill=color) + # distant mountains + for mx in range(0, w, 40): + draw.polygon([(x0 + mx, y0 + h), (x0 + mx + 20, y0 + h // 2), (x0 + mx + 40, y0 + h)], fill=lt_color) + # mist + for my in range(h // 2, h, 8): + draw.line([x0, y0 + my, x0 + w, y0 + my], fill=(20, 18, 24), width=1) + + +def draw_ui(draw, x0, y0, w, h, kind, color, lt_color): + """Draw UI element placeholders.""" + if kind == "bar": + draw.rectangle([x0, y0, x0 + w - 1, y0 + h - 1], outline=lt_color, width=1) + draw.rectangle([x0 + 1, y0 + 1, x0 + w // 2, y0 + h - 2], fill=color) + elif kind == "frame": + draw.rectangle([x0, y0, x0 + w - 1, y0 + h - 1], outline=lt_color, width=2) + draw.rectangle([x0 + 3, y0 + 3, x0 + w - 4, y0 + h - 4], outline=color, width=1) + + +def save_img(img, path): + os.makedirs(os.path.dirname(path), exist_ok=True) + img.save(path) + print(f" ✓ {os.path.relpath(path, os.path.dirname(ASSETS))}") + + +# ═══════════════════════════════════════════════════════════════════════════════ +# GENERATE SPRITE SHEETS +# ═══════════════════════════════════════════════════════════════════════════════ + +def gen_spritesheet(path, frame_w, frame_h, frames, color, lt_color, kind="human"): + """Generate a horizontal sprite sheet with N frames.""" + total_w = frame_w * frames + img = Image.new("RGBA", (total_w, frame_h), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + for f in range(frames): + x0 = f * frame_w + checkerboard(draw, x0, 0, frame_w, frame_h, cell=4) + # magenta border per frame + draw.rectangle([x0, 0, x0 + frame_w - 1, frame_h - 1], outline=C_PLACEHOLDER, width=1) + if kind == "human": + silhouette_rect(draw, x0, 0, frame_w, frame_h, color, lt_color) + elif kind == "beast": + silhouette_beast(draw, x0, 0, frame_w, frame_h, color, lt_color) + elif kind == "boss": + silhouette_boss(draw, x0, 0, frame_w, frame_h, color, lt_color) + save_img(img, path) + + +def gen_single(path, w, h, draw_fn, *args): + """Generate a single-frame image.""" + img = Image.new("RGBA", (w, h), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + draw_fn(draw, 0, 0, w, h, *args) + save_img(img, path) + + +def gen_icon(path, shape, color=C_ICON, lt_color=C_ICON_LT): + """Generate a 32x32 icon.""" + img = Image.new("RGBA", (32, 32), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + checkerboard(draw, 0, 0, 32, 32, cell=4) + draw.rectangle([0, 0, 31, 31], outline=C_PLACEHOLDER, width=1) + draw_icon_simple(draw, 0, 0, 32, shape, color, lt_color) + save_img(img, path) + + +def gen_tile(path, kind, color=C_TILE, lt_color=C_TILE_LT): + """Generate a 32x32 tile.""" + img = Image.new("RGBA", (32, 32), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + checkerboard(draw, 0, 0, 32, 32, cell=4) + draw.rectangle([0, 0, 31, 31], outline=C_PLACEHOLDER, width=1) + draw_tile(draw, 0, 0, 32, kind, color, lt_color) + save_img(img, path) + + +def gen_effect(path, kind, color=C_FIRE, lt_color=C_FIRE_LT): + """Generate a 48x48 effect.""" + img = Image.new("RGBA", (48, 48), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + checkerboard(draw, 0, 0, 48, 48, cell=4) + draw.rectangle([0, 0, 47, 47], outline=C_PLACEHOLDER, width=1) + draw_effect(draw, 0, 0, 48, kind, color, lt_color) + save_img(img, path) + + +# ── Player ─────────────────────────────────────────────────────────────────── +print("\n=== Player Sprites ===") +P = os.path.join(ASSETS, "player") +gen_spritesheet(f"{P}/player_idle.png", 48, 48, 4, C_PLAYER, C_PLAYER_LT) +gen_spritesheet(f"{P}/player_run.png", 48, 48, 6, C_PLAYER, C_PLAYER_LT) +gen_spritesheet(f"{P}/player_jump.png", 48, 48, 2, C_PLAYER, C_PLAYER_LT) +gen_spritesheet(f"{P}/player_attack.png", 48, 48, 4, C_PLAYER, C_PLAYER_LT) +gen_spritesheet(f"{P}/player_hurt.png", 48, 48, 2, C_PLAYER, C_PLAYER_LT) +gen_spritesheet(f"{P}/player_death.png", 48, 48, 6, C_PLAYER, C_PLAYER_LT) + +# ── Enemy: Ghost Soldier (鬼卒) ────────────────────────────────────────────── +print("\n=== Enemy: Ghost Soldier ===") +E = os.path.join(ASSETS, "enemy") +gen_spritesheet(f"{E}/ghost_melee_idle.png", 48, 48, 4, C_GHOST, C_GHOST_LT) +gen_spritesheet(f"{E}/ghost_melee_run.png", 48, 48, 6, C_GHOST, C_GHOST_LT) +gen_spritesheet(f"{E}/ghost_melee_attack.png", 48, 48, 4, C_GHOST, C_GHOST_LT) +gen_spritesheet(f"{E}/ghost_melee_hurt.png", 48, 48, 2, C_GHOST, C_GHOST_LT) +gen_spritesheet(f"{E}/ghost_melee_death.png", 48, 48, 4, C_GHOST, C_GHOST_LT) + +# ── Enemy: Ghost Archer (鬼弓手) ───────────────────────────────────────────── +print("\n=== Enemy: Ghost Archer ===") +gen_spritesheet(f"{E}/ghost_archer_idle.png", 48, 48, 4, C_ARCHER, C_ARCHER_LT) +gen_spritesheet(f"{E}/ghost_archer_run.png", 48, 48, 6, C_ARCHER, C_ARCHER_LT) +gen_spritesheet(f"{E}/ghost_archer_attack.png", 48, 48, 4, C_ARCHER, C_ARCHER_LT) +gen_spritesheet(f"{E}/ghost_archer_hurt.png", 48, 48, 2, C_ARCHER, C_ARCHER_LT) +gen_spritesheet(f"{E}/ghost_archer_death.png", 48, 48, 4, C_ARCHER, C_ARCHER_LT) + +# ── Enemy: Corpse Beast (尸兽) ─────────────────────────────────────────────── +print("\n=== Enemy: Corpse Beast ===") +gen_spritesheet(f"{E}/corpse_beast_idle.png", 64, 48, 4, C_BEAST, C_BEAST_LT, kind="beast") +gen_spritesheet(f"{E}/corpse_beast_run.png", 64, 48, 6, C_BEAST, C_BEAST_LT, kind="beast") +gen_spritesheet(f"{E}/corpse_beast_attack.png", 64, 48, 4, C_BEAST, C_BEAST_LT, kind="beast") +gen_spritesheet(f"{E}/corpse_beast_hurt.png", 64, 48, 2, C_BEAST, C_BEAST_LT, kind="beast") +gen_spritesheet(f"{E}/corpse_beast_death.png", 64, 48, 4, C_BEAST, C_BEAST_LT, kind="beast") + +# ── Boss: Gate Guardian Ghost General (镇关鬼将) ───────────────────────────── +print("\n=== Boss ===") +B = os.path.join(ASSETS, "boss") +gen_spritesheet(f"{B}/boss_idle.png", 96, 96, 4, C_BOSS, C_BOSS_LT, kind="boss") +gen_spritesheet(f"{B}/boss_run.png", 96, 96, 6, C_BOSS, C_BOSS_LT, kind="boss") +gen_spritesheet(f"{B}/boss_attack.png", 96, 96, 5, C_BOSS, C_BOSS_LT, kind="boss") +gen_spritesheet(f"{B}/boss_hurt.png", 96, 96, 2, C_BOSS, C_BOSS_LT, kind="boss") +gen_spritesheet(f"{B}/boss_death.png", 96, 96, 8, C_BOSS, C_BOSS_LT, kind="boss") + +# ── Weapon Icons ───────────────────────────────────────────────────────────── +print("\n=== Weapon Icons ===") +W = os.path.join(ASSETS, "icons", "weapons") +gen_icon(f"{W}/weapon_songdao.png", "sword") +gen_icon(f"{W}/weapon_spear.png", "spear") +gen_icon(f"{W}/weapon_ghostfire.png", "fire", C_FIRE, C_FIRE_LT) +gen_icon(f"{W}/weapon_talisman.png", "talisman") +gen_icon(f"{W}/weapon_gourd.png", "gourd") +gen_icon(f"{W}/weapon_flag.png", "flag") + +# ── UI Icons ───────────────────────────────────────────────────────────────── +print("\n=== UI Icons ===") +U = os.path.join(ASSETS, "icons", "ui") +gen_icon(f"{U}/icon_hp.png", "heart") +gen_icon(f"{U}/icon_stamina.png", "stamina") +gen_icon(f"{U}/icon_attack.png", "attack") +gen_icon(f"{U}/icon_armor.png", "armor") +gen_icon(f"{U}/icon_crit.png", "crit") +gen_icon(f"{U}/icon_speed.png", "speed") +gen_icon(f"{U}/icon_sacrifice.png", "sacrifice", C_BOSS, C_BOSS_LT) +gen_icon(f"{U}/icon_ghostfire.png", "fire", C_FIRE, C_FIRE_LT) +gen_icon(f"{U}/icon_boss.png", "boss", C_BOSS, C_BOSS_LT) +gen_icon(f"{U}/icon_key.png", "key") +gen_icon(f"{U}/icon_coin.png", "coin") + +# ── Tiles ──────────────────────────────────────────────────────────────────── +print("\n=== Tiles ===") +T = os.path.join(ASSETS, "tiles") +gen_tile(f"{T}/tile_floor.png", "floor") +gen_tile(f"{T}/tile_stone_brick.png", "brick") +gen_tile(f"{T}/tile_wall.png", "wall", C_TILE, C_TILE_LT) +gen_tile(f"{T}/tile_wood_bridge.png", "bridge", C_WOOD, C_WOOD_LT) +gen_tile(f"{T}/tile_brazier.png", "brazier", C_TILE, C_TILE_LT) +gen_tile(f"{T}/tile_tombstone.png", "tombstone", C_TILE, C_TILE_LT) +gen_tile(f"{T}/tile_ground_spike.png", "spike", C_TILE, C_TILE_LT) + +# ── Effects ────────────────────────────────────────────────────────────────── +print("\n=== Effects ===") +F = os.path.join(ASSETS, "effects") +gen_effect(f"{F}/effect_hit_slash.png", "slash", (180, 180, 180), (220, 220, 220)) +gen_effect(f"{F}/effect_blood_splash.png","blood", (120, 20, 20), (160, 40, 40)) +gen_effect(f"{F}/effect_ghost_fire.png", "ghostfire", C_FIRE, C_FIRE_LT) +gen_effect(f"{F}/effect_death_fade.png", "deathfade", (60, 50, 70), (90, 80, 110)) + +# ── Background ─────────────────────────────────────────────────────────────── +print("\n=== Background ===") +BG = os.path.join(ASSETS, "background") +gen_single(f"{BG}/bg_tree.png", 64, 128, draw_background, "tree", C_BG_TREE, C_BG_TREE_LT) +gen_single(f"{BG}/bg_broken_flag.png", 48, 96, draw_background, "flag", (50, 35, 30), (80, 55, 45)) +gen_single(f"{BG}/bg_chains.png", 32, 96, draw_background, "chains", (40, 40, 45), (70, 70, 80)) +gen_single(f"{BG}/bg_deep.png", 320, 180, draw_background, "deep", C_BG_DEEP, (20, 18, 26)) + +# ── UI Elements ────────────────────────────────────────────────────────────── +print("\n=== UI Elements ===") +UI = os.path.join(ASSETS, "ui") +gen_single(f"{UI}/ui_health_bar.png", 96, 16, draw_ui, "bar", (120, 20, 20), (160, 40, 40)) +gen_single(f"{UI}/ui_stamina_bar.png", 96, 16, draw_ui, "bar", (20, 100, 60), (40, 140, 80)) +gen_single(f"{UI}/ui_frame.png", 32, 32, draw_ui, "frame", C_UI, C_UI_LT) +gen_single(f"{UI}/ui_minimap_frame.png", 64, 64, draw_ui, "frame", C_UI, C_UI_LT) + +print("\n✅ All placeholder images generated.") +print(f" Total files: {sum(1 for _ in os.walk(ASSETS) for __ in _[2])}")