raygui 教程:5 分钟给 C 游戏装上可拖拽的设置面板(完整指南)
【免费下载链接】raylibA simple and easy-to-use library to enjoy videogames programming项目地址: https://gitcode.com/GitHub_Trending/ra/raylib
raylib 是轻量跨平台的游戏库,raygui 是它配套的单文件界面层,能把滑块、复选框、下拉框直接画进游戏循环。这篇 raygui 教程面向会 C 语言、没写过 GUI 的你:目标是一个能拖、能点、窗口缩放也不错位的设置面板,全程只加一个头文件。
🎮 场景切入:给 C 游戏加一个能拖能点的设置面板
想让玩家在游戏里调音量、换难度、开关音乐,最省事的路线是 raygui,因为它把控件的矩形命中、悬停高亮、按下滑块这些活全包了。传统做法得自己画矩形、手动做鼠标碰撞、再画文字,三个控件就要重复三遍,悬停、按下这些状态全靠手写。raygui 把这些收进单个头文件:你每帧把变量指针递进去,它负责渲染,并把用户操作后的新值写回。
原理速览:C 语言即时模式 GUI 在每帧做什么
即时模式的控件是渲染函数,不是对象——可以理解为每帧重填一张表单。你的主循环每跑一圈,就按顺序调用一遍控件函数:它在指定Rectangle位置画自己,读取这一帧的鼠标状态,用户动了它,就通过指针写回新值。控件本身不存任何记忆,上一帧画在哪,程序根本不关心。所以你的普通变量(float、int、bool)才是唯一真相:你改变量,下一帧控件跟着动;用户拖控件,变量被改。这也回答了一个常见疑问——为什么没有创建、销毁控件的代码:没有实例,自然没有要释放的东西。60 FPS 下"每帧重建一遍"听着浪费,但声明即渲染,没有对象分配开销。
跑通第一个 raygui 例子
先看能直接编译的最小完整示例,跑通之后逐段拆。
#include "raylib.h" #define RAYGUI_IMPLEMENTATION // 全项目只在这一个 .c 里定义,实现编进来 #include "raygui.h" int main(void) { InitWindow(720, 420, "raygui 最小设置面板"); float masterVol = 0.6f; // 变量是唯一真相,控件每帧读它、写它 bool showGrid = true; int toggleCount = 0; // 用返回值数"被点中的帧" while (!WindowShouldClose()) { BeginDrawing(); ClearBackground(RAYGRAY); // 文本参数先格式化再传,左右标签各占一边 GuiSliderBar((Rectangle){ 50, 80, 260, 24 }, "主音量", TextFormat("%d%%", (int)(masterVol * 100)), &masterVol, 0.0f, 1.0f); if (GuiCheckBox((Rectangle){ 50, 130, 22, 22 }, "显示网格", &showGrid)) toggleCount++; // 返回非 0 的帧 = 勾选/取消的瞬间 DrawText(TextFormat("切换次数:%d", toggleCount), 50, 180, 20, RAYWHITE); EndDrawing(); } CloseWindow(); return 0; }编译跑起来后你会看到:拖滑块时右侧百分比实时跳动,点复选框计数加一,全程没有任何"创建控件"的调用。
拆四点,都是坑位:
RAYGUI_IMPLEMENTATION为什么只写一次:它在当前翻译单元展开全部实现,多处定义会符号冲突;其余.c直接#include "raygui.h"就行。- 每个控件第一个参数是自己的
Rectangle,位置和尺寸全由你定,raygui 不管布局。 - 递进去的是指针:用户没动,值不变;动了,那一帧被写回。
- 返回值非 0 只在"发生动作的那一帧"出现,拿它做一次性触发最稳,别当持续状态用。
📋 raygui 控件怎么选:五种控件速查
选控件只看一件事:你要采集的输入是什么形状。连续浮点量给滑块,布尔量给复选框或状态按钮,离散整数给 spinner,"多选一"的菜单项给下拉框。
| 控件 | 适用场景 | 关键参数 | 返回值含义 |
|---|---|---|---|
GuiSliderBar | 连续浮点:音量、半径、比例 | float *value+ 最小/最大值 | 非 0 的帧 = 本帧被拖动或改值 |
GuiCheckBox | 布尔开关:显示/隐藏某样东西 | bool *checked | 非 0 的帧 = 勾选或取消的瞬间 |
GuiToggle | 面板角落的开关,带按压手感 | bool *active | 非 0 的帧 = 点击切换的瞬间 |
GuiSpinner | 带步进的整数:数量、层数 | int *value+ 上下限 +editMode | 非 0 表示值被改过 |
GuiDropdownBox | 多选一的离散项:难度、画质 | int *active(下标从 0 起)+editMode | 非 0 表示本帧有交互 |
返回值都是int形态的GuiResult,RESULT_NONE(0)表示这帧无事发生,判断时只关心"非 0"就够了。
下拉框第 4 个参数editMode是传值不传址:传false走常规行为(按住展开、松手收起),想让列表常开做成菜单式就传true。选中项通过int *active回写。看个最小用法:
int quality = 1; // 选中下标,从 0 开始 // 选项用分号拼成一个字符串,raygui 内部按 ';' 切开 if (GuiDropdownBox((Rectangle){ 60, 210, 180, 30 }, "低;中;高", &quality, false)) printf("画质切到:%d\n", quality); // 只在这一帧打日志跑起来后你会看到:按住不放列表才展开,点中某项的那一帧才返回非 0。spinner 一行搞定整数步进:GuiSpinner((Rectangle){ 300, 80, 150, 30 }, "Slices", &slices, 1, 12, false);,末尾的 bool 为true时允许直接键入数值,false时只能用两侧按钮加减。
🔧 动手搭建:raygui 设置面板与窗口缩放跟随
目标是做一个贴右缘的设置面板:难度滑块、音乐复选框、全屏状态按钮、画质下拉框,且窗口随便拉都不错位。
先定布局策略:别写死坐标,每帧从当前窗口尺寸反推锚点。为什么不写死?因为GetScreenWidth()返回的就是逻辑分辨率(高分屏缩放已经算在内),窗口一变它就变,锚点自动跟着走。
// 锚点每帧重算:窗口一缩放,panelX/panelY 下一帧就对齐新边界 int sw = GetScreenWidth(), sh = GetScreenHeight(); float panelX = (float)(sw - 240 - 20); // 240 是面板宽,20 是右边距 float panelY = (float)(sh - 250 - 20); // 贴底缘,避开主画面注意:这些值已经是 DPI 缩放后的逻辑坐标,别再乘缩放系数,否则高分屏上整体漂移。
控件全部挂在锚点的相对坐标上,业务绘制(主画面)照旧:
// 控件只相对锚点排布:窗口拉多宽,它们都留在右缘 20px 内 GuiSliderBar((Rectangle){ panelX + 20, panelY + 40, 200, 22 }, "难度", TextFormat("%d", level), &level, 1.0f, 5.0f); GuiCheckBox((Rectangle){ panelX + 20, panelY + 80, 20, 20 }, "背景音乐", &musicOn); GuiToggle((Rectangle){ panelX + 20, panelY + 120, 120, 30 }, "全屏", &fullscreen); // 下拉框按常规行为用:按住展开,选中下标从 0 开始回写 if (GuiDropdownBox((Rectangle){ panelX + 20, panelY + 170, 200, 30 }, "低;中;高", &quality, false)) settingsDirty = true; // 标记设置变了,业务逻辑去读新值跑起来后你会看到:把窗口从 1280x720 拖成 1920x1080,面板始终钉在右下角,滑块和复选框不抖。如果游戏里还有可拖拽实体(编辑器控制点之类),每帧拿GetMousePosition()去和控件 bounds 比对即可命中,道理和上面一样。
仓库 examples 里就有现成范本:饼图示例右侧一整个 raygui 控制台——spinner 调切片数、复选框切显示模式、滑块控内环半径。它示范了即时模式 GUI 的分界线:控件问参数,业务自己画。
🐛 raygui 踩坑排查:四个高频问题
问题 1:文字画到控件框外或被裁切原因:控件height给得小(20 左右),默认字号相对偏大,文本放不下。 解决:控件加高到 26~30;对齐不对时用GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT)调一下。
问题 2:中文显示成方块原因:内置默认字体不含 CJK 字形,缺的码位画成占位方框。 解决:启动时换一张带中文的字库:GuiSetFont(LoadFontEx("fonts/cjk.ttf", 24, NULL, 0));,字体文件放程序能找到的路径。
问题 3:同帧操作两个控件,只有后声明的那个有反应原因:两个控件的Rectangle重叠了,后声明的优先吃到输入。 解决:错开各控件的 bounds 保证不重叠;列表里每行一个可编辑控件时,用editMode之类的参数保证同一时刻只有一个处于可编辑状态。
问题 4:每帧调 TextFormat,内存分配抖动明显原因:每帧重新拼字符串,分配器高频 malloc/free。 解决:把格式化结果缓存到static char[32]一类的固定缓冲里,值变了才重算。
📦 接入你的项目:raygui 的 3 步接入
三步,按顺序做就行:
- 克隆仓库:
git clone https://gitcode.com/GitHub_Trending/ra/raylib。raygui.h在 examples 各模块目录下都有(如examples/shapes/raygui.h、examples/core/raygui.h),拷一份到你项目的src/,哪个都行。 - 挑一个
.c文件,在#include "raygui.h"之前写#define RAYGUI_IMPLEMENTATION;其余源文件只写 include、不写 define,避免符号重复。 - 编译时链接 raylib 本体(例如
gcc demo.c -lraylib -o demo),其余构建方式与 raylib 官方 Makefile / CMake 流程完全一致。
结尾:raygui 之后值得玩的方向
raygui 的全部约定就两条:每帧声明控件、变量为准。控件不保存状态,所以你可以放心删了重写,界面代码因此异常好删、好改,这也是它比重量级 UI 框架更适合 C 游戏的原因。
值得继续折腾的三件事:用GuiSetStyle换主题配色、字号与内边距,试出一套和自己游戏画面配的风格;GuiSetFont换成有设计感的字体,注意同步调高控件尺寸;把examples/shapes/下用 raygui 的样本(饼图、希尔伯特曲线等)逐个编译运行,看别人怎么把布局撑满整块屏幕。
【免费下载链接】raylibA simple and easy-to-use library to enjoy videogames programming项目地址: https://gitcode.com/GitHub_Trending/ra/raylib
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考