diff --git a/README.md b/README.md index 95d25e6..91113cb 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ IMX6U 运行时性能预算较紧,后续开发必须遵守 `docs/DEVELOPMENT_G 项目后续按“两个游戏 + 一个启动器 + 一套底层图形库”组织,详细边界见 `docs/APP_AND_GFX_ARCHITECTURE.md`: -- `Gfx` / 底层图形库:只提供 framebuffer、SDL2/fb0 平台适配、输入/时间源、基础绘制能力,例如点、线、矩形、四边形、sprite、tile。 +- `Gfx` / 底层图形库:只提供 framebuffer、SDL2/fb0 平台适配、输入/时间源、基础绘制能力,例如点、线、矩形、四边形、sprite、bitmap font、tilemap。 - `Apps/Launcher`:负责游戏选择、全局设置和启动流程。 - `Apps/GameA`、`Apps/GameB`:分别维护自己的游戏规则、状态、资源和渲染调用。 - `Shared`:只放应用层共享但不属于底层图形库的 UI、存档、配置、资源索引等。 @@ -244,7 +244,9 @@ IMX6U-Game/ ## 模块说明 ### Draw2D -- **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer,对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_text`、`present` 接口 +- **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer,对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_sprite_region`、`draw_text`、`draw_tilemap`、`present` 接口 +- **SpriteRegion**:描述 atlas 中的子区域,`draw_sprite_region` / `draw_sprite_region_ex` 可直接绘制子图,底层复用 `draw_sprite_ex` +- **Tilemap**:使用 `uint16_t` tile id 引用 atlas 中的固定大小 tile,`draw_tilemap` 按视口可见范围遍历 tile,并在视口边缘做像素级裁剪 ### Core - **FrameBuffer**:CPU 侧颜色缓冲,渲染结果先写在这里 @@ -269,7 +271,7 @@ IMX6U-Game/ - 可旋转立方体的 3D 渲染(MVP 变换、背面剔除、扫描线填充、深度测试) - 双平台显示后端(SDL2 / Framebuffer) - 离线资源转换工具:PNG sprite -> C++ 头文件,像素字体 -> bitmap atlas/header -- 基础 2D sprite 与 bitmap font 文本绘制,当前 demo 显示 FPS 文本和测试 sprite +- 基础 2D sprite、SpriteRegion、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap - Gfx 目录规范化,代码收敛到 `src/Gfx/` - `Gfx::DrawContext` 统一绘制入口,封装现有绘制能力 - C++11 兼容代码 @@ -279,7 +281,7 @@ IMX6U-Game/ 1. FrameBuffer 性能优化(`memset` 清屏、去掉 `at()`、定点数/NEON) 2. 应用层拆分(Launcher / GameA / GameB / Shared)和统一 `IApp` 主循环 3. SDL2 输入抽象(键盘/触摸/按键状态快照) -4. Gfx 基础 2D 绘制接口(矩形、四边形、Tilemap,继续完善 Sprite/Text 的裁剪和批处理) +4. Gfx 基础 2D 绘制接口(矩形、四边形、继续完善 Sprite/Text/Tilemap 的批处理和专用快路径) 5. 纹理贴图、OBJ 模型加载与完整光照 ## 说明 diff --git a/docs/APP_AND_GFX_ARCHITECTURE.md b/docs/APP_AND_GFX_ARCHITECTURE.md index 344f25c..3c0c462 100644 --- a/docs/APP_AND_GFX_ARCHITECTURE.md +++ b/docs/APP_AND_GFX_ARCHITECTURE.md @@ -52,7 +52,7 @@ IMX6U-Game/ 职责: - 管理 framebuffer、depthbuffer、渲染上下文。 -- 提供基础绘制接口:点、线、矩形、四边形、三角形、sprite、tile、简单文本等。 +- 提供基础绘制接口:点、线、矩形、四边形、三角形、sprite、SpriteRegion、tilemap、简单文本等。 - 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。 - 封装 SDL2 / framebuffer 显示提交、输入轮询、时间源。 - 使用离线转换后的紧凑资源数据(例如 PNG 转头文件、bitmap font atlas),运行时不直接依赖 PNG/TTF 解码。 @@ -186,16 +186,20 @@ namespace Gfx void draw_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color); void fill_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color); void draw_sprite(int32_t x, int32_t y, const RenderData::Image& image); + void draw_sprite_region(int32_t x, int32_t y, + const RenderData::SpriteRegion& region); void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y, RenderData::Color color, const char* text); + void draw_tilemap(const RenderData::Tilemap& tilemap, + int32_t screen_x, int32_t screen_y, + int32_t viewport_w, int32_t viewport_h, + int32_t camera_x, int32_t camera_y); }; } ``` 后续再扩展: -- `draw_sprite_region` -- `draw_tilemap` - `set_clip_rect` - `set_camera_2d` - `batch sprite/tile` @@ -218,7 +222,7 @@ namespace Gfx 1. ~~先抽出统一 `IApp` 和 `AppManager`,让当前 demo 成为一个 app。~~ 2. ~~把 SDL2 初始化、输入、present 固定在平台层,应用层不直接碰 SDL。~~ -3. ~~建立 `Gfx::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**(`Gfx::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_text、present) +3. ~~建立 `Gfx::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**(`Gfx::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_sprite_region、draw_text、draw_tilemap、present) 4. ~~底层代码迁移到 `src/Gfx/`,Demo 入口迁移到 `src/Apps/Demo/`。~~ **已完成** 5. 新增 Launcher app,只做最小菜单和应用切换。 6. 新增 GameA/GameB 空壳,验证三应用切换。 @@ -241,3 +245,15 @@ namespace Gfx - `tools/gen_font_atlas.py` 将共享像素字体转为 ASCII bitmap atlas,并输出同名 PNG 预览和 C++ 头文件。 - 生成头文件、源 PNG/TTF 和转换脚本应一起纳入仓库,保证资源可追溯、可再生成。 - 生成数据目前面向简单直接的调试/小型游戏资源;后续如果资源体积增长,应再评估 1-bit/8-bit mask、RLE 或自定义资源包格式。 + +## 10. SpriteRegion 与 Tilemap 约定 + +- `RenderData::SpriteRegion` 只描述某张 atlas 中的子区域,不拥有像素数据;它通过 `const Image* atlas` 引用源图。 +- `DrawContext::draw_sprite_ex` 是底层 sprite 绘制入口,负责源区域检查、目标屏幕裁剪、scale 和 flip;`draw_sprite_region` 系列只是对 atlas 子区域的语义包装。 +- `RenderData::Tilemap` 使用 `uint16_t` tile id 保存地图网格,`Tilemap::EmptyTile` (`0xFFFF`) 表示空 tile。 +- `Tilemap` 当前只支持一个 atlas、固定 tile 宽高和固定 `atlas_columns`;tile id 通过 `tile_id % atlas_columns` / `tile_id / atlas_columns` 映射到 atlas 中的源区域。 +- `DrawContext::draw_tilemap` 的裁剪分两层: + - tilemap 层按 tile 计算需要尝试绘制的可见范围; + - sprite 层按像素裁剪每个 tile,支持 camera 像素级滚动时显示半个 tile。 +- 带 `viewport_w` / `viewport_h` 的 `draw_tilemap` 重载用于子视口绘制,`screen_x` / `screen_y` 是视口左上角;旧重载默认视口延伸到 framebuffer 右下角。 +- 当前实现优先保证清晰语义和可验证行为;后续性能优化可增加 tile region 查表、不透明 tile 行拷贝、chunk/dirty rect 或专用 tile 快路径。 diff --git a/docs/DEVELOPMENT_GUIDELINES.md b/docs/DEVELOPMENT_GUIDELINES.md index a63bd52..3506faa 100644 --- a/docs/DEVELOPMENT_GUIDELINES.md +++ b/docs/DEVELOPMENT_GUIDELINES.md @@ -121,7 +121,8 @@ - `tools/gen_font_atlas.py`:像素字体 TTF -> ASCII bitmap font atlas/header。 - 生成像素格式统一为 `(R << 24) | (G << 16) | (B << 8) | A`,与 `RenderData::Color::to_rgba()` 和 `Core::FrameBuffer` 当前格式保持一致。 - 源资源、生成脚本和生成头文件应同时提交,保证资源可追溯、可复现。 -- 运行时绘制 sprite/font 时只做裁剪、透明判断、颜色替换或必要的像素拷贝;不要在绘制函数内做文件 IO、图片解码、字体栅格化或动态分配。 +- 运行时绘制 sprite/font/tilemap 时只做裁剪、透明判断、颜色替换、tile id 查表或必要的像素拷贝;不要在绘制函数内做文件 IO、图片解码、字体栅格化或动态分配。 +- Tilemap 绘制应按视口可见范围遍历 tile,不能每帧无条件扫描整张地图;视口边缘允许通过 sprite 像素裁剪显示半个 tile。 - 字体资源当前走像素风路径,生成端会把 alpha 阈值化为 0/255;如果未来要恢复抗锯齿字体,必须同步设计 framebuffer alpha blending,而不能只在绘制端把所有非 0 alpha 当作实心像素。 ## 9. 新代码提交前检查清单 diff --git a/src/Apps/Demo/main.cpp b/src/Apps/Demo/main.cpp index 52b6955..e251a37 100644 --- a/src/Apps/Demo/main.cpp +++ b/src/Apps/Demo/main.cpp @@ -126,6 +126,16 @@ int main(int argc, char *argv[]) font.columns = font_columns; font.first_char = font_first_char; + RenderData::Image sprite_img(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000); + RenderData::SpriteRegion sprite_region(&sprite_img, 0, 0, sprite_img.width, sprite_img.height); + + const std::array tileIds = { + 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, + RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, + 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, + RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0}; + RenderData::Tilemap testTilemap(tileIds.data(), 8, 4, &sprite_img, sprite_img.width, sprite_img.height, 1); + Scene::Camera camera; camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f); camera.transform.rotation = Math::Vector3(0.0f, 3.1415926535f, 0.0f); @@ -253,11 +263,10 @@ int main(int argc, char *argv[]) } // sprite 测试 - RenderData::Image sprite_img(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000); - ctx.draw_sprite(10, 10, sprite_img); - ctx.draw_sprite_ex(30, 10, sprite_img, 0, 0, sprite_img.width, sprite_img.height, 2, false, false); - ctx.draw_sprite_ex(10, 30, sprite_img, 0, 0, sprite_img.width, sprite_img.height, 3, true, false); + ctx.draw_sprite_region_ex(30, 10, sprite_region, 2, false, false); + ctx.draw_sprite_region_ex(10, 30, sprite_region, 3, true, false); + ctx.draw_tilemap(testTilemap, 650, 500, 96, 48, static_cast(display->get_time_ms() / 20) % 32, 0); // FPS 计数 ++frame_count; diff --git a/src/Gfx/Draw2D/DrawContext.cpp b/src/Gfx/Draw2D/DrawContext.cpp index 1284d66..b6d9317 100644 --- a/src/Gfx/Draw2D/DrawContext.cpp +++ b/src/Gfx/Draw2D/DrawContext.cpp @@ -60,52 +60,197 @@ namespace Gfx draw_sprite_region(dst_x, dst_y, img, 0, 0, img.width, img.height); } + void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region) + { + draw_sprite_region(dst_x, dst_y, region); + } + void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img, int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h) { draw_sprite_ex(dst_x, dst_y, img, src_x, src_y, src_w, src_h, 1, false, false); } + void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region) + { + draw_sprite_region_ex(dst_x, dst_y, region, 1, false, false); + } + + void DrawContext::draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region, + int32_t scale, bool flip_h, bool flip_v) + { + if (!region.atlas) return; + + draw_sprite_ex( + dst_x, + dst_y, + *region.atlas, + region.x, + region.y, + region.width, + region.height, + scale, + flip_h, + flip_v); + } + void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img, int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h, int32_t scale, bool flip_h, bool flip_v) { - if (scale < 1 || !img.pixels) return; + if (scale < 1 || !img.pixels || src_w <= 0 || src_h <= 0) return; + if (src_x < 0 || src_y < 0 || src_x + src_w > img.width || src_y + src_h > img.height) return; const int32_t img_w = img.width; + const int32_t screen_w = frameBuffer->get_width(); + const int32_t screen_h = frameBuffer->get_height(); + const int32_t draw_w = src_w * scale; + const int32_t draw_h = src_h * scale; + + if (dst_x >= screen_w || dst_y >= screen_h || dst_x + draw_w <= 0 || dst_y + draw_h <= 0) return; + + int32_t start_dx = 0; + int32_t start_dy = 0; + int32_t end_dx = draw_w; + int32_t end_dy = draw_h; + + if (dst_x < 0) start_dx = -dst_x; + if (dst_y < 0) start_dy = -dst_y; + if (dst_x + end_dx > screen_w) end_dx = screen_w - dst_x; + if (dst_y + end_dy > screen_h) end_dy = screen_h - dst_y; + const uint32_t* src = img.pixels; const bool has_key = img.has_color_key; const uint32_t key = img.color_key; - for (int32_t sy = 0; sy < src_h; ++sy) + if (scale == 1) { + for (int32_t sy = start_dy; sy < end_dy; ++sy) + { + const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy); + const uint32_t* row = src + read_y * img_w; + const int32_t dst_y_abs = dst_y + sy; + + for (int32_t sx = start_dx; sx < end_dx; ++sx) + { + const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx); + const uint32_t pixel = row[read_x]; + + if (has_key && pixel == key) continue; + + frameBuffer->set_pixel(dst_x + sx, dst_y_abs, pixel); + } + } + return; + } + + for (int32_t dy_abs = start_dy; dy_abs < end_dy; ++dy_abs) + { + const int32_t sy = dy_abs / scale; const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy); const uint32_t* row = src + read_y * img_w; + const int32_t dst_y_abs = dst_y + dy_abs; - for (int32_t sx = 0; sx < src_w; ++sx) + for (int32_t dx_abs = start_dx; dx_abs < end_dx; ++dx_abs) { + const int32_t sx = dx_abs / scale; const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx); const uint32_t pixel = row[read_x]; if (has_key && pixel == key) continue; - const int32_t base_x = dst_x + sx * scale; - const int32_t base_y = dst_y + sy * scale; + frameBuffer->set_pixel(dst_x + dx_abs, dst_y_abs, pixel); + } + } + } - if (scale == 1) - { - frameBuffer->set_pixel(base_x, base_y, pixel); - } - else - { - for (int32_t dy = 0; dy < scale; ++dy) - { - for (int32_t dx = 0; dx < scale; ++dx) - { - frameBuffer->set_pixel(base_x + dx, base_y + dy, pixel); - } - } - } + void DrawContext::draw_tilemap(const RenderData::Tilemap& tilemap, + int32_t screen_x, int32_t screen_y, + int32_t camera_x, int32_t camera_y) + { + draw_tilemap(tilemap, + screen_x, + screen_y, + frameBuffer->get_width() - screen_x, + frameBuffer->get_height() - screen_y, + camera_x, + camera_y); + } + + void DrawContext::draw_tilemap(const RenderData::Tilemap& tilemap, + int32_t screen_x, int32_t screen_y, + int32_t viewport_w, int32_t viewport_h, + int32_t camera_x, int32_t camera_y) + { + if (!tilemap.tiles || !tilemap.atlas || !tilemap.atlas->pixels) return; + if (tilemap.width <= 0 || tilemap.height <= 0) return; + if (tilemap.tile_w <= 0 || tilemap.tile_h <= 0 || tilemap.atlas_columns <= 0) return; + if (viewport_w <= 0 || viewport_h <= 0) return; + + const int32_t viewport_left = screen_x; + const int32_t viewport_top = screen_y; + const int32_t viewport_right = screen_x + viewport_w; + const int32_t viewport_bottom = screen_y + viewport_h; + + int32_t start_tile_x = camera_x / tilemap.tile_w; + int32_t start_tile_y = camera_y / tilemap.tile_h; + int32_t offset_x = -(camera_x % tilemap.tile_w); + int32_t offset_y = -(camera_y % tilemap.tile_h); + + if (camera_x < 0 && camera_x % tilemap.tile_w != 0) + { + --start_tile_x; + offset_x = -camera_x - (-start_tile_x * tilemap.tile_w); + } + if (camera_y < 0 && camera_y % tilemap.tile_h != 0) + { + --start_tile_y; + offset_y = -camera_y - (-start_tile_y * tilemap.tile_h); + } + + const int32_t visible_cols = viewport_w / tilemap.tile_w + 2; + const int32_t visible_rows = viewport_h / tilemap.tile_h + 2; + + for (int32_t row = 0; row < visible_rows; ++row) + { + const int32_t map_y = start_tile_y + row; + if (map_y < 0 || map_y >= tilemap.height) continue; + + const int32_t dst_y = screen_y + offset_y + row * tilemap.tile_h; + for (int32_t col = 0; col < visible_cols; ++col) + { + const int32_t map_x = start_tile_x + col; + if (map_x < 0 || map_x >= tilemap.width) continue; + + const uint16_t tile_id = tilemap.get_tile(map_x, map_y); + if (tile_id == RenderData::Tilemap::EmptyTile) continue; + + const int32_t src_x = (tile_id % tilemap.atlas_columns) * tilemap.tile_w; + const int32_t src_y = (tile_id / tilemap.atlas_columns) * tilemap.tile_h; + const int32_t dst_x = screen_x + offset_x + col * tilemap.tile_w; + const int32_t tile_right = dst_x + tilemap.tile_w; + const int32_t tile_bottom = dst_y + tilemap.tile_h; + + int32_t clipped_left = dst_x; + int32_t clipped_top = dst_y; + int32_t clipped_right = tile_right; + int32_t clipped_bottom = tile_bottom; + + if (clipped_left < viewport_left) clipped_left = viewport_left; + if (clipped_top < viewport_top) clipped_top = viewport_top; + if (clipped_right > viewport_right) clipped_right = viewport_right; + if (clipped_bottom > viewport_bottom) clipped_bottom = viewport_bottom; + + if (clipped_left >= clipped_right || clipped_top >= clipped_bottom) continue; + + const int32_t clipped_src_x = src_x + (clipped_left - dst_x); + const int32_t clipped_src_y = src_y + (clipped_top - dst_y); + const int32_t clipped_w = clipped_right - clipped_left; + const int32_t clipped_h = clipped_bottom - clipped_top; + + draw_sprite_ex(clipped_left, clipped_top, *tilemap.atlas, + clipped_src_x, clipped_src_y, clipped_w, clipped_h, + 1, false, false); } } } diff --git a/src/Gfx/Draw2D/DrawContext.h b/src/Gfx/Draw2D/DrawContext.h index 7e5e8d2..dcc029f 100644 --- a/src/Gfx/Draw2D/DrawContext.h +++ b/src/Gfx/Draw2D/DrawContext.h @@ -3,6 +3,8 @@ #include "Vector2.h" #include "Triangle.h" #include "Image.h" +#include "SpriteRegion.h" +#include "Tilemap.h" #include "BitmapFont.h" #include @@ -50,11 +52,22 @@ namespace Gfx void draw_triangle(const RenderData::Triangle& triangle, const RenderData::Color& color); void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img); + void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region); void draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img, int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h); + void draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region); + void draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region, + int32_t scale, bool flip_h, bool flip_v); void draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img, int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h, int32_t scale, bool flip_h, bool flip_v); + void draw_tilemap(const RenderData::Tilemap& tilemap, + int32_t screen_x, int32_t screen_y, + int32_t camera_x, int32_t camera_y); + void draw_tilemap(const RenderData::Tilemap& tilemap, + int32_t screen_x, int32_t screen_y, + int32_t viewport_w, int32_t viewport_h, + int32_t camera_x, int32_t camera_y); void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color); void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y, diff --git a/src/Gfx/RenderData/SpriteRegion.h b/src/Gfx/RenderData/SpriteRegion.h new file mode 100644 index 0000000..023c7c7 --- /dev/null +++ b/src/Gfx/RenderData/SpriteRegion.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include "Image.h" + +namespace RenderData +{ + struct SpriteRegion + { + const Image* atlas; + int32_t x; + int32_t y; + int32_t width; + int32_t height; + + SpriteRegion() + : atlas(nullptr), x(0), y(0), width(0), height(0) {} + + SpriteRegion(const Image* atlas, int32_t x, int32_t y, int32_t width, int32_t height) + : atlas(atlas), x(x), y(y), width(width), height(height) {} + }; +} diff --git a/src/Gfx/RenderData/Tilemap.h b/src/Gfx/RenderData/Tilemap.h new file mode 100644 index 0000000..a2dfc1c --- /dev/null +++ b/src/Gfx/RenderData/Tilemap.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include "Image.h" + +namespace RenderData +{ + struct Tilemap + { + static const uint16_t EmptyTile = 0xFFFF; + + const uint16_t* tiles; + int32_t width; + int32_t height; + const Image* atlas; + int32_t tile_w; + int32_t tile_h; + int32_t atlas_columns; + + Tilemap() + : tiles(nullptr), + width(0), + height(0), + atlas(nullptr), + tile_w(0), + tile_h(0), + atlas_columns(0) + { + } + + Tilemap(const uint16_t* tiles, int32_t width, int32_t height, + const Image* atlas, int32_t tile_w, int32_t tile_h, int32_t atlas_columns) + : tiles(tiles), + width(width), + height(height), + atlas(atlas), + tile_w(tile_w), + tile_h(tile_h), + atlas_columns(atlas_columns) + { + } + + uint16_t get_tile(int32_t x, int32_t y) const + { + return tiles[y * width + x]; + } + }; +}