Trang này ghép các phần có sẵn của engine thành một nhân vật platformer: chạy, nhảy có cảm giác tốt, dốc, bục một chiều, bục di chuyển, và camera bám theo. Game mẫu njin_platformer (Game mẫu và đóng gói) dùng đúng những thứ này. Chưa làm gì bao giờ? Bắt đầu với Nhân vật nhảy được trong 50 dòng : một nhân vật nhảy được trong 50 dòng.
Game mẫu njin_platformer: chạy, nhảy trên dốc, nhắc "E" khi lại gần con cú, bụi khi tiếp đất
#include <njin.h>
namespace {
struct game {
entt::entity player = entt::null;
} g;
void startup(njin::context &ctx) {
g.player = reg.create();
.offset = {0.0f, -7.0f},
.layer = layer_player});
body.air_jumps = 1;
body.wall_slide_speed = 55.0f;
body.wall_jump = {150.0f, 300.0f};
.deadzone = {24.0f, 40.0f},
.lookahead = {36.0f, 0.0f},
.pixel_snap = true});
}
void setup(njin::context &ctx) {
}
}
int main() {
njin::context *ctx =
njin::create({.title =
"Platformer", .width = 1280, .height = 720, .target_fps = 60});
}
rect level_bounds(const context &ctx, level_handle level)
Khung của một level trong thế giới, dùng cho camera_follow::bounds.
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,...
constexpr u32 layer_bit(i32 n)
Bit của lớp va chạm thứ n (0..31), để đặt tên lớp cho dễ đọc: constexpr u32 layer_enemy = njin::layer...
Definition njin_collision.h:21
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ổ.
level_handle level_load(context &ctx, const char *path, const level_desc &desc={})
Nạp một bản đồ và tạo entity cho nó.
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::dispatcher & events(context &ctx)
Trả về dispatcher của EnTT để gửi và nhận event giữa các system.
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
std::uint32_t u32
Số nguyên không dấu 32 bit.
Definition _types.h:22
Đị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
Event: một njin::platformer_body vừa chạm đất.
Definition njin_body.h:159
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
Định danh của một level đã nạp, trả về từ level_load().
Definition njin_level.h:20
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
f32 jump_speed
Vận tốc lên lúc bắt đầu nhảy.
Definition njin_body.h:54
Nhân vật: njin::platformer_body
Gắn njin::platformer_body cạnh một njin::transform và một njin::collider hộp. Engine di chuyển nó trong phase_fixed_update bằng collision_move(), rồi ghi lại trạng thái (grounded, on_wall, velocity...). Game chỉ cần cho nó input và đọc trạng thái để chọn animation.
Các cảm giác quen thuộc đều có sẵn, chỉnh bằng số ngay trên component:
| Cảm giác | Trường |
| Coyote time: rời mép rồi vẫn nhảy được | coyote_time |
| Jump buffer: bấm nhảy sớm trước khi chạm đất | jump_buffer |
| Thả nút nhảy sớm thì nhảy thấp | jump_cut |
| Rơi nhanh hơn lúc lên | gravity, fall_gravity, max_fall |
| Nhảy đôi | air_jumps |
| Trượt tường, nhảy khỏi tường | wall_slide_speed, wall_jump |
| Gia tốc trên đất và trên không | ground_accel, air_accel... |
Có hai cách đưa input vào:
- Tự ghi vào body.input mỗi frame (move_x, jump, jump_held, drop). jump là một yêu cầu: engine tự xóa khi đã xử lý, nên bấm ở frame không có nhịp vật lý nào vẫn không bị lỡ.
- Gắn njin::platformer_input_map với một axis và một action: engine đọc chúng giúp bạn.
Muốn đẩy lùi khi trúng đòn thì ghi thẳng vào velocity.
Nhân vật gửi hai event để game rung màn hình, phát tiếng, tạo bụi: njin::body_jumped và njin::body_landed (kèm tốc độ rơi).
Dốc, bục một chiều, ô không va chạm
Hình va chạm của một ô nằm trong njin::tilemap::shapes, đặt bằng tilemap_set_shape():
| njin::tile_shape | Việc làm |
| tile_solid | Chặn mọi phía (mặc định) |
| tile_none | Chỉ để nhìn, dù nằm trong layer vật cản |
| tile_one_way | Bục chỉ đỡ từ trên xuống; nhảy xuyên từ dưới lên |
| tile_slope_r, tile_slope_l | Dốc 45 độ, cao bên phải hoặc bên trái |
| tile_slope_*_low, *_high | Dốc 22.5 độ, hai ô liền nhau |
Khi nạp từ Tiled, đặt thuộc tính chuỗi collision trên ô trong tileset (one_way, slope_r, slope_l_high, none...). Khi nạp từ LDtk, đặt tên giá trị IntGrid, hoặc gắn enum tag / custom data trên tileset bằng cùng những tên đó. Ô dùng cho animation (nước, đuốc) cũng lấy từ tileset, xem Tilemap.
Nhân vật đi lên dốc mượt, và dính vào mặt dốc khi đi xuống thay vì nảy ra khỏi nó. Nhảy xuống khỏi bục một chiều: giữ phím xuống rồi bấm nhảy (input.drop, hoặc platformer_input_map::down).
collision_move() nhận thêm njin::collision_move_opts nếu bạn tự viết bộ điều khiển: drop_through, snap_down, test_only (dò tường hay mặt đất mà không di chuyển). Kết quả có grounded, ground, on_slope.
Bục di chuyển: njin::path_mover
Một entity có transform, collider hộp (thường one_way = true) và njin::path_mover đi theo một đường gấp khúc lặp lại. Engine dời nó bằng collision_move_platform(), chở theo mọi nhân vật đứng trên nó và đẩy những thứ nó đâm vào. Trong Tiled, vẽ bục bằng một object có polyline; các điểm của polyline là các điểm dừng.
Camera bám theo: njin::camera_follow
const entt::entity cam = njin::camera_spawn(ctx, 2.0f);
reg.emplace<njin::camera_follow>(cam, njin::camera_follow{
.target = player, .deadzone = {24, 40}, .lookahead = {36, 0},
.bounds = njin::level_bounds(ctx, level), .pixel_snap = true});
Camera trễ nhẹ (smoothing), đứng yên khi nhân vật đi trong vùng chết (deadzone), nhìn trước theo hướng chạy (lookahead), và không bao giờ lộ ra ngoài level (bounds; level nhỏ hơn màn hình thì nằm giữa). Với pixel art, bật pixel_snap để các ô không rung một pixel. Sau khi dịch chuyển nhân vật xa, đặt started = false để camera nhảy thẳng tới thay vì trượt qua cả level.
Rung màn hình của njin::camera_shake() cộng thêm lúc vẽ, không ảnh hưởng camera_follow.
Kiểm tra bằng inspector
njin_inspector hiện platformer_body (vận tốc, đang đứng đất, tường, các bộ đếm coyote và buffer) và camera_follow của entity đang chọn. Xem Công cụ debug: njin_inspector.