Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc300f4ff9 | |||
| 7d0f115fcb |
@@ -12,47 +12,47 @@ collision 1760 500 300 28
|
||||
collision 2180 455 260 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
|
||||
rect 0 0 3200 720 0.08 0.10 0.14 1.0
|
||||
rect 360 120 420 220 0.13 0.16 0.21 1.0
|
||||
rect 1380 90 560 260 0.12 0.15 0.20 1.0
|
||||
rect 2440 140 520 210 0.13 0.16 0.21 1.0
|
||||
rect 0 0 3200 720 0.04 0.06 0.05 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_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_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
|
||||
|
||||
layer background_mid BackgroundMid 0.55
|
||||
rect 80 360 280 240 0.18 0.22 0.27 1.0
|
||||
rect 1020 310 340 290 0.18 0.22 0.27 1.0
|
||||
rect 2020 330 360 270 0.18 0.22 0.27 1.0
|
||||
rect 2860 280 260 320 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_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_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_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
|
||||
|
||||
layer level_visual LevelVisual 1.0
|
||||
tile_rect graybox_ground 0 600 320 8 0 0.62 0.68 0.73
|
||||
tile_rect graybox_platform 230 500 22 2 0 0.62 0.68 0.73
|
||||
tile_rect graybox_platform 560 430 26 2 0 0.62 0.68 0.73
|
||||
tile_rect graybox_platform 930 510 26 2 0 0.62 0.68 0.73
|
||||
tile_rect graybox_platform 1250 420 23 2 0 0.62 0.68 0.73
|
||||
tile_rect graybox_platform 1760 500 30 2 0 0.62 0.68 0.73
|
||||
tile_rect graybox_platform 2180 455 26 2 0 0.62 0.68 0.73
|
||||
tile_rect graybox_platform 2650 515 36 2 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
layer props_back PropsBack 1.0
|
||||
rect 118 392 88 208 0.26 0.30 0.34 1.0
|
||||
rect 780 250 72 180 0.26 0.30 0.34 1.0
|
||||
rect 1440 300 80 300 0.26 0.30 0.34 1.0
|
||||
rect 1880 360 110 240 0.26 0.30 0.34 1.0
|
||||
rect 2520 390 92 210 0.26 0.30 0.34 1.0
|
||||
rect 3040 315 82 285 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_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_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_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_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_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
|
||||
|
||||
layer props_front PropsFront 1.0
|
||||
rect 520 580 220 34 0.14 0.17 0.18 0.90
|
||||
rect 1510 570 280 42 0.14 0.17 0.18 0.90
|
||||
rect 2580 572 260 38 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_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_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
|
||||
|
||||
battle_zone zone_01_intro 520 260 560 340 0 0 1940 720
|
||||
|
||||
@@ -6,12 +6,14 @@ This directory is reserved for the first playable side-scrolling ACT stage.
|
||||
|
||||
| Slot | Expected file | Notes | Source / License |
|
||||
| --- | --- | --- | --- |
|
||||
| Far background | `background/background_far.png` | Large or horizontally tileable image. | TBD |
|
||||
| Mid background | `background/background_mid.png` | Parallax midground image or prop sheet. | TBD |
|
||||
| Tileset | `tiles/stage_01_tileset.png` | Ground, platform, wall, edge tiles. | 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/mossy_hills.png` | Mossy hill silhouettes used by `BackgroundMid` layer. | Local asset pack: `X:\NS_unknown_game\asset\Mossy Cavern`; license 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 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 |
|
||||
| Dust | `effects/dust.png` | Run, jump, and landing dust. | 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.
|
||||
- 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.
|
||||
- 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 |
@@ -72,11 +72,18 @@ endbattle_zone
|
||||
- `tile_index` 当前表示 tileset 第一行的横向 tile 序号。
|
||||
- `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。
|
||||
- 增加空 layer、battle zone 无 spawn、tile texture 不存在等更严格错误日志。
|
||||
- 保留 `whitebox_level.cpp` 作为 fallback,不再把主地图写死在 C++。
|
||||
|
||||
@@ -104,6 +104,8 @@ http://127.0.0.1:8787/tools/map_editor/
|
||||
- 支持选择图层或图层内对象后,对对应图层上移/下移;预览和导出都按当前 `.map` 图层顺序处理。
|
||||
- 通过“导入图形”创建 `rect_sprite`,并自动追加一个同坐标、同尺寸的 `collision`。
|
||||
- 导入图形在当前浏览器会话内支持图片预览;图形移动和缩放时,同步更新绑定碰撞块。
|
||||
- 从仓库根目录启动静态服务时,`.map` 内 `assets/...` 贴图路径会自动映射到 `game/assets/...` 并加载项目内已有图片预览。
|
||||
- `rect_sprite_src` 图形支持在属性面板编辑源图裁切区域。
|
||||
- 重新打开 `.map` 时,如果 `rect_sprite` 与某个 `collision` 坐标尺寸一致,编辑器会自动恢复二者的编辑绑定。
|
||||
- 导出兼容 `StageMapLoader` 的 `.map` 文本。
|
||||
|
||||
@@ -129,6 +131,13 @@ assets/stage/stage_01/props/<图片文件名>
|
||||
|
||||
实际运行游戏前,需要把对应图片素材放到该目录。编辑器内的图形-碰撞绑定是工具层能力,导出的 `.map` 仍保持运行时兼容格式,不额外写入私有字段。
|
||||
|
||||
项目内已有素材预览规则:
|
||||
|
||||
- `assets/...` 会映射到仓库内 `game/assets/...`。
|
||||
- `game/assets/...` 会直接映射到仓库同名路径。
|
||||
- 如果文件存在,编辑器重新打开 `.map` 后也会自动预览。
|
||||
- 如果文件不存在,编辑器使用颜色块 fallback。
|
||||
|
||||
## 首版验收
|
||||
|
||||
完成时应达到:
|
||||
|
||||
@@ -34,6 +34,20 @@ UI
|
||||
- 支持 `.map` tile entry 绘制。
|
||||
- 支持 tileset 图片缺失时按 tile fallback 色绘制。
|
||||
- 资源缺失时先检查 `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。
|
||||
- 把正式 tileset 素材接入 `stage_01.map`。
|
||||
- 用地图编辑器继续调整 Mossy 场景块、前后景装饰和碰撞对齐。
|
||||
- 如果引擎提供统一 Texture cache,再迁移当前局部贴图缓存。
|
||||
|
||||
@@ -319,3 +319,14 @@ game/assets/stage/stage_01/
|
||||
3. 把灰盒 `StageLayer` 的色块替换成 sprite/tile 条目。
|
||||
4. 对齐视觉块和碰撞块,误差控制在 4 像素以内。
|
||||
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 玩法。下一步围绕地图编辑器继续微调场景块位置和碰撞对齐。
|
||||
|
||||
@@ -107,7 +107,7 @@ void StageRenderer::drawLayer(const LevelDefinition& level,
|
||||
rect.bounds.top() + parallaxOffset.y,
|
||||
rect.bounds.width(), rect.bounds.height());
|
||||
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(),
|
||||
4.0f),
|
||||
frostbite2D::Color(0.91f, 0.95f, 0.98f, 1.0f));
|
||||
|
||||
@@ -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. 在对象列表中选择一个图层,或选择该图层下的图形/铺砖区。
|
||||
@@ -205,7 +209,8 @@ spawn
|
||||
|
||||
- 不能直接覆盖本地源文件,只能下载 `.map`。
|
||||
- 导入图片的真实文件仍需手动放入项目资源目录。
|
||||
- 已导入图片的浏览器预览只在当前会话有效;重新打开 `.map` 后,如果没有重新选择图片,会显示 fallback 色块。
|
||||
- `.map` 中存在于 `game/assets/...` 的 `assets/...` 贴图路径会自动预览。
|
||||
- 如果导入图片后没有把真实文件放到 `game/assets/...` 对应路径,重新打开 `.map` 后会显示 fallback 色块。
|
||||
- 暂未实现重做。
|
||||
- 暂未实现多选和批量移动。
|
||||
- 暂未实现 tile palette 和 tileset 图片预览。
|
||||
|
||||
@@ -33,7 +33,9 @@ http://127.0.0.1:8787/tools/map_editor/
|
||||
- 通过“编辑目标”过滤画布选择对象,默认“智能选择”把碰撞块当作上层覆盖;同位置对象第一次点选碰撞块,再点一次切到下层铺砖区或图形。
|
||||
- 选中矩形对象后,拖动右下角蓝色手柄可以调整宽高。
|
||||
- 通过“导入图形”选择图片,自动生成一个 `rect_sprite` 图形对象和一个同尺寸 `collision`。
|
||||
- 导入的图形会在浏览器内临时预览,拖动或缩放图形时,同步移动或缩放绑定的碰撞块。
|
||||
- 从仓库根目录启动静态服务时,`.map` 内 `assets/...` 贴图路径会自动映射到 `game/assets/...` 并显示预览。
|
||||
- 导入的图形会在浏览器内预览,拖动或缩放图形时,同步移动或缩放绑定的碰撞块。
|
||||
- 已有 `rect_sprite_src` 图形可以在属性面板调整源图裁切区域。
|
||||
- 编辑选中对象的数值和文本属性。
|
||||
- 新增/删除图块集、图层、碰撞块、铺砖区、战斗区和出生点。
|
||||
- 支持撤回最近编辑操作,也可以使用 `Ctrl+Z`。
|
||||
@@ -71,6 +73,7 @@ http://127.0.0.1:8787/tools/map_editor/
|
||||
4. 拖动图形本体可以移动图形和绑定碰撞块。
|
||||
5. 拖动右下角蓝色手柄可以调整图形和绑定碰撞块尺寸。
|
||||
6. 也可以在右侧属性面板修改 `X`、`Y`、`W`、`H`、颜色和贴图路径。
|
||||
7. 如果图形来自 `rect_sprite_src`,属性面板还会显示 `源 X`、`源 Y`、`源 W`、`源 H`,用于调整图集裁切区域。
|
||||
|
||||
静态网页不能自动把图片复制进项目目录。导入后 `.map` 默认写入:
|
||||
|
||||
@@ -89,6 +92,7 @@ assets/stage/stage_01/props/<图片文件名>
|
||||
## 当前限制
|
||||
|
||||
- 浏览器安全限制不允许静态网页直接覆盖本地源文件。当前需要先下载 `.map`,再手动替换项目里的地图文件;部署到服务器后可以增加服务端保存接口。
|
||||
- 已导入图形的预览只在当前浏览器会话内有效;重新打开 `.map` 后,如果浏览器没有重新选择图片,图形会用颜色块显示。
|
||||
- 如果 `.map` 里的贴图路径指向 `assets/...` 且文件存在于 `game/assets/...`,重新打开后会自动预览图片。
|
||||
- 如果导入图片后没有把真实文件放入 `game/assets/...` 对应路径,重新打开后会回退为颜色块。
|
||||
- 暂未实现图块集图片预览,铺砖区当前使用 fallback 颜色显示。
|
||||
- 暂未实现重做和多选。
|
||||
|
||||
+84
-4
@@ -7,6 +7,8 @@ const state = {
|
||||
gridSize: 10,
|
||||
hitFilter: "all",
|
||||
imagePreviews: new Map(),
|
||||
imageLoading: new Set(),
|
||||
imageFailures: new Set(),
|
||||
history: [],
|
||||
dragging: null,
|
||||
};
|
||||
@@ -121,6 +123,7 @@ async function importGraphicAsset(event) {
|
||||
const image = await loadImage(previewUrl);
|
||||
const texturePath = defaultGraphicTexturePath(file.name);
|
||||
state.imagePreviews.set(texturePath, image);
|
||||
state.imageFailures.delete(texturePath);
|
||||
pushUndo("导入图形");
|
||||
addGraphicSprite(texturePath, image.naturalWidth || image.width || 64, image.naturalHeight || image.height || 64);
|
||||
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) {
|
||||
try {
|
||||
state.map = parseMap(text);
|
||||
state.selected = null;
|
||||
state.history = [];
|
||||
state.imageFailures.clear();
|
||||
preloadMapImages(state.map);
|
||||
fitWorldToCanvas();
|
||||
setStatus(status);
|
||||
refresh();
|
||||
@@ -489,6 +539,7 @@ function drawLayerRect(ctx, rect) {
|
||||
ctx.strokeRect(rect.bounds.x, rect.bounds.y, rect.bounds.width, rect.bounds.height);
|
||||
return;
|
||||
}
|
||||
if (rect.texture) queueImagePreview(rect.texture);
|
||||
drawFilledRect(ctx, rect.bounds, rect.color, 0.55, "#7d8794");
|
||||
}
|
||||
|
||||
@@ -1108,7 +1159,7 @@ function rectFields(rect) {
|
||||
}
|
||||
|
||||
function layerRectFields(item) {
|
||||
return [
|
||||
const fields = [
|
||||
numberField("X", () => item.bounds.x, (v) => {
|
||||
const next = Number.isFinite(v) ? v : item.bounds.x;
|
||||
moveLinkedCollision(item, next - item.bounds.x, 0);
|
||||
@@ -1129,11 +1180,27 @@ function layerRectFields(item) {
|
||||
}),
|
||||
textField("贴图路径", () => item.texture || "", (v) => {
|
||||
const next = String(v || "").trim();
|
||||
if (next) item.texture = next;
|
||||
else delete item.texture;
|
||||
if (next) {
|
||||
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) {
|
||||
@@ -1161,6 +1228,19 @@ function textField(label, get, set, options = null) {
|
||||
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() {
|
||||
if (!state.map.layers.some((layer) => layer.role === "LevelVisual")) {
|
||||
state.map.layers.push({ id: "level_visual", role: "LevelVisual", parallax: 1, rects: [], tileRects: [] });
|
||||
|
||||
Reference in New Issue
Block a user