SDL遊戲教程第二課 座標與塊複製

翻譯聲明:
    本系列教程來自
Dev Hub,一切解釋權歸原作者。我只是出自個人愛好,才翻譯了本系列教程。因爲本人也是個初學者,而且英語水平有限,錯誤難免,望各路高手指正。

本課原文地址:http://www.sdltutorials.com/sdl-coordinates-and-blitting/

有第一節課做基礎,我們將深入研究SDL表面世界。正如我在上節課所述,SDL表面就是存儲在內存中的圖像。假想我們有一個空白的320*240像素的表面。SDL座標系統如下圖所示:


這裏的座標系統和你熟悉的可能不大一樣。注意,Y座標向下遞增,X座標向右遞增。瞭解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,增加如下:
  1. #ifndef _CSURFACE_H_
  2. #define _CSURFACE_H_
  3. #include <SDL.h>
  4. class CSurface {
  5.     public:
  6.         CSurface();
  7.     public:
  8.         static SDL_Surface* OnLoad(char* File);
  9. };
  10. #endif

我們已經創建了一個簡單的靜態函數,OnLoad,它會爲我們加載一個表面。現在打開CSurface.cpp:

#include "CSurface.h"
  1. CSurface::CSurface() {
  2. }
  3. SDL_Surface* CSurface::OnLoad(char* File) {
  4.     SDL_Surface* Surf_Temp = NULL;
  5.     SDL_Surface* Surf_Return = NULL;
  6.     if((Surf_Temp = SDL_LoadBMP(File)) == NULL) {
  7.         return NULL;
  8.     }
  9.     Surf_Return = SDL_DisplayFormat(Surf_Temp);
  10.     SDL_FreeSurface(Surf_Temp);
  11.     return Surf_Return;
  12. }

在此有兩件事需要注意。首先,切記在創建一個指針的時候一定要把它設置爲NULL或0。不然,稍後你會遇到很多的麻煩。其次,注意SDL_DisplayFormat()是如何返回一個新表面,但並不覆蓋原有的表面。這個一定要牢記,因爲它創建了一個新的表面,我們必須釋放那個舊錶面。否則,我們會有一個表面還留在內存裏(譯者注:如果不釋放舊資源,容易造成內存泄漏)。

現在我們有了把表面加載到內存的方法,我們還需要一個把他們繪製到其他表面的方法。就像SDL提供了一個函數來加載圖像,它也提供了一個函數來繪製(blit)圖像:SDL_BlitSurface()。不幸的是,此函數並不像SDL_LoadBMP()那麼簡單,儘管如此,它還是很簡單的。打開CSurface.h添加如下函數原型:
  1. #ifndef _CSURFACE_H_
  2. #define _CSURFACE_H_
  3. #include <SDL.h>
  4. class CSurface {
  5.     public:
  6.         CSurface();
  7.     public:
  8.         static SDL_Surface* OnLoad(char* File);
  9.         static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y);
  10. };
  11. #endif

現在打開CSurface.cpp,添加如下:
  1. #include "CSurface.h"
  2. CSurface::CSurface() {
  3. }
  4. SDL_Surface* CSurface::OnLoad(char* File) {
  5.     SDL_Surface* Surf_Temp = NULL;
  6.     SDL_Surface* Surf_Return = NULL;
  7.     if((Surf_Temp = SDL_LoadBMP(File)) == NULL) {
  8.         return NULL;
  9.     }
  10.     Surf_Return = SDL_DisplayFormat(Surf_Temp);
  11.     SDL_FreeSurface(Surf_Temp);
  12.     return Surf_Return;
  13. }
  14. bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y) {
  15.     if(Surf_Dest == NULL || Surf_Src == NULL) {
  16.         return false;
  17.     }
  18.     SDL_Rect DestR;
  19.     DestR.x = X;
  20.     DestR.y = Y;
  21.     SDL_BlitSurface(Surf_Src, NULL, Surf_Dest, &DestR);
  22.     return true;
  23. }

首先,看看傳遞給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:
  1. #ifndef _CAPP_H_
  2. #define _CAPP_H_
  3. #include <SDL.h>
  4. #include "CSurface.h"
  5. class CApp {
  6.     private:
  7.         bool            Running;
  8.         SDL_Surface*    Surf_Display;
  9.         SDL_Surface*    Surf_Test;
  10.     public:
  11.         CApp();
  12.         int OnExecute();
  13.     public:
  14.         bool OnInit();
  15.         void OnEvent(SDL_Event* Event);
  16.         void OnLoop();
  17.         void OnRender();
  18.         void OnCleanup();
  19. };
  20. #endif

同樣的,在構造函數裏初始化這個表明爲NULL:

CApp::CApp() {
    Surf_Test = NULL;
    Surf_Display = NULL;

    Running = true;
}


並且,還要記得清除:
  1. #include "CApp.h"
  2. void CApp::OnCleanup() {
  3.     SDL_FreeSurface(Surf_Test);
  4.     SDL_FreeSurface(Surf_Display);
  5.     SDL_Quit();
  6. }

現在,讓我們實際加載一幅圖像。打開CApp_OnInit.cpp,然後添加代碼來加載一個表面:
  1. #include "CApp.h"
  2. bool CApp::OnInit() {
  3.     if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
  4.         return false;
  5.     }
  6.     if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
  7.         return false;
  8.     }
  9.     if((Surf_Test = CSurface::OnLoad("myimage.bmp")) == NULL) {
  10.         return false;
  11.     }
  12.     return true;
  13. }

確保要把"myimage.bmp"改成一幅實際存在的位圖文件。如果你沒,那就打開畫圖,隨便畫點什麼東西上去,保存到和可執行文件同目錄下。

現在我們已經加載了圖片,讓我們繪製它。打開CApp_OnRender.cpp,添加如下:
  1. #include "CApp.h"
  2. void CApp::OnRender() {
  3.     CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0);

  4.     SDL_Flip(Surf_Display);
  5. }

注意這裏的一個新函數SDL_Flip()。它只是更新緩衝區,然後顯示Surf_Display到屏幕。這就叫做雙緩存。整個過程就是把所有東西繪製到內存,然後把所有東西最終繪製到屏幕。若不這麼做,我們會看到圖像在屏幕上閃爍。還記得SDL_DOUBLEFUL標誌位嗎?這就是我們爲什麼要打開雙緩存的原因。

編譯代碼,然後確保一切運行正常。你應該可以在屏幕左上角可以看到你的圖像。若是這樣,祝賀你,你離一個真實的遊戲更近了一步。若不然,確保在你的可執行文件夾裏有myimage.bmp存在。也要保證它是一個有效的位圖文件。



現在讓我們把這個過程進行的更深遠點。儘管它可以很好並足夠把一幅圖像繪製到屏幕上了,通常我們僅需要繪製一幅圖像的一部分。例如,一個貼圖集合:



儘管這只是一單幅圖像,我們只想繪製其中一部分。打開CSurface.h,添加如下代碼:
  1. #ifndef _CSURFACE_H_
  2. #define _CSURFACE_H_
  3. #include <SDL.h>
  4. class CSurface {
  5.     public:
  6.         CSurface();
  7.     public:
  8.         static SDL_Surface* OnLoad(char* File);
  9.         static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y);
  10.         static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H);
  11. };
  12. #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,然後添加:
  1. #include "CApp.h"
  2. void CApp::OnRender() {
  3.     CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0);
  4.     CSurface::OnDraw(Surf_Display, Surf_Test, 100, 100, 0, 0, 50, 50);
  5.     SDL_Flip(Surf_Display);
  6. }

你應該能看到你的圖片會被在100,100處繪製,且僅顯示了一部分。你應該搞清楚這一切是怎麼運行的,以及SDL的座標系統是如何建立的,這將會很有用。

跳到下一課,在那我們會看到更多的SDL消息,以及如何使那個過程更簡單。

SDL 座標和塊複製 —— 課程文件:
Win32:ZipRar
Linux:Tar(感謝Gaten)
發佈了8 篇原創文章 · 獲贊 11 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章