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动画/游戏教程(3)图像文件与图层

1 图层绘制原理
在比较复杂的动画中,每一帧中都会有背景和多个角色,角色的位置、动作等也都会有变化。如果在处理每一帧时都把要显示的内容直接画在同一张图片上,计算和处理起来会非常不方便。因此,通常的做法是把不同的角色和背景分别绘制在不同的图片上。在显示时按照顺序把这些图片叠加在一起,就可以得到最终要显示的结果。在这种方法中,每一张图片就叫做一个图层(Layer)。

比如,下面的两张图片分别绘制了一个小浣熊和一个森林小路背景,注意小浣熊图标的背景是透明的。


小浣熊角色图片(透明背景)


背景图片
将小浣熊图层叠加在背景图层上,就得到了下面的结果:


图层叠加结果
2 读入图片文件
在制作动画和游戏时,我们往往会使用photoshop等图片编辑软件预先制作好想要的图片,保存在图片文件中。通常我们会使用png格式文件来保存角色图片,因为png能很好的支持透明背景;而不需要透明的背景图片则常用jpg格式来保存。但raylib只支持png格式的图片文件,所以我们需要预先把所有的图片都转换成png格式(注意,图片格式是图片内容的实际存储形式,仅仅把文件后缀从jpg改成png并不会改变图片本身的格式)。

在raylib中,可以使用LoadImage函数来将png格式的图片文件读取成Image对象,并把每个Image对象视为一个图层来处理。

下面的示例程序读入background.png和racoon.png并叠加显示。

注意:

图片文件见附件
两个图片要和程序放到同一个文件夹下。

#include <raylib.h>
int main() {
	InitWindow(800,600,"test");
	SetTraceLogLevel(LOG_WARNING);
	//读入图片文件
	Image imgBackground=LoadImage("background.png");
	Image imgRacoon=LoadImage("racoon1.png");
	Texture tBackground = LoadTextureFromImage(imgBackground);
	UnloadImage(imgBackground);
	Texture tRacoon = LoadTextureFromImage(imgRacoon);
	UnloadImage(imgRacoon);
	while(!WindowShouldClose()) {
		BeginDrawing();
			//叠加显示图层。注意显示的顺序
			DrawTexture(tBackground,0,0,WHITE);
			DrawTexture(tRacoon,30,300,WHITE);
		EndDrawing();
	}
	UnloadTexture(tRacoon);
	UnloadTexture(tBackground);
	CloseWindow();
}


3 让图层动起来
在下面的例子中,我们把小浣熊走路的动作分解成了12张图,在每一帧中显示不同的图形,从而形成小浣熊走路的动画效果:

#include <raylib.h>
int main() {
	InitWindow(800,600,"test");
	SetTraceLogLevel(LOG_WARNING);
	SetTargetFPS(24);
	//读入图片文件
	Image imgBackground=LoadImage("background.png");
	Texture tBackground = LoadTextureFromImage(imgBackground);
	UnloadImage(imgBackground);
	Image imgRacoon[12];
	for (int i=0;i<12;i++) {
		const char* filename=TextFormat("racoon%d.png",i+1);
		imgRacoon[i]=LoadImage(filename);
	}
	int stepX=1,racoonX=0,racoonY=300,racoonFrame=0;
	while(!WindowShouldClose()) {
		racoonFrame++;
		if (racoonFrame>=12)racoonFrame=0;
		Texture tRacoon = LoadTextureFromImage(imgRacoon[racoonFrame]);
		racoonX+=stepX;
		BeginDrawing();
			//叠加显示图层。注意显示的顺序
			DrawTexture(tBackground,0,0,WHITE);
			DrawTexture(tRacoon,racoonX,racoonY,WHITE);
		EndDrawing();
		UnloadTexture(tRacoon);
	}
	for (int i=0;i<12;i++)UnloadImage(imgRacoon[i]);
	UnloadTexture(tBackground);
	CloseWindow();
}


动图封面--横穿马路的小浣熊
4 图片拼合
无论是从网络上下载,还是从磁盘中读取,大量小文件都要比一两个大文件要花费更长的时间。当程序中需要用到较多的图层图片文件时,为了加快图片文件的载入速度,往往会采取预先将多个小图片拼接在一起组成一个图片文件来读取,在程序中再裁开的方法。这种方法就是图片拼合(Image Sprite),而由多个小图片拼合组成的大图片文件就叫做拼合图片(Sprite)。


小浣熊跑动的拼合图片

4.1 ImageDraw()函数
在raylib中可以使用ImageDraw()函数来将大图片裁剪成小图片,或者将小图片拼接到大图片中。下面这段代码读入racoon-total.png,然后将其裁切成12张小图片。注意x和y的计算,以及ImageDraw()的第3个参数和第4个参数。

	//读入拼接图片
	Image imgRacoonTotal = LoadImage("racoon-total.png");
	//裁切成小图片
	for (int i=0;i<RACOON_FRAMES;i++) {
		imgRacoon[i]=GenImageColor(200,200,BLANK);
		int x=(i%4)*200,y=(i/4)*200;
		ImageDraw(&imgRacoon[i],imgRacoonTotal,
			(Rectangle){x,y,200,200},
			(Rectangle){0,0,200,200},
			WHITE);
	}
	UnloadImage(imgRacoonTotal);

ImageDraw()函数的作用是将源图片(参数2)的一部分绘制到目标图片(参数1)上,其他参数的含义是:

参数3指定了要复制的区域(矩形,左上角坐标为x,y,长宽均为200);
参数4指定了复制到的区域(矩形,左上角坐标为0,0,长宽均为200);
参数5指定了源图片颜色混合预处理使用的颜色。一般均应使用WHITE,表示直接使用源图片中的原始颜色。
4.2 示例程序
下面是完整的示例程序:

#include <raylib.h>
#define RACOON_FRAMES 12
int main() {
	InitWindow(800,600,"test");
	SetTraceLogLevel(LOG_WARNING);
	SetTargetFPS(24);
	//读入图片文件
	Image imgBackground=LoadImage("background.png");
	Texture tBackground = LoadTextureFromImage(imgBackground);
	UnloadImage(imgBackground);
	//读入拼接图片
	Image imgRacoon[RACOON_FRAMES];
	Image imgRacoonTotal = LoadImage("racoon-total.png");
	//裁切成小图片
	for (int i=0;i<RACOON_FRAMES;i++) {
		imgRacoon[i]=GenImageColor(200,200,BLANK);
		int x=(i%4)*200,y=(i/4)*200;
		ImageDraw(&imgRacoon[i],imgRacoonTotal,
			(Rectangle){x,y,200,200},
			(Rectangle){0,0,200,200},
			WHITE);
	}
	UnloadImage(imgRacoonTotal);
	int stepX=1,racoonX=0,racoonY=300,racoonFrame=0;
	while(!WindowShouldClose()) {
		racoonFrame++;
		if (racoonFrame>=RACOON_FRAMES)racoonFrame=0;
		racoonX+=stepX;
		Texture tRacoon = LoadTextureFromImage(imgRacoon[racoonFrame]);
		BeginDrawing();
			//叠加显示图层。注意显示的顺序
			DrawTexture(tBackground,0,0,WHITE);
			DrawTexture(tRacoon,racoonX,racoonY,WHITE);
		EndDrawing();
		UnloadTexture(tRacoon);
	}
	for (int i=0;i<RACOON_FRAMES;i++)UnloadImage(imgRacoon[i]);
	UnloadTexture(tBackground);
	CloseWindow();
}

4.3 简化处理示例
如果显存较大,或者图片占用空间不大,我们可以不用Image对象,直接使用Texture来读取和绘制图片,从而简化程序:

#include <raylib.h>
#define RACOON_FRAMES 12
int main() {
	InitWindow(800,600,"test");
	SetTraceLogLevel(LOG_WARNING);
	SetTargetFPS(24);
	//读入图片文件
	Texture tBackground=LoadTexture("background.png");
	//读入拼接图片
	Texture tRacoon = LoadTexture("racoon-total.png");
	int stepX=1,racoonX=0,racoonY=300,racoonFrame=0;
	while(!WindowShouldClose()) {
		racoonFrame++;
		if (racoonFrame>=RACOON_FRAMES)racoonFrame=0;
		racoonX+=stepX;
		int frameX=(racoonFrame%4)*200;
		int frameY=(racoonFrame/4)*200;
		BeginDrawing();
			//叠加显示图层。注意显示的顺序
			DrawTexture(tBackground,0,0,WHITE);
			DrawTextureRec(tRacoon,
				(Rectangle){frameX,frameY,200,200},
				(Vector2){racoonX,racoonY},
				WHITE);
		EndDrawing();
	}
	UnloadTexture(tRacoon);
	UnloadTexture(tBackground);
	CloseWindow();
}

本文中所用的原始图片来自opengameart.org,可以直接在百度网盘下载:
链接:https://pan.baidu.com/s/1wGqJ_gBC3KzXmH5tNiR1kA
提取码:vsro