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
Video

Trang này phát video trong game: cutscene đầu màn, đoạn phim kết thúc, màn hình TV hay bảng quảng cáo trong thế giới 3D. Mọi thứ khai báo trong njin_video.h; giải mã bằng pl_mpeg, nằm trong engine.

Cần biết trước: Vẽ ảnh và shader (texture) và Âm thanh (kênh âm lượng).

Chỉ MPEG-1

njin chỉ đọc MPEG-1: file .mpg có hình MPEG-1 và tiếng MP2. Đổi một video khác (mp4, mov, webm...) sang dạng này bằng ffmpeg:

ffmpeg -i in.mp4 -c:v mpeg1video -q:v 4 -c:a mp2 -b:a 192k -f mpeg out.mpg

-q:v từ 2 (đẹp nhất, file lớn) đến 31; 4 là cân bằng. Thêm -vf scale=1280:-2 để thu nhỏ hình, -an để bỏ tiếng. MPEG-1 cũ nhưng giải mã rất nhẹ, không cần thư viện ngoài, và không vướng bản quyền codec.

Mở và phát

const njin::video_handle intro = njin::video_open(ctx, {.path = "videos/intro.mpg"});
// mỗi frame, trong phase_render:
njin::video_draw_fit(ctx, intro, {{0, 0}, njin::window_size(ctx)});
if (njin::video_finished(ctx, intro)) { /* vào game */ }
bool video_finished(const context &ctx, video_handle handle)
Video đã phát hết chưa (không lặp và đã đến cuối): lúc chuyển cảnh sau cutscene.
void video_draw_fit(const context &ctx, video_handle handle, rect area, rgba bars={0.0f, 0.0f, 0.0f, 1.0f})
Vẽ frame hiện tại vừa khít trong area, giữ tỉ lệ khung hình: phần thừa hai bên hay trên dưới tô bars ...
video_handle video_open(context &ctx, const video_desc &desc)
Mở một video.
vec2 window_size(const context &ctx)
Kích thước thật của cửa sổ, pixel, kể cả khi có độ phân giải ảo.
Định danh của một video, mở bởi video_open().
Definition _types.h:293

video_open() mở và phát ngay (đặt play = false để mở rồi đứng ở hình đầu). Engine giải mã video trong phase_post_update theo thời gian thật: không theo time_set_scale(), vẫn chạy khi game pause, nên cutscene không chậm theo slow motion. Hình và tiếng khớp nhau.

Hàm Làm gì
video_play(), video_pause() Phát tiếp, tạm dừng. Phát một video đã hết thì phát lại từ đầu
video_seek() Nhảy tới một giây (hình hiện là frame gần nhất trước chỗ đó)
video_set_loop(), video_set_volume() Lặp, âm lượng riêng
video_time(), video_duration(), video_finished() Đang ở đâu, dài bao lâu, đã hết chưa
video_size(), video_framerate(), video_has_audio() Cỡ hình, frame mỗi giây, có tiếng không
video_close() Đóng, giải phóng hình và tiếng

Tiếng đi qua kênh video_desc::bus (mặc định bus_music) và bus_master, nên thanh âm lượng trong menu cài đặt áp cho nó như mọi âm thanh khác.

Vẽ

  • 2D: video_draw() co giãn hình vào một hình chữ nhật; video_draw_fit() đặt vừa khít một vùng, giữ tỉ lệ, tô dải thừa (dải đen của cutscene).
  • 3D: video_texture() là texture chứa frame hiện tại, dùng như mọi texture: dán lên hình khối qua material3d::texture (bật unlit để màn hình không bị tối theo ánh sáng), hay lên model qua model_material. Texture thuộc về video: đừng texture_unload() nó.

Ví dụ đầy đủ

Cutscene toàn màn hình, Enter để bỏ qua; xong thì vào một căn phòng có TV phát một video lặp.

#include <njin.h>
namespace {
using namespace njin;
video_handle intro; // cutscene đầu game, toàn màn hình
video_handle tv; // video lặp trên màn hình TV trong phòng
bool playing_intro = true;
void load(context &ctx) {
// File phải là MPEG-1 (.mpg); xem lệnh ffmpeg ở njin::video_desc.
intro = video_open(ctx, {.path = "videos/intro.mpg"});
// Tiếng TV ở kênh hiệu ứng, nhỏ hơn; chưa phát cho tới khi hết cutscene.
tv = video_open(ctx, {.path = "videos/news.mpg", .loop = true, .play = false, .bus = bus_sfx, .volume = 0.4f});
}
void update(context &ctx) {
if (!playing_intro)
return;
// Hết video, hay người chơi bấm Enter để bỏ qua: vào game.
if (video_finished(ctx, intro) || key_pressed(ctx, key_enter)) {
video_close(ctx, intro);
playing_intro = false;
video_play(ctx, tv);
}
}
void render(context &ctx) {
if (playing_intro) {
// Vừa khít màn hình, giữ tỉ lệ, dải đen hai bên.
video_draw_fit(ctx, intro, {{0.0f, 0.0f}, window_size(ctx)});
return;
}
begin_3d(ctx, camera3d{.position = {0.0f, 1.6f, 4.0f}, .target = {0.0f, 1.2f, 0.0f}});
draw_cube3d(ctx, {0.0f, 0.4f, 0.0f}, {2.2f, 0.8f, 0.6f}, rgba{0.3f, 0.2f, 0.15f, 1.0f}); // kệ
// Màn hình: frame hiện tại dán lên mặt trước của một hộp mỏng, không nhận ánh sáng.
material3d screen{};
screen.unlit = true;
screen.texture = video_texture(ctx, tv);
material3d_set(ctx, screen);
draw_cube3d(ctx, {0.0f, 1.45f, 0.0f}, {1.6f, 0.9f, 0.05f}, colors::white);
material3d_set(ctx, {});
end_3d(ctx);
}
void setup(context &ctx) {
ecs_register(ctx, phase_startup, load, "load");
ecs_register(ctx, phase_update, update, "update");
ecs_register(ctx, phase_render, render, "render");
}
} // namespace
mod_desc cinema_module() { return {.name = "cinema", .setup = setup}; }
void material3d_set(context &ctx, const material3d &material)
Đặt bề mặt cho các hình 3D vẽ sau lệnh này, đến lần gọi tiếp theo hoặc end_3d().
void begin_3d(context &ctx, const camera3d &camera)
Bắt đầu vẽ 3D qua camera.
void draw_cube3d(const context &ctx, vec3 center, vec3 size, rgba color)
Vẽ hộp đặc, các cạnh song song với trục.
void end_3d(context &ctx)
Kết thúc vẽ 3D và quay về vẽ 2D trong không gian thế giới.
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).
@ key_enter
Enter.
Definition _types.h:412
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.
@ 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
@ phase_render
Mỗi frame, vẽ trong không gian thế giới.
Definition _mod.h:38
@ bus_sfx
Hiệu ứng trong game. Mặc định của sound.
Definition njin_audio.h:20
void video_play(context &ctx, video_handle handle)
Phát tiếp (hoặc phát lại từ đầu nếu đã hết).
void video_close(context &ctx, video_handle handle)
Đóng video, giải phóng texture và tiếng của nó.
texture_handle video_texture(const context &ctx, video_handle handle)
Texture chứa frame hình hiện tại.
constexpr rgba white
Trắng.
Definition _math.h:234
Camera phối cảnh cho begin_3d().
Definition njin_3d.h:15
Bề mặt của các hình 3D vẽ sau material3d_set().
Definition njin_3d.h:143
bool unlit
Bỏ qua ánh sáng, giữ nguyên màu (vệt đạn, tia laze, UI trong thế giới).
Definition njin_3d.h:152
Mô tả một module: một nhóm system có tên.
Definition _mod.h:67
Màu gồm đỏ, xanh lá, xanh dương và alpha.
Definition _types.h:64

Giới hạn

  • Chỉ MPEG-1/MP2. Không đọc được mp4, webm hay mkv trực tiếp: đổi bằng ffmpeg như trên.
  • Giải mã trên CPU: video 720p chạy tốt, 1080p 60 frame/giây tốn một nhân CPU đáng kể trên máy yếu.
  • Tua (video_seek()) dừng ở keyframe gần nhất trước chỗ cần, nên có thể sớm hơn vài phần mười giây.
  • Không có phụ đề: vẽ chữ theo video_time() bằng UI.