raylib 多语言显示实战:从 0 到 1 让中日俄文不再乱码
【免费下载链接】raylibA simple and easy-to-use library to enjoy videogames programming项目地址: https://gitcode.com/GitHub_Trending/ra/raylib
做游戏出海时,最尴尬的画面往往是:英文版一切正常,切到中文、俄文后满屏问号。raylib 并没有内置"多语言开关",它的核心是字体图集机制——哪些字形被打包进字体纹理,完全由你通过码点列表决定。搞懂这个机制后,用不到 50 行代码就能让窗口同时渲染六种语言。
为什么非英文会乱码:字体图集机制速览
raylib 里的Font本质上是一张纹理图集加一份字符元数据:所有字形位图被打包进font.texture,每个字符的偏移、步进信息存在font.glyphs数组里。LoadFont加载时默认只烘焙 Basic ASCII(码点 32~127)这 96 个字符,中日韩字符位于更靠后的码点区间,图集里没有对应字形,画出来自然是方框或问号。
解决办法是LoadFontEx:传入一份码点列表,raylib 会把字体文件中这些码点字形重新打包成图集。
// LoadFont 默认只加载 Basic ASCII [32-127] Font font = LoadFont("resources/NotoSansTC-Regular.ttf"); // LoadFontEx 可自定义码点列表,第 3/4 参传 NULL/0 即为默认行为 Font font2 = LoadFontEx("resources/NotoSansTC-Regular.ttf", 32, codepoints, count);各语言常用码点区间对照(摘自示例examples/text/text_unicode_ranges.c实际加载的范围):
| 语言/区域 | 码点区间 |
|---|---|
| 中文(CJK 基本汉字) | 0x4E00 ~ 0x9FFF |
| 日文平假名 | 0x3040 ~ 0x309F |
| 日文片假名 | 0x30A0 ~ 0x30FF |
| 韩文 | 0xAC00 ~ 0xD7AF |
| 俄文(西里尔) | 0x0400 ~ 0x4FF |
| 欧洲语言扩展拉丁 | 0xC0 ~ 0x17F |
动手实现:四步跑通多语言文本渲染
1. 确认字体文件覆盖目标语言
图集的上限取决于字体文件本身——文件里没有的字形,码点加得再多也没用。仓库examples/text/resources/下自带了几个可用的开源字体:
NotoSansTC-Regular.ttf:覆盖拉丁、希腊、西里尔与 CJK 字符,官方多语言示例用的就是它DotGothic16-Regular.ttf:日文哥特体(像素风)KAISG.ttf、anonymous_pro_bold.ttf、pixantiqua.ttf:其他风格备选
注意示例代码里写的是"resources/NotoSansTC-Regular.ttf",因为示例的工作目录是examples/text/,这个相对路径实际指向仓库里的examples/text/resources/目录。
2. 加载字体并设置缩放过滤
目的:先把字体加载进显存,并为后续放大做准备。
// fontSize 是字形像素高度;codepoints 传 NULL/0 表示加载默认 ASCII 集 Font font = LoadFontEx("resources/NotoSansTC-Regular.ttf", 32, NULL, 0); // 双线性过滤:缩放渲染时字形边缘更平滑 SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR);加载完成后font.texture就是图集纹理,font.glyphCount告诉你当前图集里有多少个字形——这个值后面验证加载效果会用到。
3. 为目标语言追加码点区间
目的:在已有字体上"追加"指定语言区间,而不是一次性写死。下面这段逻辑来自examples/text/text_unicode_ranges.c里的AddCodepointRange辅助函数:
// 把 [start, stop] 区间追加进已有字体:先拷贝旧码点,再拼上新区间,重建图集 int *updatedCodepoints = RL_CALLOC(currentRangeSize + rangeSize, sizeof(int)); for (int i = 0; i < currentRangeSize; i++) updatedCodepoints[i] = font->glyphs[i].value; // 保留原有码点 for (int i = currentRangeSize; i < updatedCodepointCount; i++) updatedCodepoints[i] = start + (i - currentRangeSize); // 追加新区间 UnloadFont(*font); *font = LoadFontEx(fontPath, 32, updatedCodepoints, updatedCodepointCount);之后按需调用即可,示例中一次可叠加多个区间:
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x4e00, 0x9fff); // 中文 AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x3040, 0x309f); // 平假名关键参数是start/stop两个码点边界,区间内的每个码点都会生成一个字形,所以区间越大、图集越慢(后面避坑指南会讲)。
4. 用 DrawTextEx 渲染多语言文本
目的:验证图集是否生效。DrawTextEx接受字体、文本、位置、像素字号、字距、颜色六个参数,文本以 UTF-8 传入:
BeginDrawing(); ClearBackground(RAYWHITE); DrawTextEx(font, "> English: Hello World!", (Vector2){50, 70}, 32, 1, DARKGRAY); DrawTextEx(font, "> 中文: 你好世界!", (Vector2){50, 270}, 32, 1, DARKGRAY); DrawTextEx(font, "> 日本語: こんにちは世界!", (Vector2){50, 320}, 32, 1, DARKGRAY); EndDrawing();示例窗口还会把图集纹理放大画在右侧,并显示CODEPOINTS GLYPHS LOADED计数,按下 1~4 键可实时切换欧洲/希腊/西里尔/CJK 语言集,效果如下:
5. 性能调优:只加载实际用到的字符
目的:避免一次性烘焙上万个 CJK 码点导致加载卡顿。更省的做法是反过来——先扫描游戏里所有文本,提取实际用到的码点,再去生成图集,这正是examples/text/text_codepoints_loading.c的思路:
// 从全部游戏文本中提取码点(源文件需保存为 UTF-8) int codepointCount = 0; int *codepoints = LoadCodepoints(text, &codepointCount); // 去重后再生成图集,图集只包含真正用到的字形 int *noDups = CodepointRemoveDuplicates(codepoints, codepointCount, &noDupsCount); Font font = LoadFontEx("resources/DotGothic16-Regular.ttf", 36, noDups, noDupsCount);示例中的整首"伊吕波歌"含 75 个字符,去重后只剩约 50 个码点,图集体积和生成时间都减半,空格键可随时切换查看生成的图集:
进阶玩法:搭一个轻量语言管理层
场景:语言集加载好之后,文本散落在各处的DrawTextEx调用里,切换语言就要逐个改字符串。方案:把文案收敛到一张按语言索引的扁平表里,渲染处只引用编号。
const char *TEXTS[2][4] = { { "My Game", "Start", "Pause", "Quit" }, // 英语 { "我的游戏", "开始", "暂停", "退出" } // 中文 }; const char *GetText(int lang, int id) { return TEXTS[lang][id]; }场景:文案需要策划可维护,不想动代码。方案:改用 JSON 配置,用 raylib 的文件 API 读入(可参考examples/core/core_directory_files.c的文件读取方式):
{ "en": { "title": "My Game", "start": "Start" }, "zh": { "title": "我的游戏", "start": "开始游戏" } }表情符号也是同一套机制——给字体加上对应码点区间即可渲染,示例效果可见examples/text/text_unicode_emojis.c:
避坑指南
加载了字体,部分字符还是方框。两个可能:码点区间没覆盖该字符,或者字体文件本身不含这个字形——text_unicode_ranges.c里明确警告过,仓库自带的 NotoSansTC 并不含天城文、阿拉伯文、希伯来文。先选覆盖目标语言的字体,再用font.glyphCount验证加载结果。🛠
代码能编译,屏幕上中文却是一串问号。这是源码文件编码问题:Windows 下编辑器常以 GBK 保存,而 raylib 按 UTF-8 解析字符串。text_codepoints_loading.c的注释特意提醒"must be UTF-8",把工程统一设为 UTF-8(无 BOM)即可。
切换语言集时画面卡住好几秒。一次性加载数千个 CJK 码点需要重新烘焙整张图集,示例为此专门画了 "GENERATING FONT ATLAS..." 的遮罩层。两个对策:用LoadCodepoints把范围收窄到实际用到的字符;或在过场时预加载下一语言的字体,切完再UnloadFont旧的。
延伸阅读
- examples/text/text_unicode_ranges.c — 完整示例:按 1~4 键切换欧洲/希腊/西里尔/CJK 码点集
- examples/text/text_codepoints_loading.c — 从文本自动提取码点,生成最小字体图集
- examples/text/text_unicode_emojis.c — 表情符号的加载与渲染
- src/raylib.h — 搜索
LoadFontEx、LoadCodepoints、DrawTextEx,查字体相关 API 的完整参数说明
下一篇可以看看examples/text/text_font_sdf.c里的 SDF 字体——它能让任意缩放字号下的文字边缘依然锐利,做多语言 UI 时配合本文的码点管理会非常顺手。觉得有用顺手点个赞吧。
【免费下载链接】raylibA simple and easy-to-use library to enjoy videogames programming项目地址: https://gitcode.com/GitHub_Trending/ra/raylib
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考