☰
raylib 多语言显示实战:从 0 到 1 让中日俄文不再乱码
2026/10/17 0:44:43 网站建设 项目流程

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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询