raylib绘图库--瞿华
2022.7.1
知乎 1、raylib绘图库简介
知乎 2、raylib 2d动画/游戏教程(1)动画基本原理
知乎 3、raylib 2d动画/游戏教程(2)坐标系与颜色系统
知乎 4、raylib 2d动画/游戏教程(3)图像文件与图层
知乎 5、raylib 2d动画/游戏教程(4)raylib-drawing库
知乎 6、raylib 2d动画/游戏教程(5)键盘与鼠标输入
知乎 7、raylib 2d动画/游戏教程(6)游戏手柄输入
知乎 8、raylib 2d动画/游戏教程(7)音乐和音效
知乎 9、raylib绘制中文内容
知乎 10、使用raygui绘制控件
知乎 11、raylib 3d绘图基础教程(1)坐标系和摄像机
知乎 12、raylib 3d绘图基础教程(2)网格(Mesh)
知乎 13、raylib 3d绘图基础教程(3)几何变换
知乎 14、raylib 3d绘图基础教程(4)3d模型的载入与绘制
raylib绘图库--瞿华
2022.7.1
1、raylib绘图库简介
2、raylib 2d动画/游戏教程(1)动画基本原理
3、raylib 2d动画/游戏教程(2)坐标系与颜色系统
4、raylib 2d动画/游戏教程(3)图像文件与图层
5、raylib 2d动画/游戏教程(4)raylib-drawing库
6、raylib 2d动画/游戏教程(5)键盘与鼠标输入
7、raylib 2d动画/游戏教程(6)游戏手柄输入
8、raylib 2d动画/游戏教程(7)音乐和音效
9、raylib绘制中文内容
10、使用raygui绘制控件
11、raylib 3d绘图基础教程(1)坐标系和摄像机
12、raylib 3d绘图基础教程(2)网格(Mesh)
13、raylib 3d绘图基础教程(3)几何变换
14、raylib 3d绘图基础教程(4)3d模型的载入与绘制
raylib 2d动画教程(1)动画基本原理
1 动画基本原理
我们知道,人具有所谓的"似动知觉","当两个刺激物(光点、直线、图形或图片)按一定空间间隔或时间间隔相继呈现时,我们会看到从一个刺激物向另一个刺激物的连续运动"[1]。因此,将多张图片连续快速的切换显示,就会被观众认为是一段连续播放的动画。
所以,要通过程序来显示一段动画,只需要每隔一定时间在屏幕上显示(绘制)一幅图片即可。习惯上,我们将动画中的一幅图片称为一帧(Frame);图片和图片之间的切换间隔,我们一般用帧率来表示,单位是fps(frame per second,每秒帧数)。电影和电视动画的帧率一般是24fps,而现代3d电子游戏的帧率一般都在30fps以上,动作游戏的帧率一般在60fps甚至更高。
因此,我们在做动画编程时,必须要做两件事:
设定帧率
绘制/显示每一帧的实际内容
2 raylib动画绘制基本框架
在raylib库中,使用SetTargetFPS()函数即可设置目标帧率。而帧的绘制,是程序中需要反复进行的工作,显然应该放在循环里。所以raylib动画程序(包括2d和3d)的基本框架是这样的:
InitWindow(800,600);//初始化raylib窗口
SetTraceLogLevel(LOG_WARNING);//在控制台窗口中只显示警告和错误信息
SetTargetFPS(60);//设置帧率为60fps
while (!WindowShouldClose())//当用户关闭窗口或者按下ESC键时返回true,否则返回false
{
//更新和计算相关状态变量
BeginDrawing();//准备进行帧的绘制
ClearBackground(WHITE);//清除之前帧绘制的内容
//绘制一帧
EndDrawing();//完成帧的绘制,并根据设置的帧率延时(自动暂停一段时间,以满足帧率要求)。
}
CloseWindow();
在这个框架中,while每循环一次,就会重新绘制/显示一帧的内容。
2.1 raylib 2d帧绘制
我们知道,在现代计算机中,图像是要通过显卡(GPU)输出到显示器上,从而显示出来的。为了高效的显示3d场景,现代GPU内有专门用于图像处理用的存储(显存)和计算单元。因此,在程序中,需要将要显示的数据内容从电脑内存中传输到GPU显存中,然后才能显示。因此,在raylib库中,存在两个和2d图形绘制相关的类型:
Image类型。它代表存放在电脑的内存中的一幅图片,相关的操作和计算在电脑CPU中完成。
Texture类型。它代表存在显存(GPU)中的一幅图片,相关的操作在GPU中完成。
我们用Image来载入、计算和保存要显示图片,然后用LoadTextureFromImage函数,将Image对象复制到GPU中,得到对应的Texture对象。接下来,在BeginDrawing()/EndDrawing()代码段中,使用DrawTexture或者DrawTextureRec函数来完成图片的绘制。最后,UnloadTexture()来删除不再需要使用的texture对象,释放对应的显存;使用UnloadImage()函数来删除和释放不再需要使用的Image对象。
注意:
raylib内部使用双帧缓存(Double Framebuffer)来进行显示,在EndDrawing()函数中完成帧缓存的最终刷新。因此,必须保证在EndDrawing()函数执行时,要绘制的Texture对象依然是有效的。这就要求必须在EndDrawing()函数执行完毕后,才能用UnloadTexture释放Texture对象。
无论内存和显存都是有限的。因此,一定要记得释放不需要使用的Texture和Image对象。特别是在帧绘制循环内获取的Image和Texture对象,因为帧绘制循环每秒要执行几十次,如果不及时释放的话,很快这些对象就会耗尽显存,导致程序不能正常工作。
3 示例:移动的圆1
下面的示例程序绘制了一个不断移动并且不断变色的圆。注意:
此程序中使用C标准库time.h中的time()函数获取当前时间,并用它来初始化raylib的随机数发生器(SetRandomSeed函数)。
为了实现比较均匀的变色效果,此程序使用HSV颜色空间[2]来指定绘制的颜色。
因为raylib中没有2d图形填充函数,所以绘制了20个同圆心但不同半径的圆,以模拟填充圆的效果。
注意GenImageColor、UnloadImage、LoadTextureFromImage和UnloadTexture函数在程序中的位置。
#include
#include
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
int main() {
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT,"Test");
SetTraceLogLevel(LOG_WARNING);
SetTargetFPS(60);
SetRandomSeed(time(NULL));//用当前时间初始化随机数种子。
//创建一个和屏幕同高同宽透明背景的图片
Image img=GenImageColor(WINDOW_WIDTH,WINDOW_HEIGHT,BLANK);
int i,x=0,y=0,hue=0;
while (!WindowShouldClose()) {
//更新圆的位置(texture显示的位置)
x+=5;
if (x>WINDOW_WIDTH-40) {
y+=40;x=0;
if (y>WINDOW_HEIGHT-40)y=0;
}
//更新颜色
hue=(hue+1)%360;
Color color = ColorFromHSV(hue,0.9,0.5);
//重新绘制圆
ImageClearBackground(&img,BLANK);
for(i=0;i<20;i++)ImageDrawCircle(&img,x+20,y+20,i,color);
Texture texture = LoadTextureFromImage(img);
//绘制帧
BeginDrawing();
ClearBackground(WHITE);
DrawTexture(texture,0,0,WHITE);
EndDrawing();
UnloadTexture(texture);//释放texture对象
}
UnloadImage(img);//不再需要使用img,释放掉
CloseWindow();
}
4 示例:移动的圆2
如果要绘制的图形不变,只是位置发生了变化,那么我们不需要每次重新绘制Image和复制Texture,只需要改变Texture对象在屏幕上绘制的位置即可。下面的示例程序用这种方式绘制了一个不断移动的圆。请注意GenImageColor、UnloadImage、LoadTextureFromImage和UnloadTexture函数在程序中的位置。
思考:
为什么可以在进入绘制循环前,就调用UnloadImage函数释放img对象?
可不可以把LoadTextureFromImage和UnloadTexture都放在绘制循环内?放在循环外有何好处?
为什么第一个程序不能用这种方式来绘制?
#include
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
int main() {
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT,"Test");
SetTraceLogLevel(LOG_WARNING);
SetTargetFPS(60);
//创建一个40*40,透明背景的图片
Image img=GenImageColor(40,40,BLANK);
//在图片中绘制一个以(20,20)为圆心,半径为20的红色圆
ImageDrawCircle(&img,20,20,19,RED);
Texture texture = LoadTextureFromImage(img);
UnloadImage(img);//不再需要使用img,释放掉
int x=0,y=0,step=5;
while (!WindowShouldClose()) {
//更新圆的位置(texture显示的位置)
x+=5;
if (x>WINDOW_WIDTH-texture.width) {
y+=texture.height;
x=0;
if (y>WINDOW_HEIGHT-texture.height)y=0;
}
//绘制帧
BeginDrawing();
ClearBackground(WHITE);
DrawTexture(texture,x,y,WHITE);
EndDrawing();
}
UnloadTexture(texture);//释放texture对象
CloseWindow();
}
参考
^动景运动。百度百科 https://baike.baidu.com/item/%E5%8A%A8%E6%99%AF%E8%BF%90%E5%8A%A8
^HSV。百度百科 https://baike.baidu.com/item/HSV