第三课: 不要把全部东西都放在main函数里面
博客专区 > geange 的博客 > 博客详情
第三课: 不要把全部东西都放在main函数里面
geange 发表于1个月前
第三课: 不要把全部东西都放在main函数里面
  • 发表于 1个月前
  • 阅读 10
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

Lesson 2: 不要把全部东西都放在main函数里面

这节课,我们将处理纹理和渲染的代码移出main方法。我们还将编写一个简单通用的错误记录器,并了解如何在SDL渲染时对图像进行定位和缩放。

我们先声明一些常量,为窗口设置宽度和高度。在定位图片时,我们将用到这些参数。

const int SCREEN_WIDTH  = 640;
const int SCREEN_HEIGHT = 480;

SDL错误记录器

在课程1,我们使用了很多的重复的代码打印错误(除了一些关于函数出错的信息)。我们可以使用一个更加通用的错误记录函数来改进这一点。这个函数采用任何syd::stream来写入要打印的消息,并将要打印的信息和SDL_GetError中的错误信息打印出来。

/**
* Log an SDL error with some error message to the output stream of our choice
* @param os The output stream to write the message to
* @param msg The error message to write, format will be msg error: SDL_GetError()
*/
void logSDLError(std::ostream &os, const std::string &msg){
	os << msg << " error: " << SDL_GetError() << std::endl;
}

纹理加载方法

根据课程1,我们将创建一个函数,它需要文件的路径去加载一个bmp文件,并使用渲染器将图片加载纹理返回一个SDL_Texture*指针。这个函数同样执行同样的错误检查,当出错时会返回一个nullptr。我们将这个方法名定义为loadTexture。

首先,我们将SDL_Texture指针初始化为nullptr,以便如果出错可以返回一个nullptr而不是NULL。接下来, 我们将像以前一样加载BMP图片, 并检查错误, 使用我们的新 logSDLError 函数打印出发生的任何错误。如果表面加载确定, 我们然后创建纹理从表面, 并执行错误检查。如果一切顺利, 我们得到了一个有效的指针, 如果不是, 我们将返回一个 nullptr, 错误信息将显示在日志中。

/**
* Loads a BMP image into a texture on the rendering device
* @param file The BMP image file to load
* @param ren The renderer to load the texture onto
* @return the loaded texture, or nullptr if something went wrong.
*/
SDL_Texture* loadTexture(const std::string &file, SDL_Renderer *ren){
	//Initialize to nullptr to avoid dangling pointer issues
	SDL_Texture *texture = nullptr;
	//Load the image
	SDL_Surface *loadedImage = SDL_LoadBMP(file.c_str());
	//If the loading went ok, convert to texture and return the texture
	if (loadedImage != nullptr){
		texture = SDL_CreateTextureFromSurface(ren, loadedImage);
		SDL_FreeSurface(loadedImage);
		//Make sure converting went ok too
		if (texture == nullptr){
			logSDLError(std::cout, "CreateTextureFromSurface");
		}
	}
	else {
		logSDLError(std::cout, "LoadBMP");
	}
	return texture;
}

纹理渲染方法

这一小节,我们绘制顶点位置为(x, y),宽度和高度不变的纹理(Texture)。为了做到这一点,我们需要创建一个目标矩阵,将它传递到SDL_RenderCopy,获得纹理的宽度和高度与 SDL_QueryTexture,渲染时可以保存纹理的宽度和高度。每次我们想绘制图案,就调用这个函数,函数会使用x和y坐标去绘制图案,纹理和渲染器会正确设置目标矩阵的位置并绘制纹理。

目标矩阵是一个SDL_Rect,纹理的左上角的坐标为(x,y),宽度和高度设置为纹理的宽度和高度。通过 SDL_QueryTexture 检索宽度和高度值。当想要绘制整个纹理, 需要在目标矩形处呈现纹理,将源矩阵设置为NULL。你还可以根据需要设置自己的宽度和高度值以收缩或拉伸纹理。

/**
* Draw an SDL_Texture to an SDL_Renderer at position x, y, preserving
* the texture's width and height
* @param tex The source texture we want to draw
* @param ren The renderer we want to draw to
* @param x The x coordinate to draw to
* @param y The y coordinate to draw to
*/
void renderTexture(SDL_Texture *tex, SDL_Renderer *ren, int x, int y){
	//Setup the destination rectangle to be at the position we want
	SDL_Rect dst;
	dst.x = x;
	dst.y = y;
	//Query the texture to get its width and height to use
	SDL_QueryTexture(tex, NULL, NULL, &dst.w, &dst.h);
	SDL_RenderCopy(ren, tex, NULL, &dst);
}

创建窗口和渲染器

我们初始化SDL并创建窗口和渲染器的方式与1课相似, 但现在我们使用 logSDLError 函数打印出发生的任何错误, 并使用我们早先定义的屏幕宽度和高度的常量创建窗口。

if (SDL_Init(SDL_INIT_EVERYTHING) != 0){
	logSDLError(std::cout, "SDL_Init");
	return 1;
}

SDL_Window *window = SDL_CreateWindow("Lesson 2", 100, 100, SCREEN_WIDTH,
	SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (window == nullptr){
	logSDLError(std::cout, "CreateWindow");
	SDL_Quit();
	return 1;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1,
	SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (renderer == nullptr){
	logSDLError(std::cout, "CreateRenderer");
	cleanup(window);
	SDL_Quit();
	return 1;
}

加载纹理

在本课中, 我们将绘制一个平铺背景和一个居中的前景图像, 现在把两个图案列在下面, 或者使用您自己的 BMP 图像。

背景图

前景图

我们将使用我们的 loadTexture 函数加载纹理和退出, 如果无法加载。您应该更新 filepaths 以匹配您的项目结构。

const std::string resPath = getResourcePath("Lesson2");
SDL_Texture *background = loadTexture(resPath + "background.bmp", renderer);
SDL_Texture *image = loadTexture(resPath + "image.bmp", renderer);
if (background == nullptr || image == nullptr){
	cleanup(background, image, renderer, window);
	SDL_Quit();
	return 1;
}

SDL坐标系和绘图顺序

SDL的坐标系中,左上角坐标为(0,0),右下角的坐标为(SCREEN_WIDTH, SCREEN_HEIGHT)。调用SDL_RenderCopy将新的纹理绘制到当前场景的顶部,我们需要先绘制背景,然后再绘制前景图像。

绘制平铺背景

背景图是320×240像素,如果我们想要将这个图片平铺到整个640*480的屏幕,需要绘制这个图案四次。每个图块都将通过纹理宽度、高度或两者冲, 这取决于我们所需要的位置, 以便平铺边缘全部排成一行(没看懂什么意思)。我们可以像在 renderTexture 中一样, 通过 SDL_QueryTexture 来检索纹理的宽度, 然后绘制每个图块, 根据需要调整每个绘图。

练习问题: 虽然它不是那么糟糕, 绘制出来的画的位置, 只有四个, 这将是荒谬的, 如果我们要在屏幕防止大量的图片。我们如何计算图片的位置来完全填满屏幕?

注意: 所有这些渲染代码将放在我们的主循环中, 类似于课程1。

SDL_RenderClear(renderer);

int bW, bH;
SDL_QueryTexture(background, NULL, NULL, &bW, &bH);
renderTexture(background, renderer, 0, 0);
renderTexture(background, renderer, bW, 0);
renderTexture(background, renderer, 0, bH);
renderTexture(background, renderer, bW, bH);

绘制前景图像

前景图像将在窗口中居中绘制, 但由于我们指定了纹理左上角的绘制位置, 因此需要对其进行偏移, 以将图像的中心放在屏幕中央。此偏移量是通过将 x 绘制位置向左移动一半的纹理宽度和 y 位置由屏幕中央的图像宽度的一半来计算的。如果我们不这样做, 图像的左上角将被绘制在屏幕的中心。

绘制纹理后, 我们将呈现渲染, 并给自己几秒钟的时间来欣赏我们的工作。

int iW, iH;
SDL_QueryTexture(image, NULL, NULL, &iW, &iH);
int x = SCREEN_WIDTH / 2 - iW / 2;
int y = SCREEN_HEIGHT / 2 - iH / 2;
renderTexture(image, renderer, x, y);

SDL_RenderPresent(renderer);
SDL_Delay(1000);

释放内存

在退出之前, 我们必须释放我们的纹理、渲染器和窗口, 然后退出 SDL。

cleanup(background, image, renderer, window);
SDL_Quit();

结束

如果一切顺利, 你使用的图像提供你应该看到绘制到窗口的图像。

如果您有任何问题, 请检查您的错误日志, 查看可能出现问题的位置和/或张贴评论。

共有 人打赏支持
粉丝 0
博文 18
码字总数 9453
×
geange
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: