Add stage map tile rendering path

This commit is contained in:
2026-06-09 23:32:47 +08:00
parent 0257c8058e
commit 51a3776494
10 changed files with 200 additions and 19 deletions
+17
View File
@@ -32,6 +32,21 @@ struct StageRect {
StageLayerSource source;
};
struct TileSetDefinition {
std::string id;
std::string texturePath;
float tileWidth = 0.0f;
float tileHeight = 0.0f;
frostbite2D::Color fallbackColor;
};
struct StageTile {
std::string tileSetId;
frostbite2D::Rect bounds;
frostbite2D::Rect sourceRect;
frostbite2D::Color fallbackColor;
};
struct SpawnPoint {
std::string id;
frostbite2D::Vec2 position;
@@ -60,6 +75,7 @@ struct StageLayer {
StageLayerRole role = StageLayerRole::LevelVisual;
float parallax = 1.0f;
std::vector<StageRect> rects;
std::vector<StageTile> tiles;
};
struct LevelDefinition {
@@ -68,6 +84,7 @@ struct LevelDefinition {
frostbite2D::Vec2 playerSpawn;
frostbite2D::Rect cameraBounds;
PlatformWorld collision;
std::vector<TileSetDefinition> tileSets;
std::vector<StageLayer> layers;
std::vector<BattleZoneDefinition> battleZones;
};
+93 -2
View File
@@ -5,6 +5,7 @@
#include <SDL2/SDL.h>
#include <charconv>
#include <cmath>
#include <sstream>
namespace ns_game {
@@ -66,6 +67,16 @@ parseColor(const std::vector<std::string>& tokens, size_t start) {
return frostbite2D::Color(*r, *g, *b, *a);
}
std::optional<TileSetDefinition*>
findTileSet(ParseContext& context, const std::string& id) {
for (TileSetDefinition& tileSet : context.level.tileSets) {
if (tileSet.id == id) {
return &tileSet;
}
}
return std::nullopt;
}
std::optional<StageLayerRole> parseLayerRole(const std::string& value) {
if (value == "BackgroundFar") {
return StageLayerRole::BackgroundFar;
@@ -188,6 +199,28 @@ bool parseLine(ParseContext& context, const std::string& path,
return true;
}
if (command == "tileset") {
if (!ensureNoOpenBlock(context, path, lineNumber) || tokens.size() != 9) {
return warnLine(path, lineNumber,
"expected: tileset <id> <texture> <tile_w> <tile_h> <r> <g> <b> <a>");
}
if (findTileSet(context, tokens[1])) {
return warnLine(path, lineNumber, "duplicate tileset id: " + tokens[1]);
}
const auto tileWidth = parseFloat(tokens[3]);
const auto tileHeight = parseFloat(tokens[4]);
const auto fallbackColor = parseColor(tokens, 5);
if (!tileWidth || !tileHeight || *tileWidth <= 0.0f ||
*tileHeight <= 0.0f || !fallbackColor) {
return warnLine(path, lineNumber, "invalid tileset definition");
}
context.level.tileSets.push_back(
{tokens[1], tokens[2], *tileWidth, *tileHeight, *fallbackColor});
return true;
}
if (command == "layer") {
if (!ensureNoOpenBlock(context, path, lineNumber) || tokens.size() != 4) {
return warnLine(path, lineNumber,
@@ -226,6 +259,59 @@ bool parseLine(ParseContext& context, const std::string& path,
return true;
}
if (command == "tile_rect") {
if (!context.currentLayer || tokens.size() != 10) {
return warnLine(path, lineNumber,
"expected in layer: tile_rect <tileset> <x> <y> <columns> <rows> <tile_index> <r> <g> <b>");
}
const auto tileSet = findTileSet(context, tokens[1]);
if (!tileSet) {
return warnLine(path, lineNumber, "unknown tileset id: " + tokens[1]);
}
const auto x = parseFloat(tokens[2]);
const auto y = parseFloat(tokens[3]);
const auto columnsFloat = parseFloat(tokens[4]);
const auto rowsFloat = parseFloat(tokens[5]);
const auto tileIndexFloat = parseFloat(tokens[6]);
const auto r = parseFloat(tokens[7]);
const auto g = parseFloat(tokens[8]);
const auto b = parseFloat(tokens[9]);
if (!x || !y || !columnsFloat || !rowsFloat || !tileIndexFloat || !r ||
!g || !b) {
return warnLine(path, lineNumber, "invalid tile_rect values");
}
const int columns = static_cast<int>(*columnsFloat);
const int rows = static_cast<int>(*rowsFloat);
const int tileIndex = static_cast<int>(*tileIndexFloat);
if (columns <= 0 || rows <= 0 || tileIndex < 0 ||
std::floor(*columnsFloat) != *columnsFloat ||
std::floor(*rowsFloat) != *rowsFloat ||
std::floor(*tileIndexFloat) != *tileIndexFloat) {
return warnLine(path, lineNumber,
"tile_rect columns, rows and tile_index must be non-negative integers");
}
const TileSetDefinition& definition = **tileSet;
const frostbite2D::Color fallbackColor(*r, *g, *b, 1.0f);
for (int row = 0; row < rows; ++row) {
for (int column = 0; column < columns; ++column) {
const frostbite2D::Rect bounds(
*x + static_cast<float>(column) * definition.tileWidth,
*y + static_cast<float>(row) * definition.tileHeight,
definition.tileWidth, definition.tileHeight);
const frostbite2D::Rect sourceRect(
static_cast<float>(tileIndex) * definition.tileWidth, 0.0f,
definition.tileWidth, definition.tileHeight);
context.currentLayer->tiles.push_back(
{definition.id, bounds, sourceRect, fallbackColor});
}
}
return true;
}
if (command == "rect_sprite" || command == "rect_sprite_src") {
if (!context.currentLayer) {
return warnLine(path, lineNumber,
@@ -386,9 +472,14 @@ std::optional<LevelDefinition> LoadStageMap(const std::string& path) {
return std::nullopt;
}
SDL_Log("StageMapLoader: loaded %s from %s (%zu platforms, %zu layers, %zu zones)",
size_t tileCount = 0;
for (const StageLayer& layer : level->layers) {
tileCount += layer.tiles.size();
}
SDL_Log("StageMapLoader: loaded %s from %s (%zu platforms, %zu layers, %zu tiles, %zu zones)",
resource->path.c_str(), sourceName(resource->source),
level->collision.platforms.size(), level->layers.size(),
level->collision.platforms.size(), level->layers.size(), tileCount,
level->battleZones.size());
return level;
}
+37
View File
@@ -38,6 +38,16 @@ float worldWidthOf(const LevelDefinition& level) {
return level.collision.levelRight - level.collision.levelLeft;
}
const TileSetDefinition* findTileSet(const LevelDefinition& level,
const std::string& id) {
for (const TileSetDefinition& tileSet : level.tileSets) {
if (tileSet.id == id) {
return &tileSet;
}
}
return nullptr;
}
} // namespace
void StageRenderer::Render(const LevelDefinition& level,
@@ -102,6 +112,13 @@ void StageRenderer::drawLayer(const LevelDefinition& level,
frostbite2D::Color(0.91f, 0.95f, 0.98f, 1.0f));
}
}
for (const StageTile& tile : layer.tiles) {
frostbite2D::Rect bounds(tile.bounds.left() + parallaxOffset.x,
tile.bounds.top() + parallaxOffset.y,
tile.bounds.width(), tile.bounds.height());
drawStageTile(level, tile, bounds);
}
}
void StageRenderer::drawStageRect(const StageRect& rect,
@@ -130,6 +147,26 @@ void StageRenderer::drawStageRect(const StageRect& rect,
texture, rect.source.tint);
}
void StageRenderer::drawStageTile(const LevelDefinition& level,
const StageTile& tile,
const frostbite2D::Rect& bounds) const {
const TileSetDefinition* tileSet = findTileSet(level, tile.tileSetId);
if (!tileSet || tileSet->texturePath.empty()) {
drawRect(bounds, tile.fallbackColor);
return;
}
frostbite2D::Ptr<frostbite2D::Texture> texture =
getTexture(tileSet->texturePath);
if (!texture) {
drawRect(bounds, tile.fallbackColor);
return;
}
drawTexture(bounds, tile.sourceRect, texture,
frostbite2D::Color(1.0f, 1.0f, 1.0f, 1.0f));
}
frostbite2D::Ptr<frostbite2D::Texture>
StageRenderer::getTexture(const std::string& texturePath) const {
const auto existing = textureCache_.find(texturePath);
+2
View File
@@ -20,6 +20,8 @@ private:
void drawLayer(const LevelDefinition& level, const StageLayer& layer) const;
void drawStageRect(const StageRect& rect,
const frostbite2D::Rect& bounds) const;
void drawStageTile(const LevelDefinition& level, const StageTile& tile,
const frostbite2D::Rect& bounds) const;
frostbite2D::Ptr<frostbite2D::Texture>
getTexture(const std::string& texturePath) const;