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
Sprite và animation

Sprite là component: gắn njin::transform và njin::sprite vào một entity, engine tự vẽ nó mỗi frame. Không cần viết system vẽ.

#include <njin.h>
namespace {
// Sprite sheet 4 cột, mỗi frame 32x32: hàng 0 là đứng yên (4 frame),
// hàng 1 là chạy (6 frame, tràn sang hàng 2).
constexpr njin::vec2 frame_size{32, 32};
entt::entity hero = entt::null;
void spawn(njin::context &ctx) {
entt::registry &reg = njin::world(ctx);
const njin::texture_handle sheet = njin::texture_load(ctx, "assets/hero.png");
hero = reg.create();
reg.emplace<njin::transform>(hero, njin::transform{.pos = {100, 100}, .scale = 3});
reg.emplace<njin::sprite>(hero, njin::sprite{.texture = sheet,
.origin = {0.5f, 1.0f}, // chân nhân vật
.layer = 10});
reg.emplace<njin::sprite_anim>(
hero, njin::sprite_anim{.frame_size = frame_size, .first = 0, .count = 4, .fps = 6});
// Một cái cây ở lớp thấp hơn: vẽ trước, nằm sau nhân vật.
const entt::entity tree = reg.create();
reg.emplace<njin::transform>(tree, njin::transform{.pos = {160, 100}, .scale = 3});
reg.emplace<njin::sprite>(tree, njin::sprite{.texture = sheet,
.source = {{0, 96}, {32, 32}},
.origin = {0.5f, 1.0f},
.layer = 5});
}
void control(njin::context &ctx) {
entt::registry &reg = njin::world(ctx);
auto &tr = reg.get<njin::transform>(hero);
auto &spr = reg.get<njin::sprite>(hero);
auto &anim = reg.get<njin::sprite_anim>(hero);
njin::f32 dir = 0;
dir -= 1;
dir += 1;
tr.pos.x += dir * 120.0f * njin::delta(ctx);
if (dir != 0)
spr.flip_x = dir < 0; // quay mặt theo hướng đi
// Gọi mỗi frame được: chỉ bắt đầu lại khi đổi sang animation khác.
if (dir != 0)
njin::anim_play(anim, 4, 6, 12.0f);
else
njin::anim_play(anim, 0, 4, 6.0f);
}
void setup(njin::context &ctx) {
}
} // namespace
njin::mod_desc sprites_module() { return {.name = "sprites", .setup = setup}; }
void anim_play(sprite_anim &anim, i32 first, i32 count, f32 fps, bool loop=true)
Đổi sang một animation khác trong cùng sprite sheet.
Definition _comps.h:104
const char * tr(const context &ctx, const char *key)
Chuỗi đã dịch của key trong ngôn ngữ đang dùng.
bool key_pressed(const context &ctx, key_code key)
Đúng ở frame mà phím vừa được nhấn xuống (chỉ một frame).
bool key_held(const context &ctx, key_code key)
Đúng khi phím đã được giữ từ frame trước và vẫn đang giữ.
@ key_d
Phím D.
Definition _types.h:347
@ key_a
Phím A.
Definition _types.h:344
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.
entt::registry & world(context &ctx)
Trả về registry của EnTT chứa mọi entity và component của game.
@ phase_update
Mỗi frame, logic chính của game.
Definition _mod.h:35
@ phase_startup
Một lần, trước frame đầu tiên.
Definition _mod.h:32
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
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.
float f32
Số thực 32 bit.
Definition _types.h:28
Mô tả một module: một nhóm system có tên.
Definition _mod.h:67
Animation theo frame từ một sprite sheet.
Definition _comps.h:83
Ảnh vẽ tại vị trí của entity.
Definition _comps.h:44
Định danh của một texture, tạo bởi texture_load().
Definition _types.h:146
Vị trí, góc xoay và tỉ lệ của một entity trong thế giới.
Definition _comps.h:14
Vector 2 chiều. Dùng cho vị trí, kích thước, độ dời.
Definition _types.h:39

Component sprite

Trường Ý nghĩa
texture Ảnh cần vẽ
source Vùng trong ảnh (pixel). Kích thước 0 là cả ảnh
origin Điểm neo theo tỉ lệ: {0.5, 0.5} là tâm (mặc định), {0.5, 1} là giữa đáy
tint Màu nhân vào ảnh. a < 1 là trong suốt
layer Lớp vẽ: lớp nhỏ vẽ trước, lớp lớn đè lên
flip_x, flip_y Lật ảnh
visible Ẩn tạm mà không cần gỡ component

Sprite dùng cả ba trường của njin::transform: pos là nơi điểm neo rơi vào, rot là góc xoay quanh điểm neo, scale là tỉ lệ.

Thứ tự vẽ

Module sprite của engine vẽ trong phase_render:

  1. Mọi sprite, tilemap và particle, theo layer tăng dần. Cùng lớp thì tilemap vẽ trước, rồi sprite, rồi particle.
  2. Sau đó mới đến các system vẽ của game trong phase_render, nên chúng đè lên sprite.

Cùng một lớp, thứ tự giữa các sprite là thứ tự tạo entity.

Animation

Thêm njin::sprite_anim để chạy animation từ một sprite sheet: một ảnh chứa các frame cùng kích thước, đánh số từ 0 theo hàng từ trái sang phải rồi từ trên xuống.

Trường Ý nghĩa
frame_size Kích thước một frame
first, count Animation dùng frame từ first đến first + count - 1
fps Số frame mỗi giây
loop Lặp lại khi hết
playing Đặt false để dừng ở frame hiện tại
finished Đã chạy hết (chỉ khi không lặp)

Engine chuyển frame trong phase_post_update và ghi vào sprite.source.

Cần thời lượng riêng cho từng frame, nạp từ Aseprite, hay máy trạng thái idle/run/jump thì dùng njin::animator, xem Animation và máy trạng thái. Nháy trắng khi trúng đòn: njin::sprite_flash(), xem Particle và hiệu ứng.

Đổi animation bằng njin::anim_play(). Hàm này không làm gì nếu animation đó đang chạy, nên gọi mỗi frame được mà animation không bị bắt đầu lại liên tục.

Animation dùng thời gian đã nhân tốc độ, nên chậm lại theo njin::time_set_scale() và đứng yên khi tạm dừng.