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
Màn hình ảo, hẹn giờ và tween

Màn hình ảo cho pixel art

Game pixel art nên vẽ trên một màn hình nhỏ cố định (320 x 180) rồi phóng lên, để mọi pixel to bằng nhau ở mọi cỡ cửa sổ.

#include <njin.h>
int main() {
// Pixel art vẽ trên màn 320 x 180, engine phóng ra cửa sổ theo bội số nguyên:
// pixel sắc nét ở mọi cỡ cửa sổ, phần thừa là viền.
njin::context *ctx = njin::create({.title = "Pixel art",
.width = 1280,
.height = 720,
.target_fps = 60,
.resizable = true,
.virtual_size = {320.0f, 180.0f},
.integer_scale = true});
// Bật tắt lúc chạy được: window_set_virtual_size(*ctx, {0, 0}) để vẽ thẳng lên cửa sổ.
njin::window_set_bar_color(*ctx, {0.05f, 0.05f, 0.08f, 1.0f});
// screen_size() trả 320 x 180 và mouse_pos() tính theo pixel ảo.
// window_size() và window_viewport() cho cỡ thật của cửa sổ.
}
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 window_set_bar_color(context &ctx, rgba color)
Màu viền quanh màn hình ảo.

Khi bật, mọi thứ đi qua màn hình ảo: thế giới, UI, toast, chuyển scene. Code của game không cần biết cửa sổ to bao nhiêu:

  • screen_size() trả về cỡ ảo, không đổi khi kéo cửa sổ.
  • mouse_pos() và mouse_delta() tính theo pixel ảo, nên bấm chuột trúng nút dù cửa sổ bị phóng.
  • Ảnh được phóng không làm mượt. integer_scale chỉ phóng 1, 2, 3... lần (viền dày hơn nhưng pixel đều); tắt thì phóng vừa khít cửa sổ.
  • window_size() và window_viewport() cho cỡ thật của cửa sổ và vùng ảnh chiếm, khi cần.

Với camera, zoom = 2 trên màn hình ảo 640 x 360 cho một thế giới 320 x 180. Kết hợp với camera_follow::pixel_snap (xem Làm game platformer).

Hẹn giờ theo entity

njin::timer trong _tween.h là bộ đếm bạn tự giữ và tick. Với việc "sau 0.5 giây thì làm X", có các hàm không cần biến đếm:

#include <njin.h>
// Hẹn giờ và tween gắn với entity: không cần tự giữ biến đếm.
void on_pickup(njin::context &ctx, entt::entity coin, entt::entity player) {
entt::registry &reg = njin::world(ctx);
const njin::vec2 pos = reg.get<njin::transform>(coin).pos;
// Bay lên rồi mờ đi, xong thì tự hủy.
njin::tween_move(ctx, coin, pos + njin::vec2{0.0f, -18.0f}, 0.35f, njin::ease::out_cubic);
njin::tween_tint(ctx, coin, {1, 1, 1, 0}, 0.35f, njin::ease::linear,
{.done = [coin](njin::context &c) {
if (njin::world(c).valid(coin))
njin::world(c).destroy(coin);
}});
// Sau 1.5 giây tải lại màn. Người chơi bị hủy thì hẹn giờ tự hủy theo.
njin::timer_after(ctx, 1.5f, [](njin::context &c) { njin::scene_fade(c, njin::scene_find(c, "play")); },
{.owner = player});
// Một giá trị bất kỳ: thanh máu tụt từ từ.
njin::tween_value(ctx, 1.0f, 0.4f, 0.5f, [](njin::context &, njin::f32 value) { (void)value; },
}
// Nhịp lặp: sinh quái mỗi 3 giây, đúng 5 lần.
void start_wave(njin::context &ctx) {
njin::timer_every(ctx, 3.0f, [](njin::context &c) { (void)c; }, 5);
}
// Nút bấm nảy lên rồi thu về: tween lặp qua lại (yoyo).
void pulse(njin::context &ctx, entt::entity button) {
njin::tween_scale(ctx, button, 1.15f, 0.12f, njin::ease::out_quad, {.repeat = 1, .yoyo = true});
}
int main() {
njin::context *ctx = njin::create({.title = "Timers", .width = 640, .height = 360, .target_fps = 60});
(void)on_pickup;
(void)start_wave;
(void)pulse;
}
entt::registry & world(context &ctx)
Trả về registry của EnTT chứa mọi entity và component của game.
scene_handle scene_find(const context &ctx, const char *name)
Tìm scene theo tên.
void scene_fade(context &ctx, scene_handle scene, const scene_transition &transition={})
Chuyển sang scene khác với hiệu ứng mờ dần (fade).
tween_handle tween_tint(context &ctx, entt::entity entity, rgba to, f32 seconds, ease curve=ease::linear, const tween_desc &desc={})
Đổi sprite.tint của entity tới to (cả độ trong suốt).
timer_handle timer_every(context &ctx, f32 interval, std::function< void(context &)> fn, i32 count=-1, const timer_desc &desc={})
Gọi fn mỗi interval giây: sinh quái theo nhịp, hồi máu từ từ.
tween_handle tween_scale(context &ctx, entt::entity entity, f32 to, f32 seconds, ease curve=ease::out_quad, const tween_desc &desc={})
Đổi transform.scale của entity tới to.
timer_handle timer_after(context &ctx, f32 seconds, std::function< void(context &)> fn, const timer_desc &desc={})
Gọi fn một lần sau seconds giây.
tween_handle tween_move(context &ctx, entt::entity entity, vec2 to, f32 seconds, ease curve=ease::out_quad, const tween_desc &desc={})
Dời transform.pos của entity tới to trong seconds giây.
tween_handle tween_value(context &ctx, f32 from, f32 to, f32 seconds, std::function< void(context &, f32)> apply, ease curve=ease::linear, const tween_desc &desc={}, entt::entity owner=entt::null)
Chạy một giá trị từ from tới to và đưa nó cho apply mỗi frame: tween cho bất cứ thứ gì (âm lượng,...
@ out_cubic
Giảm tốc mạnh.
Definition _tween.h:60
@ linear
Đều.
Definition _tween.h:55
@ out_quad
Giảm tốc nhẹ.
Definition _tween.h:57
float f32
Số thực 32 bit.
Definition _types.h:28
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
Hàm Việc làm
timer_after() Gọi hàm một lần sau một khoảng
timer_every() Gọi mỗi khoảng, mãi mãi hoặc đủ số lần
tween_move(), tween_scale(), tween_rotate(), tween_tint() Chuyển vị trí, tỉ lệ, góc, màu của entity
tween_value() Chuyển một số bất kỳ và đưa cho hàm của bạn mỗi frame
timer_cancel(), tween_cancel(), tween_cancel_all() Hủy

Quy tắc chung:

  • owner: gắn hẹn giờ với một entity. Entity bị hủy thì hẹn giờ tự hủy, nên hàm không bao giờ chạy với entity đã chết. Tween trên entity tự có quy tắc này.
  • Hẹn giờ và tween thuộc về scene đang chạy lúc tạo và bị hủy khi rời scene, trừ khi keep_across_scenes.
  • Theo giờ của game: dừng khi pause và hitstop. real_time = true để chạy theo giờ thật (menu, UI).
  • Tween thay tween cùng loại trên cùng entity: gọi tween_move hai lần thì lần sau thắng.
  • repeat và yoyo cho chuyển động qua lại (nút nảy, đồng xu nhấp nhô).
  • Hàm của hẹn giờ được thêm, hủy entity, tạo hẹn giờ mới thoải mái: chúng chạy ở phase_update trước system của game.