Add stage layer asset sources

This commit is contained in:
2026-06-09 15:02:04 +08:00
parent 3e2e25eb93
commit cf7b7dfb5a
12 changed files with 140 additions and 6 deletions
+22
View File
@@ -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.
@@ -0,0 +1 @@
@@ -0,0 +1 @@
@@ -0,0 +1 @@
@@ -0,0 +1 @@
@@ -0,0 +1 @@
@@ -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`:做第一个敌人占位,接入出生点。
+4 -2
View File
@@ -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 能从起点移动到终点并看到相机推进。
+10
View File
@@ -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 {
+18
View File
@@ -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
+64 -1
View File
@@ -4,6 +4,8 @@
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/graphics/types.h>
#include <frostbite2D/graphics/texture.h>
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<frostbite2D::Texture> texture,
const frostbite2D::Color& tint) {
frostbite2D::Renderer& renderer = frostbite2D::Renderer::get();
frostbite2D::Quad quad = frostbite2D::Quad::createTextured(
bounds, sourceRect,
frostbite2D::Vec2(static_cast<float>(texture->getWidth()),
static_cast<float>(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<frostbite2D::Texture> 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<float>(texture->getWidth()),
static_cast<float>(texture->getHeight())),
texture, rect.source.tint);
}
frostbite2D::Ptr<frostbite2D::Texture>
StageRenderer::getTexture(const std::string& texturePath) const {
const auto existing = textureCache_.find(texturePath);
if (existing != textureCache_.end()) {
return existing->second;
}
frostbite2D::Ptr<frostbite2D::Texture> texture =
frostbite2D::Texture::loadFromFile(texturePath);
if (texture) {
texture->setSampling(frostbite2D::TextureSampling::PixelArt);
}
textureCache_[texturePath] = texture;
return texture;
}
} // namespace ns_game
+11
View File
@@ -3,6 +3,10 @@
#include "../data/level_definition.h"
#include "../core/debug_flags.h"
#include <frostbite2D/graphics/texture.h>
#include <string>
#include <unordered_map>
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<frostbite2D::Texture>
getTexture(const std::string& texturePath) const;
mutable std::unordered_map<std::string, frostbite2D::Ptr<frostbite2D::Texture>>
textureCache_;
};
} // namespace ns_game