From cf7b7dfb5add3d5d536b532e14f2013b79525370 Mon Sep 17 00:00:00 2001 From: 1165565692-lang <1165565692@qq.com> Date: Tue, 9 Jun 2026 15:02:04 +0800 Subject: [PATCH] Add stage layer asset sources --- game/assets/stage/stage_01/ASSETS.md | 22 +++++++ game/assets/stage/stage_01/audio/.gitkeep | 1 + .../assets/stage/stage_01/background/.gitkeep | 1 + game/assets/stage/stage_01/effects/.gitkeep | 1 + game/assets/stage/stage_01/props/.gitkeep | 1 + game/assets/stage/stage_01/tiles/.gitkeep | 1 + game/docs/planning/game_architecture_board.md | 9 ++- game/docs/planning/scene_build_plan.md | 6 +- game/src/data/level_definition.h | 10 +++ game/src/data/stage_asset_slots.h | 18 +++++ game/src/stage/stage_renderer.cpp | 65 ++++++++++++++++++- game/src/stage/stage_renderer.h | 11 ++++ 12 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 game/assets/stage/stage_01/ASSETS.md create mode 100644 game/assets/stage/stage_01/audio/.gitkeep create mode 100644 game/assets/stage/stage_01/background/.gitkeep create mode 100644 game/assets/stage/stage_01/effects/.gitkeep create mode 100644 game/assets/stage/stage_01/props/.gitkeep create mode 100644 game/assets/stage/stage_01/tiles/.gitkeep create mode 100644 game/src/data/stage_asset_slots.h diff --git a/game/assets/stage/stage_01/ASSETS.md b/game/assets/stage/stage_01/ASSETS.md new file mode 100644 index 0000000..191f505 --- /dev/null +++ b/game/assets/stage/stage_01/ASSETS.md @@ -0,0 +1,22 @@ +# Stage 01 Assets + +This directory is reserved for the first playable side-scrolling ACT stage. + +## Slots + +| 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 | +| Props sheet | `props/stage_01_props.png` | Crates, signs, lamps, blockers, exits. | TBD | +| Hit spark | `effects/hit_spark.png` | Small combat impact effect. | TBD | +| Dust | `effects/dust.png` | Run, jump, and landing dust. | TBD | +| Music / ambience | `audio/` | Optional loopable stage audio. | TBD | + +## Import Rules + +- Keep file names lowercase with underscores. +- 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. diff --git a/game/assets/stage/stage_01/audio/.gitkeep b/game/assets/stage/stage_01/audio/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/game/assets/stage/stage_01/audio/.gitkeep @@ -0,0 +1 @@ + diff --git a/game/assets/stage/stage_01/background/.gitkeep b/game/assets/stage/stage_01/background/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/game/assets/stage/stage_01/background/.gitkeep @@ -0,0 +1 @@ + diff --git a/game/assets/stage/stage_01/effects/.gitkeep b/game/assets/stage/stage_01/effects/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/game/assets/stage/stage_01/effects/.gitkeep @@ -0,0 +1 @@ + diff --git a/game/assets/stage/stage_01/props/.gitkeep b/game/assets/stage/stage_01/props/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/game/assets/stage/stage_01/props/.gitkeep @@ -0,0 +1 @@ + diff --git a/game/assets/stage/stage_01/tiles/.gitkeep b/game/assets/stage/stage_01/tiles/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/game/assets/stage/stage_01/tiles/.gitkeep @@ -0,0 +1 @@ + diff --git a/game/docs/planning/game_architecture_board.md b/game/docs/planning/game_architecture_board.md index e63ca22..5018cb3 100644 --- a/game/docs/planning/game_architecture_board.md +++ b/game/docs/planning/game_architecture_board.md @@ -201,11 +201,14 @@ mindmap - `movement/platform_world.*` - `stage/stage_renderer.*` - `StageLayer` 灰盒数据已接入:远景、中景、地面/平台、后景 props 先用色块占位。 +- `StageLayerSource` 已接入:每个 `StageRect` 可选择色块占位或 sprite 资源路径,加载失败时回退色块。 +- `StageAssetSlots` 已建立:`stage_01` 资源目录和素材来源记录已预留,素材回来后可直接落位。 - 第一关宽度已扩展到 3200 像素,并加入 3 个 battle zone 占位与敌人出生点。 下一步: -- 将 `StageLayer` 色块替换为 tileset、背景图和 props 素材。 +- 将 `StageLayer` 色块逐步替换为 tileset、背景图和 props 素材。 +- 将后景/前景 props 统一到 `StageLayer` 数据。 - 将 battle zone 占位升级为触发、锁镜头、清场解锁逻辑。 - 将敌人出生点接入 `EnemyActor`。 @@ -580,8 +583,8 @@ mindmap 下一步建议按这个顺序做: -1. `StageLayerSource`:让 `StageLayer` 同时支持色块占位和 sprite 资源路径。 -2. `StageAssetSlots`:建立 `stage_01` 资源槽位和素材来源记录,做到素材可直接落位。 +1. 已完成:`StageLayerSource` 让 `StageLayer` 同时支持色块占位和 sprite 资源路径。 +2. 已完成:`StageAssetSlots` 建立 `stage_01` 资源槽位和素材来源记录,做到素材可直接落位。 3. `StageProps`:把后景/前景 props 统一到 layer 数据。 4. `BattleZone`:把 `F2` debug 清场替换为敌人清空自动解锁。 5. `EnemyActor`:做第一个敌人占位,接入出生点。 diff --git a/game/docs/planning/scene_build_plan.md b/game/docs/planning/scene_build_plan.md index f51c47f..b5f9d80 100644 --- a/game/docs/planning/scene_build_plan.md +++ b/game/docs/planning/scene_build_plan.md @@ -25,6 +25,8 @@ - `CameraZoneLock` 已接入:玩家进入 battle zone 后摄像机临时锁定到 zone camera bounds,`F2` 临时清场并恢复全关卡跟随。 - 已修复 zone camera bounds 小于 1280 视口导致摄像机锁死的问题;所有锁镜头范围必须不小于当前视口,debug 阶段玩家离开 zone 会自动清场解锁。 - 已修复进入 battle zone 时摄像机硬切的问题;zone camera bounds 表示摄像机可移动范围,进入/离开 zone 都通过短过渡平滑切换约束。 +- `StageLayerSource` 已接入:`StageRect` 支持色块占位或 sprite 资源路径,资源不存在时回退色块。 +- `StageAssetSlots` 已建立:`game/assets/stage/stage_01/` 已预留 background、tiles、props、effects、audio 目录和素材来源记录。 ## 2. 第一关场景目标 @@ -254,8 +256,8 @@ game/assets/stage/stage_01/ 1. 已完成:`CameraController` 修复当前摄像机不会动的问题,实现玩家跟随、边界 clamp、dead zone 和平滑参数。 2. 已完成:`CameraZoneLock` 让 battle zone 可以锁定摄像机范围,先用 `F2` 做 debug 清场和解锁占位。 -3. `StageLayerSource`:扩展 `StageLayer`,让每个 rect 支持色块占位或 sprite 资源路径。 -4. `StageAssetSlots`:创建并记录 `stage_01` 资源槽位,不要求已有最终素材。 +3. 已完成:`StageLayerSource` 扩展 `StageLayer`,让每个 rect 支持色块占位或 sprite 资源路径。 +4. 已完成:`StageAssetSlots` 创建并记录 `stage_01` 资源槽位,不要求已有最终素材。 5. `StageProps`:把 props 从旧 `WhiteboxProp` 迁移到统一 layer 数据,支持 back/front 两层。 6. `SceneDebug`:补齐 F1 debug 总开关,并预留分项开关。 7. `SceneAcceptanceRun`:PC 和 Switch 都构建通过,PC 能从起点移动到终点并看到相机推进。 diff --git a/game/src/data/level_definition.h b/game/src/data/level_definition.h index 2f3d681..3063308 100644 --- a/game/src/data/level_definition.h +++ b/game/src/data/level_definition.h @@ -16,10 +16,20 @@ enum class StageLayerRole { PropsFront }; +struct StageLayerSource { + std::string texturePath; + frostbite2D::Rect sourceRect; + bool useSourceRect = false; + frostbite2D::Color tint = frostbite2D::Color(1.0f, 1.0f, 1.0f, 1.0f); + + bool hasTexture() const { return !texturePath.empty(); } +}; + struct StageRect { frostbite2D::Rect bounds; frostbite2D::Color color; StageLayerRole layer = StageLayerRole::PropsBack; + StageLayerSource source; }; struct SpawnPoint { diff --git a/game/src/data/stage_asset_slots.h b/game/src/data/stage_asset_slots.h new file mode 100644 index 0000000..fb0ded3 --- /dev/null +++ b/game/src/data/stage_asset_slots.h @@ -0,0 +1,18 @@ +#pragma once + +namespace ns_game::stage_assets::stage_01 { + +constexpr const char* kBackgroundFar = + "assets/stage/stage_01/background/background_far.png"; +constexpr const char* kBackgroundMid = + "assets/stage/stage_01/background/background_mid.png"; +constexpr const char* kTileset = + "assets/stage/stage_01/tiles/stage_01_tileset.png"; +constexpr const char* kProps = + "assets/stage/stage_01/props/stage_01_props.png"; +constexpr const char* kHitSpark = + "assets/stage/stage_01/effects/hit_spark.png"; +constexpr const char* kDust = + "assets/stage/stage_01/effects/dust.png"; + +} // namespace ns_game::stage_assets::stage_01 diff --git a/game/src/stage/stage_renderer.cpp b/game/src/stage/stage_renderer.cpp index e6424cd..510ce8a 100644 --- a/game/src/stage/stage_renderer.cpp +++ b/game/src/stage/stage_renderer.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include namespace ns_game { @@ -13,6 +15,25 @@ void drawRect(const frostbite2D::Rect& rect, const frostbite2D::Color& color) { frostbite2D::Renderer::get().drawQuad(rect, color); } +void drawTexture(const frostbite2D::Rect& bounds, + const frostbite2D::Rect& sourceRect, + frostbite2D::Ptr texture, + const frostbite2D::Color& tint) { + frostbite2D::Renderer& renderer = frostbite2D::Renderer::get(); + frostbite2D::Quad quad = frostbite2D::Quad::createTextured( + bounds, sourceRect, + frostbite2D::Vec2(static_cast(texture->getWidth()), + static_cast(texture->getHeight())), + tint.r, tint.g, tint.b, tint.a, renderer.shouldShrinkSubTextureUVs()); + auto* shader = renderer.getShaderManager().getShader("sprite"); + if (shader) { + shader->use(); + } + renderer.getBatch().submitQuad(quad, frostbite2D::Transform2D::identity(), + texture, shader, + frostbite2D::BlendMode::Normal); +} + float worldWidthOf(const LevelDefinition& level) { return level.collision.levelRight - level.collision.levelLeft; } @@ -63,7 +84,7 @@ void StageRenderer::drawLayer(const LevelDefinition& level, frostbite2D::Rect bounds(rect.bounds.left() + parallaxOffset.x, rect.bounds.top() + parallaxOffset.y, rect.bounds.width(), rect.bounds.height()); - drawRect(bounds, rect.color); + drawStageRect(rect, bounds); if (layer.role == StageLayerRole::LevelVisual) { drawRect(frostbite2D::Rect(bounds.left(), bounds.top(), bounds.width(), 4.0f), @@ -72,4 +93,46 @@ void StageRenderer::drawLayer(const LevelDefinition& level, } } +void StageRenderer::drawStageRect(const StageRect& rect, + const frostbite2D::Rect& bounds) const { + if (!rect.source.hasTexture()) { + drawRect(bounds, rect.color); + return; + } + + frostbite2D::Ptr texture = + getTexture(rect.source.texturePath); + if (!texture) { + drawRect(bounds, rect.color); + return; + } + + if (rect.source.useSourceRect) { + drawTexture(bounds, rect.source.sourceRect, texture, rect.source.tint); + return; + } + + drawTexture(bounds, + frostbite2D::Rect(0.0f, 0.0f, + static_cast(texture->getWidth()), + static_cast(texture->getHeight())), + texture, rect.source.tint); +} + +frostbite2D::Ptr +StageRenderer::getTexture(const std::string& texturePath) const { + const auto existing = textureCache_.find(texturePath); + if (existing != textureCache_.end()) { + return existing->second; + } + + frostbite2D::Ptr texture = + frostbite2D::Texture::loadFromFile(texturePath); + if (texture) { + texture->setSampling(frostbite2D::TextureSampling::PixelArt); + } + textureCache_[texturePath] = texture; + return texture; +} + } // namespace ns_game diff --git a/game/src/stage/stage_renderer.h b/game/src/stage/stage_renderer.h index 0a522d2..c855ee8 100644 --- a/game/src/stage/stage_renderer.h +++ b/game/src/stage/stage_renderer.h @@ -3,6 +3,10 @@ #include "../data/level_definition.h" #include "../core/debug_flags.h" +#include +#include +#include + namespace ns_game { class StageRenderer { @@ -13,6 +17,13 @@ private: void drawBackground(const LevelDefinition& level) const; void drawGrid(const LevelDefinition& level) const; void drawLayer(const LevelDefinition& level, const StageLayer& layer) const; + void drawStageRect(const StageRect& rect, + const frostbite2D::Rect& bounds) const; + frostbite2D::Ptr + getTexture(const std::string& texturePath) const; + + mutable std::unordered_map> + textureCache_; }; } // namespace ns_game