njin
Engine 2D gọn nhẹ để tự tay làm game theo ý mình. Vibe coding cũng ổn luôn
Loading...
Searching...
No Matches
_tilemap.h
1#pragma once
2#include "_collide.h"
3#include <array>
4#include <cmath>
5#include <initializer_list>
6#include <string_view>
7#include <unordered_map>
8#include <vector>
9
10namespace njin {
13
15inline constexpr i32 tile_chunk_size = 32;
16
18inline constexpr i32 tile_flip_x = 1 << 29;
20inline constexpr i32 tile_flip_y = 1 << 30;
21
25constexpr i32 tile_id(i32 value) {
26 return value < 0 ? -1 : value & ~(tile_flip_x | tile_flip_y);
27}
28
33struct tile_chunk {
36 i32 filled = 0;
38
39 tile_chunk() { tiles.fill(-1); }
40};
41
43struct cell {
44 i32 x = 0;
45 i32 y = 0;
46};
47
67
71constexpr bool tile_is_slope(tile_shape shape) {
72 return shape >= tile_slope_r && shape <= tile_slope_l_high;
73}
74
79constexpr f32 tile_surface(tile_shape shape, f32 u) {
80 u = u < 0.0f ? 0.0f : (u > 1.0f ? 1.0f : u);
81 switch (shape) {
82 case tile_slope_r: return u;
83 case tile_slope_l: return 1.0f - u;
84 case tile_slope_r_low: return u * 0.5f;
85 case tile_slope_r_high: return 0.5f + u * 0.5f;
86 case tile_slope_l_low: return (1.0f - u) * 0.5f;
87 case tile_slope_l_high: return 0.5f + (1.0f - u) * 0.5f;
88 default: return 1.0f;
89 }
90}
91
100bool tile_shape_from_name(const char *name, tile_shape &out);
101
104struct tile_anim {
105 std::vector<i32> frames;
106 std::vector<f32> durations;
107 f32 total = 0.0f;
108};
109
128struct tilemap {
130 vec2 tile_size{16.0f, 16.0f};
131 f32 margin = 0.0f;
132 f32 spacing = 0.0f;
133 i32 layer = 0;
134 rgba tint{1.0f, 1.0f, 1.0f, 1.0f};
135 bool visible = true;
137 std::unordered_map<u64, tile_chunk> chunks;
141 std::vector<u8> shapes;
143 std::unordered_map<i32, tile_anim> anims;
144};
145
150inline void tilemap_set_shape(tilemap &map, i32 id, tile_shape shape) {
151 if (id < 0)
152 return;
153 if ((usize)id >= map.shapes.size())
154 map.shapes.resize((usize)id + 1, tile_solid);
155 map.shapes[(usize)id] = shape;
156}
157
163inline tile_shape tilemap_shape(const tilemap &map, i32 value) {
164 if (value < 0)
165 return tile_none;
166 const i32 id = tile_id(value);
167 tile_shape s = (usize)id < map.shapes.size() ? (tile_shape)map.shapes[(usize)id] : tile_solid;
168 if ((value & tile_flip_x) != 0 && tile_is_slope(s)) {
169 switch (s) {
170 case tile_slope_r: s = tile_slope_l; break;
171 case tile_slope_l: s = tile_slope_r; break;
172 case tile_slope_r_low: s = tile_slope_l_low; break;
173 case tile_slope_r_high: s = tile_slope_l_high; break;
174 case tile_slope_l_low: s = tile_slope_r_low; break;
175 case tile_slope_l_high: s = tile_slope_r_high; break;
176 default: break;
177 }
178 }
179 return s;
180}
181
188inline void tilemap_animate(tilemap &map, i32 id, const std::vector<i32> &frames, f32 seconds) {
189 if (id < 0 || frames.empty() || seconds <= 0.0f)
190 return;
191 tile_anim anim{frames, std::vector<f32>(frames.size(), seconds), seconds * (f32)frames.size()};
192 map.anims[id] = std::move(anim);
193}
194
199inline i32 tile_anim_frame(const tile_anim &anim, f32 time) {
200 if (anim.frames.empty() || anim.total <= 0.0f)
201 return -1;
202 f32 t = std::fmod(time, anim.total);
203 if (t < 0.0f)
204 t += anim.total;
205 for (usize i = 0; i < anim.frames.size(); i++) {
206 const f32 d = i < anim.durations.size() ? anim.durations[i] : 0.0f;
207 if (t < d)
208 return anim.frames[i];
209 t -= d;
210 }
211 return anim.frames.back();
212}
213
217 bool hit_x = false;
218 bool hit_y = false;
219};
220
225constexpr i32 floor_div(i32 a, i32 b) {
226 return a >= 0 ? a / b : -((-a + b - 1) / b);
227}
228
233constexpr u64 tile_chunk_key(i32 cx, i32 cy) {
234 return ((u64)(u32)cx << 32) | (u64)(u32)cy;
235}
236
240constexpr cell tile_chunk_coord(u64 key) {
241 return {(i32)(u32)(key >> 32), (i32)(u32)(key & 0xffffffffu)};
242}
243
249inline i32 tilemap_get(const tilemap &map, i32 x, i32 y) {
250 const i32 cx = floor_div(x, tile_chunk_size);
251 const i32 cy = floor_div(y, tile_chunk_size);
252 const auto it = map.chunks.find(tile_chunk_key(cx, cy));
253 if (it == map.chunks.end())
254 return -1;
255 const i32 lx = x - cx * tile_chunk_size;
256 const i32 ly = y - cy * tile_chunk_size;
257 return it->second.tiles[(usize)(ly * tile_chunk_size + lx)];
258}
259
265inline void tilemap_set(tilemap &map, i32 x, i32 y, i32 id) {
266 if (id < -1)
267 id = -1;
268 const i32 cx = floor_div(x, tile_chunk_size);
269 const i32 cy = floor_div(y, tile_chunk_size);
270 const u64 key = tile_chunk_key(cx, cy);
271 auto it = map.chunks.find(key);
272 if (it == map.chunks.end()) {
273 if (id < 0)
274 return; // xóa một ô vốn đã trống
275 it = map.chunks.emplace(key, tile_chunk{}).first;
276 }
277 tile_chunk &chunk = it->second;
278 i32 &slot = chunk.tiles[(usize)((y - cy * tile_chunk_size) * tile_chunk_size +
279 (x - cx * tile_chunk_size))];
280 if (slot == id)
281 return;
282 chunk.filled += (id >= 0 ? 1 : 0) - (slot >= 0 ? 1 : 0);
283 slot = id;
284 chunk.version = ++map.revision;
285 if (chunk.filled == 0)
286 map.chunks.erase(it);
287}
288
291inline void tilemap_clear(tilemap &map) {
292 map.chunks.clear();
293 ++map.revision;
294}
295
301struct tile_key {
302 char symbol;
304 bool marker = false;
305};
306
310 char symbol;
312};
313
315inline void tilemap_apply_row(tilemap &map, std::string_view row, i32 y, std::initializer_list<tile_key> legend,
316 cell origin, std::vector<tile_marker> &markers) {
317 for (usize i = 0; i < row.size(); i++) {
318 const char c = row[i];
319 const cell at{origin.x + (i32)i, origin.y + y};
320 const tile_key *key = nullptr;
321 for (const tile_key &k : legend)
322 if (k.symbol == c) {
323 key = &k;
324 break;
325 }
326 if (key != nullptr) {
327 tilemap_set(map, at.x, at.y, key->tile);
328 if (key->marker)
329 markers.push_back({c, at});
330 } else if (c != ' ' && c != '.' && c != '\t') {
331 markers.push_back({c, at});
332 }
333 }
334}
336
361/// @param origin Ô của ký tự đầu tiên. Mặc định `(0, 0)`.
362/// @return Các ký tự đánh dấu, theo thứ tự đọc: từ trên xuống, trái sang phải.
363inline std::vector<tile_marker> tilemap_from_text(tilemap &map, std::string_view text,
364 std::initializer_list<tile_key> legend = {}, cell origin = {}) {
365 std::vector<tile_marker> markers;
366 usize pos = 0;
367 if (text.starts_with("\r\n"))
368 pos = 2;
369 else if (text.starts_with('\n'))
370 pos = 1;
371 i32 y = 0;
372 while (pos < text.size()) {
373 usize end = text.find('\n', pos);
374 if (end == std::string_view::npos)
375 end = text.size();
376 std::string_view row = text.substr(pos, end - pos);
377 if (!row.empty() && row.back() == '\r')
378 row.remove_suffix(1);
379 tilemap_apply_row(map, row, y, legend, origin, markers);
380 pos = end + 1;
381 y++;
382 }
383 return markers;
384}
385
394/// @param origin Ô của ký tự đầu tiên. Mặc định `(0, 0)`.
395/// @return Các ký tự đánh dấu, theo thứ tự đọc.
396inline std::vector<tile_marker> tilemap_from_rows(tilemap &map, std::initializer_list<std::string_view> rows,
397 std::initializer_list<tile_key> legend = {}, cell origin = {}) {
398 std::vector<tile_marker> markers;
399 i32 y = 0;
400 for (const std::string_view row : rows)
401 tilemap_apply_row(map, row, y++, legend, origin, markers);
402 return markers;
403}
404
408/// @param world_pos Điểm trong thế giới.
409/// @return Ô chứa điểm đó.
410inline cell tilemap_cell_at(const tilemap &map, vec2 origin, vec2 world_pos) {
411 const vec2 local = world_pos - origin;
412 return {(i32)std::floor(local.x / map.tile_size.x),
413 (i32)std::floor(local.y / map.tile_size.y)};
414}
415
420/// @param y Hàng.
421/// @return Hình chữ nhật của ô.
422inline rect tilemap_cell_rect(const tilemap &map, vec2 origin, i32 x, i32 y) {
423 return {{origin.x + (f32)x * map.tile_size.x,
424 origin.y + (f32)y * map.tile_size.y},
425 map.tile_size};
426}
427
432/// @param box Hình chữ nhật cần kiểm tra.
433/// @return `true` nếu `box` chồng lên ít nhất một ô không trống.
434inline bool tilemap_overlaps(const tilemap &map, vec2 origin, rect box) {
435 if (box.size.x <= 0.0f || box.size.y <= 0.0f)
436 return false;
437 const cell a = tilemap_cell_at(map, origin, box.pos);
438 // Trừ một chút để cạnh phải/dưới chạm đúng mép ô không bị tính sang ô kế.
439 const cell b =
440 tilemap_cell_at(map, origin, box.pos + box.size - vec2{1e-4f, 1e-4f});
441 for (i32 y = a.y; y <= b.y; y++) {
442 for (i32 x = a.x; x <= b.x; x++) {
443 if (tilemap_get(map, x, y) >= 0)
444 return true;
445 }
446 }
447 return false;
448}
449
464/// @param delta Độ dời mong muốn trong frame này.
465/// @return Vị trí mới và trục nào bị chặn.
466inline move_result tilemap_move(const tilemap &map, vec2 origin, rect box,
467 vec2 delta) {
468 move_result result{box.pos, false, false};
469 // Đẩy `r` ra khỏi mọi ô nó chồng lên, ngược hướng của `step` trên một trục.
470 const auto resolve = [&](rect &r, f32 step, bool horizontal) {
471 const cell a = tilemap_cell_at(map, origin, r.pos);
472 const cell b =
473 tilemap_cell_at(map, origin, r.pos + r.size - vec2{1e-4f, 1e-4f});
474 for (i32 y = a.y; y <= b.y; y++) {
475 for (i32 x = a.x; x <= b.x; x++) {
476 if (tilemap_get(map, x, y) < 0)
477 continue;
478 const rect c = tilemap_cell_rect(map, origin, x, y);
479 if (horizontal) {
480 if (step > 0.0f && r.pos.x + r.size.x > c.pos.x)
481 r.pos.x = c.pos.x - r.size.x;
482 else if (step < 0.0f && r.pos.x < c.pos.x + c.size.x)
483 r.pos.x = c.pos.x + c.size.x;
484 } else {
485 if (step > 0.0f && r.pos.y + r.size.y > c.pos.y)
486 r.pos.y = c.pos.y - r.size.y;
487 else if (step < 0.0f && r.pos.y < c.pos.y + c.size.y)
488 r.pos.y = c.pos.y + c.size.y;
489 }
490 }
491 }
492 };
493
494 rect r{{box.pos.x + delta.x, box.pos.y}, box.size};
495 if (delta.x != 0.0f && tilemap_overlaps(map, origin, r)) {
496 resolve(r, delta.x, true);
497 result.hit_x = true;
498 }
499 r.pos.y += delta.y;
500 if (delta.y != 0.0f && tilemap_overlaps(map, origin, r)) {
501 resolve(r, delta.y, false);
502 result.hit_y = true;
503 }
504 result.pos = r.pos;
505 return result;
506}
508} // namespace njin
i32 tilemap_get(const tilemap &map, i32 x, i32 y)
Ô tại cột x, hàng y.
Definition _tilemap.h:249
i32 tile_anim_frame(const tile_anim &anim, f32 time)
Số thứ tự ô đang hiện của một animation tại thời điểm time.
Definition _tilemap.h:199
void tilemap_animate(tilemap &map, i32 id, const std::vector< i32 > &frames, f32 seconds)
Cho mọi ô số id chạy animation qua các ô frames, mỗi frame seconds giây.
Definition _tilemap.h:188
constexpr i32 tile_flip_y
Bit đánh dấu ô bị lật dọc, cộng vào số thứ tự ô: id | tile_flip_y.
Definition _tilemap.h:20
move_result tilemap_move(const tilemap &map, vec2 origin, rect box, vec2 delta)
Di chuyển một hình chữ nhật trong tilemap, dừng lại khi đụng ô không trống.
Definition _tilemap.h:464
tile_shape
Hình va chạm của một loại ô, dùng bởi collision_move() và collision_raycast() với collider_tiles.
Definition _tilemap.h:55
void tilemap_clear(tilemap &map)
Xóa mọi ô.
Definition _tilemap.h:291
std::vector< tile_marker > tilemap_from_text(tilemap &map, std::string_view text, std::initializer_list< tile_key > legend={}, cell origin={})
Dựng bản đồ từ một chuỗi nhiều dòng, mỗi ký tự là một ô: cách viết bản đồ "kiểu truyền thống",...
Definition _tilemap.h:361
constexpr i32 tile_flip_x
Bit đánh dấu ô bị lật ngang, cộng vào số thứ tự ô: id | tile_flip_x.
Definition _tilemap.h:18
bool tile_shape_from_name(const char *name, tile_shape &out)
Đọc tên một hình ô, như trong thuộc tính của Tiled hay tên giá trị IntGrid của LDtk: solid,...
std::vector< tile_marker > tilemap_from_rows(tilemap &map, std::initializer_list< std::string_view > rows, std::initializer_list< tile_key > legend={}, cell origin={})
Như tilemap_from_text(), nhưng mỗi hàng là một chuỗi riêng.
Definition _tilemap.h:394
void tilemap_set(tilemap &map, i32 x, i32 y, i32 id)
Đặt ô tại cột x, hàng y.
Definition _tilemap.h:265
tile_shape tilemap_shape(const tilemap &map, i32 value)
Hình va chạm của một ô.
Definition _tilemap.h:163
bool tilemap_overlaps(const tilemap &map, vec2 origin, rect box)
Hình chữ nhật có chồng lên ô không trống nào không.
Definition _tilemap.h:432
void tilemap_set_shape(tilemap &map, i32 id, tile_shape shape)
Đặt hình va chạm cho mọi ô có số thứ tự id trong tilemap.
Definition _tilemap.h:150
cell tilemap_cell_at(const tilemap &map, vec2 origin, vec2 world_pos)
Ô chứa một điểm trong thế giới.
Definition _tilemap.h:408
constexpr i32 floor_div(i32 a, i32 b)
Chia làm tròn xuống, đúng cả với số âm: floor_div(-1, 32) == -1.
Definition _tilemap.h:225
constexpr bool tile_is_slope(tile_shape shape)
Ô có phải dốc không.
Definition _tilemap.h:71
constexpr f32 tile_surface(tile_shape shape, f32 u)
Chiều cao mặt dốc tại một điểm trong ô, tính từ đáy ô theo tỉ lệ chiều cao ô.
Definition _tilemap.h:79
rect tilemap_cell_rect(const tilemap &map, vec2 origin, i32 x, i32 y)
Hình chữ nhật của một ô trong thế giới.
Definition _tilemap.h:420
constexpr i32 tile_chunk_size
Số ô mỗi cạnh của một chunk. Một chunk có 32 x 32 ô.
Definition _tilemap.h:15
constexpr i32 tile_id(i32 value)
Số thứ tự ô trong tileset, bỏ các bit lật.
Definition _tilemap.h:25
constexpr u64 tile_chunk_key(i32 cx, i32 cy)
Khóa của chunk tại tọa độ chunk (cx, cy).
Definition _tilemap.h:233
constexpr cell tile_chunk_coord(u64 key)
Tọa độ chunk từ khóa.
Definition _tilemap.h:240
@ tile_slope_l_low
Dốc 22.5 độ cao bên trái, nửa dưới.
Definition _tilemap.h:63
@ tile_slope_l_high
Dốc 22.5 độ cao bên trái, nửa trên.
Definition _tilemap.h:64
@ tile_slope_l
Dốc 45 độ, cao bên trái, thấp bên phải.
Definition _tilemap.h:60
@ tile_solid
Chặn mọi phía. Mặc định.
Definition _tilemap.h:56
@ tile_none
Không va chạm: chỉ để nhìn, dù nằm trong layer vật cản.
Definition _tilemap.h:57
@ tile_slope_r
Dốc 45 độ, thấp bên trái, cao bên phải.
Definition _tilemap.h:59
@ tile_shape_count
Số loại hình. Không phải một hình thật.
Definition _tilemap.h:65
@ tile_one_way
Bục một chiều: chỉ đỡ từ trên xuống, nhảy xuyên từ dưới lên được.
Definition _tilemap.h:58
@ tile_slope_r_low
Dốc 22.5 độ cao bên phải, nửa dưới (0 đến nửa ô).
Definition _tilemap.h:61
@ tile_slope_r_high
Dốc 22.5 độ cao bên phải, nửa trên (nửa ô đến đầy ô).
Definition _tilemap.h:62
f32 delta(const context &ctx)
Thời gian của frame trước, tính bằng giây, đã nhân với tốc độ thời gian.
std::uint8_t u8
Số nguyên không dấu 8 bit.
Definition _types.h:20
std::int32_t i32
Số nguyên có dấu 32 bit.
Definition _types.h:14
std::size_t usize
Số nguyên không dấu cỡ con trỏ, dùng cho kích thước và chỉ số.
Definition _types.h:34
std::uint64_t u64
Số nguyên không dấu 64 bit.
Definition _types.h:23
float f32
Số thực 32 bit.
Definition _types.h:28
std::uint32_t u32
Số nguyên không dấu 32 bit.
Definition _types.h:22
@ row
Xếp hàng ngang cho các widget tiếp theo.
Definition njin_ui_layout.h:27
Vị trí một ô trong lưới. Có thể âm.
Definition _tilemap.h:43
i32 x
Cột.
Definition _tilemap.h:44
i32 y
Hàng.
Definition _tilemap.h:45
Kết quả của tilemap_move().
Definition _tilemap.h:215
bool hit_y
Bị chặn theo trục dọc.
Definition _tilemap.h:218
bool hit_x
Bị chặn theo trục ngang.
Definition _tilemap.h:217
vec2 pos
Vị trí góc trên trái mới của vật.
Definition _tilemap.h:216
Hình chữ nhật thẳng trục: pos là góc trên trái, size là rộng và cao.
Definition _math.h:13
vec2 pos
Góc trên trái.
Definition _math.h:14
vec2 size
Chiều rộng (x) và chiều cao (y).
Definition _math.h:15
Màu gồm đỏ, xanh lá, xanh dương và alpha.
Definition _types.h:64
Định danh của một texture, tạo bởi texture_load().
Definition _types.h:146
Animation của một loại ô: nước, đuốc, cỏ lay.
Definition _tilemap.h:104
std::vector< i32 > frames
Số thứ tự ô của từng frame trong tileset.
Definition _tilemap.h:105
std::vector< f32 > durations
Thời lượng từng frame, giây.
Definition _tilemap.h:106
f32 total
Tổng thời lượng, tính sẵn.
Definition _tilemap.h:107
Một khối 32 x 32 ô của tilemap.
Definition _tilemap.h:33
u32 version
Tăng mỗi lần chunk đổi, để engine biết cần vẽ lại.
Definition _tilemap.h:37
i32 filled
Số ô không trống. Chunk về 0 thì bị xóa.
Definition _tilemap.h:36
std::array< i32,(usize) tile_chunk_size *tile_chunk_size > tiles
Các ô theo hàng, -1 là ô trống.
Definition _tilemap.h:35
Một dòng trong bảng ký tự của bản đồ chữ: ký tự này đặt ô nào.
Definition _tilemap.h:301
char symbol
Ký tự trong bản đồ chữ.
Definition _tilemap.h:302
i32 tile
Số thứ tự ô đặt cho ký tự này. -1 là xóa ô (ô trống).
Definition _tilemap.h:303
bool marker
true: ngoài việc đặt ô, còn báo vị trí ký tự này về cho game.
Definition _tilemap.h:304
Vị trí của một ký tự mà bản đồ chữ chỉ dùng để đánh dấu: người chơi, kẻ địch, đồng xu.
Definition _tilemap.h:309
cell at
Ô của nó, kể cả origin đã cộng vào.
Definition _tilemap.h:311
char symbol
Ký tự đã gặp.
Definition _tilemap.h:310
Lưới ô vuông không giới hạn kích thước, vẽ từ một tileset, chia thành chunk.
Definition _tilemap.h:128
texture_handle tileset
Ảnh tileset.
Definition _tilemap.h:129
std::unordered_map< i32, tile_anim > anims
Animation theo số thứ tự ô gốc. Đặt bằng tilemap_animate().
Definition _tilemap.h:143
std::unordered_map< u64, tile_chunk > chunks
Các chunk có ô, theo khóa từ tile_chunk_key().
Definition _tilemap.h:137
std::vector< u8 > shapes
Hình va chạm theo số thứ tự ô: shapes[id].
Definition _tilemap.h:141
rgba tint
Màu nhân vào mọi ô.
Definition _tilemap.h:134
f32 spacing
Khoảng giữa hai ô liền nhau trong ảnh tileset, pixel.
Definition _tilemap.h:132
vec2 tile_size
Kích thước một ô, tính bằng pixel.
Definition _tilemap.h:130
f32 margin
Khoảng từ mép ảnh tileset đến ô đầu tiên, pixel.
Definition _tilemap.h:131
u32 revision
Tăng mỗi lần có ô đổi.
Definition _tilemap.h:138
i32 layer
Lớp vẽ, cùng thang với sprite::layer.
Definition _tilemap.h:133
bool visible
Ẩn khi vẽ.
Definition _tilemap.h:135
Vector 2 chiều. Dùng cho vị trí, kích thước, độ dời.
Definition _types.h:39
f32 y
Thành phần dọc (trong không gian màn hình, y tăng khi đi xuống).
Definition _types.h:41
f32 x
Thành phần ngang.
Definition _types.h:40