Raylib C++游戏开发入门:从零构建跨平台2D游戏原型

📅 发布时间:2026/8/11 4:29:54
Raylib C++游戏开发入门:从零构建跨平台2D游戏原型 1. 项目概述为什么选择 Raylib 开启你的 C 游戏之旅如果你对 C 有一定了解想亲手做出一个能跑起来的游戏但又对 Unity、Unreal 这类重型引擎的复杂性和漫长的学习曲线感到望而却步那么 Raylib 几乎是为这个场景量身定制的完美起点。我最初接触它也是因为厌倦了在大型引擎里配置各种项目设置、处理资源管线只想纯粹地写代码快速看到图形和交互反馈。Raylib 完美地满足了这个需求。简单来说Raylib 是一个用 C 语言编写的、极度轻量级的跨平台游戏编程库。它的设计哲学是“简单易用”把 OpenGL 的底层复杂性全部封装起来提供了一套直观、直接的 API。你不需要理解着色器、顶点缓冲对象这些底层图形概念就能画出矩形、圆形、加载图片、播放音效。对于初学者这意味着你可以把 100% 的精力投入到游戏逻辑本身而不是与图形 API 搏斗。对于有经验的开发者它则是一个快速原型验证的利器。我们这次的目标就是利用 C 和 Raylib从零开始一步步构建出你的第一个可交互的图形程序通常是一个小游戏比如一个可以控制角色移动躲避障碍物的雏形。这个项目适合谁首先你需要对 C 有最基础的理解比如知道什么是变量、函数、循环和条件判断。不需要精通面向对象或模板元编程。其次你需要一颗想动手创造的心。整个过程就像搭积木Raylib 提供了各种形状的积木API函数而你的 C 代码就是搭建的图纸。最终你将获得一个完全由你代码控制的、可以编译成独立可执行文件的游戏原型这份成就感是看多少教程都无法比拟的。2. 环境准备与项目初始化2.1 开发环境搭建选择你的“作战平台”Raylib 是跨平台的这意味着你可以在 Windows、macOS、Linux 甚至树莓派上开发。这里我以最普遍的 Windows Visual Studio 2022 社区版免费为例进行说明其他平台步骤类似主要是库的安装方式不同。第一步安装 Visual Studio 2022去微软官网下载 Visual Studio 2022 Community 安装程序。在安装时工作负载务必勾选“使用 C 的桌面开发”。这会安装 C 编译器MSVC、调试器和基本的 Windows SDK这是编译 C 程序的基石。其他像 .NET 或 Python 开发暂时不需要可以取消勾选以节省磁盘空间。第二步获取 Raylib 库文件Raylib 的官网提供了预编译的库文件这是最快的方式。访问 Raylib 的 GitHub 发布页面找到最新的稳定版本例如 raylib-5.0。下载对应你系统和编译器的版本。对于 Windows Visual Studio你需要的是raylib-5.0_win64_msvc16.zip64位或win32版本。 下载后解压你会看到一个结构清晰的文件夹里面包含raylib.h头文件、.lib静态库文件以及一些示例和工具。我们主要需要头文件和库文件。第三步配置 Visual Studio 项目打开 Visual Studio创建新项目选择“空项目”命名为MyFirstRaylibGame。在“解决方案资源管理器”中右键点击项目名 - “属性”。关键配置有两处C/C - 常规 - 附加包含目录添加你解压的 Raylib 文件夹中include子目录的路径。例如D:\Libraries\raylib-5.0_win64_msvc16\include。这告诉编译器去哪里找raylib.h。链接器 - 常规 - 附加库目录添加 Raylib 文件夹中lib子目录的路径。例如D:\Libraries\raylib-5.0_win64_msvc16\lib。这告诉链接器去哪里找.lib文件。链接器 - 输入 - 附加依赖项添加raylib.lib。对于 Release 配置就写这个对于 Debug 配置通常链接raylibd.lib如果提供了调试版库。还有一个容易被忽略的点子系统。因为我们要创建的是图形窗口程序而不是控制台黑框。在“链接器 - 系统 - 子系统”中选择“窗口(/SUBSYSTEM:WINDOWS)”。同时在“高级 - 入口点”中可以清空因为 Raylib 提供了自己的main入口在 Windows 下它会处理成WinMain。注意很多新手卡在“无法解析的外部符号”这种链接错误上90%的原因就是以上三步包含目录、库目录、附加依赖项没有配置正确。请务必仔细核对路径确保没有多余的空格或中文字符。2.2 第一个窗口“Hello, Raylib!”环境配好了我们来写第一个程序验证一切是否正常。在项目中新建一个main.cpp文件。#include raylib.h int main() { // 初始化窗口宽800像素高600像素标题为“My First Raylib Window” InitWindow(800, 600, My First Raylib Window); // 设置目标帧率每秒最大帧数这里设为60帧 SetTargetFPS(60); // 主游戏循环只要窗口没有被请求关闭就一直循环 while (!WindowShouldClose()) // WindowShouldClose() 检测窗口关闭按钮或ESC键 { // 开始绘制 BeginDrawing(); // 用浅灰色清空上一帧的画面 ClearBackground(RAYWHITE); // 在屏幕中央(400, 300)位置绘制绿色文字 DrawText(Congrats! You created your first window!, 190, 300, 20, DARKGREEN); // 结束绘制 EndDrawing(); } // 关闭窗口并释放资源 CloseWindow(); return 0; }点击运行F5。如果一切顺利你应该会看到一个浅灰色背景、中央写着绿色文字的窗口。按 ESC 或点击关闭按钮窗口会正常关闭。这个简单的程序已经包含了 Raylib 游戏的核心骨架初始化 - 游戏循环处理输入、更新逻辑、绘制 - 清理退出。实操心得SetTargetFPS(60)非常重要。它不仅仅是限制最高帧率更重要的是能稳定帧率防止循环跑得太快耗尽 CPU并且让游戏在不同性能的电脑上有一致的速度感。如果你的游戏逻辑是基于帧更新的比如每帧移动5像素没有这个限制在高性能电脑上角色会“飞”起来。3. 核心概念与图形绘制基础3.1 坐标系与基本图元在 Raylib 的 2D 绘制中屏幕左上角是坐标原点(0, 0)。X 轴向右为正Y 轴向下为正。这一点和很多数学坐标系不同但和计算机图像处理的标准一致。Raylib 提供了一系列“即开即用”的绘制函数我们称之为基本图元。它们是构建游戏视觉元素的砖瓦。// 在游戏循环的 BeginDrawing() 和 EndDrawing() 之间添加以下代码 // 1. 绘制一个填充的蓝色矩形 (位置x, 位置y, 宽度, 高度, 颜色) DrawRectangle(100, 100, 200, 150, BLUE); // 2. 绘制一个只有边框的红色矩形最后一个参数是线条粗细 DrawRectangleLines(100, 100, 200, 150, RED); // 3. 绘制一个填充的绿色圆 (中心x, 中心y, 半径, 颜色) DrawCircle(400, 300, 50, GREEN); // 4. 绘制一个黄色的线条 (起点x, 起点y, 终点x, 终点y, 颜色) DrawLine(50, 500, 750, 500, YELLOW); // 5. 绘制一个三角形的边框 (顶点1, 顶点2, 顶点3, 颜色) DrawTriangleLines((Vector2){600, 100}, (Vector2){550, 200}, (Vector2){650, 200}, DARKPURPLE);颜色可以用 Raylib 预定义的颜色常量如RED,GREEN,SKYBLUE也可以用Color{ R, G, B, A }结构体自定义其中 R/G/B/A 取值范围是 0-255。为什么从这些开始在游戏原型阶段用基本几何图形来代表游戏对象比如矩形代表玩家、圆形代表子弹、线条代表路径是最高效的做法。你可以快速迭代逻辑而不必纠结于美术资源。这是职业开发者常用的“程序化美术”或“占位符”思维。3.2 纹理加载与精灵绘制当基本图形无法满足需求时我们就需要加载图片纹理。Raylib 让这个过程变得异常简单。假设你有一张player.png的图片放在项目目录下的resources文件夹里。#include raylib.h int main() { InitWindow(800, 600, Texture Drawing); SetTargetFPS(60); // 加载纹理Image - Texture // 第一步将图片文件加载到 Image 结构体中CPU内存 Image playerImage LoadImage(resources/player.png); // 第二步将 Image 转换为 TextureGPU显存这样才能高效绘制 Texture2D playerTexture LoadTextureFromImage(playerImage); // 第三步Image 数据已上传至GPU可以释放CPU内存了 UnloadImage(playerImage); Vector2 playerPosition { 400, 300 }; // 玩家的初始位置 while (!WindowShouldClose()) { // --- 输入处理为下一节做准备--- if (IsKeyDown(KEY_RIGHT)) playerPosition.x 5.0f; if (IsKeyDown(KEY_LEFT)) playerPosition.x - 5.0f; if (IsKeyDown(KEY_DOWN)) playerPosition.y 5.0f; if (IsKeyDown(KEY_UP)) playerPosition.y - 5.0f; // --------------------------------- BeginDrawing(); ClearBackground(RAYWHITE); // 在指定位置绘制纹理 // 参数纹理对象位置x位置y tint颜色WHITE表示原色 DrawTexture(playerTexture, (int)playerPosition.x, (int)playerPosition.y, WHITE); // 再演示一个带旋转和缩放的绘制 // 参数纹理位置旋转角度度缩放倍数 tint颜色 DrawTextureEx(playerTexture, (Vector2){100, 100}, 45.0f, 2.0f, RED); EndDrawing(); } // 游戏结束释放纹理资源防止内存泄漏 UnloadTexture(playerTexture); CloseWindow(); return 0; }注意事项资源管理Load和Unload必须成对出现。LoadImage/UnloadImage,LoadTexture/UnloadTexture。忘记Unload会导致内存泄漏尤其是在热重载资源时。路径问题LoadImage(“resources/player.png”)使用的是相对路径相对于你的可执行文件.exe所在目录。在 Visual Studio 中调试时默认工作目录是项目文件夹包含.vcxproj文件的目录。你需要把resources文件夹放在那里或者使用绝对路径。更专业的做法是在项目属性中设置“调试”下的“工作目录”。绘制顺序Raylib 的绘制是“画家算法”后绘制的内容会覆盖在先绘制的内容之上。所以背景要先画角色和UI最后画。4. 实现游戏核心循环与交互4.1 输入处理让角色动起来游戏的核心是交互。Raylib 提供了极其直观的输入检测函数主要分为两类IsKeyPressed/IsKeyReleased和IsKeyDown/IsKeyUp。IsKeyDown(KEY)当某一帧按键被按住时返回true。用于连续移动如按住方向键移动角色。IsKeyPressed(KEY)只在按键被按下的那一帧返回true。用于触发一次性动作如跳跃、开枪、打开菜单。让我们完善上一个例子实现更专业的移动控制// 在游戏循环中更新逻辑的部分 float playerSpeed 300.0f; // 像素/秒 // 获取上一帧到这一帧的时间间隔以秒为单位 float deltaTime GetFrameTime(); Vector2 playerVelocity { 0, 0 }; // 速度向量 // 基于按下的键设置速度方向 if (IsKeyDown(KEY_RIGHT)) playerVelocity.x 1.0f; if (IsKeyDown(KEY_LEFT)) playerVelocity.x -1.0f; if (IsKeyDown(KEY_DOWN)) playerVelocity.y 1.0f; if (IsKeyDown(KEY_UP)) playerVelocity.y -1.0f; // 标准化对角线移动速度防止斜向移动更快 // 这是一个非常重要的细节 if (playerVelocity.x ! 0 playerVelocity.y ! 0) { playerVelocity Vector2Normalize(playerVelocity); } // 根据速度、速度和帧时间更新位置 // 核心公式位置 速度 * 时间 playerPosition.x playerVelocity.x * playerSpeed * deltaTime; playerPosition.y playerVelocity.y * playerSpeed * deltaTime; // 让玩家保持在屏幕边界内 if (playerPosition.x 0) playerPosition.x 0; if (playerPosition.x GetScreenWidth() - playerTexture.width) playerPosition.x GetScreenWidth() - playerTexture.width; if (playerPosition.y 0) playerPosition.y 0; if (playerPosition.y GetScreenHeight() - playerTexture.height) playerPosition.y GetScreenHeight() - playerTexture.height;核心原理解析这里引入了deltaTime增量时间的概念。GetFrameTime()返回上一帧渲染所花费的时间秒。由于不同电脑性能不同每帧循环的速度是不同的。如果我们用playerPosition.x 5这种每帧固定加5像素的方式在60帧的电脑上每秒移动300像素在30帧的电脑上每秒只移动150像素游戏体验会不一致。而使用速度 * deltaTime无论帧率高还是低5 * deltaTime在一秒内的累计增量是基本恒定的因为帧率高时deltaTime小帧率低时deltaTime大这就实现了帧率无关的运动是游戏编程的基石之一。标准化速度当同时按住上和右时速度向量是(1, -1)其长度是 √2 ≈ 1.414。这意味着斜向移动比单纯横向或纵向移动快了约41%。通过Vector2Normalize()将其长度缩放到1就解决了这个问题。4.2 简单的游戏状态与碰撞检测现在我们来创建一个最简单的游戏玩家控制一个方块躲避从屏幕上方不断下落的障碍物。首先我们需要定义游戏对象的结构。虽然可以用多个分散的变量但用结构体组织起来更清晰。// 在 main 函数之前定义我们的游戏对象结构 struct GameObject { Vector2 position; Vector2 size; Color color; float speed; // 用于障碍物的下落速度 }; // 在 main 函数中初始化 GameObject player { {400, 500}, {50, 50}, BLUE, 0 }; GameObject obstacles[5]; // 假设有5个障碍物 bool gameOver false; int score 0; // 初始化障碍物 for (int i 0; i 5; i) { obstacles[i].position { (float)GetRandomValue(0, GetScreenWidth()-30), (float)GetRandomValue(-100, -40) }; obstacles[i].size { 30, 30 }; obstacles[i].color RED; obstacles[i].speed (float)GetRandomValue(100, 250); // 随机速度 }在游戏循环中我们需要更新障碍物位置并检测碰撞。// 在游戏循环的更新部分如果游戏未结束 if (!gameOver) { // 更新玩家基于输入代码同上略 // 更新障碍物 for (int i 0; i 5; i) { obstacles[i].position.y obstacles[i].speed * deltaTime; // 如果障碍物掉出屏幕底部重置到顶部随机位置并加分 if (obstacles[i].position.y GetScreenHeight()) { obstacles[i].position.y (float)GetRandomValue(-100, -40); obstacles[i].position.x (float)GetRandomValue(0, GetScreenWidth() - (int)obstacles[i].size.x); score 10; } // 简单的矩形碰撞检测检查玩家矩形和障碍物矩形是否重叠 // Raylib 提供了 CheckCollisionRecs 函数 Rectangle playerRect { player.position.x, player.position.y, player.size.x, player.size.y }; Rectangle obstacleRect { obstacles[i].position.x, obstacles[i].position.y, obstacles[i].size.x, obstacles[i].size.y }; if (CheckCollisionRecs(playerRect, obstacleRect)) { gameOver true; // 发生碰撞游戏结束 } } } // 在绘制部分 BeginDrawing(); ClearBackground(DARKGRAY); // 绘制玩家 DrawRectangleV(player.position, player.size, player.color); // 绘制所有障碍物 for (int i 0; i 5; i) { DrawRectangleV(obstacles[i].position, obstacles[i].size, obstacles[i].color); } // 绘制分数和游戏状态 DrawText(TextFormat(Score: %04d, score), 20, 20, 30, LIME); if (gameOver) { DrawText(GAME OVER, GetScreenWidth()/2 - MeasureText(GAME OVER, 60)/2, GetScreenHeight()/2 - 30, 60, RED); DrawText(Press R to Restart, GetScreenWidth()/2 - MeasureText(Press R to Restart, 30)/2, GetScreenHeight()/2 40, 30, YELLOW); } EndDrawing(); // 在输入处理部分添加重启逻辑 if (gameOver IsKeyPressed(KEY_R)) { // 重置游戏状态 gameOver false; score 0; player.position (Vector2){400, 500}; // 重置所有障碍物... }避坑技巧碰撞检测的优化上面的例子对每个障碍物都进行了检测5次。在真实游戏中可能有成百上千个物体。一个常见的优化是“空间划分”比如网格法或四叉树只检测可能发生碰撞的物体。Raylib 也提供了CheckCollisionCircleRec,CheckCollisionCircles,GetCollisionRec(返回碰撞矩形) 等更多函数。随机数种子GetRandomValue使用的是伪随机数。如果你每次运行游戏障碍物出现顺序都一样可以在程序开始时调用SetRandomSeed((unsigned int)time(NULL))来用当前时间初始化随机种子确保每次运行都不同。游戏状态管理当游戏逻辑变复杂如开始菜单、游戏进行中、暂停、结束最好用一个枚举enum GameState来管理状态并用switch语句在不同的状态分支中执行不同的更新和绘制逻辑这样代码会更清晰。5. 音效、字体与项目结构优化5.1 为游戏添加听觉反馈没有声音的游戏是没有灵魂的。Raylib 加载和播放音效同样简单。它支持 WAV 和 OGG 格式。通常短促的提示音用 WAV背景音乐用 OGG压缩率高。// 初始化音频设备在 InitWindow 之后 InitAudioDevice(); // 加载音效和音乐 Sound collisionSound LoadSound(resources/collision.wav); Music bgMusic LoadMusicStream(resources/background.ogg); // 设置音乐循环播放 bgMusic.looping true; // 开始播放背景音乐流式播放不阻塞 PlayMusicStream(bgMusic); // 在游戏循环中更新音乐流必须每帧调用 UpdateMusicStream(bgMusic); // 在发生碰撞的地方播放音效 if (CheckCollisionRecs(playerRect, obstacleRect)) { gameOver true; PlaySound(collisionSound); // 播放碰撞音效 StopMusicStream(bgMusic); // 停止背景音乐 } // 游戏结束时释放音频资源 UnloadSound(collisionSound); UnloadMusicStream(bgMusic); CloseAudioDevice(); // 在 CloseWindow 之前调用重要提醒UpdateMusicStream(music)必须放在主循环中每帧调用否则音乐播放会卡住。这是因为音乐是流式解码的需要持续喂数据。5.2 使用自定义字体Raylib 默认使用一种基本字体。要使用更美观的字体如.ttf文件需要先加载它。// 加载字体文件第二个参数是字体大小 Font customFont LoadFontEx(resources/myfont.ttf, 64, 0, 0); // 在绘制文本时使用 DrawTextEx 并指定字体 DrawTextEx(customFont, My Stylish Score, (Vector2){50, 50}, (float)customFont.baseSize, 2, GOLD); // 注意使用自定义字体后MeasureText 函数就不准确了需要用 MeasureTextEx 来测量文本尺寸 Vector2 textSize MeasureTextEx(customFont, My Stylish Score, (float)customFont.baseSize, 2); DrawText(GAME OVER, GetScreenWidth()/2 - textSize.x/2, GetScreenHeight()/2 - 30, 60, RED); // 改用这个居中 // 游戏结束时卸载字体 UnloadFont(customFont);5.3 项目结构优化告别“意大利面条代码”当你的main.cpp超过几百行所有变量和函数都堆在一起时维护会变成噩梦。是时候进行简单的代码组织了。头文件分离将游戏对象的结构体定义、全局常量、函数声明放到一个头文件里比如game.h。// game.h #ifndef GAME_H #define GAME_H #include raylib.h struct GameObject { Vector2 position; Vector2 size; Color color; float speed; }; // 全局游戏状态 extern bool gameOver; extern int score; // 函数声明 void InitGame(); void UpdateGame(float deltaTime); void DrawGame(); void UnloadGameResources(); #endif创建对应的源文件game.cpp。// game.cpp #include game.h // 定义全局变量 GameObject player; GameObject obstacles[5]; bool gameOver false; int score 0; Sound collisionSound; Music bgMusic; void InitGame() { // 初始化玩家、障碍物、加载资源... player { {400, 500}, {50, 50}, BLUE, 0 }; // ... 初始化障碍物 collisionSound LoadSound(resources/collision.wav); bgMusic LoadMusicStream(resources/background.ogg); PlayMusicStream(bgMusic); } void UpdateGame(float deltaTime) { if (gameOver) return; // 处理输入、更新位置、检测碰撞... UpdateMusicStream(bgMusic); } void DrawGame() { // 绘制背景、玩家、障碍物、UI... } void UnloadGameResources() { UnloadSound(collisionSound); UnloadMusicStream(bgMusic); }简化主文件main.cpp变得非常干净。// main.cpp #include raylib.h #include game.h // 包含我们自己的头文件 int main() { InitWindow(800, 600, My Structured Game); InitAudioDevice(); SetTargetFPS(60); InitGame(); // 初始化游戏内容 while (!WindowShouldClose()) { float deltaTime GetFrameTime(); UpdateGame(deltaTime); // 更新游戏逻辑 BeginDrawing(); ClearBackground(DARKGRAY); DrawGame(); // 绘制游戏画面 EndDrawing(); } UnloadGameResources(); // 清理游戏资源 CloseAudioDevice(); CloseWindow(); return 0; }这样做的好处是逻辑清晰main.cpp只关心窗口和主循环的生命周期游戏的具体内容被封装在game.cpp中。未来你可以轻松地添加menu.cpp,pause.cpp来管理更多状态。6. 编译发布与进阶方向6.1 从调试到发布生成独立的可执行文件在 Visual Studio 中顶部工具栏的“解决方案配置”下拉菜单从“Debug”切换到“Release”。Debug 模式包含调试信息方便你设置断点和查看变量但程序体积大、运行慢。Release 模式会进行代码优化移除调试信息生成更小更快的最终版本。按 F7 编译然后在项目文件夹下的x64/Release或Release子目录里你会找到编译好的.exe文件。但是直接双击这个.exe可能会报错因为它找不到 Raylib 的运行时库通常是raylib.dll对于动态链接或者一些特定的 VC 运行时库。发布准备静态链接推荐在项目属性 - 链接器 - 输入 - 附加依赖项中将raylib.lib改为raylib_mt.lib如果存在mt表示多线程静态库。同时在 C/C - 代码生成 - 运行时库选择“多线程(/MT)”。这样Raylib 的代码会被直接打包进你的.exe无需外部 dll。你需要确保你下载的 Raylib 包里有对应的静态库文件。动态链接将 Raylib 安装包里的raylib.dll文件复制到你的.exe文件同一目录下。资源文件夹将你的resources文件夹包含图片、声音、字体也复制到.exe同级目录下。现在你的.exe就可以在任何同架构如64位的 Windows 电脑上运行了无需安装任何其他环境。6.2 常见问题排查速查表问题现象可能原因解决方案编译错误无法打开源文件 “raylib.h”附加包含目录配置错误检查项目属性中 C/C - 常规 - 附加包含目录的路径是否正确是否指向了include文件夹。链接错误无法解析的外部符号 ...库文件链接错误1. 检查附加库目录路径。2. 检查附加依赖项库文件名是否正确如raylib.lib。3. 检查是 Debug 还是 Release 配置库是否匹配。4. 子系统是否设置为“窗口”。程序运行瞬间闪退资源文件路径错误或缺失1. 检查LoadImage/LoadSound等函数的文件路径。2. 将资源文件夹放到.exe同级目录。3. 在代码开头用GetWorkingDirectory()打印当前工作目录确认。画面卡顿或撕裂没有使用deltaTime或SetTargetFPS确保运动逻辑乘以deltaTime并调用SetTargetFPS(60)。音乐播放卡顿或无声忘记调用UpdateMusicStream在主循环中每帧调用UpdateMusicStream(bgMusic)。游戏逻辑速度时快时慢运动逻辑与帧率绑定将所有与时间相关的更新位置、动画、计时器都乘以GetFrameTime()。碰撞检测不准确矩形坐标或尺寸计算错误绘制调试矩形DrawRectangleLines查看碰撞框的实际位置和大小确保与视觉表现一致。6.3 下一步可以探索什么恭喜你走到这里你已经掌握了使用 Raylib 和 C 制作 2D 游戏的核心流程。但这只是开始你可以以此为基点探索更广阔的世界动画系统学习使用Texture2D精灵图Sprite Sheet和矩形源Rectangle sourceRec来实现帧动画。DrawTexturePro函数是关键。粒子效果创建Particle结构体数组模拟火花、烟雾、爆炸等效果。每帧更新粒子的位置、速度、生命周期和颜色。场景管理实现一个简单的状态机管理“开始菜单”、“游戏关卡”、“暂停界面”、“结束画面”之间的切换。关卡数据将关卡设计敌人位置、障碍物布局保存到文本文件如 JSON或自定义的二进制文件中在游戏启动时加载。这会让你的游戏更容易修改和扩展。使用更现代的 C 特性用std::vector管理动态数量的游戏对象用智能指针管理资源用类来封装玩家、敌人等实体。探索 Raylib 的 3D 功能Raylib 同样支持简单的 3D 渲染。你可以尝试创建 3D 模型、设置相机、添加光照制作一个简单的 3D 场景漫步程序。跨平台编译尝试在 Linux 或 macOS 上使用 GCC 或 Clang 编译你的游戏。Raylib 的 CMake 构建系统让这个过程变得相对简单。记住游戏开发是实践的艺术。最好的学习方式就是定一个小目标比如“做一个 Flappy Bird”然后去实现它遇到问题就查文档、搜社区。Raylib 的官方示例raylib-5.0/examples/目录下是一个巨大的宝库几乎每个功能都有对应的示例代码是你最好的老师。