找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 84|回复: 6

【Raylib&C++教程02期】2D图像绘制Part 1

[复制链接]

18

主题

85

回帖

1414

积分

管理员

积分
1414

一周年纪念

发表于 2025-4-18 22:49:45 | 显示全部楼层 |阅读模式
各位CZLJ用户们,抱歉托更那么久。由于开学后这篇文章只写了一半,一直耽搁着。这次更新来补补。由于我使用学而思学习机的安卓环境,所以Part 2-3-1后的内容无法实时演示,请各位谅解。

上期我们学习了Raylib的介绍+环境搭建,这一期正式开始项目学习!!
Prat 0:Raylib绘图原理
不只是Raylib,许多3D引擎的画图原理都是这样的:
创建窗口—>刷新相机(根据相机变化计算下帧时每块像素是什么颜色)—>清空画布—>绘制计算好的每个像素点的颜色—>其他(侦测按键⌨、鼠标🖱、麦克风🎤等)—>循环  /终止条件成立:结束循环
                                                                                                                                                                                                                         终止条件不成立:继续循环
                            |_______________________________________________________________________________________________________________________________
       你可以这样理解:程序就相当于画家👨‍🎨,窗口就相当于一个画布🎴,画家就会不停地画一封画再擦掉,

一封画再擦掉...每次画之前或画之后,画家👨‍🎨就会根据相机在空间里数据的变化(如坐标变化、朝向变化等)计
算出下一次画时画布上每个地方画的颜色;或者根据外部输入(键盘⌨、鼠标🖱等)更改画布上的每个像素的
颜色。
        在现实中,这个过程极其缓慢,但是计算机程序运行效率高,能做到每分钟做几十至几百次“画一封画再擦掉”

的操作,这就让我们看上去这个画布(窗口)里是不停在动的动起来的画面。这在现实中就好像我们上学时玩的手

绘翻页动图一样。每翻一页就相当于画家“画一封画再擦掉”就相当于程序“更新窗口上每个像素点的颜色然后清空

               屏幕”。


接下来让我们把这个过程与程序对应起来!
Part 1-1:创建项目
在你想要的地方新建一个文件夹,名字随便,比如“Raylib学习”。许多同学没有做C++项目的经验,平时做题的文件都放在一起。因为到后来,除了.CPP源文件,我们做Raylib项目时还要用到贴图文件、读写存档文件和.exe可执行文件,把几个项目的这些文件放在一起,自己难找,程序读写文件时也难找,需要项目隔离。
打开RedPandaIDE v3.1 小熊猫C++,没下载好的看我上期帖子:【Raylib&C++教程01期】介绍+环境搭建+官网开源学习(出处: CZLJ),单击“文件”-->“新建”-->“新建C/C++文件”新建一个.cpp项目文件(手懒的可以点击菜单栏下面一行图标里的第一个📄样子的按钮,或者按下Ctrl+N)

Part 1-2:编写控制台程序
接着,在新建的untitled1.cpp文件里写上C++源代码:
  1. #include<bits/stdc++.h>//引入C++万能头文件
  2. #include<raylib.h>//引入Raylib库头文件

  3. using namespace std;//使用std命名空间

  4. int main(){
  5.         //我的Raylib学习代码
  6.         return 0;
  7. }//定义主函数
复制代码
点击上方菜单栏中“运行”-->“运行    F11”或直接点击菜单栏下方一个像Windows的图标的按钮或直接按下F11就会开始编译代码。成功后如下图:

Part 1-3:循环绘图
现在,我们开始Part 0-0中的绘图思路的第一步:创建画布
在main函数里添加代码。使用InitWindow(480,360,"我的Raylib学习程序");可以创建一个长480像素、宽360像素,窗口名称为“我的Raylib学习程序”的窗口。此时,控制台窗口里输出了一大堆东西,这说明Raylib窗口创建成功了。但是这个空白窗口是持续了2秒半就没了,所以我们要在InitWindow下面加一个循环,让窗口持续
  1. #include<bits/stdc++.h>//引入C++万能头文件
  2. #include<raylib.h>//引入Raylib库头文件

  3. using namespace std;//使用std命名空间

  4. int main(){
  5.         //我的Raylib学习代码
  6.         InitWindow(480,360,"我的Raylib学习程序");//创建一个长480px(像素),宽360px的新窗口,名为“我的Raylib学习程序”
  7.         while(1){
  8.                
  9.         }
  10.         return 0;
  11. }//定义主函数
复制代码
此处while循环的循环条件为1。在C++中非零为真,循环就会执行。这样写循环是永无止的,要手动关闭。你可以填写自己游戏的终止条件,比如按下ESC键、某个变量等于几之类。这个while循环结束后是return 0;那这个循环结束后程序就会结束。你可以大胆发挥,做多重循环。比如第一个循环有个登陆界面,直到用户输入密码后循环才终止,然后在写一个循环为软件界面,或是嵌套循环,外层绘制进入游戏前,里面循环绘制进入游戏后的3D环境...类似这样。
此处可以使用Raylib库自带的一个布尔函数作为循环条件:WindowShouldClose()当用户在Raylib创建的窗口中按下ESC键,这个函数返回true,不按表示不要关闭窗口返回false。这样放进while循环的()去逻辑是反的,所以可以这样写:
  1. //头部同上
  2. //...
  3. int main(){
  4.         //我的Raylib学习代码
  5.         InitWindow(480,360,"我的Raylib学习窗口");
  6.         while(!WindowShouldClose()){
  7.                 //!WindowShouldClose()就是“窗口需要关闭”不成立,这样的话整个游戏只有按下ESC或手动关闭才会跳出循环
  8.                
  9.         }
  10.         return 0;
  11. }//定义主函数        
复制代码
运行效果如图:

此时你把鼠标放进窗口却是无响应,按ESC或点❌都无法关闭。这是因为我们什么都没画呢。你可以点控制台的窗口的❌关闭整个程序。
到这里,我们的“画布”部分已经写好了,我们要改改“画家”往里面绘制内容了
在while循环里加上两个函数,他们是:
  1. BeginDrawing();//开始绘制
  2. //...其他绘图函数写在中间
  3. EndDrawing();//结束绘制
复制代码
他俩的作用同他俩的名字一样,一个标志开始绘制,一个标志结束绘制。这样是为了让程序知道那一段是绘制的部分。我们所有的绘图函数调用前,都必须写上BeginDrawing();而调用完都必须写上EndDrawing();
也就是说绘图函数必须在这两个函数中间调用。你可以用一个大括号连接他们,这样不仅好辩认,而且在俩函数中间临时定义的变量用完后都会抹掉。(如下代码块)
你可以把他俩理解为:BeginDrawing();就是“画家👨‍🎨”落下了笔🖊,而EndDrawing();就是“画家👨‍🎨”把笔🖌抬起来了。而落笔🖊和抬笔🖌两个动作之间的动作就是绘画。
  1. //上面代码同上
  2. while(!WindowShouldClose()){
  3.         BeginDrawing();
  4.         /*
  5.         ...绘制图形的函数在两个函数之间编写
  6.         */
  7.         EndDrawing();
  8. }
  9. //下面代码同上
复制代码
运行后鼠标伸进窗口就是正常的了,点×、按ESC可以关闭窗口。此时窗口是默认的一片漆黑。我们可以画东西在里面。
首先,是“画家👨‍🎨清屏”操作。使用ClearBackground(颜色);函数对窗口清屏为填入参数。这里需要补充一下Raylib结构体Color
Color的定义如下
  1. typedef struct Color {
  2.     unsigned char r;        // Color red value
  3.     unsigned char g;        // Color green value
  4.     unsigned char b;        // Color blue value
  5.     unsigned char a;        // Color alpha value
  6. } Color;
复制代码
可以看到,Color类型的变量有4个值,r、g、b、a,也就是Red(红色值)Green(绿色值)Blue(蓝色值)Alpha(透明度)。没错,这就是计算机视觉里的RGBA通道。这四个变量范围都在0~255之间。你可以用3种方法定义一个变量。
方法一:
  1. Color a_colour;//定义一个Color类的变量a_colour
  2. a_colour.r=255;//红色值为满
  3. a_colour.g=161;//绿色值为161
  4. a_colour.b=0;//蓝色值为空
  5. a_colour.a=255;//Alpha值为满则为不透明颜色,Alpha值越低颜色越透明
复制代码
方法二:
  1. Color a_colour={255,161,0,255};//定义一个r、g、b、a值分别为255、161、0、255的Color类变量a_colour
复制代码
方法三:
  1. //当使用一个绘图函数(如清屏函数void ClearBackgroung(Color color);)时函数参数为Color类时,可以这样传参,无须刻意定义一个变量
  2. ClearBackground((Color){255,161,0,255});
复制代码
以上3种方法均可以定义一个RGBA值为{255,161,0,255}的变量。这个值的变量表示出来的颜色为橙色。
当然,如果你懒得找每种颜色的RGBA值的话,你可以使用Raylib.h头文件里已经定义好的颜色变量。这些Raylib库自带变量如下表:
  1. //这些可以在raylib.h里找到.你可以按Ctrl+单击上面源码里第二行里的<raylib.h>打开头文件查找

  2. // Some Basic Colors
  3. // NOTE: Custom raylib color palette for amazing visuals on WHITE background
  4. #define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray  译:亮灰色
  5. #define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray  译:灰色
  6. #define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray  译:深灰色
  7. #define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow  译:黄色
  8. #define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold  译:金色
  9. #define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange  译:橙色             <-----上面方法定义的值来自这里
  10. #define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink  译:粉色
  11. #define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red  译:红色
  12. #define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon  译:魔力红(深一些的红)
  13. #define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green  译:绿色
  14. #define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime  译:草绿色
  15. #define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green  译:深绿色
  16. #define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue  译:天蓝色
  17. #define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue  译:蓝色
  18. #define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue  译:深蓝色
  19. #define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple  译:紫色
  20. #define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet  译:紫罗兰(淡一些的紫)
  21. #define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple  译:深紫色
  22. #define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige  译:米黄色
  23. #define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown  译:棕色
  24. #define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown  译:深棕色

  25. #define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White  译:白色
  26. #define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black  译:黑色
  27. #define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent)  译:...?空颜色,值为0,0,0,0
  28. #define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta  译:...?有点紫有点红有点粉的红
  29. #define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo)  译:...射线白?这是Raylib  Logo中用的白色
复制代码
所以你可以直接使用这些定义好的变量。注意:这些变量均为大写。比如ClearBackground(ORANGE);
下面我们使用ClearBackground();和另外两个绘图函数绘制出一个页面.现在暂时不需要理解另外两个函数的用法。
  1. #include<bits/stdc++.h>//引入C++万能头文件
  2. #include<raylib.h>//引入Raylib库头文件

  3. using namespace std;//使用std命名空间

  4. int main(){
  5.         //我的Raylib学习代码
  6.         InitWindow(480,360,"我的Raylib学习窗口");
  7.         while(!WindowShouldClose()){
  8.                 BeginDrawing();
  9.                 {
  10.                         ClearBackground(BLUE);//“画家👨‍🎨”把“画布”清空为蓝色(即刷新一帧)
  11.                         /*...绘制图形的函数在两个函数之间编写*/
  12.                         DrawRectangle(100,130,280,80,YELLOW);
  13.                         DrawText("My First Raylib Program",120,160,20,RED);
  14.                 }
  15.                 EndDrawing();
  16.         }
  17.         return 0;
  18. }//定义主函数
复制代码
运行效果如图:

下面Part 2-1~2-3-6我们开始系统地学习Raylib库的主要2D绘图函数.
我会以绘制内容+函数变体目录+用法+实例的流程为大家讲解。
内容有点复杂,大家坐好扶稳,~%?…,# *'☆&℃$︿★  启动!
Part 2-1:基础图形之像素点Pixel🔸
像素点这个东西是构成一切一切的基础。这里讲的像素点指的是屏幕上的像素点,很小很小的哦
如果你绘制一个像素点,那可是很难找到的。
DrawPixel函数的所有变体:
|__RLAPI void DrawPixel(int posX, int posY, Color color);//(本体)
|__RLAPI void DrawPixelV(Vector2 position, Color color);
·1 DrawPixel();
用法:DrawPixel(X坐标,Y坐标,颜色);
例子:
  1. DrawPixel(100,100,(Color){255,255,255,255});//在窗口坐标的100,100处渲染一个白色像素
  2. DrawText("Can you ^ see the white pixel?",50,100,12,RED);
复制代码
·2 DrawPixelV()
用法:DrawPixelV(一个Vector2的变量表示x、y,一个Color变量表示颜色);
例子:
  1. DrawPixelV((Vector2){100,100},(Color){255,255,255,255});//在窗口坐标的100,100处渲染一个白色像素
  2. DrawText("Can you ^ see the white pixel?",50,100,12,RED);
复制代码
两次运行效果均如下:
是不是很小?
Part 2-2:基础图形之线Line➿
线是由像素组成的。Raylib中没有只规定直线or竖线,而是规定线的两端可以是任意坐标,也就是说你不用考虑线的角度,这个角度背后的计算会由Raylib底层数学代码自动执行。
线的变体有5个。你总不会喜欢一条单调、一像素细两端秃秃的线吧。
DrawLine函数的全部变体:
|__RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color);//(本体)
|__RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color);
|__RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color);
|__RLAPI void DrawLineStrip(Vector2 *points, int pointCount, Color color);
|__RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color);
·1 DrawLine();
用法:DrawLine(起始的X坐标,起始的Y坐标,结束的X坐标,结束的Y坐标,颜色);
示例:
  1. DrawText("Cut Down!",50,100,30,RED);
  2.                         DrawLine(90,50,140,180,ORANGE);//从窗口坐标的90,50处到140,180处画一条橙色的线(只有一像素粗)
复制代码
·2 DrawLineV();
用法:DrawLineV(一个表示起始X、Y坐标的Vector2变量,一个表示结束的X、Y坐标的Vector2变量,一个表示颜色的Color变量);
示例:
  1. Vector2 start_pos={90,50},end_pos={140,180};//起始点坐标为90,50,结束点坐标为140,180
  2.                         DrawText("Cut Down!",50,100,30,RED);
  3.                         DrawLineV(start_pos,end_pos,ORANGE);//从窗口坐标的90,50处到140,180处画一条橙色的线(只有一像素粗)
复制代码
效果均如图:

·3 DrawLineEx();
Ex有增强、更好的意思,这个函数顾名思义就是DrawLine的pro版。具体它是在前一个
Part 2-3-1:基础图形之面Face之矩形Rectangle⬛

从零维的点到一维的线,接着就是二维的面了。

Part 2-3-2:基础图形之面Face之三角形Triangle▲
三角形作为基础几何元素,在游戏开发中常用于绘制箭头、山体轮廓等。Raylib提供三种三角形绘制方式:

|__RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color);//实心三角形
|__RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color);//线框三角形
|__RLAPI void DrawTriangleFan(Vector2 *points, int pointCount, Color color);//扇形三角集合

·1 DrawTriangle()
示例代码:
```cpp
Vector2 top = {240, 50};
Vector2 left = {140, 250};
Vector2 right = {340, 250};
DrawTriangle(top, left, right, SKYBLUE);
```
效果:绘制实心天蓝色三角形,顶点坐标分别为(240,50)、(140,250)、(340,250)

·2 DrawTriangleLines()
示例代码:
```cpp
DrawTriangleLines(top, left, right, DARKBLUE);
```
效果:用深蓝色线条勾勒三角形轮廓

·3 DrawTriangleFan()
适用于批量绘制共顶点的三角形:
```cpp
Vector2 points[] = {{240,120}, {200,200}, {280,200}, {240,280}};
DrawTriangleFan(points, 4, PURPLE);
```
效果:以第一个点(240,120)为公共顶点,绘制三个相连的紫色三角形

Part 2-3-3:基础图形之面Face之圆Circle⚫
圆形在游戏开发中常用于碰撞检测、技能范围等场景:

|__RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color);
|__RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color);
|__RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2);
|__RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color);

·1 DrawCircle()
```cpp
DrawCircle(240, 180, 80.0f, YELLOW); // 在(240,180)绘制半径80的黄色实心圆
```

·2 DrawCircleSector()
```cpp
DrawCircleSector((Vector2){240,180}, 60, 45, 135, 36, ORANGE); // 绘制45°到135°的扇形
```

·3 DrawCircleGradient()
```cpp
DrawCircleGradient(240, 180, 60, RED, BLANK); // 从中心红色渐变到边缘透明
```
Part 2-3-4:基础绘图之面Face之椭圆Ellipse🥚

椭圆常用于绘制倾斜的圆

|__RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color);
|__RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color);

示例:
```cpp
DrawEllipse(240, 180, 100, 60, PINK); // 绘制横轴100px,纵轴60px的粉色椭圆
DrawEllipseLines(240, 180, 80, 40, MAROON); // 绘制线框椭圆
```
Part 2-3-5:基础绘图之面Face之环🔘

环形元素常用于制作加载动画:

|__RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color);

示例:
```cpp
DrawRing((Vector2){240,180}, 50, 70, 0, 270, 36, BLUE); // 绘制270°的蓝色圆环
```

Part 2-3-6:基础绘图之面Face之多边形Poly🗯

多边形是构建复杂图形的基础

|__RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color);
|__RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color);

示例:
```cpp
DrawPoly((Vector2){240,180}, 6, 80, 30.0f, GREEN); // 绘制旋转30度的六边形
DrawPolyLines((Vector2){240,180}, 5, 60, 0, DARKGREEN); // 五边形线框
```

Part 3:Raylib 2D绘图函数速查表
为方便查阅,整理核心绘图函数速查:

【基础图形】
- 点:DrawPixel(), DrawPixelV()
- 线:DrawLineEx()支持设置粗细,DrawLineBezier()绘制贝塞尔曲线
- 形状:所有图形均有Fill(填充)和Lines(线框)两种版本

【高级特性】
- 渐变:DrawRectangleGradient(), DrawCircleGradient()
- 自定义图形:通过组合基础图形+数学计算实现复杂效果
- 批绘制:使用BeginBlendMode()开启混合模式制作透明效果

【性能提示】
1. 避免在循环内创建Color/Vecter2对象
2. 复杂图形优先使用rlgl模块的底层绘制
3. 静态元素使用纹理渲染替代实时绘制

下期预告:2D绘制Part2-2D贴图加载绘制!记得用F11运行代码观察动态效果哦~(◕ᴗ◕✿)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×

18

主题

85

回帖

1414

积分

管理员

积分
1414

一周年纪念

 楼主| 发表于 2025-4-18 22:50:49 | 显示全部楼层
由于帖子年久失修,part2-3-1后面都是deepseek代写的
回复

使用道具 举报

3

主题

12

回帖

230

积分

蓝锐

积分
230
发表于 7 天前 | 显示全部楼层
我尽然没有第一时间看到这篇帖子,失敬失敬

点评

你不必难过,因为这帖子去年底就开始写了,拖这么久是我的不敬🙏😐  发表于 7 天前
Make Programming Great Again
回复

使用道具 举报

3

主题

12

回帖

230

积分

蓝锐

积分
230
发表于 7 天前 | 显示全部楼层
不过我有一个疑问,raylib编译后似乎总会有一个控制台窗口,若是我希望不显示它,怎么做?
Make Programming Great Again
回复

使用道具 举报

18

主题

85

回帖

1414

积分

管理员

积分
1414

一周年纪念

 楼主| 发表于 7 天前 | 显示全部楼层
xinhaitianze 发表于 2025-4-19 12:56
不过我有一个疑问,raylib编译后似乎总会有一个控制台窗口,若是我希望不显示它,怎么做? ...

貌似要改一改发给编译器的指令

或者用小熊猫C++编译,先在设置/编译选项里面找找,
我记得有一个页面可以设置什么
nowindow不产生控制台
nostdlib不实用标准库
...的,选上不产生控制台保存选项再编译即可

不过吧,有时找bug在哪可以控制台输出来检查问题,我一般是软件全做好了再编译成无窗口的程序
回复

使用道具 举报

3

主题

12

回帖

230

积分

蓝锐

积分
230
发表于 7 天前 | 显示全部楼层
Nixx0328 发表于 2025-4-19 16:13
貌似要改一改发给编译器的指令

或者用小熊猫C++编译,先在设置/编译选项里面找找,

3Q解决了大问题
Make Programming Great Again
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|CZLJ

GMT+8, 2025-4-26 17:53 , Processed in 0.072906 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表