UI của njin là kiểu immediate mode: không tạo đối tượng nút, chỉ gọi hàm mỗi frame trong phase_post_render. Nút nào được gọi thì hiện, và hàm trả về true ở frame nó được bấm.
#include <njin.h>
namespace {
enum class screen { main, settings, none };
screen open = screen::main;
bool fullscreen = false;
void startup(njin::context &ctx) {
}
void menus(njin::context &ctx) {
if (open == screen::main) {
open = screen::none;
open = screen::settings;
} else if (open == screen::settings) {
njin::ui_begin(ctx, {.id =
"settings", .title =
"Cài đặt", .width = 480});
if (reset) {
volume = 0.8f;
difficulty = 1;
}
open = screen::main;
}
}
void setup(njin::context &ctx) {
}
}
njin::mod_desc ui_menu_module() {
return {.name =
"ui_menu", .setup = setup}; }
vec2 screen_size(const context &ctx)
Trả về kích thước màn hình mà game vẽ lên, tính bằng pixel.
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_startup
Một lần, trước frame đầu tiên.
Definition _mod.h:32
@ phase_post_render
Mỗi frame, vẽ trong không gian màn hình (UI).
Definition _mod.h:39
font_handle font_load(context &ctx, const char *path, i32 size=0, font_style style=font_smooth)
Nạp một font TrueType/OpenType, kèm sẵn các ký tự tiếng Việt.
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
bool ui_slider(context &ctx, const char *label, f32 &value, f32 min, f32 max, f32 step=0.0f, bool percent=false)
Một thanh trượt.
void ui_begin(context &ctx, const ui_panel_desc &desc={})
Bắt đầu một panel.
bool ui_back(context &ctx)
true ở frame người chơi bấm quay lại (Esc, Backspace, nút B) trong lúc có panel đang hiện.
void ui_end(context &ctx)
Kết thúc panel và vẽ nó.
void ui_style_set(context &ctx, const ui_style &style)
Đặt style cho mọi widget vẽ sau đó.
ui_style ui_default_style()
Giao diện mặc định: panel tối trong suốt, nút bo góc, màu nhấn xanh.
bool ui_toggle(context &ctx, const char *label, bool &value)
Một công tắc bật/tắt.
bool ui_choice(context &ctx, const char *label, i32 &index, std::initializer_list< const char * > options)
Chọn một trong nhiều lựa chọn bằng trái/phải hoặc bấm: độ khó, độ phân giải, ngôn ngữ.
bool ui_button(context &ctx, const char *label, bool enabled=true)
Một nút bấm.
void ui_row(context &ctx, i32 columns)
Xếp columns widget tiếp theo thành một hàng ngang, chia đều bề rộng.
void window_set_fullscreen(context &ctx, bool fullscreen)
Bật hoặc tắt toàn màn hình.
void quit(context &ctx)
Yêu cầu thoát game.
Mô tả một module: một nhóm system có tên.
Definition _mod.h:67
Toàn bộ giao diện của UI: phông chữ, kích thước, diện mạo từng loại widget, âm thanh.
Definition njin_ui.h:54
f32 scale
Nhân mọi kích thước. Đặt theo chiều cao màn hình để UI co giãn.
Definition njin_ui.h:57
font_handle font
Phông chữ. Mặc định là phông của engine.
Definition njin_ui.h:55
f32 y
Thành phần dọc (trong không gian màn hình, y tăng khi đi xuống).
Definition _types.h:41
Menu có nhiều widget hoặc hay đổi thì dựng bằng kéo thả trong njin_ui_editor rồi nạp file vào game: xem UI editor: dựng menu bằng kéo thả.
Menu chính của game mẫu Mầm Leo Núi, dựng bằng njin::ui_button(). Nút đang chọn có viền xanh; đổi nút bằng phím, chuột hoặc tay cầm
Widget
| Hàm | Là gì | Trả về true khi |
| njin::ui_begin(), njin::ui_end() | Một panel: nền, tiêu đề, xếp widget từ trên xuống | |
| njin::ui_button() | Nút bấm, có thể tắt (enabled = false) | Được bấm |
| njin::ui_toggle() | Công tắc bật/tắt | Giá trị đổi |
| njin::ui_slider() | Thanh trượt, hiện số hoặc phần trăm | Giá trị đổi |
| njin::ui_choice() | Chọn một trong nhiều mục bằng trái/phải | Lựa chọn đổi |
| njin::ui_progress() | Thanh tiến độ: máu, thời gian nạp | |
| njin::ui_progress_circle() | Vòng tiến độ: hồi chiêu, nạp đạn. Tùy chỉnh độ dày, màu, góc bắt đầu, chiều chạy, đầu bo tròn, chữ giữa | |
| njin::ui_label(), njin::ui_image(), njin::ui_space() | Chữ, ảnh, khoảng trống | |
| njin::ui_last_rect() | Khung của widget vừa đặt, để vẽ thêm lên nó (sau njin::ui_end()) | |
| njin::ui_row() | Xếp vài widget tiếp theo thành một hàng ngang | |
| njin::ui_back() | | Người chơi bấm quay lại |
Nhãn là định danh của widget trong panel. Hai widget cùng chữ thì thêm hậu tố ẩn sau ##: "Xóa##1", "Xóa##2" (phần sau ## không được vẽ).
Panel đặt theo anchor (điểm trên màn hình, theo tỉ lệ) và pivot (điểm của panel), nên {.anchor = {1, 0}, .pivot = {1, 0}} là góc trên phải. Chiều cao tự tính theo nội dung.
HUD luôn hiện trong lúc chơi (thanh tài nguyên, dãy nút chiêu mộ) thì đặt .navigable = false: nút của nó chỉ bấm bằng chuột, không bao giờ được chọn sẵn, và mũi tên, Enter, Space, Esc vẫn thuộc về game. Muốn vẽ thêm lên một widget (hình trong nút, bản đồ nhỏ trong một khoảng njin::ui_space()) thì lấy khung của nó bằng njin::ui_last_rect() và vẽ sau njin::ui_end().
Vòng tiến độ
njin::ui_progress_circle() nhận một njin::ui_circle_desc. Mọi trường đều tùy chọn; màu có alpha 0 thì lấy từ style của game (track, fill, panel.text), nên một vòng không chỉnh gì vẫn hợp với theme.
.round_caps = true, .fill = {0.95f, 0.35f, 0.25f, 1.0f}, .percent = true});
void ui_progress_circle(context &ctx, const ui_circle_desc &desc)
Một thanh tiến độ hình tròn, không bấm được: hồi chiêu, nạp đạn, máu dạng vòng.
| Trường | Tác dụng |
| diameter, thickness | Đường kính ngoài và độ dày vòng. Độ dày từ nửa đường kính trở lên thì thành hình tròn đặc |
| start_angle, clockwise | Vòng bắt đầu ở đâu (độ, 0 là đỉnh, 90 là 3 giờ) và đầy dần theo chiều nào |
| round_caps | Hai đầu phần đầy bo tròn |
| show_track, track, fill | Vòng nền có vẽ không, màu vòng nền, màu phần đầy |
| text, percent, text_color | Chữ giữa vòng; hoặc percent để hiện "75%" |
Widget chiếm một dòng cao bằng diameter và được căn giữa; trong njin::ui_row() nó thu nhỏ vừa cột.
Chuột, bàn phím, tay cầm
Mọi widget dùng được bằng cả ba, không cần viết thêm gì:
| Việc | Chuột | Bàn phím | Tay cầm |
| Chọn | Trỏ vào | Mũi tên lên/xuống (trái/phải trong hàng) | D-pad hoặc cần trái |
| Bấm | Click | Enter, Space | A (nút mặt dưới) |
| Đổi slider, choice | Kéo, click | Trái/phải | Trái/phải |
| Quay lại | | Esc, Backspace | B (nút mặt phải) |
Giữ một hướng thì lựa chọn tự lặp. Đi lên từ widget trên cùng thì vòng xuống dưới cùng. Widget bị tắt được bỏ qua. Vừa mở menu, widget đầu tiên được chọn sẵn cho tay cầm; muốn chọn nút khác thì gọi njin::ui_focus() lúc mở.
Trong lúc có panel đang hiện, UI giữ các phím điều hướng (mũi tên, Enter, Space, Esc, Backspace) và click chuột lên panel: game không thấy chúng, nên nhân vật không chạy khi người chơi đang chọn menu. Kiểm tra bằng njin::ui_active(). UI chỉ nuốt nút trái; game bấm chuột phải hay cuộn bánh xe vào thế giới thì hỏi njin::ui_mouse_over() trước, để cú bấm lên HUD không lọt xuống.
- Ghi chú
- Esc mặc định đóng cửa sổ (config::exit_key). Menu dùng Esc để quay lại thì đặt .exit_key = njin::key_none trong config và thoát bằng một nút "Thoát".
Popup
njin::ui_popup() là hộp thoại xác nhận: nền tối, tiêu đề, nội dung tự xuống dòng và tối đa 4 nút. Gọi nó mỗi frame khi popup đang mở; biến open do game giữ và popup tự đặt về false khi đóng.
#include <njin.h>
namespace {
bool quit_popup_open = false;
bool has_unsaved = true;
void save_game(njin::context &ctx) {
has_unsaved = false;
}
void menus(njin::context &ctx) {
save_game(ctx);
quit_popup_open = true;
if (quit_popup_open) {
ctx,
{.id = "quit",
.title = "Thoát game?",
.message = has_unsaved ? "Tiến trình chưa lưu sẽ mất." : "Hẹn gặp lại!",
.buttons = {"Ở lại", "Lưu và thoát", "Thoát"},
.default_button = 0,
.cancel_button = 0},
quit_popup_open);
if (pick == 1) {
save_game(ctx);
} else if (pick == 2) {
}
}
}
bool settings_open = false;
void settings_popup(njin::context &ctx) {
if (!settings_open)
return;
settings_open = false;
}
void setup(njin::context &ctx) {
}
}
njin::mod_desc ui_popup_toast_module() {
return {.name =
"ui_popup_toast", .setup = setup}; }
void ui_popup_begin(context &ctx, const ui_popup_desc &desc)
Bắt đầu một popup có nội dung tùy ý: làm tối nền, mở panel modal.
void ui_toast(context &ctx, const char *text, const ui_toast_desc &desc={})
Hiện một thông báo nhỏ ở góc màn hình, tự biến mất: "Đã lưu game", "Nhặt được5 vàng".
void ui_popup_end(context &ctx)
Kết thúc popup bắt đầu bằng ui_popup_begin().
i32 ui_popup(context &ctx, const ui_popup_desc &desc, bool &open)
Một popup xác nhận: nền tối, tiêu đề, nội dung và vài nút.
@ ui_toast_success
Việc vừa làm thành công.
Definition njin_ui.h:317
Hàm trả về số thứ tự nút vừa bấm (theo buttons), hoặc -1 nếu chưa có gì. Quay lại (Esc, Backspace, nút B) đóng popup và trả về cancel_button, hoặc -1 nếu không đặt.
Popup là modal:
- Các panel khác vẫn được vẽ, nhưng không nhận chuột, phím hay tay cầm cho đến khi popup đóng. Menu phía sau không cần ẩn đi, và không cần if/else giữa các màn hình.
- njin::ui_back() chỉ báo cho popup, nên cùng một cú Esc không vừa đóng popup vừa quay lại menu.
- Khi mở, nút default_button được chọn sẵn: đặt nó là nút an toàn ("Ở lại", "Hủy") để bấm Enter nhầm không mất dữ liệu. Khi đóng, lựa chọn trở lại đúng widget cũ của menu phía sau.
- Frame popup vừa mở bỏ qua nút quay lại. Nhờ vậy menu mở popup bằng Esc (if (ui_back(ctx))
open = true;) không bị popup đóng ngay bởi chính cú Esc đó.
- Cú bấm vừa đóng popup không rơi xuống widget phía sau.
Cần nội dung tùy ý (thanh trượt, công tắc...) thì dùng njin::ui_popup_begin() và njin::ui_popup_end() bao quanh các widget bình thường, xem settings_popup trong ví dụ. Chỉ id, title và width của njin::ui_popup_desc được dùng; tự đóng popup bằng cách ngừng gọi nó.
Màu phủ làm tối nền là ui_style::dim. Panel của popup dùng chung diện mạo ui_style::panel.
Toast
njin::ui_toast() hiện một thông báo nhỏ ở góc màn hình rồi tự biến mất: "Đã lưu game", "Nhặt được 5
vàng", "Mất kết nối".
@ ui_toast_warning
Cảnh báo.
Definition njin_ui.h:318
Gọi từ bất cứ đâu, ở bất cứ phase nào (kể cả trong phase_update hay trong một event handler). Không cần ui_begin: engine tự xếp hàng, trượt vào, mờ dần và vẽ đè lên mọi thứ, kể cả popup, trừ hiệu ứng chuyển scene. Chữ dài tự xuống dòng theo ui_style::toast_width.
| Tính chất | Chi tiết |
| Không nuốt phím | Khác panel của UI: toast chỉ để đọc, game phía dưới vẫn nhận mũi tên, Enter, Esc và chuột |
| Giờ thật | Đếm theo njin::delta_real(): vẫn chạy và tự hết hạn khi game đang pause hay hitstop |
| Loại | njin::ui_toast_info, _success, _warning, _error đổi màu vạch bên trái (ui_style::toast_accent) |
| Số lượng | Tối đa ui_style::toast_max (mặc định 5); cái cũ nhất bị bỏ khi đầy |
| Vị trí | ui_style::toast_anchor: {1, 1} là góc dưới phải (mặc định), {0.5, 0} là giữa cạnh trên. Toast mới nhất nằm sát góc, cái cũ xếp dần vào trong |
| Diện mạo | ui_style::toast: màu, bo góc, ảnh 9-slice và shader như mọi widget khác |
njin::ui_toast_clear() xóa hết toast đang hiện, ví dụ khi đổi scene.
- Ghi chú
- Nếu dựng toast bằng ui_begin, panel đó sẽ nuốt phím điều hướng của game mỗi khi nó hiện, và nhân vật đứng khựng. Dùng njin::ui_toast().
Tùy biến giao diện
Toàn bộ giao diện nằm trong njin::ui_style: phông, cỡ chữ, scale, khoảng cách, và diện mạo (njin::ui_look) của từng loại widget: panel, label, button, track, fill, knob, toast. Lấy bản mặc định bằng njin::ui_default_style(), sửa, rồi njin::ui_style_set(). Đổi style giữa hai panel được, ví dụ một hộp thoại cảnh báo màu đỏ.
Mỗi njin::ui_look có bốn mặt (njin::ui_skin) cho bốn trạng thái: normal, focused, pressed, disabled, và màu chữ tương ứng. Một mặt vẽ bằng:
- màu phẳng: color, bo góc roundness, viền outline;
- ảnh: texture (và source nếu dùng một vùng trong atlas), nhân màu color;
- ảnh 9-slice: thêm border: bốn góc giữ nguyên, cạnh và giữa kéo giãn, nên một khung vẽ nhỏ dùng được cho nút mọi kích thước.
s.
button.
focused = {.texture = frame, .border = 6, .color = {1.0f, 0.9f, 0.6f, 1.0f}};
shader_handle shader_load(context &ctx, const char *vspath, const char *fspath)
Nạp và biên dịch một shader.
texture_handle texture_load(context &ctx, const char *path)
Nạp một texture từ file ảnh.
Định danh của một texture, tạo bởi texture_load().
Definition _types.h:146
ui_skin normal
Bình thường.
Definition njin_ui.h:42
ui_skin focused
Đang được chọn (chuột trỏ vào, hoặc bàn phím / tay cầm chọn tới).
Definition njin_ui.h:43
shader_handle shader
Shader tùy chỉnh cho mặt widget. Có thể để trống.
Definition njin_ui.h:49
ui_look button
Nút; cũng là nền dòng của toggle, slider, choice.
Definition njin_ui.h:65
Shader. ui_look::shader được bật khi vẽ các mặt của widget. Engine tự đặt các uniform sau nếu shader khai báo chúng (không khai báo thì bỏ qua, không cảnh báo):
| Uniform | Kiểu | Giá trị |
| uiState | float | 0 thường, 1 đang chọn, 2 đang nhấn, 3 bị tắt |
| uiTime | float | Giây từ lúc chạy, cho hiệu ứng động |
| uiRect | vec4 | x, y, rộng, cao của widget, pixel màn hình |
| uiValue | float | Tiến độ 0..1 của slider hay thanh tiến độ |
Ví dụ nút phát sáng nhấp nháy khi được chọn:
#version 330
in vec2 fragTexCoord;
in vec4 fragColor;
uniform sampler2D texture0;
uniform vec4 colDiffuse;
uniform float uiState;
uniform float uiTime;
out vec4 finalColor;
void main() {
vec4 c = texture(texture0, fragTexCoord) * colDiffuse * fragColor;
float glow = uiState >= 1.0 ? 0.35 + 0.1 * sin(uiTime * 6.0) : 0.0;
finalColor = vec4(c.rgb + vec3(glow, glow * 0.6, 0.0), c.a);
}
Âm thanh. sound_move, sound_accept, sound_back trong style phát khi chuyển lựa chọn, khi bấm hoặc đổi giá trị, và khi quay lại.
Phông. Phông mặc định của engine (JetBrains Mono, nhúng sẵn) đã có chữ tiếng Việt. Muốn phông khác, nạp bằng njin::font_load() và đặt vào ui_style::font (xem Vẽ hình và chữ).
UI vẽ trong không gian màn hình, sau post-processing, nên hiệu ứng như blur hay CRT không làm mờ menu (xem Hậu kỳ (post-processing); njin::post::paused() hợp với menu pause).