Trang này dựng một nhân vật platformer chạy và nhảy được trên một bản đồ ô vuông, với dưới 50 dòng code (45 dòng, không tính dòng trống và dòng chú thích). Nó dùng ba thứ: njin::tilemap làm mặt đất, njin::platformer_body làm nhân vật, và njin::platformer_input_map nối phím vào nhân vật.
#include <njin.h>
namespace {
using namespace njin;
void startup(context &ctx) {
entt::registry ® =
world(ctx);
for (
i32 x = -10; x < 40; x++) {
}
for (
i32 x = 8; x < 12; x++)
const entt::entity level = reg.create();
reg.emplace<
tilemap>(level, std::move(map));
const entt::entity player = reg.create();
reg.emplace<
collider>(player,
collider{.size = {10.0f, 14.0f}, .offset = {0.0f, -7.0f}});
}
void draw_player(context &ctx) {
});
}
void setup(context &ctx) {
}
}
int main() {
context *ctx =
create({.title =
"Nhảy", .width = 960, .height = 540, .target_fps = 60});
}
entt::entity camera_spawn(context &ctx, f32 zoom=1.0f, vec2 pos={})
Tạo một entity camera đang dùng (transform, njin::camera_2d, njin::camera_on) nhìn vào pos,...
@ collider_tiles
Mọi ô không trống của njin::tilemap trên cùng entity là vật cản.
Definition njin_collision.h:29
void run(context &ctx)
Chạy vòng lặp chính cho đến khi cửa sổ đóng.
context * create(const config &cfg)
Mở cửa sổ và trả về context của engine.
void destroy(context *ctx)
Giải phóng mọi tài nguyên của engine và đóng cửa sổ.
void draw_rect(const context &ctx, rect r, rgba color)
Vẽ hình chữ nhật đặc.
const char * tr(const context &ctx, const char *key)
Chuỗi đã dịch của key trong ngôn ngữ đang dùng.
constexpr rect rect_from_center(vec2 center, vec2 size)
Tạo hình chữ nhật từ tâm và kích thước.
Definition _math.h:228
void ecs_register(context &ctx, sys_phase phase, sys_fnc fnc, const char *name=nullptr)
Thêm một system vào lịch chạy của một phase.
void mod_register(context &ctx, const mod_desc &desc)
Chạy setup của module và đưa các system của nó vào lịch chạy.
entt::registry & world(context &ctx)
Trả về registry của EnTT chứa mọi entity và component của game.
@ phase_startup
Một lần, trước frame đầu tiên.
Definition _mod.h:32
@ phase_render
Mỗi frame, vẽ trong không gian thế giới.
Definition _mod.h:38
void texture_set_filter(context &ctx, texture_handle handle, texture_filter filter)
Đổi cách lấy mẫu của texture.
texture_handle texture_load(context &ctx, const char *path)
Nạp một texture từ file ảnh.
@ filter_nearest
Giữ nguyên từng pixel, sắc cạnh. Dùng cho pixel art.
Definition njin_draw.h:198
void tilemap_set(tilemap &map, i32 x, i32 y, i32 id)
Đặt ô tại cột x, hàng y.
Definition _tilemap.h:265
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
@ 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
std::int32_t i32
Số nguyên có dấu 32 bit.
Definition _types.h:14
constexpr rgba yellow
Vàng.
Definition _math.h:239
Định danh của một action nhập liệu, tạo bởi action_register().
Definition _types.h:85
Định danh của một axis nhập liệu, tạo bởi axis_register().
Definition _types.h:93
Cho camera bám theo một entity: mượt, có vùng chết, nhìn trước theo hướng chạy, và không bao giờ lộ r...
Definition njin_camera.h:31
Hình va chạm của một entity.
Definition njin_collision.h:46
vec2 offset
Độ lệch của tâm so với transform.pos.
Definition njin_collision.h:50
vec2 size
Kích thước hình hộp.
Definition njin_collision.h:48
Nhân vật platformer: chạy, nhảy, rơi, trèo dốc, đứng trên bục di chuyển.
Definition njin_body.h:44
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
Bấm mũi tên trái/phải để chạy, Space để nhảy. Nhân vật rơi xuống đất, chạy được, nhảy được, và nhảy xuyên từ dưới lên bục rồi đứng lên trên nó.
Chạy sang phải, giữ Space để nhảy xuyên bục rồi đứng lên trên nó (hình chữ nhật vàng là nhân vật)
Giữ Space thì nhảy cao, nhả sớm thì nhảy thấp: đây là jump_cut của njin::platformer_body. Vì vậy chỉ gõ nhẹ Space thì nhân vật không lên nổi bục cao 32 pixel.
Chuẩn bị
Cần một ảnh tileset gồm các ô 16 x 16 tại assets/tiles.png. Dùng luôn ảnh của game mẫu: src/games/platformer/assets/tiles.png (ô 0 là đất có cỏ, ô 1 là đất, ô 3 là bục). Thư mục assets phải nằm cạnh file exe: gọi njin_add_assets(my_game assets) trong CMake để nó được copy mỗi lần build (xem Bắt đầu và Cửa sổ, file và lưu game).
Từng bước
1. Phím: axis và action
const njin::axis_handle move = njin::axis_define(ctx, "move", {{njin::key_left, njin::key_right}});
const njin::action_handle jump = njin::action_define(ctx, "jump", {njin::key_space, njin::pad_face_down});
Một axis là một trục từ -1 đến 1, ở đây gộp hai phím thành trục ngang. Một action là một tên logic ("jump") gắn với một hoặc nhiều nguồn: phím, nút chuột, nút tay cầm. Game không hỏi "phím
Space có đang bấm không" mà hỏi "action `jump` có đang bấm không", nên sau này đổi phím hay thêm tay cầm chỉ là thêm một phần tử vào danh sách. Muốn thêm phím A/D cho axis: {{key_left, key_right},
{key_a, key_d}}, và thêm cần analog: njin::axis_define(ctx, "move", {...}, {njin::pad_axis_left_x}). Xem Nhập liệu.
2. Bản đồ: njin::tilemap
njin::tilemap_set(map, x, y, id) đặt ô (x, y) thành ô số id của tileset. Không cần khai báo kích thước bản đồ: đặt ô ở đâu, bản đồ mở rộng tới đó. Ô có thể ở tọa độ âm.
Bản đồ cần hai thứ nữa để nhân vật đứng lên được:
- Gắn một njin::collider có shape = collider_tiles lên entity của bản đồ. Khi đó mọi ô không trống thành vật cản.
- njin::tilemap_set_shape() cho ô số 3 hình tile_one_way: một bục một chiều, chỉ đỡ từ trên xuống, còn nhảy từ dưới lên thì xuyên qua. Hình va chạm đặt theo số thứ tự ô nên áp dụng cho mọi ô loại đó. Ngoài ra còn có dốc, xem Làm game platformer.
transform của entity là góc trên trái của ô (0, 0), nên ở đây ô hàng 10 có mặt trên nằm ở y = 160.
3. Nhân vật: njin::platformer_body
Nhân vật là một entity có ba component:
platformer_body{} với mọi giá trị mặc định đã cho một cú nhảy tốt: có coyote time (rời mép vẫn nhảy được một chút), jump buffer (bấm nhảy sớm một chút vẫn được tính), và nhảy thấp khi thả nút sớm. Engine di chuyển nó trong phase_fixed_update nên kết quả không phụ thuộc FPS.
njin::platformer_input_map nối axis move và action jump vào body: engine đọc chúng mỗi frame và ghi body.input giúp bạn. Không cần viết một system nào để xử lý phím.
4. Camera: njin::camera_follow
njin::camera_spawn(ctx, 3.0f) tạo camera phóng to 3 lần (hợp với pixel art), và njin::camera_follow cho nó bám theo target. Xem Camera.
5. Vẽ nhân vật
Chưa có sprite, nên nhân vật là một hình chữ nhật vàng vẽ trong phase_render (không gian thế giới, đi qua camera). njin::rect_from_center() đổi tâm và kích thước thành hình chữ nhật. Khi có ảnh nhân vật, đổi thành njin::sprite và njin::sprite_anim, xem Sprite và animation.
Chỉnh cảm giác nhảy
Mọi thông số nằm ngay trên njin::platformer_body. Tạo body, sửa, rồi emplace:
njin::platformer_body body{};
body.jump_speed = 350.0f; // nhảy cao hơn
body.air_jumps = 1; // nhảy đôi
body.wall_slide_speed = 55.0f; // trượt tường
body.wall_jump = {150.0f, 300.0f}; // nhảy khỏi tường
reg.emplace<njin::platformer_body>(player, body);
Độ cao cú nhảy xấp xỉ jump_speed² / (2 * gravity): với giá trị mặc định (300 và 1000) là chừng 45 pixel, gần ba ô (đo thực tế được 42 pixel vì vật lý đi theo nhịp rời rạc). Bục ở hàng 8 cao hơn mặt đất 32 pixel nên nhảy lên được; đặt nó cao hơn 42 pixel thì phải tăng jump_speed.
Bước tiếp theo