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
Prefab và transform cha–con

Prefab

Prefab là mẫu để tạo entity: một hàm dựng có tên, gắn component vào entity vừa tạo. Viết hàm dựng một lần, rồi spawn bao nhiêu lần cũng được, kể cả tìm theo tên.

#include <njin.h>
namespace {
struct health {
njin::i32 hp = 3;
};
struct enemy {};
struct orbit {
njin::f32 speed = 180.0f; // độ mỗi giây
};
// Hàm dựng: entity đã có transform (vị trí spawn) và scene_owned.
void build_sword(njin::context &ctx, entt::entity e) {
njin::world(ctx).emplace<njin::sprite>(
e, njin::sprite{.texture = atlas, .source = {{64, 0}, {16, 32}}, .layer = 11});
}
void build_goblin(njin::context &ctx, entt::entity e) {
entt::registry &reg = njin::world(ctx);
reg.emplace<enemy>(e);
reg.emplace<health>(e, health{.hp = 3});
reg.emplace<njin::sprite>(e, njin::sprite{.texture = atlas,
.source = {{0, 0}, {32, 32}},
.layer = 10});
// Kiếm là con của goblin: đi theo, xoay theo, và bị hủy cùng goblin.
const entt::entity blade = njin::prefab_spawn_child(ctx, sword, e, {.pos = {14, 2}});
reg.emplace<orbit>(blade);
}
void startup(njin::context &ctx) {
atlas = njin::texture_load(ctx, "assets/atlas.png");
sword = njin::prefab_register(ctx, {.name = "sword", .build = build_sword});
njin::prefab_register(ctx, {.name = "goblin", .build = build_goblin});
// Tìm theo tên: tiện khi tên đọc từ file màn chơi.
for (njin::i32 i = 0; i < 5; i++)
njin::prefab_spawn(ctx, "goblin", {.pos = {100.0f + 120.0f * (njin::f32)i, 300}});
}
// Xoay kiếm quanh tay: sửa `local` của child_of, không sửa transform.
void spin(njin::context &ctx) {
const njin::f32 dt = njin::delta(ctx);
for (auto [e, link, o] : njin::world(ctx).view<njin::child_of, orbit>().each())
link.local.rot += o.speed * dt;
}
void setup(njin::context &ctx) {
}
} // namespace
njin::mod_desc prefabs_module() { return {.name = "prefabs", .setup = setup}; }
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
prefab_handle prefab_register(context &ctx, const prefab_desc &desc)
Đăng ký một prefab.
entt::entity prefab_spawn(context &ctx, prefab_handle prefab, const transform &at={})
Tạo một entity từ prefab, đặt tại at.
entt::entity prefab_spawn_child(context &ctx, prefab_handle prefab, entt::entity parent, const transform &local={})
Tạo một entity từ prefab và gắn nó làm con của parent.
texture_handle texture_load(context &ctx, const char *path)
Nạp một texture từ file ảnh.
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::int32_t i32
Số nguyên có dấu 32 bit.
Definition _types.h:14
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
Định danh của một prefab, tạo bởi prefab_register().
Definition _types.h:314
Ả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

njin::prefab_spawn() làm lần lượt:

  1. tạo entity;
  2. gắn njin::transform bằng tham số at;
  3. gắn njin::scene_owned trỏ tới scene đang chạy (nếu có, và nếu prefab_desc::scene_owned không bị tắt), nên rời scene là entity tự mất;
  4. gọi hàm dựng.

Muốn khác đi một chút (máu nhiều hơn, màu khác) thì sửa trên entity trả về:

const entt::entity boss = njin::prefab_spawn(ctx, "goblin", {.pos = {500, 300}, .scale = 3});
njin::world(ctx).get<health>(boss).hp = 50;
Hàm Việc làm
njin::prefab_register() Đăng ký prefab. Tên đã có thì trả về cái cũ
njin::prefab_find() Tìm theo tên
njin::prefab_spawn() Tạo entity từ prefab (handle hoặc tên)
njin::prefab_spawn_child() Tạo entity từ prefab và gắn làm con của entity khác

Transform cha–con

Gắn njin::child_of vào một entity để nó đi theo entity cha: vũ khí trong tay, bánh xe, cái bóng dưới chân, emitter lửa ở đuôi tên lửa.

reg.emplace<njin::child_of>(gun, njin::child_of{.parent = player, .local = {.pos = {12, -4}}});
Gắn entity vào một entity cha: vũ khí trong tay nhân vật, bánh xe của xe.
Definition _comps.h:139

Mỗi frame, trong phase_post_update, module hierarchy của engine ghi đè transform của con bằng transform của cha kết hợp với local (xem njin::transform_combine()):

Của con Được tính
pos cha.pos + xoay(local.pos × cha.scale, cha.rot)
rot cha.rot + local.rot
scale cha.scale × local.scale

Vì vậy:

  • Di chuyển con bằng local, không phải bằng transform của nó (sẽ bị ghi đè).
  • Cha có thể là con của entity khác; engine tính từ gốc xuống.
  • Khi cha bị hủy, con bị hủy theo. Đặt destroy_with_parent = false để con ở lại, tách ra tại vị trí cuối cùng.
  • System trong phase_update đọc transform của con sẽ thấy giá trị của frame trước. Cần chính xác ngay thì tự tính bằng njin::transform_combine().
  • Gắn một entity đang đứng sẵn trong thế giới vào cha mà không làm nó nhảy chỗ: dùng njin::transform_relative() để tính local.

Lật hướng (sprite.flip_x) không tự lật con. Khi nhân vật quay trái, đổi dấu local.pos.x của vũ khí.