raygui 即时模式 GUI 实战指南:从保留模式做出能点的 C 游戏设置面板
【免费下载链接】raylibA simple and easy-to-use library to enjoy videogames programming项目地址: https://gitcode.com/GitHub_Trending/ra/raylib
用 raylib 开发 C 游戏,设置面板往往是最后一块拼图:控件状态到底存在哪、中文为什么变方块、窗口一拖界面就错位。raylib 生态里的即时模式 GUI 库 raygui 对这些问题给出了直接答案——控件每帧重画,状态只在你自己的变量里。这是一篇从两种 GUI 范式差异切入的 raygui 教程,讲清即时模式 GUI 的原理,并把一个能点的 C 游戏设置面板搭出来。
📋 先看清差异:保留模式与即时模式
两种范式的根本区别只有一条:控件状态由谁保存——保留模式存在控件对象里,即时模式存在你的变量里。
| 维度 | 保留模式(Qt、Win32 等) | 即时模式(raygui) |
|---|---|---|
| 控件生命周期 | 创建一次,长期存在的对象 | 每帧声明一次,帧末即消失 |
| 状态存放 | 控件对象内部 | 你代码里的普通变量 |
| 事件处理 | 回调、消息队列 | 读返回值 + 写回变量指针 |
| 代码形态 | 初始化、绑定、事件函数分离 | 全部写在绘制循环里,和画场景同处 |
即时模式的代价是布局全靠手工:raygui 头文件在 LIMITATIONS 一节明确写着没有自动布局机制,每个控件的位置尺寸都由你传的Rectangle决定。换来的是代码与状态完全同位——界面对应的就是绘制循环里那几行声明,随时可以删了重写,不用维护任何对象树。文件层面也简单:raygui.h单文件即整个库,仓库examples/下各模块目录各放了一份(如examples/shapes/raygui.h),拷一份进你的src/就能开始。
🧩 第一个可运行程序:三种控件搭一个设置面板
最小可用形态靠三条约定:RAYGUI_IMPLEMENTATION全项目只在一个.c文件里定义;每个控件的第一个参数都是它自己的Rectangle;数值通过指针参数写回你的变量。
#include "raylib.h" #define RAYGUI_IMPLEMENTATION // 全项目只在一个 .c 文件里定义 #include "raygui.h" int main(void) { InitWindow(720, 420, "设置面板"); int difficulty = 2; // 下拉框选中项:0 简单 / 1 普通 / 2 困难 bool dropOpen = false; // 下拉框开合状态,必须自己保存 float shadow = 0.6f; // 阴影强度 0~1 bool vsync = true; // 垂直同步开关 while (!WindowShouldClose()) { BeginDrawing(); ClearBackground(BLACK); GuiCheckBox((Rectangle){ 30, 50, 22, 22 }, "垂直同步", &vsync); GuiSliderBar((Rectangle){ 30, 100, 260, 20 }, "阴影", TextFormat("%.0f%%", shadow * 100), &shadow, 0.0f, 1.0f); GuiDropdownBox((Rectangle){ 30, 150, 180, 30 }, "简单;普通;困难", &difficulty, dropOpen); EndDrawing(); } CloseWindow(); return 0; }三个要点。其一,编译时链接 raylib 即可,Linux 下gcc panel.c -lraylib -lm -lpthread -ldl -o panel,其余平台走官方 Makefile 或 CMake 同样流程。其二,控件的返回值统一为结果码:1 表示本帧被按下/勾选,2 表示本帧数值发生了变化,判非零即可知道"这一帧被操作过",适合在触发那一帧播放音效或落盘存档。其三,复选框和滑杆把指针传进去就够了——&vsync、&shadow指向的内存就是控件与程序共享的那块数据,这是"变量是唯一真相"的具体落点。
🔍 每帧重新声明控件为什么不会卡
"声明即渲染"是即时模式 GUI 的核心:控件没有构造函数,不分配对象,每帧做的事只有画几个矩形和一段文字,开销与你每帧本来就要下的绘制调用同一量级。
60 FPS 下"每个控件都重新创建一遍"听起来浪费,实际不存在创建这个动作。以 raygui v5.0 为例,它的全部主题状态就是一个全局数组guiStyle,头文件注释里写得很直白:384 个 int,总共 1.5 KB,图标集也是固定的 8 KB 数组——每帧没有需要"新建"的控件实例,只有绘制本身。所以性能预算应该花在真正有成本的地方:文本。TextFormat每帧都产生临时字符串,值没变化时没必要重算,把结果缓存进静态缓冲、仅当变量变化时再拼一次,绘制循环就干净了:
static char shadowBuf[32]; if (shadow != lastShadow) { snprintf(shadowBuf, 32, "%.0f%%", shadow * 100); lastShadow = shadow; }📦 跨帧状态哪些要自己保管
raygui 不记忆任何东西,所以一切"这一帧和下一帧之间要活下来"的东西都归你,最典型的就是下拉框的开合状态。
GuiDropdownBox的最后一个参数editMode是列表当前的开合状态:本帧传进去,列表展开与否由它决定,选中结果则写回int *active。漏掉这个 bool、每帧传 false,列表会在鼠标松手瞬间消失,看起来就像"下拉框坏了"。
bool menuOpen = false; // 声明在绘制循环外,活在你的作用域里 int quality = 0; if (GuiDropdownBox((Rectangle){ 400, 60, 200, 30 }, "低;中;高;极高", &quality, menuOpen)) SaveQualitySetting(quality); // 非零返回 = 本帧被操作同类的跨帧状态还有滚动列表的偏移量、编辑中文本的焦点行,做法完全一致:变量声明在循环外,每帧传入传出。如果同一帧里两个可编辑控件抢输入(比如两个 TextBox 重叠),v5.0 提供了全局互斥模式guiControlExclusiveMode,让先获得焦点的控件独占本次操作;临时锁定某个控件则用GuiDisable()/GuiEnable()成对包起来。
这张截图对应的仓库示例examples/shapes/shapes_pie_chart.c是状态自管的好范本:右侧面板里切片数用GuiSpinner调、显示模式用三个GuiCheckBox切、内环半径用GuiSliderBar拖,滚动区里每行再放一个滑杆——所有状态变量全部声明在main里,控件只负责"问用户要参数"。
📐 窗口拖拽时布局怎么写
硬编码坐标的界面,窗口一拉就错位;正确做法是每帧从逻辑分辨率反推锚点,控件只相对锚点排布。
// 每帧取逻辑分辨率:开了高分屏缩放后这里已经是缩放后的值 int sw = GetScreenWidth(); int sh = GetScreenHeight(); float px = (float)(sw - 300 - 14); // 右缘锚点:距右边 300px、留 14px 边距 float py = (float)(sh - 210 - 14); // 底缘锚点 GuiWindowBox((Rectangle){ px, py, 300, 210 }, "图形设置"); GuiSliderBar((Rectangle){ px + 20, py + 50, 260, 20 }, "泛光", TextFormat("%.1f", bloom), &bloom, 0.0f, 2.0f);注意GetScreenWidth/GetScreenHeight返回的是逻辑尺寸:启用了 HIGHDPI 配置后它已经是系统缩放后的值,别再手动乘一次 DPI 系数,否则面板会被顶出屏幕。窗口里的控件多了就引入滚动区:GuiScrollPanel返回可视窗口view,配合BeginScissorMode裁剪,内容按固定行高(示例里是每行 35px)循环绘制,滚动偏移量同样声明在循环外自管。
🌏 raygui 中文显示:字体替换写法
内置字体不含 CJK 字形,中文标签会渲染成一排方块,解决方式是在启动时用GuiSetFont换一张带中文的 TTF。
// 把带中文字形的字体文件放进项目;charset = 0 表示按字体全部字形加载 Font cn = LoadFontEx("fonts/source-han.ttf", 22, 0, 256); GuiSetFont(cn); // 全局换字体,立即生效 GuiSetStyle(DEFAULT_TEXT_SIZE, 22); // 控件字号与字体设计字号保持一致 // 换回内置字体时: // UnloadFont(cn); GuiSetFont(GetFontDefault());两个容易忽略的点:LoadFontEx的chars参数传 0 会加载字体全字集,首次加载稍慢但一劳永逸;换完字体记得同步DEFAULT_TEXT_SIZE,字号和字体实际尺寸不匹配是"文字画出控件框外"最常见的原因,必要时再配合GuiSetStyle(DEFAULT_TEXT_ALIGNMENT, TEXT_ALIGN_LEFT_CENTER)调整对齐。
🎨 主题配色怎么改:GuiSetStyle 是唯一入口
raygui 的主题就是前面说的那个 1.5 KB 全局数组,所有颜色、字号、边宽改动都走GuiSetStyle(control, property, value),随时可改、改完下一帧生效。
GuiSetStyle(DEFAULT_NORMAL_COLOR, 0x2e3440ff); // 影响全部控件的基础色 GuiSetStyle(SLIDER_BAR, SLIDER_BAR_COLOR_PRESSED, 0x88c0d0ff); // 只改滑杆的按下色 GuiLoadStyleDefault(); // 任何时候一键还原内置主题基础属性(DEFAULT_*)设置后会自动铺到所有控件,所以单个控件的专属值要写在基础值之后;写乱了想重来,GuiLoadStyleDefault()直接覆盖回去,这就是即时模式"放心删了重写"心态在主题层的体现。
接下来值得尝试的三件事
一,用GuiScrollPanel加GuiWindowBox把面板撑满整块屏幕,对照examples/shapes/shapes_pie_chart.c的行高、边距参数抄一遍布局手感。二,给控件换一套配色:先用GuiSetStyle逐项调,调满意后用GuiLoadStyle从外部风格文件加载,主题就能脱离代码管理。三,把本文的返回码判断接进真实存档:勾选那一帧落盘、读档后把值灌回变量,你会很直观地体会到"变量是唯一真相"这句话的分量。
【免费下载链接】raylibA simple and easy-to-use library to enjoy videogames programming项目地址: https://gitcode.com/GitHub_Trending/ra/raylib
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考