SDL遊戲開發教程05(顯示文字和其他格式的圖片)

     本節將介紹如何加載其他格式的圖片,同時,介紹如何顯示文字。效果圖如下
 

 
 其中背景圖片是我從網上下載的一張JPG圖片,已經將它上傳在了附件中。

 

 

顯示其它格式圖片:

 

要顯示png、jpg、gif等格式的圖片,我們需要下載SDL_image庫

http://www.libsdl.org/projects/SDL_image/



 
 下載上圖中標記出來的文件,然後解壓。

1、將解壓後include目錄下的SDL_image.h文件拷貝到C:\MinGW\include\SDL目錄下

2、將解壓後include目錄下的SDL_image.lib文件拷貝到C:\MinGW\lib目錄下

3、將解壓後include目錄下的所有dll文件拷貝到C:\MinGW\bin目錄下。

注意:我在這樣做的時候出現了一個問題,當程序寫好之後,在Eclipse下面運行報找不到驅動程序的錯誤,但自己手動進入到生成的EXE目錄中雙擊運行程序沒有問題。後來我將include目錄下的所有dll文件拷貝到C:\WINDOWS\system32就好了,如果你也遇到和我一樣的問題,可以參考這種解決方法。至於造成這個問題的原因,暫時還不清楚。

 

經過上面的準備,我們可以開始寫代碼了,其實加載圖片的代碼很簡單。

SDLSurfacePtr SDLVideo::LoadImage(std::string fileName)
{
	SDL_Surface *surface = IMG_Load(fileName.c_str());
	if(NULL == surface)
	{
		throw SDLException(std::string("IMG_Load加載圖片時發生錯誤:") + SDL_GetError());
	}
	return SDLSurfacePtr(new SDLSurface(surface));
}

 通過比較原來加載BMP格式圖片的代碼

SDLSurfacePtr	SDLVideo::LoadBMP(std::string file)
{
	SDL_Surface *surface = SDL_LoadBMP(file.c_str());
	if(NULL == surface)
	{
		throw SDLException(std::string("SDL_LoadBMP加載BMP圖片時發生錯誤:") + SDL_GetError());
	}

	return SDLSurfacePtr(new SDLSurface(surface));
}

     其實就一個地方不同,一個用的是IMG_load函數,一個是SDL_LoadBMP函數。這裏將加載圖片的函數放到了SDLVideo類中。如果需要加載其他格式的圖片調用LoadImage函數就可以了,同時LoadImage也可以加載BMP格式的圖片,所以在這裏LoadBMP函數基本上沒什麼用處了。

 

    我們下一步要做的就是封裝一個專門加載圖片的類SDLImageManager,用來對加載的圖片進行管理,這裏主要是對圖片進行緩衝,避免頻繁的訪問硬盤,提高程序效率。頭文件:

#ifndef RESOURCEMANAGER_H_
#define RESOURCEMANAGER_H_

#include "SDLSurface.h"
#include <map>
class SDLImageManager
{
	friend class SDL;
private:
	SDLImageManager();
public:
	virtual ~SDLImageManager();
public:
	//獲取圖片
	SDLSurfacePtr loadImage(std::string fileName);

	//獲取圖片,並將圖片中的某種顏色去掉,使去掉的那部分顏色爲透明,這樣就不會覆蓋掉底層圖片的顏色
	//詳細介紹見http://lazyfoo.net/SDL_tutorials/lesson05/index.php
	SDLSurfacePtr loadImageWithoutColor(std::string fileName, Uint8 r, Uint8 g, Uint8 b );
private:
	std::map<std::string, SDLSurfacePtr>	buffer;

};

#endif /* RESOURCEMANAGER_H_ */
 

其中loadImage用來獲取一張圖片,成員變量buffer用來緩衝已經加載的圖片。loadImageWithoutColor的功能主要是去除圖片的底色,避免底色覆蓋掉底層圖片的顏色,這樣解釋可能不好懂,我也沒有什麼更好的辦法,大家可以去http://lazyfoo.net/SDL_tutorials/lesson05/index.php,上面有詳細的介紹。

    源文件:

#include "SDLImageManager.h"
#include "SDLCore.h"
#include <boost/lexical_cast.hpp>

SDLImageManager::SDLImageManager()
{

}

SDLImageManager::~SDLImageManager()
{

}

SDLSurfacePtr SDLImageManager::loadImage(std::string fileName)
{
	//先到緩衝區去找
	std::map<std::string, SDLSurfacePtr>::iterator it = buffer.find(fileName);
	//如果找到就返回
	if(it != buffer.end())
	{
		return it->second;
	}


	//否則從硬盤加載圖片
	SDLSurfacePtr loadedImage = SDL::video()->LoadImage( fileName.c_str() );

	//將文件格式調整成程序需要的格式
	SDLSurfacePtr image = SDL::video()->DisplayFormat( loadedImage );

	//放入緩衝區
	buffer.insert(std::make_pair(fileName, image));

	return image;
}

SDLSurfacePtr SDLImageManager::loadImageWithoutColor(std::string fileName, Uint8 r, Uint8 g, Uint8 b )
{
	/**
	 * 根據文件名和要去除的顏色生成一個唯一的鍵,
	 * 在緩衝區類同文件名,但一個是原圖,一個是去除某種顏色的圖可能共存,
	 * 所以這裏用fileName作爲鍵值有可能覆蓋掉緩衝區中的原圖。
	 * 存儲每種顏色分量的空間是一個字節,所以最大數據不會超過255,這裏簡單的乘上1000就可以滿足要求了。
	 * 比如這裏傳入的參數是aaa.jpg, 255, 255, 255;那麼得到的結果就是aaa.jpg255255255。
	 * 這裏boost::lexical_cast是boost裏面一個常用的函數,用來做數據之間的轉換。
	 */
	std::string key = fileName
		+ boost::lexical_cast<std::string>(r*1000*1000)
		+ boost::lexical_cast<std::string>(g*1000)
		+ boost::lexical_cast<std::string>(b);
	std::map<std::string, SDLSurfacePtr>::iterator it = buffer.find(key);
	if(it != buffer.end())
	{
		return it->second;
	}

	SDLSurfacePtr loadedImage = SDL::video()->LoadImage( fileName.c_str() );
	SDLSurfacePtr image = SDL::video()->DisplayFormat( loadedImage );
	Uint32 colorKey = SDL::video()->MapRGB(image->value()->format, r, g, b);
	SDL::video()->SetColorKey(image, SDL_SRCCOLORKEY, colorKey);
	buffer.insert(std::make_pair(key, image));

	return image;
}

 加載圖片的相關準備工作已經完成。

 

 

顯示文字:

要顯示true type字體,我們需要下載SDL_ttf庫。http://www.libsdl.org/projects/SDL_ttf/
 

 
   然後按照上面介紹SDL_image的方法,將下載下來的文件放到相應的目錄下。這邊不再重述。

爲了管理字體,我們同上面一樣也新建了一個SDLFontManager類

#ifndef SDLFONTMANAGER_H_
#define SDLFONTMANAGER_H_

#include "SDLFont.h"
class SDLFontManager
{
	friend class SDL;
private:
	SDLFontManager();
private:
	/**
	 * 初始化TTF環境
	 */
	void Init();
public:
	virtual ~SDLFontManager();
public:
	/**
	 * 打開字體
	 * name		字體文件路徑
	 * size		待打開字體的大小
	 */
	SDLFontPtr OpenFont(std::string name, int size);
};

#endif /* SDLFONTMANAGER_H_ */
 
#include "SDLFontManager.h"

SDLFontManager::SDLFontManager()
{
	// TODO Auto-generated constructor stub

}

SDLFontManager::~SDLFontManager()
{
	TTF_Quit();//退出TTF環境
}

void SDLFontManager::Init()
{
	if(TTF_WasInit())
	{
		return;
	}

	if(-1 == TTF_Init())
	{
		throw SDLException(std::string("TTF_Init初始化TTF庫時發生錯誤:") + SDL_GetError());
	}
}

SDLFontPtr SDLFontManager::OpenFont(std::string name, int size)
{
	TTF_Font *font = TTF_OpenFont(name.c_str(), size);
	if(NULL == font)
	{
		throw SDLException(std::string("TTF_OpenFont打開字體時發生錯誤:") + SDL_GetError());
	}
	return SDLFontPtr(new SDLFont(font));
}

 上面的代碼很簡單,初始化TTF環境 -> 打開字體 ->退出TTF環境

其中SDLFontPtr的定義爲

typedef boost::shared_ptr<SDLFont> SDLFontPtr;

 由於字體打開後需要關閉,所以這裏用了智能指針,在SDLFontPtr不再被使用的時候,它會調用SDLFont的delete函數。SDLFont的定義

#ifndef SDLFONT_H_
#define SDLFONT_H_
#include <SDL/SDL_ttf.h>
#include "boost/shared_ptr.hpp"
#include "SDLSurface.h"
#include <string>
#include "SDLException.h"
class SDLFont
{
	friend class SDLFontManager;
private:
	SDLFont(TTF_Font *font);
public:
	virtual ~SDLFont();
public:
	/**
	 * 渲染字體
	 * message	文字內容
	 * color	文字顏色
	 */
	SDLSurfacePtr RenderTextSolid(std::string message, SDL_Color color);
	SDLSurfacePtr RenderUNICODESolid(std::string message, SDL_Color color);
	SDLSurfacePtr RenderUNICODEBlended(std::string message, SDL_Color color);
	SDLSurfacePtr RenderUNICODEShaded(std::string message, SDL_Color fg, SDL_Color bg);

	/**
	 * 獲取和設置字體的樣式
	 */
	int  GetFontStyle();
	void SetFontStyle(int style);//參見SDL/SDL_ttf.h中的宏定義
private:
	/*
	 * 獲取message中包含的Unicode字符個數
	 * 如果message="你好abc", 則返回5
	 * 如果message="你好啊", 則返回3
	 */
	int getUnicodeCharCountByGb2312(std::string message);
	/**
	 * 將GB2312編碼轉換成UNICODE編碼
	 */
	Uint16 * getUnicodeByGb2312(std::string message);
private:
	TTF_Font *font;
};
typedef boost::shared_ptr<SDLFont> SDLFontPtr;
#endif /* SDLFONT_H_ */
#include "SDLFont.h"
#include <iostream>
SDLFont::SDLFont(TTF_Font *font)
{
	this->font = font;
}

SDLFont::~SDLFont()
{
	if (font != NULL)
	{
		TTF_CloseFont(font);
	}
}

SDLSurfacePtr SDLFont::RenderTextSolid(std::string message, SDL_Color color)
{
	SDL_Surface* image = TTF_RenderText_Solid(font, message.c_str(), color);
	if (image == NULL)
	{
		throw SDLException(std::string("TTF_RenderText_Solid渲染字體時發生錯誤:")
				+ SDL_GetError());
	}
	return SDLSurfacePtr(new SDLSurface(image));
}

SDLSurfacePtr SDLFont::RenderUNICODESolid(std::string message, SDL_Color color)
{
	Uint16 * text = getUnicodeByGb2312(message);
	SDL_Surface* image = TTF_RenderUNICODE_Solid(font, text, color);
	delete[] text;
	if (image == NULL)
	{
		throw SDLException(std::string("TTF_RenderUNICODE_Solid渲染字體時發生錯誤:")
				+ SDL_GetError());
	}
	return SDLSurfacePtr(new SDLSurface(image));
}


int SDLFont::getUnicodeCharCountByGb2312(std::string message)
{
	int		number = 0;
	for (unsigned int i = 0; i < message.length(); i++)
	{
		//判斷最高位是不是0,如果是0表示是一個ASCII碼,否則是一個漢字
		//一個漢字佔用兩個字符,所以變量i需要++
		if ((message.at(i) & 0x80) != 0)
		{
			i++;
		}
		number++;
	}

	return number;
}

Uint16 * SDLFont::getUnicodeByGb2312(std::string message)
{
		/**
		 * 獲取字符串中包含的UNICODE字符數量,主要是爲了後面轉換時分配內存
		 * 這裏其實不用精確具體的個數,只要保證chNum大於實際的個數就行了
		 * 因爲一個漢字佔用兩個字節,所以這裏用int chNum = message.length()也可以
		 * 如果message中不含漢字,則message.length()==getUnicodeCharCountByGb2312(message)
		 * 如果message含有漢字,則message.length()>getUnicodeCharCountByGb2312(message)
		 */
		int chNum = getUnicodeCharCountByGb2312(message);
		//int chNum = message.length();

		wchar_t * wmessage=new wchar_t[chNum];
		char *chset = setlocale(LC_CTYPE,"chs");//設置當前字符集,返回字符集的描述
		if(chset == NULL)//如果返回NULL,表示該環境中沒有這種字符集
		{
			throw SDLException("setlocale設置當前環境爲簡體中文時發生錯誤,可能是系統不支持簡體中文:");
		}

		//將GB2312轉換成UNICODE字符集
		//返回實際轉換的UNICODE字符個數
		int wchNum = mbstowcs(wmessage,message.c_str(),chNum);
		if(wchNum == -1)//如果裏面包含了非法的GB2312字符編碼
		{
			throw SDLException(std::string("mbstowcs中文字符轉UNICODE時發生錯誤:"));
		}
		setlocale(LC_CTYPE,"");//設置回原來的字符集

		Uint16 *text = new Uint16[wchNum+1];
		text[wchNum] = 0;
		for (int i = 0; i < wchNum; i++)
		{
			text[i] = wmessage[i];
		}
		delete[] wmessage;

		return text;
}

SDLSurfacePtr SDLFont::RenderUNICODEBlended(std::string message, SDL_Color color)
{
	Uint16 * text = getUnicodeByGb2312(message);
	SDL_Surface* image = TTF_RenderUNICODE_Blended(font, text, color);
	delete[] text;
	if (image == NULL)
	{
		throw SDLException(std::string("TTF_RenderUNICODE_Blended渲染字體時發生錯誤:")
				+ SDL_GetError());
	}
	return SDLSurfacePtr(new SDLSurface(image));
}
SDLSurfacePtr SDLFont::RenderUNICODEShaded(std::string message, SDL_Color fg, SDL_Color bg)
{
	Uint16 * text = getUnicodeByGb2312(message);
	SDL_Surface* image = TTF_RenderUNICODE_Shaded(font, text, fg, bg);
	delete[] text;
	if (image == NULL)
	{
		throw SDLException(std::string("TTF_RenderUNICODE_Blended渲染字體時發生錯誤:")
				+ SDL_GetError());
	}
	return SDLSurfacePtr(new SDLSurface(image));
}

int  SDLFont::GetFontStyle()
{
	return TTF_GetFontStyle(font);
}

void SDLFont::SetFontStyle(int style)
{
	TTF_SetFontStyle(font, style);
}
 

    SDLFont對TTF庫中的函數進行了封裝,並且增加了對gb2312格式編碼的處理。這裏使用的規範是封裝SDL庫的成員函數開頭字母大寫,其餘的成員函數開頭字母小寫 ,後面的章節中將繼續採用該規範。

    在程序中,像std::string a="你好"這樣的代碼,編譯器會根據系統的默認編碼方式對"你好"這個字符串進行存儲,我們現在用的系統都是簡體中文,所以"你好"在內存中的編碼方式一般爲簡體中文的方式,簡體中文的編碼方式有多種,這裏給函數取的名字和寫的註釋都是GB2312,但實際的編碼方式可能不是這個。只是GB2312比較常見,不管是GB2312還是GB其它的編碼格式,mbstowcs都會將它轉換成UNICODE編碼格式。

    這裏用mbstowcs進行編碼轉換,並不代表只有這一種方式,也不是因爲它是最好的,而是由於它使用起來比較簡單。網上有很過關於編碼轉換的討論,也有一些開源的項目,其中比較出名的是libiconv。我們在這裏進行編碼轉換單純是爲了顯示中文,並且是以系統中有簡體中文環境爲前提,怎樣使我們的程序能在其他語言環境中運行,需要對程序做國際化支持的考慮。這裏推薦Gettext項目。

 

    上面怎麼加載圖片和顯示文字的準備工作都已經做好,下一步是怎麼使用這些類進行顯示。代碼如下

#include "Lesson02.h"

Lesson02::Lesson02()
{
	// TODO Auto-generated constructor stub

}

Lesson02::~Lesson02()
{
	// TODO Auto-generated destructor stub
}

void Lesson02::onRender()
{
	//先繪製背景圖片
	SDL::video()->BlitSurface(image, NULL, screen, NULL);

	//將文字顯示在screen的中央
	SDL_Rect rect;
	rect.x = screen->value()->w/2 - message->value()->w/2;
	rect.y = screen->value()->h/2 - message->value()->h/2;
	SDL::video()->BlitSurface(message, NULL, screen, &rect);
}
void Lesson02::onInit()
{
	//加載背景圖片
	image = SDL::imageManager()->loadImage("E:\\code_picture\\464d64dd7512752d5982dd84.jpg");
	//創建字體
	SDLFontPtr font = SDL::fontManager()->OpenFont("E:\\code_picture\\wqy-zenhei.ttc", 20);
	//設置字體樣式
	font->SetFontStyle(TTF_STYLE_UNDERLINE | TTF_STYLE_ITALIC);

	//渲染文字,這裏的兩種顏色一個是前景色,一個是背景色
	//渲染文字後返回的其實就是一張圖片,和loadImage加載的圖片沒什麼兩樣
	message = font->RenderUNICODEShaded("大家好(Hello, Everybody)"
			, SDL::assistant()->makeColor(0, 0, 0)
			, SDL::assistant()->makeColor(255, 0, 0));

	//大家可以試試下面註釋掉的這兩行代碼,看看他們的不同效果
	/*message = font->RenderUNICODESolid("大家好(Hello, Everybody)", SDL::assistant()->makeColor(255, 0, 0));
	message = font->RenderUNICODEBlended("大家好(Hello, Everybody)", SDL::assistant()->makeColor(255, 0, 0));
*/

}

     由於我們前期的準備工作比較充分,所以到具體使用的時候代碼就很簡單,這裏不再做解釋。其中wqy-zenhei.ttc是文泉驛字體,網上有相關介紹,你也可以使用C:\WINDOWS\Fonts目錄下隨便一個可以支持中文的字體,比如simhei.ttf。

 

最後,需要添加連接選項,增加SDL_image和SDL_ttf,如下圖:

 

最後重新編譯整個工程即可。這裏貼出來並不是全部代碼,而是一些比較重要的代碼,完整的代碼見附件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章