从2D时钟到3D旋转立方体:手把手教你用小龙/小熊猫C++玩转EGE和raylib
从2D时钟到3D旋转立方体手把手教你用小龙/小熊猫C玩转EGE和raylib在编程学习的过程中没有什么比通过实际项目来掌握新技能更有效了。今天我们将通过两个视觉上引人入胜的项目——一个使用EGE库的2D模拟时钟和一个使用raylib的3D旋转立方体——来探索C图形编程的奇妙世界。这两个项目不仅能让你的代码活起来还能让你直观地理解不同图形库的特点和适用场景。1. 环境准备与工具选择在开始我们的图形编程之旅前选择合适的开发环境至关重要。对于C图形编程初学者来说小龙Dev-C和小熊猫C是两个非常友好的选择。开发工具对比特性小龙Dev-C小熊猫C支持的图形库EGE, EasyX, raylibEGE, raylib, FreeGLUT等跨平台支持仅WindowsWindows和Linux安装方式标准安装程序安装版和绿色版可选更新维护持续更新持续更新初学者友好度高极高提示如果你是Windows用户且主要关注2D图形小龙Dev-C是个不错的选择如果你需要跨平台支持或对3D图形更感兴趣小熊猫C可能更适合你。安装步骤非常简单访问官网下载最新版本运行安装程序小龙或解压绿色版小熊猫启动IDE新建项目即可开始编码2. EGE入门打造精美2D时钟EGE(Easy Graphics Engine)是一个轻量级的2D图形库特别适合初学者入门图形编程。让我们通过创建一个模拟时钟来体验EGE的魅力。2.1 项目结构与基础设置首先我们需要设置EGE项目的基本框架#include stdio.h #include math.h #include egegraphics.h const int WIDTH 800; const int HEIGHT 600; int main() { initgraph(WIDTH, HEIGHT); // 初始化图形窗口 setbkcolor(BLACK); // 设置背景色 cleardevice(); // 清屏 // 主循环代码将在这里 getch(); // 等待按键 closegraph(); // 关闭图形窗口 return 0; }2.2 绘制时钟表盘时钟的核心是表盘和指针。让我们先绘制静态的表盘部分void drawClockFace(int cx, int cy, int rad) { setcolor(WHITE); setlinestyle(SOLID_LINE, 0, 2); // 绘制外圆 circle(cx, cy, rad); // 绘制刻度 for (int i 0; i 60; i) { int len (i % 5 0) ? 20 : 8; // 小时刻度更长 double angle i * 6 * PI / 180; // 每6度一个刻度 int x1 cx rad * sin(angle); int y1 cy - rad * cos(angle); int x2 cx (rad - len) * sin(angle); int y2 cy - (rad - len) * cos(angle); line(x1, y1, x2, y2); } // 绘制中心点 setfillcolor(WHITE); fillellipse(cx, cy, 5, 5); }2.3 添加动态指针时钟的灵魂在于它的动态指针。我们需要根据当前时间计算指针位置void drawClockHands(int hour, int minute, int second, int cx, int cy, int rad) { // 计算各指针角度弧度 double secAngle second * 6 * PI / 180; double minAngle minute * 6 * PI / 180 secAngle / 60; double hourAngle hour * 30 * PI / 180 minAngle / 12; // 绘制时针 setlinestyle(SOLID_LINE, 0, 8); setcolor(WHITE); line(cx, cy, cx rad*0.5*sin(hourAngle), cy - rad*0.5*cos(hourAngle)); // 绘制分针 setlinestyle(SOLID_LINE, 0, 5); setcolor(LIGHTGRAY); line(cx, cy, cx rad*0.7*sin(minAngle), cy - rad*0.7*cos(minAngle)); // 绘制秒针 setlinestyle(SOLID_LINE, 0, 2); setcolor(RED); line(cx, cy, cx rad*0.9*sin(secAngle), cy - rad*0.9*cos(secAngle)); }2.4 实现实时更新最后我们需要让时钟动起来实时显示当前时间int main() { // ...初始化代码... int cx WIDTH / 2, cy HEIGHT / 2; int rad (WIDTH HEIGHT ? WIDTH : HEIGHT) / 2 - 20; // 设置XOR绘图模式便于擦除 setwritemode(R2_XORPEN); while (!kbhit()) { // 获取当前时间 SYSTEMTIME tm; GetLocalTime(tm); // 绘制静态表盘 drawClockFace(cx, cy, rad); // 绘制指针第一次绘制 drawClockHands(tm.wHour, tm.wMinute, tm.wSecond, cx, cy, rad); // 显示数字时间 char timeStr[20]; sprintf(timeStr, %02d:%02d:%02d, tm.wHour, tm.wMinute, tm.wSecond); setcolor(WHITE); outtextxy(cx - 30, cy rad / 2, timeStr); // 延时1秒 Sleep(1000); // 擦除指针通过再次绘制 drawClockHands(tm.wHour, tm.wMinute, tm.wSecond, cx, cy, rad); } // ...清理代码... }3. raylib进阶创建3D旋转立方体现在让我们转向更高级的3D图形编程。raylib是一个功能强大但易于上手的跨平台游戏开发库非常适合3D图形入门。3.1 初始化3D场景3D编程的第一步是设置场景和摄像机#include raylib.h #include math.h int main() { // 初始化窗口 const int screenWidth 800; const int screenHeight 600; InitWindow(screenWidth, screenHeight, 3D旋转立方体); // 初始化摄像机 Camera3D camera { 0 }; camera.position (Vector3){ 10.0f, 10.0f, 10.0f }; camera.target (Vector3){ 0.0f, 0.0f, 0.0f }; camera.up (Vector3){ 0.0f, 1.0f, 0.0f }; camera.fovy 45.0f; camera.projection CAMERA_PERSPECTIVE; // 设置目标帧率 SetTargetFPS(60);3.2 创建3D立方体在raylib中创建和渲染3D对象非常简单// 主游戏循环 while (!WindowShouldClose()) { // 更新摄像机位置实现环绕效果 float time GetTime(); camera.position.x cos(time) * 15.0f; camera.position.z sin(time) * 15.0f; // 开始绘制 BeginDrawing(); ClearBackground(RAYWHITE); // 开始3D模式 BeginMode3D(camera); // 绘制立方体 DrawCube(Vector3{0, 0, 0}, 2.0f, 2.0f, 2.0f, VIOLET); DrawCubeWires(Vector3{0, 0, 0}, 2.0f, 2.0f, 2.0f, BLACK); // 绘制参考网格和坐标轴 DrawGrid(10, 1.0f); DrawAxis(); EndMode3D(); // 显示FPS DrawFPS(10, 10); EndDrawing(); } CloseWindow(); return 0; }3.3 添加旋转动画为了让立方体更有活力我们可以让它自身旋转// 在循环开始前定义旋转角度变量 float rotationAngle 0.0f; // 在主循环中更新旋转角度 rotationAngle 0.5f; if (rotationAngle 360) rotationAngle - 360; // 修改立方体绘制代码添加旋转参数 DrawCubeV(Vector3{0, 0, 0}, Vector3{2.0f, 2.0f, 2.0f}, VIOLET); DrawCubeWiresV(Vector3{0, 0, 0}, Vector3{2.0f, 2.0f, 2.0f}, BLACK);3.4 增强视觉效果我们可以通过添加光照和材质来提升视觉效果// 在初始化后设置光照 SetCameraMode(camera, CAMERA_ORBITAL); // 修改立方体绘制代码使用更高级的模型 Model cube LoadModelFromMesh(GenMeshCube(2.0f, 2.0f, 2.0f)); // 在主循环中绘制模型而不是原始立方体 DrawModel(cube, Vector3{0, 0, 0}, 1.0f, VIOLET); DrawModelWires(cube, Vector3{0, 0, 0}, 1.0f, BLACK); // 不要忘记在程序结束时卸载模型 UnloadModel(cube);4. EGE与raylib深度对比通过这两个项目的实践我们可以清晰地看到EGE和raylib的不同特点和适用场景。核心特性对比特性EGEraylib主要用途2D图形、教学演示2D/3D游戏开发跨平台支持仅Windows全平台(Windows, Linux, macOS等)3D支持无完整支持学习曲线非常平缓中等文档质量中文文档丰富英文文档为主性能适合简单应用高性能适合游戏社区生态国内社区活跃国际社区更活跃选择建议如果你是教育用途或只需要简单的2D图形EGE是更好的选择如果你需要开发跨平台应用或涉及3D图形raylib更合适对于游戏开发raylib提供了更完整的游戏开发框架对于教学演示和小型工具开发EGE更简单易用注意两个库都可以在小龙Dev-C和小熊猫C中使用但raylib在小熊猫C中的跨平台支持更好。5. 常见问题与调试技巧在实际开发过程中你可能会遇到一些问题。以下是一些常见问题的解决方案EGE常见问题图形窗口不显示确保调用了initgraph函数检查是否有杀毒软件阻止了程序运行尝试以管理员身份运行程序中文显示乱码// 在程序开始时设置字符集 setfont(16, 0, 宋体);动画闪烁严重使用双缓冲技术initgraph(WIDTH, HEIGHT, INIT_RENDERMANUAL); setbkmode(TRANSPARENT);raylib常见问题3D模型不显示检查摄像机位置和朝向确保模型在摄像机视野内检查模型加载是否成功跨平台编译问题在Linux上需要安装OpenGL开发库确保小熊猫C配置了正确的编译选项性能优化技巧// 在资源加载时 SetTextureFilter(texture, FILTER_BILINEAR); // 在主循环中避免频繁资源加载/卸载调试技巧使用printf或DrawText输出变量值逐步构建场景先验证简单图形再添加复杂元素利用IDE的调试功能设置断点查阅官方示例代码和文档