• 沒有找到結果。

市場 CG 的顯示

在文檔中 虛擬寵物與市場設計實作 (頁 43-59)

第四章 交易市場與網路

4.1 市場 CG 的顯示

BMP

Windows 所處理的 BMP 可分為下面 3 種:

l DDB(Device Dependent Bitmap;與設備有關的 BMP 格式) 由 HMITMAP 行處理常式來處理,使用 CreateBitmap 等產生

l DIB(Device Inpendent Bitmap;與設備無關的 BMP 格式)規定記憶 體的格式,沒有產生的 API

l DIBSection( 兼 具 以 上 兩 者 特 長 的 BMP 格 式 ) 使 用 CreateDIBSection 產生

為什麼會有 3 種不同的 BMP 格式?原因應該是在早期 Windows 系 統 上無法有效處理與 設備無關規定記憶 體上規 格的 BMP 格式( 即 DIB),DDB 雖然跟設備有關但處理速度快,而 DIB 因為與設備無關,所 以能直接從程式端操作記憶體,即使有百般的不情願,當時的電腦配 備規格仍然必須同時具備這兩種不同的格式,第三種 DIBSection 為後 來才新增的格式。以現在的電腦配備規格(顯示卡)來看,他對 DIB 的 DDB 繪製速度幾乎一樣,因此如果需要直接操作記憶體影像並顯示在畫 面上,直接用 DIBSection 也沒問題。如果不顯示到畫面上的話,使用 DIBSection 會消耗珍貴的 Windows 系統資源,DIB 既然有能力進行跟 DIBSection 影像同樣的處理,除非有其他特殊理由,否則應該使用 DIB 會比較好。那麼,「什麼時候才要用 DDB,答案定必須有 HBITMAP 型處 理常式,且不曾操作到記憶體影像;換句話說,如果程式有需要的時候 才用 DDB。這是「只能這麼做」的特殊情形,不必煩惱要用哪一種 BMP 格式,應該很容易分辨(也許還有其他 DDB、DIB 曾經存裡的原因,不 過這都已經是「過去式」。

DIB 的架構

在此先把 DIE 建立和操作整理成類別,以便後面使用。DIB 不是用 Windows 的 API 產生,而是要根據 Windows 系統的「規則」才能產生。

先介紹一下 DIB 的架構。

DIB 是圖形影像 ,具有尺寸大小 、色彩等的資訊 。這些資訊都 記錄在「BITMAPINFO 結構體」上。處理 DIB 的 API(SetDIBitsToDevice 等)則使用 BITMAPINFO 的指標(pointer)做為參數。

BITMAPINFO 結構體的格式如下 typedef struct tagBITMAPINFO {

BITMAPINFOHEADER brniHeader;

RGBQUAD bmiColors[l];

}BITHAPINFO;

DIB 的 大 小 、 色 彩 則 記 錄 在 BITMAPINFOHEADER 結 構 體 裡 。 BITMAPINFOHEADER 結構的格式如下:

typedef struct tagBITMAPINFOHEADER {

DWORD bisize;

LONG biwidth;

LONG biHeight;

WORD biBitCount;

DWORD bicompression;

DWORD biSizelmage;

LONG biXPelsPerMeter;

LONG biYPelsPerMeter;

DWORD biCIrUsed;

DWORD biCIrUsed;

DWORD biCIrImportant;

}

BITHAPINFOHEADER, *PBITMAPINFOHEADER.;

DTB 的可顯示色彩包括有「2 色(l bpp)」、「16 色(4 bpp)」、「256 色(8 bpp)」、「65,536 色(16 bpp)」和「1,677 萬色(24/32 bpp)」,到

「256 色」為止的 DIB 足「調色盤」上的系統色彩。 在這些色彩顯示 模式常中,l bpp 和 4 bpp 比較接近用 1 byte 處埋多個像素的感覺,

所以操作比較複雜。其實這兩種顯示模式所能表現的色彩並不多,各 位可以暫時把它放在一旁。

BITMAPINFOHEADER 之成員變數的設定值即如表所示。

成員變數 設定值

biSize BITMAPINFOHEADER 結 構 的 大 小 (sizeof(BITMAPINFOHEADER).

biWidith DIB 寬 biHeight DIB 高 biplanes 永遠設為 1

biBitCount 每一像素的位元數(bit per pixel) biCompression 設定 Bl_RGB

biSizeImaqe 整個圖形的位元組數 biXPelsPerMeter 設為 0

biYPelsPerMeter 設為 0 biCirUsed 設為 0 biClriImportant 設為 0 biXPelsPerMeter.

biYPelsPerMeter

是設定解析度(即每公尺所含像素數),但程式範 例並不會用到,故設為「0」

如果有先寫好標頭檔(header),後面只要再保留圖形要用的記憶 體空間即可,不過這裡也有一定的規則。這條規則就是"每條掃描線要 包括 4byte”,所以要做一個照條件進位的換算動作。DIB 的掃描線

(scan line)是指「1 條橫線」。照條件進位的計算如下:求出 1 條掃描 線的必要 byte 數

inline unsigned CDib::ScanBytes(int pixWidth,in t pixDepth)

{

return (unsigned)(((long)pixWidth * pixDepth + 31)/ 32) * 4;

}

pixDepth 則昰位元數,計算單位是 1 個位元。計算式是先加 31 後再除以 32,所得商數即為 byte 數,最後再乘以 4 就可以得知該圖形 換算後應該是幾個 4byte。將前面所得數值乘上圖形高度,即可得到必 要 byte 數

bitsAlloc = ScanBytes(Width, detth) * height;

保留這個數量的記憶體空閒之後.DIB 的產生過程也到此結束。這 裡使 GlobalAlloc 來保留足夠的記憶體空間,如想改用 new、HeapAlloc 也沒關係,當然也可以自己另寫一個記憶體空間保留函式。只要能「保 留記憶體空閒」的動作就行。

讀入 BMP 檔-CDib::LoadBMP

DTB 類別的程式碼中還有另一個「讀入 BMP 檔」的函式,這也是一個很 重要的函式。BMP 檔的作用不只是要簡化結構而已,BMP 的格式還要能 把 DIB 直接轉成檔案,資料排列等都要跟 DIB 一模一樣。

BMP 檔的資料結構

BITMAPFILEHEAD 表示這是 BMP 檔的標頭 BITMAPINFO

BITMAPINFOHEADER DIB 寬、高等相關資訊(如有必要時,則 為調色盤的資訊)

RGBQUAD 位元影像-

接在 BITMAPFILEHEADER 的後面是 BITMAPINFO,裡面的資訊則跟產 生 DIB 時所使用的資訊一樣。後面的位元影像也是跟 DIB 同樣的陣列。

地圖的顯示

CG 的顯示與圖形合成

重疊處理是要在記憶體上完成。如果等到顯示時才重疊在一起,

會變成兩段式先「繪製背景」、再「繪製重疊的 CG,,可能會只顯示背 景而已。

在合成背景和人物時,記憶體上必須保留「背景」、「人物」和「顯 示圖形」這三個 CG 影像(假設現在想把另一個人物跟背景重疊峙,只 要記憶體一直保留著背景 CG,就不需要重新讀取 CG 到記憶體上)。當 CG 資料可以「用完就丟」,當然就不需要保留了。

製作合成用的類別

合成的第一步驟是先把背景複製到顯示用影像 (記憶體),按著冉 跟人物合成。這兩個動作需要有「複製」和「合成」的函式。

有時直接把 CDIb 類別拿來用也不一定有好處。CDib 類別不會決定 色彩,因此如不讓它能決定色彩的話,就必須設計一個”不受色彩影 響”的函式關於地圖的顯示,必須先有一個座標係,才能顯示地圖或 人物還要按照 8bpp、16bpp、24bpp 的分類等級一一設計其專用類別,

實在是工程浩大,而實際又不是每個都非要不可,所以我們選擇繼承 CDib 再設計一個 24bpi 專用的類別。合成用的類別 lmagc.Cpp 則繼承 CDIb,只修改必要的部分。

//合成用類別 Image.h class CDC;

class Cimage:

{

public:

CImage ( ) : CDib( ) {}

Cimage ( int width , int height);

BOOL Create (int width, int height);

void Copy(const CImage *image, const CRect &rect);

void Copy(const CImage *image);

void MixImage (const CImage *image, const CRect &re ct,

COLORREF trans=RGB(0, 255, 0));

} ;

//成員函式

inline void Cimage::Copy(const CImage *image) {

Copy (image, CRect(0, 0, image->Width( ), image->He ight( )))

Create(width, height);

}

//產生 DTB

BOOL CImage:: Create (int width, int height) {

return CDib;:Create(width, height, 24);

}

//複製區域

voidCImage::Copy(const CImage *image, const CRect &

int len = rect.Width ( ) * 3;

for (int y=rect .top; y<rect .bottom; y++) {

memcpy(GetBits (rect.left, y), image->GetBits(rect.

left, y), len);

}

//複製(有考慮到透明色部分)

voidCIage::MixImage(const CImage*image , const CR ect &rect ,

COLORREF trans_color)

const unsigned char trans b = GetBValue(trans colo r);

const unsigned char trans_g = GetGValue(trans_colo r);

const unsigned char trans_r = GetRValue(trans_colo r);

for(int y=reck .top; y<rect .bottom; y++) { byte_t *p = (byte_t *)GetBits(rect. left, y);

const byte_t *q = (byte_t *)image->GetBits(rect. le ft, y);

for (int x=rect.left; x<rect.right; x++) { const byte_t b = *q++;

const byte_t g = *q++;

const byte t r = *q++;

if(b!=trans_b||g!=trans_g||r!=trans_r) {

}

複製用的函式

雖然講是講「圖形」,不過其實是記憶體上的資料,因此複製的動作可 使用「memCpy」。

void CImage::Copy(const CImage *image,const CRec t &rect)

{

int len = rect.Width( ) * 3;

for( int y = rect.top;y<rect.bottom;y++) {

memcpy(GetBits(rect.left,y),image->GetBits(rect.le ft,y),len);

} )

寬度的地方乘以 3」是因為「1 像素=:3byte」的關係。不斷複製 必須要的高度,即可複製圖形。

合成

合成的過程中必須決定出一個規則,否則程式無法判斷哪些部分 要合成、哪些部分又不需要合成,當然就不會進行複製。這個判斷的 方法必須要訂清楚。說穿了,就足捉供「遮罩圖形」的方法或設定「透 明色彩」的方法。

如欲使用遮罩圖形,則須另行製作遮罩用的圖形,所以 CG 製作比 較麻煩。我們使用透明色彩來進行合成。至於透明色彩的選擇方式,

最常使用的是 RGB(0,255.0),的亮綠色。當然也可以自訂其他色彩,

不過最好是“非常極端、CG 不會用到的色彩。

const byte_t b = *q++; 藍

const byte_t g = *q++; 綠 const byte_t r = *q++; 紅 //是否為透明色彩?

if(b!trans_b||g!=traans_g||r!=trrans_r) {

p[0]=b; //藍色部分的複製 p[1]=g; //綠色部分的複製 p[2]=r; //紅色部分的複製

p+=3;

}

合成處理是先在迴圈中逐一判斷各個像素足 「透明色彩」或「非 透明色彩」,若為「非透明色彩」則進行複製。

如果不控制 DIB 的色彩數量,這個處理動作會越變越複雜,因此 這裡設限為 24bpp。若為 16bpp 或 32bpp,則須另外設計其他適合的常 式。

座標系的轉換

如果使用的地圖是垂直向下看的方格地圖,畫面顯示的座標係級 同地圖的座標系(只需放大或縮小),不必花太多時間,但如是 45 度全 視角的畫面,就需要轉換座標值實作時,先作出平面的地圖再配合轉 換座標即可作成 45 度的地圖必須先有一一個座標系,才能顯示地圖或 人物。

如果使用的地圖足垂百向下看的方格地圖,畫面顯示的座標系即 同地圖的座標丟(只須放大或縮小),不必太花時間,但如果是 45 度全 視角的畫面,就需要轉換蝴值。

不先處理好座標轉換的問題,甚至會無法做移動測試。45 度全視 角的座標系即如圖所示。

圖 4.1 45 度全視角的座標系

從地圖座標轉換成畫面座標

我們先想想從地圖座標轉換成畫面座標時的轉換式。轉換成畫面 座標時,就已經從從「點」變成「方格」,所以要求出框住菱形外圍的 矩形的「左上角」座標。

圖 4.2 畫面座標的位置

框住菱形外圍的矩形大小是「64X32」,而在 640X480 的畫面(程式 區域)上則可顯示 10X10 的地圖。因為方格間有互相重疊,所以畫面座 標的最小單位是這個尺寸大小的 1/2(長 X 寬=16X32)。

畫面座標是設菱形 外圍矩形的左上角

圖 4.3 矩形大小與介面的關係

這時候,可以先用一個比較容易算的數值試算看看。最容易算的 數值當然是「0」。地圖座標為「0,0」時,則畫面座標為「x=0」,Y 座標則偏移 6 個方格(最小單位 12:,故可得出下列公式:

MAPGRID_WVIDTH =64 MAPGRID_HEIGHT=32

view_x=0

view_y=12*(MAPGRID_HEIGHT/2)

圖 4.4 地圖座標原點

深灰色部分 是地圖

地圖 X 座標(map_X)每增加 1,畫面 Y 座標(view_Y)就會相對減少 1。也就是說

view_x=map_x*(MAPGRID_WIDTH/2)

view_y=(12-map_x)*(MAPGRID_HEIGHT/2)

而地圖 Y 座標(Map_Y)每增加 1,畫面 X 座標(view_X)也同樣增加 1

view_x=(map_x+map_y)*(MAPGRID_WIDTH/2) view_y=(12-map_x+map_y)*(MAPGRID_HEIGHT/2)

地圖座標和畫面座標兩者的轉換公式即如上所示。

從畫面座標轉換成地圖座標

反過來的話,要如何從畫面座標求出地圖座標?

公式轉換的計算單位是像素,不過像素座標都是整數,如果求出 的結果不是整數時還是會強迫改成整數。因此計算結果可能會有加減 1 像素的誤差(這是無條件進位或無條件捨去時所產生的誤差,所以或許 可以視為一定會有誤差)。

利用漸層色地圖

先在記憶體上做個「漸層色地圖」搭配使用也是一種方法。

圖 4.5 漸層色地圖

這種方法的優點是能適用於各種不同形狀的地固。受限於印刷問 題,雖然上圖使用黑白兩色,實際上 x 座標用紅色、Y 座標用藍色會更 容易判斷座標位置。

因為這裡的目的只是為了「用滑鼠指出」,加不需要太嚴格要求,

所以先用計算式就好了。

座標轉換的計算式

首先,方格的長寬比例是「2:1」,所以畫面 Y 座標要放大 2 倍。

然後地圖座標也要從方格單位改成像素單位,即地圖座標乘以 64 倍,

0~63 為「0」、64~127 為「11….以此類惟(因為畫面座標是以像素為 計算單位) 試求畫而座標「0,0」時的地圖座標。地圖 Y 座標跟畫面 座標總共偏移了 6.5 個方格,所以要把偏移的部分補正回來。

0~63 為「0」、64~127 為「11….以此類惟(因為畫面座標是以像素為 計算單位) 試求畫而座標「0,0」時的地圖座標。地圖 Y 座標跟畫面 座標總共偏移了 6.5 個方格,所以要把偏移的部分補正回來。

在文檔中 虛擬寵物與市場設計實作 (頁 43-59)

相關文件