Implement TileSet and TileMap classes
This commit is contained in:
parent
bb9d108102
commit
bb43d308c0
6 changed files with 243 additions and 8 deletions
BIN
assets/tilesets/terrain.png
Normal file
BIN
assets/tilesets/terrain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 622 B |
|
|
@ -3,7 +3,6 @@ module;
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
export module core.drawing.sprite;
|
export module core.drawing.sprite;
|
||||||
|
|
||||||
|
|
@ -47,7 +46,7 @@ export namespace core
|
||||||
assert(texture_boundaries_.w > 0 && texture_boundaries_.h > 0);
|
assert(texture_boundaries_.w > 0 && texture_boundaries_.h > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Sprite create_with_texture(
|
static Sprite create_from_texture(
|
||||||
RenderServer& render_server,
|
RenderServer& render_server,
|
||||||
const std::string& filename,
|
const std::string& filename,
|
||||||
std::optional<sdl::FRect> texture_boundaries,
|
std::optional<sdl::FRect> texture_boundaries,
|
||||||
|
|
|
||||||
109
src/core/drawing/tile_map.cppm
Normal file
109
src/core/drawing/tile_map.cppm
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
export module core.drawing.tile_map;
|
||||||
|
|
||||||
|
import core.drawing.tile_set;
|
||||||
|
import core.render_server;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace core
|
||||||
|
{
|
||||||
|
// Restrict map size to 16 bit per dimension, so that width*height still fits within size_t
|
||||||
|
using MapCoord = uint16_t;
|
||||||
|
|
||||||
|
class TileMap
|
||||||
|
{
|
||||||
|
// Tile set ID
|
||||||
|
// TODO: Needed later when we centralize tile set management using a ResourceManager
|
||||||
|
[[maybe_unused]]
|
||||||
|
TileSetID tile_set_id_;
|
||||||
|
|
||||||
|
// Tile size
|
||||||
|
int tile_size_;
|
||||||
|
|
||||||
|
// Map boundaries
|
||||||
|
MapCoord width_;
|
||||||
|
MapCoord height_;
|
||||||
|
|
||||||
|
// Tile map origin in world coordinates
|
||||||
|
sdl::FPoint position_{0, 0};
|
||||||
|
|
||||||
|
// Tile map data: Tiles in a continuous array
|
||||||
|
std::unique_ptr<TileID[]> tiles_{nullptr};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TileMap(
|
||||||
|
const TileSetID tile_set_id,
|
||||||
|
const int tile_size,
|
||||||
|
const MapCoord width,
|
||||||
|
const MapCoord height,
|
||||||
|
const sdl::FPoint position = {0, 0}
|
||||||
|
)
|
||||||
|
: tile_set_id_(tile_set_id),
|
||||||
|
tile_size_(tile_size),
|
||||||
|
width_(width),
|
||||||
|
height_(height),
|
||||||
|
position_{position}
|
||||||
|
{
|
||||||
|
// TODO: This maybe shouldn't be an assert...
|
||||||
|
assert(width_ > 0 && height_ > 0);
|
||||||
|
tiles_ = std::make_unique<TileID[]>(
|
||||||
|
static_cast<std::size_t>(width * height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::size_t map_coords_to_index(const MapCoord x, const MapCoord y) const
|
||||||
|
{
|
||||||
|
// TODO: Properly handle x<0, y<0, x>=width, y>=height
|
||||||
|
assert(x < width_ && y < height_);
|
||||||
|
return y * width_ + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr TileID get_tile(const MapCoord x, const MapCoord y) const
|
||||||
|
{
|
||||||
|
return tiles_[map_coords_to_index(x, y)];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void set_tile(const MapCoord x, const MapCoord y, const TileID tile_id)
|
||||||
|
{
|
||||||
|
tiles_[map_coords_to_index(x, y)] = tile_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should we get the TileSet from the RenderServer, similar to how we get textures?
|
||||||
|
void draw(const RenderServer& render_server, const TileSet& tile_set) const
|
||||||
|
{
|
||||||
|
// Get tile set texture
|
||||||
|
const sdl::Texture& tile_set_texture = render_server.get_texture(tile_set.get_texture_id());
|
||||||
|
|
||||||
|
// TODO: Implement more efficiently (we could just iterate over tiles_ directly)
|
||||||
|
for (MapCoord y = 0; y < height_; y++) {
|
||||||
|
for (MapCoord x = 0; x < width_; x++) {
|
||||||
|
const int tile_index = get_tile(x, y);
|
||||||
|
|
||||||
|
// Ignore tiles with ID 0
|
||||||
|
if (tile_index == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto [tile_atlas_src_rect] = tile_set.get_tile_definition(tile_index);
|
||||||
|
const sdl::FRect tile_dest_rect{
|
||||||
|
position_.x + x * tile_size_,
|
||||||
|
position_.y + y * tile_size_,
|
||||||
|
static_cast<float>(tile_size_),
|
||||||
|
static_cast<float>(tile_size_),
|
||||||
|
};
|
||||||
|
|
||||||
|
render_server.render_texture(
|
||||||
|
tile_set_texture,
|
||||||
|
&tile_atlas_src_rect,
|
||||||
|
&tile_dest_rect
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
94
src/core/drawing/tile_set.cppm
Normal file
94
src/core/drawing/tile_set.cppm
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
export module core.drawing.tile_set;
|
||||||
|
|
||||||
|
import core.render_server;
|
||||||
|
import wrappers.sdl;
|
||||||
|
|
||||||
|
export namespace core
|
||||||
|
{
|
||||||
|
using TileSetID = unsigned int;
|
||||||
|
using TileID = unsigned int;
|
||||||
|
|
||||||
|
struct TileDefinition
|
||||||
|
{
|
||||||
|
sdl::FRect atlas_src_rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TileSet
|
||||||
|
{
|
||||||
|
// Texture ID of tileset atlas texture
|
||||||
|
TextureID texture_id_;
|
||||||
|
|
||||||
|
// Size of each tile in pixels
|
||||||
|
// TODO: Do we need this?
|
||||||
|
// int tile_size_;
|
||||||
|
|
||||||
|
// Tile definitions: Maps tile ID to a struct describing the tile, e.g. the texture source boundaries,
|
||||||
|
// collision data, etc.
|
||||||
|
std::vector<TileDefinition> tile_definitions_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TileSet() = delete;
|
||||||
|
|
||||||
|
explicit TileSet(
|
||||||
|
const TextureID texture_id,
|
||||||
|
std::vector<TileDefinition> tile_definitions
|
||||||
|
)
|
||||||
|
: texture_id_{texture_id},
|
||||||
|
tile_definitions_{std::move(tile_definitions)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
static TileSet create_from_texture(
|
||||||
|
RenderServer& render_server,
|
||||||
|
const std::string& filename,
|
||||||
|
const int tile_size,
|
||||||
|
const std::vector<sdl::Point>& tile_atlas_coords
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const TextureID texture_id = render_server.load_texture(filename);
|
||||||
|
const sdl::Texture& texture = render_server.get_texture(texture_id);
|
||||||
|
|
||||||
|
assert(tile_size > 0);
|
||||||
|
// TODO: These probably shouldn't be asserts...
|
||||||
|
assert(texture.get_width() % tile_size == 0);
|
||||||
|
assert(texture.get_height() % tile_size == 0);
|
||||||
|
|
||||||
|
std::vector<TileDefinition> tile_definitions;
|
||||||
|
|
||||||
|
// TODO: Should we automatically create tile 0 (no tile) instead of listing it in tile_atlas_coords?
|
||||||
|
for (const auto& [x, y] : tile_atlas_coords) {
|
||||||
|
const TileDefinition tile{
|
||||||
|
{
|
||||||
|
static_cast<float>(x * tile_size),
|
||||||
|
static_cast<float>(y * tile_size),
|
||||||
|
static_cast<float>(tile_size),
|
||||||
|
static_cast<float>(tile_size),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: These probably shouldn't be asserts...
|
||||||
|
assert(tile.atlas_src_rect.x + tile.atlas_src_rect.w <= texture.get_width());
|
||||||
|
assert(tile.atlas_src_rect.y + tile.atlas_src_rect.h <= texture.get_height());
|
||||||
|
tile_definitions.push_back(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TileSet(texture_id, tile_definitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr TextureID get_texture_id() const
|
||||||
|
{
|
||||||
|
return texture_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const TileDefinition& get_tile_definition(const TileID tile_id) const
|
||||||
|
{
|
||||||
|
return tile_definitions_.at(tile_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ module;
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
export module core.engine;
|
export module core.engine;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ module;
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
export module game.game;
|
export module game.game;
|
||||||
|
|
||||||
import core.drawing.sprite;
|
import core.drawing.sprite;
|
||||||
|
import core.drawing.tile_map;
|
||||||
|
import core.drawing.tile_set;
|
||||||
import core.engine;
|
import core.engine;
|
||||||
import core.render_server;
|
import core.render_server;
|
||||||
import wrappers.sdl;
|
import wrappers.sdl;
|
||||||
|
|
@ -28,19 +29,48 @@ export namespace game
|
||||||
core::Sprite player_sprite_;
|
core::Sprite player_sprite_;
|
||||||
std::vector<core::Sprite> sprites_;
|
std::vector<core::Sprite> sprites_;
|
||||||
|
|
||||||
|
// Tile set and tile map (TODO: tile set should be moved to resource manager in engine)
|
||||||
|
core::TileSet tile_set_;
|
||||||
|
core::TileMap tile_map_;
|
||||||
|
|
||||||
// Private constructor
|
// Private constructor
|
||||||
explicit Game(core::Engine& engine)
|
explicit Game(core::Engine& engine)
|
||||||
: engine_(engine),
|
: engine_(engine),
|
||||||
player_sprite_{
|
player_sprite_{
|
||||||
core::Sprite::create_with_texture(
|
core::Sprite::create_from_texture(
|
||||||
engine_.get_render_server(),
|
engine_.get_render_server(),
|
||||||
"assets/sprites/neocat_64.png",
|
"assets/sprites/neocat_64.png",
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
sdl::FPoint{0, 0},
|
sdl::FPoint{0, 0},
|
||||||
sdl::FPoint{-32, -32}
|
sdl::FPoint{-32, -32}
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
{}
|
tile_set_{
|
||||||
|
core::TileSet::create_from_texture(
|
||||||
|
engine_.get_render_server(),
|
||||||
|
"assets/tilesets/terrain.png",
|
||||||
|
32,
|
||||||
|
{
|
||||||
|
// Tile 0: No tile
|
||||||
|
{0, 0},
|
||||||
|
// Tile 1: Actual first tile in the texture
|
||||||
|
{0, 0},
|
||||||
|
{1, 0},
|
||||||
|
{2, 0},
|
||||||
|
{3, 0},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
tile_map_(0, 32, 16, 12, {50, 50})
|
||||||
|
{
|
||||||
|
// Initialize tile map with example data
|
||||||
|
for (auto x = 0; x < 10; x++) {
|
||||||
|
for (auto y = 0; y < 10; y++) {
|
||||||
|
const auto tile_index = 2 + (x + y) % 2;
|
||||||
|
tile_map_.set_tile(x, y, tile_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Game() = delete;
|
Game() = delete;
|
||||||
|
|
@ -75,7 +105,7 @@ export namespace game
|
||||||
|
|
||||||
if (event->type == sdl::EventType::MouseButtonUp) {
|
if (event->type == sdl::EventType::MouseButtonUp) {
|
||||||
sprites_.push_back(
|
sprites_.push_back(
|
||||||
core::Sprite::create_with_texture(
|
core::Sprite::create_from_texture(
|
||||||
engine_.get_render_server(),
|
engine_.get_render_server(),
|
||||||
"assets/sprites/neofox_64.png",
|
"assets/sprites/neofox_64.png",
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
|
|
@ -98,6 +128,10 @@ export namespace game
|
||||||
const auto& render_server = engine_.get_render_server();
|
const auto& render_server = engine_.get_render_server();
|
||||||
render_server.start_frame();
|
render_server.start_frame();
|
||||||
|
|
||||||
|
// Render tilemap
|
||||||
|
tile_map_.draw(render_server, tile_set_);
|
||||||
|
|
||||||
|
// Render sprites
|
||||||
for (const core::Sprite& sprite : sprites_) {
|
for (const core::Sprite& sprite : sprites_) {
|
||||||
sprite.draw(render_server);
|
sprite.draw(render_server);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue