diff --git a/design/art/asset-path-map.md b/design/art/asset-path-map.md new file mode 100644 index 00000000..d6c6c342 --- /dev/null +++ b/design/art/asset-path-map.md @@ -0,0 +1,35 @@ +# 素材路径分工约定(asset-path-map) + +- 定立:2026-09-26(用户立约指令「把哪些路径、哪些位置是干什么的确定好」——台账 161) +- 效力:全仓素材批的路径判据单点;新素材批开工前对照,违例=实施缺陷 + +## 1. 目录分工总表 + +| 路径 | 角色 | 入 git | 游戏运行时引用 | +|---|---|---|---| +| `design/art/candidates/**` | 设计侧源档(母版 PNG/.pxo/GIF/对比图/验收留底)——**素材的原产地与档案库,游戏禁直接引用** | 否(`.gitignore` 三行规则;`*.md` 指引例外) | **禁**(res:// 路径禁指 candidates) | +| `game/assets/art/**` | **游戏运行时素材唯一落地处**(res://assets/art/...)——定稿素材从 candidates 拷贝入此,改名按圣经 §8.3 | 否(untracked,勿 git add;worktree 间需脚本拷贝同步) | **唯一合法引用源** | +| `game/assets/ui/icons/**` | UI 图标(状态图标批特例——唯一已提交入 git 的素材目录) | **是**(2026-09-24 批特例) | 是 | +| `production/mockups/**` | 混合样张/版式对比图(设计评审产物,非游戏素材) | 否 | 禁 | + +## 2. 素材流(新素材批标准路径) + +1. **产出/候选**:candidates 对应子目录(如 `hero-idle/`) +2. **验收定稿**:用户拍板(候选目录留档不动) +3. **入包**:拷贝 → `game/assets/art/<语义子目录>/`,改名 `§8.3` 域键式(如 `character_hero_portrait.png`) +4. **接线**:代码 `res://assets/art/...` 引用(路径常量单点归 `UiKit.TEX_*`);装载一律 `UiKit.load_tex`(exists 守卫——素材不入 git,缺位树回退),**禁 preload** +5. **提交**:只提交代码/文档;PNG 与 `.import`(全局忽略)均不入 git + +## 3. game/assets/art 语义子目录 + +| 子目录 | 内容 | 现役 | +|---|---|---| +| `character/` | 角色棋盘帧 + B 区立绘 | character_hero_idle/walk(棋盘 32×32 朝左基准)、character_hero_portrait(B 区立绘 48×48) | +| `fx/` | 特效帧 | shovel_swing_f0/f1/f2(挥铲三帧,握把锚 (18,12)) | +| `board/` | 砖面/墙/地板(beast-cave 复用包,地砖复用批) | board_floor/wall/pillar_dungeon_* | + +## 4. 坐标/朝向等工程侧约定备查 + +- 素材基准朝向:角色/铲帧一律朝**左**入库,右向=运行时 `flip_h`(镜像帧不入包,right 变体留 candidates) +- 挥铲握把锚:画布 (18,12)(三帧同位——以手为圆心旋转的预烤相位) +- 缩放纪律:圣经 §8.2 整数缩放为一般规则;棋盘小人 1.5× 系 2026-09-26 用户明示裁决(豁免注记归 art-bible 批另落) diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index 1b4dda3b..34c30961 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -57,14 +57,17 @@ const DEPTH_BEVEL := 0.6 # 顶面倒角强度 # 英雄单位演出参数(棋盘小人贴图批 2026-09-25——计划书 §3.B/C 时长档) const HERO_FRAME_PX := 32.0 # 英雄帧画布边长(素材 32×32 锁定——缩放比分母) -const HERO_SPRITE_SCALE := 1.0 # 整数 1× 档、32×32 盒格内居中(2026-09-25 用户裁决:不要求覆盖 - # 格子——圣经 §8.2「禁非整数缩放」硬规则,v1.9 注记 45px 格 1× 占 - # ≈71% 合规;格边 <32 板溢出 ≤(32−cell)px 接受;计划书原 cell_edge/32 - # 填充公式作废) +const HERO_SPRITE_SCALE := 1.5 # 1.5× 档(2026-09-26 用户二裁:实机看 1× 偏小放大 1.5 倍——明示 + # 裁决压圣经 §8.2「禁非整数缩放」一般规则,§8.2 豁免注记归 art-bible + # 批另落;头可越格上沿不裁剪) +const HERO_GROUND_FRAC := 0.75 # 脚锚纵向落点 = 格高 75%(自格顶起;即「脚在格子 1/4 处」自底边量—— + # 2026-09-26 用户裁决:不完全居中、脚踩 1/4 线,头越上格沿造立体感) const HERO_MOVE_S := 0.14 # 位移时长(~140ms) const HERO_STEP_S := 0.18 # 走路帧周期 = 原地撞墙帧闪时长(~180ms) const HERO_DIG_S := 0.15 # 挖掘弹跳总时长(~150ms,半程压半程回) const HERO_BOUNCE := 0.82 # 弹跳压缩比(scale.y 1→0.82→1 脚底收敛) +const HERO_SHADOW_PX := Vector2(32, 12) # 脚下椭圆影屏幕尺寸(Godot 组件合成——径向渐变纹理压扁, + # 贴图零改动,2026-09-26 用户裁决走引擎侧) var _wrap: CenterContainer var _svp_container: SubViewportContainer @@ -254,7 +257,7 @@ func _update_hero(hero: Vector2i) -> void: if _hero_tex_idle == null: return if _world.hero == null: - var sp: Sprite2D = _world.ensure_hero(_hero_tex_idle) + var sp: Sprite2D = _world.ensure_hero(_hero_tex_idle, HERO_SHADOW_PX, HERO_SPRITE_SCALE) sp.scale = Vector2.ONE * HERO_SPRITE_SCALE sp.position = _hero_feet(hero) _hero_last = hero # 首建/随 rebuild 重建——即时落位无演出 @@ -339,18 +342,21 @@ func cell_origin(c: Vector2i) -> Vector2: return _svp_container.position + Vector2(c) * (_cell + float(SEP)) -# 英雄 32×32 盒格内居中的脚底锚点——**世界坐标**(精灵在 SubViewport 世界内:格原点 = 格×步距, -# 禁带壳层居中偏移。2026-09-26 修正:上批误用壳层 cell_origin(含 _svp_container.position 居中 -# 偏移)定位世界内精灵,实测整体右偏 ~7 格+偏离格心——实机截图用户报缺陷) +# 英雄脚底锚点——**世界坐标**(精灵在 SubViewport 世界内:格原点 = 格×步距,禁带壳层居中偏移。 +# 2026-09-26 修正:上批误用壳层 cell_origin(含 _svp_container.position 居中偏移)定位世界内精灵, +# 实测整体右偏 ~7 格+偏离格心——实机截图用户报缺陷)。 +# 纵向 = 格高 75% 线(脚踩格子 1/4 处·自底边量——2026-09-26 用户二裁);横向格中线。身体向上延伸, +# 头越上格沿不裁剪(立体感裁决);锚点与精灵缩放解耦(缩放只改身体向上长多少)。 func _hero_feet(c: Vector2i) -> Vector2: - var m := (_cell - HERO_FRAME_PX) * 0.5 - return Vector2(c) * (_cell + float(SEP)) + Vector2(m + HERO_FRAME_PX * 0.5, m + HERO_FRAME_PX) + return Vector2(c) * (_cell + float(SEP)) + Vector2(_cell * 0.5, _cell * HERO_GROUND_FRAC) # 英雄持握手位(fx 同层坐标系 = 壳层坐标——fx 在 SubViewport 之外,须含 _svp_container.position -# 居中偏移:世界脚底锚 + 壳层偏移即得)。手 = 盒底往上 1/4 盒高、水平居中(2026-09-25 用户裁决) +# 居中偏移:世界脚底锚 + 壳层偏移即得)。手 = 盒底往上 1/4 盒高、水平居中(2026-09-25 用户裁决; +# 盒高随 HERO_SPRITE_SCALE——手位钉在身体上不随缩放漂移) func hero_hand_point() -> Vector2: - return _svp_container.position + _hero_feet(_hero_last) + Vector2(0.0, -HERO_FRAME_PX * 0.25) + return _svp_container.position + _hero_feet(_hero_last) \ + + Vector2(0.0, -HERO_FRAME_PX * 0.25 * HERO_SPRITE_SCALE) # 英雄精灵缩放档(fx 挥铲同档出口——两演出比例恒一致) diff --git a/game/presentation/views/board_world.gd b/game/presentation/views/board_world.gd index ac077d8f..fbdcff47 100644 --- a/game/presentation/views/board_world.gd +++ b/game/presentation/views/board_world.gd @@ -18,17 +18,42 @@ var hero: Sprite2D = null # 英雄单位精灵(棋盘小人贴图批 2026- # 英雄精灵惰性创建(幂等):脚底锚(offset 上移半高——scale.y 弹跳向脚底收敛不悬空)+ z_index=1 # 压格面之上(挥铲特效在 SubViewport 之外恒更上层,满足「格面之上、特效之下」叠序);NEAREST = -# SubViewport 缩放下像素清晰(默认 Linear 会糊)。返回值便于 BoardView 即时落位。 -func ensure_hero(tex: Texture2D) -> Sprite2D: +# SubViewport 缩放下像素清晰(默认 Linear 会糊);use_parent_material = 随世界去饱和材质灰化 +#(结算期与 CellUnit 同口径——基批漏挂,本批补齐)。返回值便于 BoardView 即时落位。 +# 脚下椭圆影(2026-09-26 用户裁决走 Godot 组件合成、贴图零改动):径向渐变纹理 ×黑色半透明 +# modulate 压扁,挂英雄子件(z_index=-1 相对回落格面之上、英雄之下——位移/翻转/弹跳随父件 +# 同步免费拿;尺寸换算归 BoardView.HERO_SHADOW_PX 常量)。纹理 filter 用 LINEAR 保渐变平滑 +#(NEAREST 会把渐变打回条带)。 +func ensure_hero(tex: Texture2D, shadow_px: Vector2, scale: float) -> Sprite2D: if hero != null: return hero + var grad := Gradient.new() + grad.set_color(0, Color(1, 1, 1, 1)) + grad.set_color(1, Color(1, 1, 1, 0)) + var shadow_tex := GradientTexture2D.new() + shadow_tex.gradient = grad + shadow_tex.width = 64 + shadow_tex.height = 64 + shadow_tex.fill = GradientTexture2D.FILL_RADIAL + shadow_tex.fill_from = Vector2(0.5, 0.5) + shadow_tex.fill_to = Vector2(0.5, 0.0) + var shadow := Sprite2D.new() + shadow.texture = shadow_tex + shadow.modulate = Color(0, 0, 0, 0.35) + shadow.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR + shadow.scale = shadow_px / (64.0 * scale) # 子件缩放叠父件 scale——目标屏幕尺寸 ÷ (纹理边 × 父档) + shadow.position = Vector2(0.0, 2.0) # 脚线下 2px——贴地接触感 + shadow.z_index = -1 # 相对父件回落:格面(z=0 树序在先)之上、英雄本体之下 + shadow.use_parent_material = true # 随英雄链世界去饱和材质 hero = Sprite2D.new() hero.texture = tex hero.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST hero.centered = true hero.offset = Vector2(0.0, -16.0) # 纹理中点上移半高——position = 脚底中点 hero.z_index = 1 + hero.use_parent_material = true add_child(hero) + hero.add_child(shadow) return hero # ── 立体悬崖(BoardView.set_cell_area / _update_world_size 单点驱动;厚度定稿 8px=中档 2026-09-24)── diff --git a/game/smoke/ui_input_probe.gd b/game/smoke/ui_input_probe.gd index 790a74aa..eb26fa97 100644 --- a/game/smoke/ui_input_probe.gd +++ b/game/smoke/ui_input_probe.gd @@ -122,15 +122,14 @@ func _ready() -> void: if not ms.is_revealed(dig_t): _problems.append("case⑥ 核心揭格未落位(目标=%s)" % dig_t) # ---- case⑦ 英雄精灵世界落位(棋盘小人贴图批回归闸 2026-09-26):精灵在 SubViewport 世界内, - # 落位 = 格×步距 + 居中脚底锚(禁带壳层 _svp_container.position 居中偏移——上批误用壳层 - # cell_origin 实测右偏 ~7 格+偏离格心,实机截图用户报缺陷)。素材缺位树(hero==null, - # 字形「人」回退在役)跳过;断言前等 0.3s 墙钟——headless 帧率无上限,帧数等待 < 位移 - # Tween 140ms 时会采到半途位置(首跑实证),必须按墙钟等到位。 ---- + # 落位 = 格×步距 + 脚底锚(横=格中线/纵=格高 75% 线——2026-09-26 二裁「脚踩格子 1/4 处」; + # 禁带壳层 _svp_container.position 居中偏移——上批误用壳层 cell_origin 实测右偏 ~7 格+偏离 + # 格心,实机截图用户报缺陷)。素材缺位树(hero==null,字形「人」回退在役)跳过;断言前等 + # 0.3s 墙钟——headless 帧率无上限,帧数等待 < 位移 Tween 140ms 时会采到半途位置(首跑实证)。 ---- if bv._world.hero != null: await get_tree().create_timer(0.3).timeout - var hm: float = (bv.cell_size - bv.HERO_FRAME_PX) * 0.5 var expect: Vector2 = Vector2(ms.hero_pos()) * (bv.cell_size + float(bv.SEP)) \ - + Vector2(hm + bv.HERO_FRAME_PX * 0.5, hm + bv.HERO_FRAME_PX) + + Vector2(bv.cell_size * 0.5, bv.cell_size * bv.HERO_GROUND_FRAC) if bv._world.hero.position.distance_to(expect) > 0.5: _problems.append("case⑦ 英雄精灵世界落位偏移 pos=%s expect=%s" % [str(bv._world.hero.position), str(expect)]) if _problems.is_empty():