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 <optional>
|
||||
#include <string>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
export module core.drawing.sprite;
|
||||
|
||||
|
|
@ -47,7 +46,7 @@ export namespace core
|
|||
assert(texture_boundaries_.w > 0 && texture_boundaries_.h > 0);
|
||||
}
|
||||
|
||||
static Sprite create_with_texture(
|
||||
static Sprite create_from_texture(
|
||||
RenderServer& render_server,
|
||||
const std::string& filename,
|
||||
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 <memory>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
export module core.engine;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ module;
|
|||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
export module game.game;
|
||||
|
||||
import core.drawing.sprite;
|
||||
import core.drawing.tile_map;
|
||||
import core.drawing.tile_set;
|
||||
import core.engine;
|
||||
import core.render_server;
|
||||
import wrappers.sdl;
|
||||
|
|
@ -28,19 +29,48 @@ export namespace game
|
|||
core::Sprite player_sprite_;
|
||||
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
|
||||
explicit Game(core::Engine& engine)
|
||||
: engine_(engine),
|
||||
player_sprite_{
|
||||
core::Sprite::create_with_texture(
|
||||
core::Sprite::create_from_texture(
|
||||
engine_.get_render_server(),
|
||||
"assets/sprites/neocat_64.png",
|
||||
std::nullopt,
|
||||
sdl::FPoint{0, 0},
|
||||
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:
|
||||
Game() = delete;
|
||||
|
|
@ -75,7 +105,7 @@ export namespace game
|
|||
|
||||
if (event->type == sdl::EventType::MouseButtonUp) {
|
||||
sprites_.push_back(
|
||||
core::Sprite::create_with_texture(
|
||||
core::Sprite::create_from_texture(
|
||||
engine_.get_render_server(),
|
||||
"assets/sprites/neofox_64.png",
|
||||
std::nullopt,
|
||||
|
|
@ -98,6 +128,10 @@ export namespace game
|
|||
const auto& render_server = engine_.get_render_server();
|
||||
render_server.start_frame();
|
||||
|
||||
// Render tilemap
|
||||
tile_map_.draw(render_server, tile_set_);
|
||||
|
||||
// Render sprites
|
||||
for (const core::Sprite& sprite : sprites_) {
|
||||
sprite.draw(render_server);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue