本系列教程來自Dev Hub,一切解釋權歸原作者。我只是出自個人愛好,才翻譯了本系列教程。因爲本人也是個初學者,而且英語水平有限,錯誤難免,望各路高手指正。
本課原文地址:http://www.sdltutorials.com/sdl-coordinates-and-blitting/
有第一節課做基礎,我們將深入研究SDL表面世界。正如我在上節課所述,SDL表面就是存儲在內存中的圖像。假想我們有一個空白的320*240像素的表面。SDL座標系統如下圖所示:
既然我們已經搞定了主表面(Surf_Display),那就需要一種方式來在其上繪製圖像了。這個過程就叫做"塊複製"(譯者注:英文是Blitting),由此我們可以把一個圖像遷移到另一幅圖像。但在此之前,我們需要一種方式來把圖像加載到內存。SDL提供了一個簡單的函數來實現,那就是SDL_LoadBMP()。一些僞代碼可能是這樣的:
SDL_Surface* Surf_Temp;
if((Surf_Temp = SDL_LoadBMP("mypicture.bmp")) == NULL) {
//Error!
}
超簡單,SDL_LoadBMP()只需要一個參數,就是你要加載的文件,並且返回一個表面。如果文件找不到,已損壞,或其他什麼錯誤,此函數就會返回NULL。不幸的是,有時這種方法還不夠。加載圖片的像素格式往往和要顯示的不一致。這樣的話,當我們想顯示器繪製圖像的時候會導致性能損失、圖像顏色失真、或是別的什麼。但是,多虧了SDL,它還爲此提供了一個快捷的解決方法,SDL_DisplayFormat()。此函數需要一個已經加載的表面,然後返回一個和顯示器格式一致的新表面。
讓我們完成這個過程,並把它放在一個可再用的類裏。使用SDL課程1作爲基礎代碼,然後添加以下兩個新文件:CSurface.h,CSurface.cpp。打開CSurface.h,增加如下:
- #ifndef _CSURFACE_H_
- #define _CSURFACE_H_
- #include <SDL.h>
- class CSurface {
- public:
- CSurface();
- public:
- static SDL_Surface* OnLoad(char* File);
- };
- #endif
我們已經創建了一個簡單的靜態函數,OnLoad,它會爲我們加載一個表面。現在打開CSurface.cpp:
#include "CSurface.h"
- CSurface::CSurface() {
- }
- SDL_Surface* CSurface::OnLoad(char* File) {
- SDL_Surface* Surf_Temp = NULL;
- SDL_Surface* Surf_Return = NULL;
- if((Surf_Temp = SDL_LoadBMP(File)) == NULL) {
- return NULL;
- }
- Surf_Return = SDL_DisplayFormat(Surf_Temp);
- SDL_FreeSurface(Surf_Temp);
- return Surf_Return;
- }
在此有兩件事需要注意。首先,切記在創建一個指針的時候一定要把它設置爲NULL或0。不然,稍後你會遇到很多的麻煩。其次,注意SDL_DisplayFormat()是如何返回一個新表面,但並不覆蓋原有的表面。這個一定要牢記,因爲它創建了一個新的表面,我們必須釋放那個舊錶面。否則,我們會有一個表面還留在內存裏(譯者注:如果不釋放舊資源,容易造成內存泄漏)。
現在我們有了把表面加載到內存的方法,我們還需要一個把他們繪製到其他表面的方法。就像SDL提供了一個函數來加載圖像,它也提供了一個函數來繪製(blit)圖像:SDL_BlitSurface()。不幸的是,此函數並不像SDL_LoadBMP()那麼簡單,儘管如此,它還是很簡單的。打開CSurface.h添加如下函數原型:
- #ifndef _CSURFACE_H_
- #define _CSURFACE_H_
- #include <SDL.h>
- class CSurface {
- public:
- CSurface();
- public:
- static SDL_Surface* OnLoad(char* File);
- static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y);
- };
- #endif
現在打開CSurface.cpp,添加如下:
- #include "CSurface.h"
- CSurface::CSurface() {
- }
- SDL_Surface* CSurface::OnLoad(char* File) {
- SDL_Surface* Surf_Temp = NULL;
- SDL_Surface* Surf_Return = NULL;
- if((Surf_Temp = SDL_LoadBMP(File)) == NULL) {
- return NULL;
- }
- Surf_Return = SDL_DisplayFormat(Surf_Temp);
- SDL_FreeSurface(Surf_Temp);
- return Surf_Return;
- }
- bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y) {
- if(Surf_Dest == NULL || Surf_Src == NULL) {
- return false;
- }
- SDL_Rect DestR;
- DestR.x = X;
- DestR.y = Y;
- SDL_BlitSurface(Surf_Src, NULL, Surf_Dest, &DestR);
- return true;
- }
首先,看看傳遞給OnDraw()的參數。兩個表面和兩個整型。第一個表面爲目標表面,或是我們要在其上進行繪製的表面。第二個表面是源表面,或是我們要將其繪製到另一個表面的表面。基本上是這樣,我們把Surf_Src放到Surf_Dest的上面。其中X,Y變量是在Surf_Dest上要繪製表面的位置。
函數的開始確保我們得到的是有效的表面,否則,返回false。然後,就是SDL_Rect。他是一個SDL結構體,基本包含4個成員:x,y,w,h。這個,當然是創建一個特定規格的矩形了。我們僅關心要在哪畫,並不是繪製的大小。所以,我們把X,Y座標分配到目標表面上。如果你想知道SDL_BlitSurface裏的那個NULL是什麼意思,它也是一個SDL_Rect的參數。本課後面還會在提到。
最後,我們可以調用這個函數來繪製圖像,並返回true。
現在,爲了確保所有這些可以運行,就讓我們創建一個測試表面。打開CApp.h,然後創建一個新的表面,幷包含進新創建的CSurface.h:
- #ifndef _CAPP_H_
- #define _CAPP_H_
- #include <SDL.h>
- #include "CSurface.h"
- class CApp {
- private:
- bool Running;
- SDL_Surface* Surf_Display;
- SDL_Surface* Surf_Test;
- public:
- CApp();
- int OnExecute();
- public:
- bool OnInit();
- void OnEvent(SDL_Event* Event);
- void OnLoop();
- void OnRender();
- void OnCleanup();
- };
- #endif
同樣的,在構造函數裏初始化這個表明爲NULL:
CApp::CApp() {
Surf_Test = NULL;
Surf_Display = NULL;
Running = true;
}
並且,還要記得清除:
- #include "CApp.h"
- void CApp::OnCleanup() {
- SDL_FreeSurface(Surf_Test);
- SDL_FreeSurface(Surf_Display);
- SDL_Quit();
- }
現在,讓我們實際加載一幅圖像。打開CApp_OnInit.cpp,然後添加代碼來加載一個表面:
- #include "CApp.h"
- bool CApp::OnInit() {
- if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
- return false;
- }
- if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
- return false;
- }
- if((Surf_Test = CSurface::OnLoad("myimage.bmp")) == NULL) {
- return false;
- }
- return true;
- }
確保要把"myimage.bmp"改成一幅實際存在的位圖文件。如果你沒,那就打開畫圖,隨便畫點什麼東西上去,保存到和可執行文件同目錄下。
現在我們已經加載了圖片,讓我們繪製它。打開CApp_OnRender.cpp,添加如下:
- #include "CApp.h"
- void CApp::OnRender() {
- CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0);
- SDL_Flip(Surf_Display);
- }
注意這裏的一個新函數SDL_Flip()。它只是更新緩衝區,然後顯示Surf_Display到屏幕。這就叫做雙緩存。整個過程就是把所有東西繪製到內存,然後把所有東西最終繪製到屏幕。若不這麼做,我們會看到圖像在屏幕上閃爍。還記得SDL_DOUBLEFUL標誌位嗎?這就是我們爲什麼要打開雙緩存的原因。
編譯代碼,然後確保一切運行正常。你應該可以在屏幕左上角可以看到你的圖像。若是這樣,祝賀你,你離一個真實的遊戲更近了一步。若不然,確保在你的可執行文件夾裏有myimage.bmp存在。也要保證它是一個有效的位圖文件。
現在讓我們把這個過程進行的更深遠點。儘管它可以很好並足夠把一幅圖像繪製到屏幕上了,通常我們僅需要繪製一幅圖像的一部分。例如,一個貼圖集合:
儘管這只是一單幅圖像,我們只想繪製其中一部分。打開CSurface.h,添加如下代碼:
- #ifndef _CSURFACE_H_
- #define _CSURFACE_H_
- #include <SDL.h>
- class CSurface {
- public:
- CSurface();
- public:
- static SDL_Surface* OnLoad(char* File);
- static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y);
- static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H);
- };
- #endif
打開CSurface.cpp,添加如下函數:
bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H) {
if(Surf_Dest == NULL || Surf_Src == NULL) {
return false;
}
SDL_Rect DestR;
DestR.x = X;
DestR.y = Y;
SDL_Rect SrcR;
SrcR.x = X2;
SrcR.y = Y2;
SrcR.w = W;
SrcR.h = H;
SDL_BlitSurface(Surf_Src, &SrcR, Surf_Dest, &DestR);
return true;
}
注意下,它基本上和前一個函數一樣,只不過我們增加了另一個SDL_Rect。我們通過這個源矩形指定從源中要拷貝那些像素到目標。如果我們指定了0,0,50,50作爲X2,Y2,W,H的參數,那麼只有源的左上角(一個50*50的方形)被繪製。
我們同樣試試這個新函數,打開CApp_OnRender.cpp,然後添加:
- #include "CApp.h"
- void CApp::OnRender() {
- CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0);
- CSurface::OnDraw(Surf_Display, Surf_Test, 100, 100, 0, 0, 50, 50);
- SDL_Flip(Surf_Display);
- }
你應該能看到你的圖片會被在100,100處繪製,且僅顯示了一部分。你應該搞清楚這一切是怎麼運行的,以及SDL的座標系統是如何建立的,這將會很有用。
跳到下一課,在那我們會看到更多的SDL消息,以及如何使那個過程更簡單。
SDL 座標和塊複製 —— 課程文件:
Win32:Zip,Rar
Linux:Tar(感謝Gaten)