Compare commits

...

2 Commits

Author SHA1 Message Date
wy bc300f4ff9 Improve map editor image previews 2026-06-10 10:17:25 +08:00
wy 7d0f115fcb Integrate mossy cavern stage scene 2026-06-10 02:21:44 +08:00
16 changed files with 176 additions and 43 deletions
+28 -28
View File
@@ -12,47 +12,47 @@ collision 1760 500 300 28
collision 2180 455 260 28 collision 2180 455 260 28
collision 2650 515 360 28 collision 2650 515 360 28
tileset graybox_ground assets/stage/stage_01/tiles/graybox_ground.png 10 10 0.62 0.68 0.73 1.0
tileset graybox_platform assets/stage/stage_01/tiles/graybox_platform.png 10 14 0.62 0.68 0.73 1.0
layer background_far BackgroundFar 0.25 layer background_far BackgroundFar 0.25
rect 0 0 3200 720 0.08 0.10 0.14 1.0 rect 0 0 3200 720 0.04 0.06 0.05 1.0
rect 360 120 420 220 0.13 0.16 0.21 1.0 rect_sprite_src -140 80 980 620 0.34 0.54 0.36 0.38 assets/stage/stage_01/background/mossy_background_decoration.png 0 0 1030 4096
rect 1380 90 560 260 0.12 0.15 0.20 1.0 rect_sprite_src 860 35 940 620 0.30 0.50 0.34 0.34 assets/stage/stage_01/background/mossy_background_decoration.png 1180 0 900 3500
rect 2440 140 520 210 0.13 0.16 0.21 1.0 rect_sprite_src 2020 100 980 560 0.31 0.52 0.34 0.34 assets/stage/stage_01/background/mossy_background_decoration.png 2680 170 1060 1520
rect_sprite_src 2660 410 1060 360 0.26 0.44 0.30 0.30 assets/stage/stage_01/background/mossy_background_decoration.png 2460 2920 1180 880
endlayer endlayer
layer background_mid BackgroundMid 0.55 layer background_mid BackgroundMid 0.55
rect 80 360 280 240 0.18 0.22 0.27 1.0 rect_sprite_src -180 390 560 260 0.45 0.70 0.45 0.42 assets/stage/stage_01/background/mossy_hills.png 50 580 420 420
rect 1020 310 340 290 0.18 0.22 0.27 1.0 rect_sprite_src 520 335 760 300 0.46 0.72 0.46 0.36 assets/stage/stage_01/background/mossy_hills.png 720 40 1160 500
rect 2020 330 360 270 0.18 0.22 0.27 1.0 rect_sprite_src 1390 360 720 270 0.42 0.68 0.43 0.36 assets/stage/stage_01/background/mossy_hills.png 760 650 1120 720
rect 2860 280 260 320 0.18 0.22 0.27 1.0 rect_sprite_src 2310 330 760 310 0.40 0.66 0.42 0.36 assets/stage/stage_01/background/mossy_hills.png 645 650 1280 840
rect_sprite_src 2860 400 560 240 0.43 0.68 0.44 0.34 assets/stage/stage_01/background/mossy_hills.png 1460 1520 520 420
endlayer endlayer
layer level_visual LevelVisual 1.0 layer level_visual LevelVisual 1.0
tile_rect graybox_ground 0 600 320 8 0 0.62 0.68 0.73 rect_sprite_src -70 520 1680 270 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 420 1120 1560 490
tile_rect graybox_platform 230 500 22 2 0 0.62 0.68 0.73 rect_sprite_src 1520 520 1680 270 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 420 1120 1560 490
tile_rect graybox_platform 560 430 26 2 0 0.62 0.68 0.73 rect_sprite_src 230 460 240 100 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 500 40 1050 420
tile_rect graybox_platform 930 510 26 2 0 0.62 0.68 0.73 rect_sprite_src 540 390 300 105 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 500 540 1060 430
tile_rect graybox_platform 1250 420 23 2 0 0.62 0.68 0.73 rect_sprite_src 910 470 310 105 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 500 40 1050 420
tile_rect graybox_platform 1760 500 30 2 0 0.62 0.68 0.73 rect_sprite_src 1230 380 270 105 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 500 540 1060 430
tile_rect graybox_platform 2180 455 26 2 0 0.62 0.68 0.73 rect_sprite_src 1740 460 340 105 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 500 40 1050 420
tile_rect graybox_platform 2650 515 36 2 0 0.62 0.68 0.73 rect_sprite_src 2160 415 300 105 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 500 540 1060 430
rect_sprite_src 2625 475 400 120 1.0 1.0 1.0 1.0 assets/stage/stage_01/tiles/mossy_floating_platforms.png 420 1120 1560 490
endlayer endlayer
layer props_back PropsBack 1.0 layer props_back PropsBack 1.0
rect 118 392 88 208 0.26 0.30 0.34 1.0 rect_sprite_src 110 120 150 500 0.78 0.94 0.72 0.72 assets/stage/stage_01/props/mossy_hanging_plants.png 70 0 360 1240
rect 780 250 72 180 0.26 0.30 0.34 1.0 rect_sprite_src 780 80 130 430 0.78 0.94 0.72 0.62 assets/stage/stage_01/props/mossy_hanging_plants.png 550 0 330 1180
rect 1440 300 80 300 0.26 0.30 0.34 1.0 rect_sprite_src 1430 115 150 520 0.78 0.94 0.72 0.68 assets/stage/stage_01/props/mossy_hanging_plants.png 990 0 360 1300
rect 1880 360 110 240 0.26 0.30 0.34 1.0 rect_sprite_src 1880 155 190 420 0.78 0.94 0.72 0.62 assets/stage/stage_01/props/mossy_hanging_plants.png 1340 0 470 1040
rect 2520 390 92 210 0.26 0.30 0.34 1.0 rect_sprite_src 2510 160 170 450 0.78 0.94 0.72 0.64 assets/stage/stage_01/props/mossy_hanging_plants.png 540 1260 420 1040
rect 3040 315 82 285 0.26 0.30 0.34 1.0 rect_sprite_src 3030 110 160 500 0.78 0.94 0.72 0.66 assets/stage/stage_01/props/mossy_hanging_plants.png 1020 1320 420 1120
endlayer endlayer
layer props_front PropsFront 1.0 layer props_front PropsFront 1.0
rect 520 580 220 34 0.14 0.17 0.18 0.90 rect_sprite_src 510 560 260 80 0.85 1.0 0.82 0.78 assets/stage/stage_01/tiles/mossy_floating_platforms.png 0 540 430 430
rect 1510 570 280 42 0.14 0.17 0.18 0.90 rect_sprite_src 1490 545 330 95 0.85 1.0 0.82 0.78 assets/stage/stage_01/tiles/mossy_floating_platforms.png 0 540 430 430
rect 2580 572 260 38 0.14 0.17 0.18 0.90 rect_sprite_src 2560 550 330 90 0.85 1.0 0.82 0.78 assets/stage/stage_01/tiles/mossy_floating_platforms.png 0 540 430 430
endlayer endlayer
battle_zone zone_01_intro 520 260 560 340 0 0 1940 720 battle_zone zone_01_intro 520 260 560 340 0 0 1940 720
+7 -4
View File
@@ -6,12 +6,14 @@ This directory is reserved for the first playable side-scrolling ACT stage.
| Slot | Expected file | Notes | Source / License | | Slot | Expected file | Notes | Source / License |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Far background | `background/background_far.png` | Large or horizontally tileable image. | TBD | | Far background | `background/mossy_background_decoration.png` | Mossy cave silhouettes used by `BackgroundFar` layer. | Local asset pack: `X:\NS_unknown_game\asset\Mossy Cavern`; license TBD |
| Mid background | `background/background_mid.png` | Parallax midground image or prop sheet. | TBD | | Mid background | `background/mossy_hills.png` | Mossy hill silhouettes used by `BackgroundMid` layer. | Local asset pack: `X:\NS_unknown_game\asset\Mossy Cavern`; license TBD |
| Tileset | `tiles/stage_01_tileset.png` | Ground, platform, wall, edge tiles. | TBD | | Tileset | `tiles/mossy_tileset.png` | Full Mossy tileset atlas, reserved for later tile-id based editing. | Local asset pack: `X:\NS_unknown_game\asset\Mossy Cavern`; license TBD |
| Floating platforms | `tiles/mossy_floating_platforms.png` | Current `LevelVisual` ground and platform sprites in `stage_01.map`. | Local asset pack: `X:\NS_unknown_game\asset\Mossy Cavern`; license TBD |
| Graybox ground tiles | `tiles/graybox_ground.png` | Optional temporary tileset referenced by `stage_01.map`; fallback color is used if missing. | TBD | | Graybox ground tiles | `tiles/graybox_ground.png` | Optional temporary tileset referenced by `stage_01.map`; fallback color is used if missing. | TBD |
| Graybox platform tiles | `tiles/graybox_platform.png` | Optional temporary platform tileset referenced by `stage_01.map`; fallback color is used if missing. | TBD | | Graybox platform tiles | `tiles/graybox_platform.png` | Optional temporary platform tileset referenced by `stage_01.map`; fallback color is used if missing. | TBD |
| Props sheet | `props/stage_01_props.png` | Crates, signs, lamps, blockers, exits. | TBD | | Props sheet | `props/mossy_hanging_plants.png` | Current hanging plant props used by `PropsBack` layer. | Local asset pack: `X:\NS_unknown_game\asset\Mossy Cavern`; license TBD |
| Decoration / hazards sheet | `props/mossy_decorations_hazards.png` | Reserved for later foreground props and hazards; not used for gameplay yet. | Local asset pack: `X:\NS_unknown_game\asset\Mossy Cavern`; license TBD |
| Hit spark | `effects/hit_spark.png` | Small combat impact effect. | TBD | | Hit spark | `effects/hit_spark.png` | Small combat impact effect. | TBD |
| Dust | `effects/dust.png` | Run, jump, and landing dust. | TBD | | Dust | `effects/dust.png` | Run, jump, and landing dust. | TBD |
| Music / ambience | `../../music/stage_01.ogg` | Optional loopable stage audio cue. | TBD | | Music / ambience | `../../music/stage_01.ogg` | Optional loopable stage audio cue. | TBD |
@@ -23,3 +25,4 @@ This directory is reserved for the first playable side-scrolling ACT stage.
- Prefer PNG for visual assets. - Prefer PNG for visual assets.
- Record the original download URL, author, license, and any attribution text before using an asset. - Record the original download URL, author, license, and any attribution text before using an asset.
- Keep collision data separate from visual tiles; visual alignment should stay within 4 pixels of collision. - Keep collision data separate from visual tiles; visual alignment should stay within 4 pixels of collision.
- Current Mossy Cavern integration only covers map scene visuals. Character animations, slimes, plant animations, hazards, and combat use are intentionally deferred.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

+9 -2
View File
@@ -72,11 +72,18 @@ endbattle_zone
- `tile_index` 当前表示 tileset 第一行的横向 tile 序号。 - `tile_index` 当前表示 tileset 第一行的横向 tile 序号。
- `r g b` 是该批 tile 的灰盒 fallback 色,alpha 固定为 `1.0` - `r g b` 是该批 tile 的灰盒 fallback 色,alpha 固定为 `1.0`
当前 `stage_01.map``level_visual` 地面和平台从长色块替换为 `tile_rect`,碰撞仍由 `collision` 独立定义。 当前 `stage_01.map`接入 Mossy Cavern 场景图集:
- `BackgroundFar` 使用 `mossy_background_decoration.png` 做远景洞穴轮廓。
- `BackgroundMid` 使用 `mossy_hills.png` 做中景苔藓轮廓。
- `LevelVisual` 使用 `mossy_floating_platforms.png` 做地面和悬空平台。
- `PropsBack` / `PropsFront` 使用苔藓悬挂植物和平台边缘装饰。
本轮只接入地图场景视觉,碰撞仍由 `collision` 独立定义;角色、怪物、植物动画和 hazard 玩法暂不接入。
## 下一步 ## 下一步
- 下一阶段开始正式做地图场景,优先建设简易 `.map` 编辑器,详见 `map_editor.md` - 继续用地图编辑器调整 Mossy 场景视觉块和碰撞对齐
-`tile_rect` 扩展成可表达 tile row/column 或 tile id。 -`tile_rect` 扩展成可表达 tile row/column 或 tile id。
- 增加空 layer、battle zone 无 spawn、tile texture 不存在等更严格错误日志。 - 增加空 layer、battle zone 无 spawn、tile texture 不存在等更严格错误日志。
- 保留 `whitebox_level.cpp` 作为 fallback,不再把主地图写死在 C++。 - 保留 `whitebox_level.cpp` 作为 fallback,不再把主地图写死在 C++。
+9
View File
@@ -104,6 +104,8 @@ http://127.0.0.1:8787/tools/map_editor/
- 支持选择图层或图层内对象后,对对应图层上移/下移;预览和导出都按当前 `.map` 图层顺序处理。 - 支持选择图层或图层内对象后,对对应图层上移/下移;预览和导出都按当前 `.map` 图层顺序处理。
- 通过“导入图形”创建 `rect_sprite`,并自动追加一个同坐标、同尺寸的 `collision` - 通过“导入图形”创建 `rect_sprite`,并自动追加一个同坐标、同尺寸的 `collision`
- 导入图形在当前浏览器会话内支持图片预览;图形移动和缩放时,同步更新绑定碰撞块。 - 导入图形在当前浏览器会话内支持图片预览;图形移动和缩放时,同步更新绑定碰撞块。
- 从仓库根目录启动静态服务时,`.map``assets/...` 贴图路径会自动映射到 `game/assets/...` 并加载项目内已有图片预览。
- `rect_sprite_src` 图形支持在属性面板编辑源图裁切区域。
- 重新打开 `.map` 时,如果 `rect_sprite` 与某个 `collision` 坐标尺寸一致,编辑器会自动恢复二者的编辑绑定。 - 重新打开 `.map` 时,如果 `rect_sprite` 与某个 `collision` 坐标尺寸一致,编辑器会自动恢复二者的编辑绑定。
- 导出兼容 `StageMapLoader``.map` 文本。 - 导出兼容 `StageMapLoader``.map` 文本。
@@ -129,6 +131,13 @@ assets/stage/stage_01/props/<图片文件名>
实际运行游戏前,需要把对应图片素材放到该目录。编辑器内的图形-碰撞绑定是工具层能力,导出的 `.map` 仍保持运行时兼容格式,不额外写入私有字段。 实际运行游戏前,需要把对应图片素材放到该目录。编辑器内的图形-碰撞绑定是工具层能力,导出的 `.map` 仍保持运行时兼容格式,不额外写入私有字段。
项目内已有素材预览规则:
- `assets/...` 会映射到仓库内 `game/assets/...`
- `game/assets/...` 会直接映射到仓库同名路径。
- 如果文件存在,编辑器重新打开 `.map` 后也会自动预览。
- 如果文件不存在,编辑器使用颜色块 fallback。
## 首版验收 ## 首版验收
完成时应达到: 完成时应达到:
+15 -1
View File
@@ -34,6 +34,20 @@ UI
- 支持 `.map` tile entry 绘制。 - 支持 `.map` tile entry 绘制。
- 支持 tileset 图片缺失时按 tile fallback 色绘制。 - 支持 tileset 图片缺失时按 tile fallback 色绘制。
- 资源缺失时先检查 `Asset::exists()`,再回退色块,不触发纹理加载错误日志。 - 资源缺失时先检查 `Asset::exists()`,再回退色块,不触发纹理加载错误日志。
- `LevelVisual` 里的贴图矩形不再叠加灰盒顶边辅助线;只有纯色占位块会显示该辅助线。
## 当前素材接入
第一关已经通过 `rect_sprite_src` 接入 Mossy Cavern 场景图集:
- `background/mossy_background_decoration.png`
- `background/mossy_hills.png`
- `tiles/mossy_floating_platforms.png`
- `props/mossy_hanging_plants.png`
- `props/mossy_decorations_hazards.png`
- `tiles/mossy_tileset.png`
由于当前 `tile_rect` 只能取 tileset 第一行,正式 Mossy 场景暂时优先使用 `rect_sprite_src` 从大图集裁切。`mossy_tileset.png` 已落位,等后续扩展 tile row/column 或 tile id 后再用于精细铺砖。
## 约束 ## 约束
@@ -44,5 +58,5 @@ UI
## 下一步 ## 下一步
- 给 tile entry 增加 tile id 或二维 source index。 - 给 tile entry 增加 tile id 或二维 source index。
- 把正式 tileset 素材接入 `stage_01.map` - 用地图编辑器继续调整 Mossy 场景块、前后景装饰和碰撞对齐
- 如果引擎提供统一 Texture cache,再迁移当前局部贴图缓存。 - 如果引擎提供统一 Texture cache,再迁移当前局部贴图缓存。
+11
View File
@@ -319,3 +319,14 @@ game/assets/stage/stage_01/
3. 把灰盒 `StageLayer` 的色块替换成 sprite/tile 条目。 3. 把灰盒 `StageLayer` 的色块替换成 sprite/tile 条目。
4. 对齐视觉块和碰撞块,误差控制在 4 像素以内。 4. 对齐视觉块和碰撞块,误差控制在 4 像素以内。
5. 更新素材来源和授权记录。 5. 更新素材来源和授权记录。
### Mossy Cavern 场景接入
当前第一关已经先接入 `X:\NS_unknown_game\asset\Mossy Cavern` 的场景图集,范围只包含地图视觉:
1. 已完成:把 Mossy 背景、平台、悬挂植物和装饰图集复制到 `game/assets/stage/stage_01/`,并统一为小写下划线路径。
2. 已完成:`stage_01.map``BackgroundFar``BackgroundMid``LevelVisual``PropsBack``PropsFront` 已改用 `rect_sprite_src` 裁切 Mossy 图集。
3. 已完成:碰撞、玩家出生点、camera bounds、battle zone 和 spawn point 保持原数据,不把视觉块当碰撞使用。
4. 已完成:贴图 `LevelVisual` 不再叠加灰盒辅助顶线,避免平台素材显示异常。
暂不接入 `BlueWizard Animations``Plant Animations``Slimes` 和 hazard 玩法。下一步围绕地图编辑器继续微调场景块位置和碰撞对齐。
+1 -1
View File
@@ -107,7 +107,7 @@ void StageRenderer::drawLayer(const LevelDefinition& level,
rect.bounds.top() + parallaxOffset.y, rect.bounds.top() + parallaxOffset.y,
rect.bounds.width(), rect.bounds.height()); rect.bounds.width(), rect.bounds.height());
drawStageRect(rect, bounds); drawStageRect(rect, bounds);
if (layer.role == StageLayerRole::LevelVisual) { if (layer.role == StageLayerRole::LevelVisual && !rect.source.hasTexture()) {
drawRect(frostbite2D::Rect(bounds.left(), bounds.top(), bounds.width(), drawRect(frostbite2D::Rect(bounds.left(), bounds.top(), bounds.width(),
4.0f), 4.0f),
frostbite2D::Color(0.91f, 0.95f, 0.98f, 1.0f)); frostbite2D::Color(0.91f, 0.95f, 0.98f, 1.0f));
+6 -1
View File
@@ -133,6 +133,10 @@ assets/stage/stage_01/props/<图片文件名>
静态网页不能自动复制图片到项目目录。实际运行游戏前,需要手动把对应图片素材放到该路径。 静态网页不能自动复制图片到项目目录。实际运行游戏前,需要手动把对应图片素材放到该路径。
如果从仓库根目录启动静态服务,编辑器会把 `.map` 里的 `assets/...` 贴图路径映射到 `game/assets/...` 并自动加载预览。也就是说,项目内已经存在的 Mossy Cavern 等 PNG 会在重新打开地图后直接显示;只有文件没有放到对应目录时才会回退为颜色块。
`rect_sprite_src` 图形会在属性面板显示 `源 X``源 Y``源 W``源 H`,可以直接调整大图集的裁切区域。普通 `rect_sprite` 仍按整张图绘制,不会因为打开属性面板而自动变成 `rect_sprite_src`
### 调整图层顺序 ### 调整图层顺序
1. 在对象列表中选择一个图层,或选择该图层下的图形/铺砖区。 1. 在对象列表中选择一个图层,或选择该图层下的图形/铺砖区。
@@ -205,7 +209,8 @@ spawn
- 不能直接覆盖本地源文件,只能下载 `.map` - 不能直接覆盖本地源文件,只能下载 `.map`
- 导入图片的真实文件仍需手动放入项目资源目录。 - 导入图片的真实文件仍需手动放入项目资源目录。
- 已导入图片的浏览器预览只在当前会话有效;重新打开 `.map` 后,如果没有重新选择图片,会显示 fallback 色块 - `.map` 中存在于 `game/assets/...``assets/...` 贴图路径会自动预览
- 如果导入图片后没有把真实文件放到 `game/assets/...` 对应路径,重新打开 `.map` 后会显示 fallback 色块。
- 暂未实现重做。 - 暂未实现重做。
- 暂未实现多选和批量移动。 - 暂未实现多选和批量移动。
- 暂未实现 tile palette 和 tileset 图片预览。 - 暂未实现 tile palette 和 tileset 图片预览。
+6 -2
View File
@@ -33,7 +33,9 @@ http://127.0.0.1:8787/tools/map_editor/
- 通过“编辑目标”过滤画布选择对象,默认“智能选择”把碰撞块当作上层覆盖;同位置对象第一次点选碰撞块,再点一次切到下层铺砖区或图形。 - 通过“编辑目标”过滤画布选择对象,默认“智能选择”把碰撞块当作上层覆盖;同位置对象第一次点选碰撞块,再点一次切到下层铺砖区或图形。
- 选中矩形对象后,拖动右下角蓝色手柄可以调整宽高。 - 选中矩形对象后,拖动右下角蓝色手柄可以调整宽高。
- 通过“导入图形”选择图片,自动生成一个 `rect_sprite` 图形对象和一个同尺寸 `collision` - 通过“导入图形”选择图片,自动生成一个 `rect_sprite` 图形对象和一个同尺寸 `collision`
- 导入的图形会在浏览器内临时预览,拖动或缩放图形时,同步移动或缩放绑定的碰撞块 - 从仓库根目录启动静态服务时,`.map``assets/...` 贴图路径会自动映射到 `game/assets/...` 并显示预览
- 导入的图形会在浏览器内预览,拖动或缩放图形时,同步移动或缩放绑定的碰撞块。
- 已有 `rect_sprite_src` 图形可以在属性面板调整源图裁切区域。
- 编辑选中对象的数值和文本属性。 - 编辑选中对象的数值和文本属性。
- 新增/删除图块集、图层、碰撞块、铺砖区、战斗区和出生点。 - 新增/删除图块集、图层、碰撞块、铺砖区、战斗区和出生点。
- 支持撤回最近编辑操作,也可以使用 `Ctrl+Z` - 支持撤回最近编辑操作,也可以使用 `Ctrl+Z`
@@ -71,6 +73,7 @@ http://127.0.0.1:8787/tools/map_editor/
4. 拖动图形本体可以移动图形和绑定碰撞块。 4. 拖动图形本体可以移动图形和绑定碰撞块。
5. 拖动右下角蓝色手柄可以调整图形和绑定碰撞块尺寸。 5. 拖动右下角蓝色手柄可以调整图形和绑定碰撞块尺寸。
6. 也可以在右侧属性面板修改 `X``Y``W``H`、颜色和贴图路径。 6. 也可以在右侧属性面板修改 `X``Y``W``H`、颜色和贴图路径。
7. 如果图形来自 `rect_sprite_src`,属性面板还会显示 `源 X``源 Y``源 W``源 H`,用于调整图集裁切区域。
静态网页不能自动把图片复制进项目目录。导入后 `.map` 默认写入: 静态网页不能自动把图片复制进项目目录。导入后 `.map` 默认写入:
@@ -89,6 +92,7 @@ assets/stage/stage_01/props/<图片文件名>
## 当前限制 ## 当前限制
- 浏览器安全限制不允许静态网页直接覆盖本地源文件。当前需要先下载 `.map`,再手动替换项目里的地图文件;部署到服务器后可以增加服务端保存接口。 - 浏览器安全限制不允许静态网页直接覆盖本地源文件。当前需要先下载 `.map`,再手动替换项目里的地图文件;部署到服务器后可以增加服务端保存接口。
- 已导入图形的预览只在当前浏览器会话内有效;重新打开 `.map` 后,如果浏览器没有重新选择图片,图形会用颜色块显示 - 如果 `.map` 里的贴图路径指向 `assets/...` 且文件存在于 `game/assets/...`,重新打开后会自动预览图片
- 如果导入图片后没有把真实文件放入 `game/assets/...` 对应路径,重新打开后会回退为颜色块。
- 暂未实现图块集图片预览,铺砖区当前使用 fallback 颜色显示。 - 暂未实现图块集图片预览,铺砖区当前使用 fallback 颜色显示。
- 暂未实现重做和多选。 - 暂未实现重做和多选。
+84 -4
View File
@@ -7,6 +7,8 @@ const state = {
gridSize: 10, gridSize: 10,
hitFilter: "all", hitFilter: "all",
imagePreviews: new Map(), imagePreviews: new Map(),
imageLoading: new Set(),
imageFailures: new Set(),
history: [], history: [],
dragging: null, dragging: null,
}; };
@@ -121,6 +123,7 @@ async function importGraphicAsset(event) {
const image = await loadImage(previewUrl); const image = await loadImage(previewUrl);
const texturePath = defaultGraphicTexturePath(file.name); const texturePath = defaultGraphicTexturePath(file.name);
state.imagePreviews.set(texturePath, image); state.imagePreviews.set(texturePath, image);
state.imageFailures.delete(texturePath);
pushUndo("导入图形"); pushUndo("导入图形");
addGraphicSprite(texturePath, image.naturalWidth || image.width || 64, image.naturalHeight || image.height || 64); addGraphicSprite(texturePath, image.naturalWidth || image.width || 64, image.naturalHeight || image.height || 64);
setStatus(`已导入图形 ${file.name},并自动创建同尺寸碰撞块。请把素材放到 ${texturePath}`); setStatus(`已导入图形 ${file.name},并自动创建同尺寸碰撞块。请把素材放到 ${texturePath}`);
@@ -140,11 +143,58 @@ function loadImage(url) {
}); });
} }
function preloadMapImages(map) {
for (const layer of map.layers) {
for (const rect of layer.rects) {
if (rect.texture) queueImagePreview(rect.texture);
}
}
}
async function queueImagePreview(texturePath) {
if (!texturePath ||
state.imagePreviews.has(texturePath) ||
state.imageLoading.has(texturePath) ||
state.imageFailures.has(texturePath)) {
return;
}
const url = previewUrlForTexture(texturePath);
if (!url) {
state.imageFailures.add(texturePath);
return;
}
state.imageLoading.add(texturePath);
try {
const image = await loadImage(url);
state.imagePreviews.set(texturePath, image);
state.imageFailures.delete(texturePath);
refresh();
} catch (error) {
state.imageFailures.add(texturePath);
setStatus(`贴图预览加载失败:${texturePath}`);
} finally {
state.imageLoading.delete(texturePath);
}
}
function previewUrlForTexture(texturePath) {
const clean = String(texturePath || "").replace(/\\/g, "/").replace(/^\/+/, "");
if (!clean) return "";
if (/^(blob:|data:|https?:\/\/)/i.test(clean)) return clean;
if (clean.startsWith("assets/")) return `../../game/${clean}`;
if (clean.startsWith("game/assets/")) return `../../${clean}`;
return clean;
}
function loadMapText(text, status) { function loadMapText(text, status) {
try { try {
state.map = parseMap(text); state.map = parseMap(text);
state.selected = null; state.selected = null;
state.history = []; state.history = [];
state.imageFailures.clear();
preloadMapImages(state.map);
fitWorldToCanvas(); fitWorldToCanvas();
setStatus(status); setStatus(status);
refresh(); refresh();
@@ -489,6 +539,7 @@ function drawLayerRect(ctx, rect) {
ctx.strokeRect(rect.bounds.x, rect.bounds.y, rect.bounds.width, rect.bounds.height); ctx.strokeRect(rect.bounds.x, rect.bounds.y, rect.bounds.width, rect.bounds.height);
return; return;
} }
if (rect.texture) queueImagePreview(rect.texture);
drawFilledRect(ctx, rect.bounds, rect.color, 0.55, "#7d8794"); drawFilledRect(ctx, rect.bounds, rect.color, 0.55, "#7d8794");
} }
@@ -1108,7 +1159,7 @@ function rectFields(rect) {
} }
function layerRectFields(item) { function layerRectFields(item) {
return [ const fields = [
numberField("X", () => item.bounds.x, (v) => { numberField("X", () => item.bounds.x, (v) => {
const next = Number.isFinite(v) ? v : item.bounds.x; const next = Number.isFinite(v) ? v : item.bounds.x;
moveLinkedCollision(item, next - item.bounds.x, 0); moveLinkedCollision(item, next - item.bounds.x, 0);
@@ -1129,11 +1180,27 @@ function layerRectFields(item) {
}), }),
textField("贴图路径", () => item.texture || "", (v) => { textField("贴图路径", () => item.texture || "", (v) => {
const next = String(v || "").trim(); const next = String(v || "").trim();
if (next) item.texture = next; if (next) {
else delete item.texture; item.texture = next;
state.imageFailures.delete(next);
queueImagePreview(next);
} else {
delete item.texture;
}
}), }),
...colorFields(item.color, true),
]; ];
if (item.texture && item.source) {
fields.push(
numberField("源 X", () => sourceRectFor(item).x, (v) => { sourceRectFor(item).x = Math.max(0, v); }),
numberField("源 Y", () => sourceRectFor(item).y, (v) => { sourceRectFor(item).y = Math.max(0, v); }),
numberField("源 W", () => sourceRectFor(item).width, (v) => { sourceRectFor(item).width = Math.max(1, v); }),
numberField("源 H", () => sourceRectFor(item).height, (v) => { sourceRectFor(item).height = Math.max(1, v); })
);
}
fields.push(...colorFields(item.color, true));
return fields;
} }
function pointFields(point) { function pointFields(point) {
@@ -1161,6 +1228,19 @@ function textField(label, get, set, options = null) {
return { label, type: "text", options, get, set }; return { label, type: "text", options, get, set };
} }
function sourceRectFor(item) {
if (!item.source) {
const image = item.texture ? state.imagePreviews.get(item.texture) : null;
item.source = {
x: 0,
y: 0,
width: image ? (image.naturalWidth || image.width || Math.max(1, item.bounds.width)) : Math.max(1, item.bounds.width),
height: image ? (image.naturalHeight || image.height || Math.max(1, item.bounds.height)) : Math.max(1, item.bounds.height),
};
}
return item.source;
}
function ensureLevelVisualLayer() { function ensureLevelVisualLayer() {
if (!state.map.layers.some((layer) => layer.role === "LevelVisual")) { if (!state.map.layers.some((layer) => layer.role === "LevelVisual")) {
state.map.layers.push({ id: "level_visual", role: "LevelVisual", parallax: 1, rects: [], tileRects: [] }); state.map.layers.push({ id: "level_visual", role: "LevelVisual", parallax: 1, rects: [], tileRects: [] });