【SDL游戏编程入门第十四卷】使用字体SDL_ttf (True Type Font)

一、前言

使用 SDL 呈现文本的一种方法是使用扩展库 SDL_ttf

SDL_ttf 允许您从 TrueType 字体创建图像,我们将在此处使用这些字体来创建纹理 从字体文本。

✨扩展库连接

✨下载

配置方式跟之前讲解 SDL 和 SDL_image 库的配置基本类似

✨官方文档

二、使用 SDL_ttf 加载字体

本卷使用资源是在电脑上找的 【不仅可以加载 ttf 格式的,还可以加载其他格式的,例如 ttc、otf,可以去官网查阅】

Texture()
    : m_Width(0), m_Height(0), m_Texture(nullptr)
{
}

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

// 从文件中加载纹理
bool LoadFromFile(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);
    return m_Texture != nullptr;
}

// 从字体字符串中创建图片
bool LoadFromRenderedText(const char* textureText, SDL_Color textColor)
{
    SDL_Surface* textSurface = TTF_RenderText_Solid(gFont, textureText, textColor);
    if (!textSurface)
    {
        std::cout << "[Error]: Unable to render text surface! SDL_ttf Error: " << TTF_GetError() << std::endl;
    }
    else
    {
        m_Texture = SDL_CreateTextureFromSurface(gRenderer, textSurface);
        if (!m_Texture)
        {
            std::cout << "[Error]: Unable to create texture from rendered text! SDL_Error: " << SDL_GetError() << std::endl;
        }
        else
        {
            // 【这里设置了字体纹理大小】
            m_Width = textSurface->w;
            m_Height = textSurface->h;
        }
    }
    // 释放临时表面
    SDL_FreeSurface(textSurface);
    return m_Texture != nullptr;
}

在这里,我们将向纹理类添加两个函数,LoadFromRenderedTextLoadFromFileSDL_ttf 的工作方式是你 从字体和颜色创建新图像。对于我们的纹理类,这意味着我们将从 SDL_ttf 渲染的文本而不是文件加载图像。

这是我们实际创建要从字体渲染的文本纹理的地方。此函数 接收 我们要呈现的 文本字符串 以及我们要使用的 颜色 渲染它。之后,这个函数几乎就像从文件加载一样工作,只是这次我们使用 SDL_ttf 创建的 SDL_Surface 而不是文件。

释放任何预先存在的纹理后,我们使用 TTF_RenderText_Solid 加载表面。这 根据给定的字体、文本和颜色创建纯色表面。如果表面创建成功,我们将从中创建纹理,就像之前从文件加载表面时所做的那样。文本之后 纹理被创建,我们可以像任何其他纹理一样使用它进行渲染。

还有其他方法可以呈现更平滑或混合的文本。感兴趣可以先去尝试一下 ✨

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

// TTF 字体
TTF_Font* gFont = nullptr;
Texture gTextTexture;

就像 SDL_image 一样,我们必须初始化它,否则字体加载和渲染函数将无法正常工作。我们使用 TTF_init 启动 SDL_ttf。我们可以使用 TTF_GetError() 检查错误。

// 初始化 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;
}

// 初始化 SDL_ttf
if (TTF_Init() == -1)
{
    std::cout << "[Error]: SDL_ttf could not initialize! SDL_ttf Error: " << TTF_GetError() << std::endl;
    return false;
}

在我们的加载函数中,我们使用 TTF_OpenFont ✨参考文档 加载字体参数需要 路径 字体文件 和 我们要渲染的点大小

如果字体加载成功,我们希望使用加载方法加载文本纹理。一般您希望最大程度地减少呈现文本的时间。仅仅当您需要时重新渲染它,并且由于我们对整个程序使用相同的文本表面,因此我们只想渲染一次。

/**
 * @brief 加载媒体
 */
bool LoadMedia()
{
	// 打开字体
	gFont = TTF_OpenFont(R"(res\fonts\COOPBL.TTF)", 28);
	if (!gFont)
	{
		std::cout << "[Error]: Failed to load COOPBL font! SDL_ttf Error: " << TTF_GetError() << std::endl;
		return false;
	}
	else
	{
		// 字体颜色(白色)
		SDL_Color textColor{ 255, 255, 255 };
		// 这句话使用很多不同的字母
		if (!gTextTexture.LoadFromRenderedText("The quick brown fox jumps over the lazy dog", textColor))
		{
			std::cout << "[Error]: Failed to render text texture!" << std::endl;
			return false;
		}
	}
	return true;
}

在我们的清理函数中,我们希望使用 TTF_CloseFont 释放字体。我们也想退出 SDL_ttf 库, TTF_Quit 完成清理。

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

三、示例代码

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

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

// 链接库
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")
#pragma comment(lib, "SDL2_image.lib")
#pragma comment(lib, "SDL2_ttf.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;			// 主窗口渲染器

// TTF 字体
TTF_Font* gFont = nullptr;

// 纹理类
class Texture
{
private:
	int m_Width;
	int m_Height;
	SDL_Texture* m_Texture;
public:
	Texture()
		: m_Width(0), m_Height(0), m_Texture(nullptr)
	{
	}

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

	// 从文件中加载纹理
	bool LoadFromFile(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);
		return m_Texture != nullptr;
	}

	// 从字体字符串中创建图片
	bool LoadFromRenderedText(const char* textureText, SDL_Color textColor)
	{
		SDL_Surface* textSurface = TTF_RenderText_Solid(gFont, textureText, textColor);
		if (!textSurface)
		{
			std::cout << "[Error]: Unable to render text surface! SDL_ttf Error: " << TTF_GetError() << std::endl;
		}
		else
		{
			m_Texture = SDL_CreateTextureFromSurface(gRenderer, textSurface);
			if (!m_Texture)
			{
				std::cout << "[Error]: Unable to create texture from rendered text! SDL_Error: " << SDL_GetError() << std::endl;
			}
			else
			{
				// 【这里设置了字体纹理大小】
				m_Width = textSurface->w;
				m_Height = textSurface->h;
			}
		}
		// 释放临时表面
		SDL_FreeSurface(textSurface);
		return m_Texture != nullptr;
	}

	// 设置纹理颜色
	void SetColor(Uint8 r, Uint8 g, Uint8 b)
	{
		SDL_SetTextureColorMod(m_Texture, r, g, b);
	}

	// 设置纹理透明度
	void SetAlpha(Uint8 alpha)
	{
		SDL_SetTextureAlphaMod(m_Texture, alpha);
	}

	// 设置混合模式
	void SetBlendMode(SDL_BlendMode blendMode)
	{
		SDL_SetTextureBlendMode(m_Texture, blendMode);
	}

	// 渲染纹理
	inline void Render(int x, int y, SDL_Rect* clip = nullptr,
		double angle = 0.0, SDL_Point* center = nullptr, SDL_RendererFlip flip = SDL_FLIP_NONE)
	{
		SDL_Rect dstRect{ x, y, m_Width, m_Height };
		// center 为 nullptr 默认为纹理中心
		SDL_RenderCopyEx(gRenderer, m_Texture, clip, &dstRect,
			angle, center, flip);  // 扩展功能
	}

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

Texture gTextTexture;

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);

			// 渲染字体纹理
			gTextTexture.Render((SCREEN_WIDTH - gTextTexture.GetWidth()) / 2, (SCREEN_HEIGHT - gTextTexture.GetHeight()) / 2);

			// 更新屏幕
			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 | SDL_RENDERER_PRESENTVSYNC);  // -1 初始化支持所请求标志的第一个
			if (!gRenderer)
			{
				std::cout << "[Error]: Renderer could not bo created! SDL_Error: " << SDL_GetError() << std::endl;
				return false;
			}
			else
			{
				// 初始化 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;
				}

				// 初始化 SDL_ttf
				if (TTF_Init() == -1)
				{
					std::cout << "[Error]: SDL_ttf could not initialize! SDL_ttf Error: " << TTF_GetError() << std::endl;
					return false;
				}
			}
		}
	}
	return true;
}

/**
 * @brief 加载媒体
 */
bool LoadMedia()
{
	// 打开字体
	gFont = TTF_OpenFont(R"(res\fonts\COOPBL.TTF)", 28);
	if (!gFont)
	{
		std::cout << "[Error]: Failed to load COOPBL font! SDL_ttf Error: " << TTF_GetError() << std::endl;
		return false;
	}
	else
	{
		// 字体颜色(白色)
		SDL_Color textColor{ 255, 255, 255 };
		// 这句话使用很多不同的字母
		if (!gTextTexture.LoadFromRenderedText("The quick brown fox jumps over the lazy dog", textColor))
		{
			std::cout << "[Error]: Failed to render text texture!" << std::endl;
			return false;
		}
	}
	return true;
}

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

1. 运行结果



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

The End.

posted @ 2022-12-08 22:02  AnnihilateSword  阅读(727)  评论(0编辑  收藏  举报