【SDL游戏编程入门第十卷】切片渲染和精灵表

一、前言

有时您只想渲染纹理的一部分。很多时候,游戏喜欢将多个图像保留在同一个精灵表上,而不是拥有一堆 纹理。使用剪辑渲染,我们可以定义要渲染的纹理的一部分,而不是渲染整个东西。

二、纹理类

// 纹理类
class Texture
{
private:
	int m_Width;
	int m_Height;
	SDL_Texture* m_Texture;
public:
	Texture(const char* path, int width = SCREEN_WIDTH, int height = SCREEN_HEIGHT, bool enableColorKey = false, Uint8 r = 0, Uint8 g = 0, Uint8 b = 0)
		: m_Width(width), m_Height(height)
	{
		SDL_Surface* loadedSurface = IMG_Load(path);
		if (enableColorKey)  // 颜色键控
			SDL_SetColorKey(loadedSurface, SDL_TRUE, SDL_MapRGB(loadedSurface->format, r, g, b));
		m_Texture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);  // 加载纹理
		if (!m_Texture)
			std::cout << "[Error]: Unable to create texture from" << path << " SDL_Error: " << SDL_GetError() << std::endl;
		SDL_FreeSurface(loadedSurface);
	}

	~Texture()
	{
		if (m_Texture)
		{
			SDL_DestroyTexture(m_Texture);
			m_Texture = nullptr;
		}
	}

	// 渲染纹理
	inline void Render(int x, int y, SDL_Rect* clip = nullptr)
	{
		SDL_Rect dstRect{ x, y, m_Width, m_Height };

		if (clip)  // 根据不同情况可以编写自己的,并不一定按我这里的来
		{
			dstRect.w = clip->w;
			dstRect.h = clip->h;
		}

		SDL_RenderCopy(gRenderer, m_Texture, clip, &dstRect);
	}

	inline int GetWidth() const { return m_Width; }
	inline int GetHeight() const { return m_Height; }
};

这里简单的封装了一些纹理类,配合注释看一下应该很容易理解,这里不多赘述了

三、切片渲染

本卷使用的资源

我们将采用该精灵表,将每个精灵渲染在不同的角落

因此,我们需要一个纹理图像和 4 个矩形来定义精灵,这是您在此处看到的变量。

SDL_Rect gSpriteClips[4];
std::unique_ptr<Texture> gSpriteSheetTexture;	// 精灵纹理表

这里用了智能指针,记得包含头文件 <memory>

1. 加载媒体资源

/**
 * @brief 加载媒体
 */
bool LoadMedia()
{
	gSpriteSheetTexture = std::make_unique<Texture>(R"(res\textures\sprites.png)", 200, 200, true, 0, 255, 255);

	// 设置左上角精灵
	gSpriteClips[0].x = 0;
	gSpriteClips[0].y = 0;
	gSpriteClips[0].w = 100;
	gSpriteClips[0].h = 100;

	// 设置右上角精灵
	gSpriteClips[1].x = 100;
	gSpriteClips[1].y = 0;
	gSpriteClips[1].w = 100;
	gSpriteClips[1].h = 100;

	// 设置左下角精灵
	gSpriteClips[2].x = 0;
	gSpriteClips[2].y = 100;
	gSpriteClips[2].w = 100;
	gSpriteClips[2].h = 100;

	// 设置右下角精灵
	gSpriteClips[3].x = 100;
	gSpriteClips[3].y = 100;
	gSpriteClips[3].w = 100;
	gSpriteClips[3].h = 100;

	return true;
}

2. 示例代码

/* 此源代码版权归 AnnihilateSword (2022-*)所有,未经书面许可不得转载。*/

// 使用 SDL 和 iostream
#include <SDL.h>
#include <SDL_image.h>
#include <iostream>
#include <memory>

// 链接库
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")
#pragma comment(lib, "SDL2_image.lib")

// 屏幕尺寸常量
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;

bool Init();								// 启动 SDL 并创建窗口
bool LoadMedia();							// 加载媒体
void Close();								// 清理资源,关闭窗口以及SDL

SDL_Window* gWindow = nullptr;				// 主窗口
SDL_Renderer* gRenderer = nullptr;			// 主窗口渲染器

// 纹理类
class Texture
{
private:
	int m_Width;
	int m_Height;
	SDL_Texture* m_Texture;
public:
	Texture(const char* path, int width = 100, int height = 100, bool enableColorKey = false, Uint8 r = 0, Uint8 g = 0, Uint8 b = 0)
		: m_Width(width), m_Height(height)
	{
		SDL_Surface* loadedSurface = IMG_Load(path);
		if (enableColorKey)  // 颜色键控
			SDL_SetColorKey(loadedSurface, SDL_TRUE, SDL_MapRGB(loadedSurface->format, r, g, b));
		m_Texture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);  // 加载纹理
		if (!m_Texture)
			std::cout << "[Error]: Unable to create texture from" << path << " SDL_Error: " << SDL_GetError() << std::endl;
		SDL_FreeSurface(loadedSurface);
	}

	~Texture()
	{
		if (m_Texture)
		{
			SDL_DestroyTexture(m_Texture);
			m_Texture = nullptr;
		}
	}

	// 渲染纹理
	inline void Render(int x, int y, SDL_Rect* clip = nullptr)
	{
		SDL_Rect dstRect{ x, y, m_Width, m_Height };

		if (clip)
		{
			dstRect.w = clip->w;
			dstRect.h = clip->h;
		}

		SDL_RenderCopy(gRenderer, m_Texture, clip, &dstRect);
	}

	inline int GetWidth() const { return m_Width; }
	inline int GetHeight() const { return m_Height; }
};

SDL_Rect gSpriteClips[4];
std::unique_ptr<Texture> gSpriteSheetTexture;	// 精灵纹理表

int main(int argc, char* argv[])				// 必须要填写此参数,不然会出现链接错误
{
	// 初始化 SDL
	if (Init())
	{
		// 加载媒体资源
		if (!LoadMedia())
			std::cout << "[Error]: Failed to load media!" << std::endl;
		
		// 窗口循环
		SDL_Event e;
		bool quit = false;
		while (quit == false)
		{
			// 处理事件
			while (SDL_PollEvent(&e))
			{
				if (e.type == SDL_QUIT)
					quit = true;
			}

			// --- rendering -----------------------------------------------------------------------
			// 设置渲染器绘制颜色(黑色)
			SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 255);
			// 清屏
			SDL_RenderClear(gRenderer);

			gSpriteSheetTexture->Render(0, 0, &gSpriteClips[0]);
			gSpriteSheetTexture->Render(SCREEN_WIDTH - gSpriteClips[1].w, 0, &gSpriteClips[1]);
			gSpriteSheetTexture->Render(0, SCREEN_HEIGHT - gSpriteClips[2].h, &gSpriteClips[2]);
			gSpriteSheetTexture->Render(SCREEN_WIDTH - gSpriteClips[3].w, SCREEN_HEIGHT - gSpriteClips[3].h, &gSpriteClips[3]);

			// 更新屏幕
			SDL_RenderPresent(gRenderer);
		}
	}
	// 清理资源
	Close();
	return 0;
}

/**
 * @brief 启动 SDL 并创建窗口
 */
bool Init()
{
	// 初始化 SDL
	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		std::cout << "[Error]: SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
		return false;
	}
	else
	{
		// 设置纹理线性过滤
		if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"))
			std::cout << "[Warning]: Linear texture filtering not enabled!" << std::endl;

		// 创建窗口
		gWindow = SDL_CreateWindow("HelloSDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
		if (!gWindow)
		{
			std::cout << "[Error]: Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;
			return false;
		}
		else
		{
			// 创建窗口渲染器
			gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);  // -1 初始化支持所请求标志的第一个
			if (!gRenderer)
			{
				std::cout << "[Error]: Renderer could not bo created! SDL_Error: " << SDL_GetError() << std::endl;
				return false;
			}
			else
			{
				// 初始化渲染器绘制颜色(蓝色),这次在主循环中设置
				//SDL_SetRenderDrawColor(gRenderer, 51, 76, 204, 255);

				// 初始化 PNG 加载
				int imgFlags = IMG_INIT_PNG;
				if (!(IMG_Init(imgFlags) & imgFlags))
				{
					std::cout << "[Error]: SDL_image could not initialize! SDL_image Error: " << IMG_GetError() << std::endl;
					return false;
				}
			}
		}
	}
	return true;
}

/**
 * @brief 加载媒体
 */
bool LoadMedia()
{
	gSpriteSheetTexture = std::make_unique<Texture>(R"(res\textures\sprites.png)", 200, 200, true, 0, 255, 255);

	// 设置左上角精灵
	gSpriteClips[0].x = 0;
	gSpriteClips[0].y = 0;
	gSpriteClips[0].w = 100;
	gSpriteClips[0].h = 100;

	// 设置右上角精灵
	gSpriteClips[1].x = 100;
	gSpriteClips[1].y = 0;
	gSpriteClips[1].w = 100;
	gSpriteClips[1].h = 100;

	// 设置左下角精灵
	gSpriteClips[2].x = 0;
	gSpriteClips[2].y = 100;
	gSpriteClips[2].w = 100;
	gSpriteClips[2].h = 100;

	// 设置右下角精灵
	gSpriteClips[3].x = 100;
	gSpriteClips[3].y = 100;
	gSpriteClips[3].w = 100;
	gSpriteClips[3].h = 100;

	return true;
}

/**
 * @brief 清理资源,关闭窗口以及SDL
 */
void Close()
{
	// 销毁窗口渲染器
	if (gRenderer)
	{
		SDL_DestroyRenderer(gRenderer);
		gRenderer = nullptr;
	}
	// 销毁窗口
	if (gWindow)
	{
		SDL_DestroyWindow(gWindow);
		gWindow = nullptr;
	}
	// 退出 IMG 和 SDL
	IMG_Quit();
	SDL_Quit();
}

3. 运行结果



本节内容就到这里了,下卷会继续分享 SDL 的基本使用

The End.

posted @ 2022-12-08 19:14  AnnihilateSword  阅读(38)  评论(0编辑  收藏  举报