資料來源:網路
主講人:(為顧及授課教師之隱私,故不在此公佈其姓名)
課程主旨:這門課所講述的是遊戲程式的開發技術與實務
講義精要:
1. 程式概論
● 遊戲的成型:
遊戲的成型主要分為表裡兩部分,表面指的是呈現在玩家面前的視覺、聽覺上的感官,裡指的是複雜的資料結構,一般遊戲的畫面成型概念如下:
◎ 元件:
或稱為 Block、地圖圖素、圖素,泛指在畫面上與主角間有遮罩關係者 (與人物有前後關係)
◎ 格線:
座標,紀錄各元件的位置
◎ 地圖:
§ 拼湊模式 (可重複運用)
§ Pre-Render 模式 (精緻美觀)
§ 90 度角,畫面的感官為正上視圖
§ 3D 45 度角,格線為 45 度角傾斜
§ 3D,角度任意
地圖屬性:
○ 可通過與否
○ 機關事件
◎ 資料結構:
支撐遊戲性,上述每一向皆須由企劃輸入,早期 DOS 時代,許多的資料大多由 PE2 等文書編輯器輸入,程式須與企劃人員約定輸入格式,最後程式必須撰寫編碼程式將之匯入遊戲,如:
ID 100
NAME 張三
HP 200
MP 100
此種方法耗時費力,經常因為企劃人員的筆誤,造成後續遊戲開發的困擾,如 ID 筆誤成 1D。Windows 95 後,程式大多利用 MFC 工具,開發制式的編輯器,取代 PE2 等文書編輯工具的工作,優點是方便企劃、程式在資料建立同時,便可剔除語法、資料重複的基本錯誤,加速遊戲開發速度;缺點是,編輯工具的開發,往往耗日費時,而且佔去遊戲開發時間的多數,對整個遊戲的進度毫無幫助。也有些人以折衷方法,利用 Windows 的 Word、Excel 可以圖形化的編輯器來輸入資料
● 與企劃、美術分工:
遊戲小組的成員,一般分為程式、企劃與美術人員,真正運作時,有製作人在統籌,調解三方的合作、意見糾紛與進度掌控,另外,比較獨立作業的有音樂音效製作,但就開發而言,程式、企劃與美術三方缺一不可
◎ 企劃與程式:
企劃發想各個遊戲的驚奇點子,往往是天馬行空的創意,在遊戲開發初期,企劃最重要的工作,是與程式規劃遊戲系統,企劃定出要求,程式就現有技術與時間提供可不可行與替代方案給企劃,就實際開發的例子,往往受限於程式現有技術與時間,許多好的創意便窒礙難行。一但定案,程式便協助企劃訂定各種表單的格式 (或製作編輯工具),讓企劃依循一定的模式,完成一個遊戲的資料輸入,最後,重點是完成主程式的開發,確定雙方同意的創意無誤的在電腦上執行
◎ 程式與美術:
美術內部的分工,一般分為原畫設計與實作人員。美術通常比較不參與系統的討論,大多是在遊戲定案後,給予企劃與程式美術上的專業諮詢,譬如遊戲圖形採取何種格式、色盤如何安排、風格走向、製作測試圖形,讓程式試做無誤等等,工作量十分的大,一般與程式的互動,小到圖形格式、命名原則的約定,大到遊戲拼圖程式的規劃,這個編輯器也是唯一美術必須參與的,因為圖形製作美術最清楚元件設計時該擺在哪
◎ 美術與企劃:
兩者的關係互動性很大,企劃無時無刻不關心美術的圖形風格進度,拼好的地圖與企劃的發想是否有落差
● C / C++ 概論:
C++ 是 C 的延伸,它允許程式設計者撰寫物件導向式的程式,是由 Bjarne Stroustrup 設計,原來的 C 語言於 1978 年由 Brain W.Kernighan 和 Denis M.Ritchie 在 AT&T Bell 實驗室研發而出,C++ 當作物件導向程式語言的一個主要優點是,產生類別物件庫,方便其他專案程式使用、擴充、修改
● 其他概念:
◎ FPS (Frames Per Second):
每秒顯示張數,人的眼睛對快速移動的物體會有視覺暫留的現象,這個數值在電腦上最低大約需要 15 ~ 20 FPS,在這個範圍以下的畫面更新,會讓我們有延遲的感覺,發生這樣的問題時,通常最大的原因在繪圖圖素過多,繪圖的技術不良,或者所選用的程式語言不適所致
◎ Pixel:
圖素,螢幕上單一點,一般市面上的遊戲 Pixel 規格有分 256 色、16 Bit 色 (Hi-Color) 與 32 Bit 色 (True-Color) 等
◎ 遊戲解析度:
遊戲畫面的長寬點數,通常以 XXX * XXX 表示,一般常見的解析度為 640 * 480、800 * 600、1024 * 768,畫面解析度的增加,意味著處理時間的加長、FPS 的下降
◎ 透明色 (Color-Key):
選定一顏色 (或數色),凡圖片上任何一點顏色與之相同,皆會變成透明,此種方法會讓圖形邊緣有銳利的鋸齒出現
◎ 透明程度 (Alpha):
每一 Pixel 皆設定其透明度,0 表示完全透明,255 表示完全不透明,半透明的顏色會與背景顏色混色,公式如下:
Color = Alpha * (Source Color) + (255 - Alpha) * (Destination Color)
此種技術由於有混色的效果,邊緣鋸齒狀的現象會消失
◎ 抗失真:
利用補色的技術,讓鋸齒不那麼明顯的方法
◎ Real_Time (即時):
指遊戲的進行,不會音玩者任何動作而停頓,節奏明快,多數用在格鬥如鐵拳、快打系列,網路遊戲如 UO、天堂、金庸群俠等
◎ Low-Polygon (低面數技術):
利用很少的面數形成一物件,形成遊戲畫面,此種技術用於即時 3D 的遊戲,優點在可以很快的 Render 出畫面,缺點在圖形精細度不足,需搭配 2D 材質來修飾,如太空戰士 7 以後系列等
◎ High-Polygon (高面數技術):
與 Low-Polygon 相反,用數以萬計以上的面數完成單一元件,此種方法為一般美術作圖方式,優點是美觀,缺點是耗時與無法應用於即時 3D 遊戲,通常在遊戲的應用為片頭片尾全螢幕動畫,或者 Pre-Render 出各個精美圖形後,由程式將之放入遊戲畫面中,此類遊戲佔大部分的遊戲市場,如軒轅劍、新蜀山劍俠等
◎ Direct X:
Microsoft 開發,專為程式發展遊戲專用,其中常用分為 DirectDraw (畫面處理)、DirectSound (音樂音效處理)、Direct3D (3D 功能處理)、DirectInput (操作介面處理)、DirectPlay (網路功能處理)
◎ OpenGL:
專門處理 3D 遊戲的引擎
◎ 網路遊戲:
目前分兩大類,一種是點對點的遊戲,最多到 16 人同時連線對打,像世紀帝國、Diablo 等,一種是 Client-Server 架構的千人連線遊戲,像 UO、天堂、金庸群俠傳等等
2. 編輯器
● 地圖編輯器
要件:
◎ 地圖解析,企劃必須設定地圖最大與最小的範圍和最高層數,讓程式在規劃成事實 有所依據,美術必須提出功能需求,如 Undo / Layer、測試功能等
◎ block 最大圖形與命名規則,以拼湊圖方式的 block 一般都是以 file 的整數倍數 為最大圖形,命名用意在與程式溝通,方便程式能順利將美術圖檔 import 進入編 輯器內,例如 tree0000.bmp,一般而言,block 有兩個要素,一是編號,一是會動 與不動,會動者,又需要設定其總共有幾張動態,如火焰等 (早期的遊戲,多半規定 會動的全部統一張數,如火焰 8 張,飄揚的旗子必須也是 8 張),另外,由於 block 亦需設定貼圖點,此點又稱貼圖基準點
◎ 地圖屬性,企劃人員必須規劃出地圖屬性的類別與類別上限,地圖屬性作業方式有兩種, 一種直接放置於 block 上,拼圖的同時就決定了此座標可否通過,一種是在拼完之後, 再在座標上設定此處屬性
◎ 遊戲資訊,一張地圖至少需有一唯一 ID 與名字,以供遊戲時期玩家與程式所需
● 事件編輯器
◎ 旗標,事件影響遊戲流程,與主程式間的連結乃透過程式語言上的旗標 (FLAG) 達成, 旗標對企劃而言乃是一介於 0 ~ X (X 上限由程式企劃合定) 的數字,我們可利用此數 字改變遊戲流程,例如,FLAG1 成立,玩者決定先殺魔王,FLAG3 為主角成功殺死魔王了; 或者藉由 FLAG 控制遊戲狀態,如 FLAG123 成立時沒有隨機戰鬥等,每個 FLAG 企劃皆須告訴程式,有否特殊的功能
◎ 事件種類,以操作不同滑鼠觸發事件與玩者腳踏事件,滑鼠觸發事件又分遠端遙控型與近身 操作型態兩類,分述如下:
§ 滑鼠觸發: 指遊戲中玩者利用滑鼠點選而引發的事件,如寶箱等
○ 遠端遙控:指主角不用移動到事件旁邊,可見即可操作,對程式而言方便撰寫,比較常見於 國產遊戲
○ 近身操作:程式必須撰寫一段尋路程式,將主角位置移動到事件旁邊,到達後才可操作事件
§ 主角腳踏: 必須主角座標位置與事件重疊而引發此類事件,例如地圖切換點,特殊機關等
● 劇情編輯器
此處做法各家不一,但是大多數的做法是提供企劃人員眾多的指令集,讓企劃人員去運用組合成想像中的劇情環境,例如,主角發現母親被奸人所害身亡,悲痛萬分,音樂改變、主角走位到母親遺體處,跪下痛哭,此處企劃與美術密切配合,繪出主角、母親各個相關動作
劇情編輯器與企劃、主程式的關聯方式,乃透過旗標處理,一個旗標最多掛一個劇情
劇情編輯器,指令繁多,指令相關的物件繁多,舉凡人、物品、事件控制、音樂音效等
● 物品編輯器
物品分為兩類,一是過關用物品或稱劇情物品,一是冒險用物品,冒險用物品一般而言沒有屬性,帶在背包中,用在正確的事件點時,可開啟新的劇情,冒險物品多半跟戰鬥有關,如補血藥、補魔法藥、衣甲、武器等,此類物品企劃必須規劃出相關屬性的細部項目,例如對 MP、HP 等等的影響,注意一事,屬性分為現值與上限兩方的影響,再做資料設定同時必須一併考慮規劃進去
● 人物編輯器
編寫遊戲中所有的人物資料,包括人物對話、買賣資料、問答等等,另外常見的人物閒逛模式也在此設定
● 戰鬥編輯器
包括怪物與戰場編輯,戰鬥系統關係遊戲的耐玩性,企劃在規劃時期必須對市面上相關類型的遊戲市場多做了解與調查,避免閉門造車的窘境發生
企劃對系統的規劃,主要在設定戰鬥流程、公式、怪物資料輸入,以及平衡性的調整
3. 常用的檔案處理函數
● DeleteFile:刪除檔案
● GetFilePath:取得檔案完整路徑與檔名
● GetFileTitle:取得檔案名稱 (不含副檔名)
● GetFileName:取得檔案名成 (含副檔名)
● FindFile:開始搜尋檔案
● GetNextFile:持續搜尋檔案 (之前先呼叫 GetFile)
● IsDirectory:檢查搜尋到的檔案是否為資料夾 (FILE_ATTRIBUTE_DIRECTORY)
● IsDots:檢查目錄或母目錄 (. 或 ..)
4. TGA Header Definition
typedef struct _TgaHeader //一共有 18 個 Byte
{
BYTE nIDLen;
BYTE nColorMapType;
BYTE nImageType; //編碼模式
WORD nPaIMapStart;
WORD nPaIMapLen;
BYTE nPaIBitDepth;
WORD nXOffset;
WORD nYOffset;
WORD nDx;
WORD nDy;
BYTE nPixelDepth; //像素色彩深度
BYTE nImageDescript;
}SxTgaHeader;
5. TGA 編碼模式解讀
switch(nImageType)
{
case 0: //No Image (全黑影像)
ZeroMemory(m_pImage, m_nDx * m_nDy *m_nByte); //清空記憶體
break;
case 1: //ColorMap Image (不壓縮調色盤模式)
case 2: //True Color Image, No Encode (不壓縮全彩模式)
memcpy(m_pImage, pValid, m_nByte);
break;
case 3: //Mono Color
if(m_nByte)
{
memcpy(m_pImage, pValid, m_nDx * m_nDy * m_nByte);
}
else
{
ZeroMemory(m_pImage, m_nDx * m_nDy **(WORD*)(bHead + 16) / 8);
}
break;
case 9: //ColorMap Image, Encode (壓縮調色盤模式)
Decode(pValid, m_pImage, m_nByte);
break;
case 10:
case 11:
Decode(pValid, m_pImage, m_nByte);
break;
}
6. 載入 Bitmap 影像方式 (摘要)
CDIBitmap::CDIBitmap(int dx, int dy)
{
m_pInfo = NULL;
m_pSurface = NULL;
m_pRGB = NULL;
m_nDx = dx;
m_nDy = dy;
int nSize = sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD);
m_pInfo = (LPBITMAPINFO) new BYTE[nSize];
memset(m_pInfo, 0, nSize);
BITMAPINFOHEADER * pInfo = (BITMAPINFOHEADER*) m_pInfo;
pInfo->biSize = sizeof(BITMAPINFOHEADER);
pInfo->biWidth = m_nDx;
pInfo->biHeight = (-1) * m_nDy; //Load 進來時是倒圖,所以將其反向
pInfo->biPlanes = 1; //其值永遠為 1
pInfo->biBitCount = 32; //32 內包含 Alpha
pInfo->biCompression = BI_RGB;
m_pSurface = new SxDxColor[m_nDx * m_nDy];
FillColor(RGB(0x7F, 0x7F, 0x7F));
}
7. 讀寫檔方式 (摘要)
class CSmartMemFile:public CMemFile
{
public:
inline const BYTE *AccessBuffer();
void Attach(BYTE *lpBuffer, UINT nBufferSize, BOOL nAutoErase = TRUE);
CSmartMemFile();
CSmartMemFile(BYTE *lpBuffer, UINT nSize, BOOL nAutoErase = TRUE);
virtual ~CSmartMemFile();
BOOL IsEOF();
template <class DT> inline CSmartMemFile & operator >> (DT &nGet)
{
Read(&nGet, sizeof(DT));
return(*this);
}
CSmartMemFile & operator >> (LPCTSTR nGet); //Get String
CSmartMemFile & operator >> (CString &nGet); //Get String
template <class DT> inline CSmartMemFile & operator << (DT &nPut)
{
Write(&nPut, sizeof(DT));
return(*this);
}
CSmartMemFile & operator << (LPCTSTR nPut); //Put String
inline CSmartMemFile & operator << (CString &nPut)
{
operator << ((LPCTSTR)nPut);
return(*this);
}
};
8. 產生亂數方式 (摘要)
class CRandom
{
public:
inline static int Random(DWORD __num)
{
return (rand() * __num) / (RAND_MAX + 1);
}
inline static BOOL IsRate(int nRate)
{
return(Random(100) < nRate);
}
inline static int ThrowDice(int nFace)
{
return(Random(nFace) + 1);
}
CRandom();
virtual ~CRandom();
}; //SRAND 重新產生新的 Seed, 避免週期性重複
9. 佇列處理 (摘要)
template <class DT> class CSmartQueue
{
protected:
CArray <DT, DT> m_pList;
int m_nReadPtr;
int m_nWritePtr;
public:
inline CSmartQueue(int nSize) //建構
{
SetSize(nSize);
}
inline CSmartQueue(){};
//設定佇列大小及移除所有資料
inline void SetSize(int nSize)
{
m_pList.SetSize(nSize);
m_nReadPtr = m_nWritePtr = 0;
}
inline ~CSmartQueue(){}
inline BOOL GetObject(DT &pData)
{
if(!IsEmpty())
{
pData = m_pList.GetAt(m_nReadPtr);
m_nReadPtr++;
m_nReadPtr %= m_pList.GetSize();
return(TRUE);
}
return(FALSE);
}
inline BOOL PutObject(DT pData)
{
if (!IsFull())
{
m_pList.SetAt(m_nWritePtr, pData);
m_nWritePtr++;
m_nWritePtr %= m_pList.GetSize();
return(TRUE);
}
return(FALSE);
}
inline BOOL operator >> (DT &pData) {return GetObject(pData);}
inline BOOL operator << (DT pData) {return PutObject(pData);}
inline BOOL IsEmpty()
{
return(m_nReadPtr == m_nWritePtr);
}
inline BOOL IsFull()
{
return((m_nWritePtr + 1) % m_pList.GetSize() == m_nReadPtr);
}
inline void RemoveAll()
{
m_nReadPtr = m_nWritePtr;
}
};
10. 堆疊處理 (摘要)
template <class DT> class CStack
{
public:
CArray <DT, DT> m_pList;
public:
CStack(){}
CStack(CStack <DT> &st) //Reverse
{
*this = st;
}
virtual ~CStack(){}
BOOL Push(DT &pData)
{
m_pList.Add(pData);
return(TRUE);
}
BOOL Pop(DT &pData)
{
int nSize = m_pList.GetSize();
if(nSize == 0) return (FALSE);
pData = m_pList.GetAt(nSize - 1);
m_pList.RemoveAt(nSize - 1);
return(TRUE);
}
BOOL IsEmpty()
{
return(m_pList.GetSize() == 0 ? TRUE : FALSE); //Stack Empty
}
void RemoveAll(){m_pList.RemoveAll();}
void operator = (CStack <DT> &dt)
{
RemoveAll();
m_pList.Copy(dt.m_pList);
}
void Reverse()
{
for (int count = 0; count < m_pList.GetSize() / 2; count++)
{
DT pData = m_pList.GetAt(count);
m_pList[count] = m_pList[m_pList.GetSize() - count - 1];
m_pList[m_pList.GetSize() - count - 1] = pData;
}
}
};
11. Timer 設計 (摘要)
void CTimer::Breath()
{
DWORD nTime = GetTickCount();
m_nGameClock += nTime - m_nStartGameTick;
SxTimeCallBack *pNode = m_pCallBack.GetRoot();
CGame *pGame = CGame::GetObject();
while(pNode)
{
if(!pNode->nStatus & FLAG_LOCK) && nTime >= pNode->nNextTimeOut)
{
pNode->nStatus |= FLAG_TIMEOUT;
if(!pNode->pCallBack)
{
pNode->nNextTimeOut = pNode->nFreq + nTime; //m_nNextTimeOut
SxMessage msg = {MSGDT_TIMEOUT, pNode->nID};
pGame->m_pMessage << msg;
}
else
{
pNode->pCallBack->Breath(pNode->nID, pNode->nFreq);
pNode->nNextTimeOut = pNode->nFreq + nTime;
//m_nNextTimeOut; //GetTickCount();
}
}
pNode = m_pCallBack.GetChild();
}
}
12. 地圖轉換 (摘要)
class CTimeSeed
{
public:
int m_nTSMode; //Type of change map
int m_nFlag;
public:
virtual void GetPosition(int &sx, int &sy, int &dx, int &dy);
virtual void Breath(int nID, DWORD &nFreq);
CTimeSeed(int nType = TIMESEED_TYPENORM);
virtual ~CTimeSeed();
}
13. 遊戲開發流程
14. 主程式程式流程
額外補充:
1. 編輯器需求大致分為以下幾種
● 地圖編輯器
● 人物編輯器
● 物品編輯器
● 戰鬥系統編輯器:包含戰場、怪物 (主角)
● 劇情編輯器
● 事件編輯器
2. Win32 SDK 主用於開發遊戲主程式、MFC 主用於開發介面程式
3. TGA 檔案格式是唯一支援 32 bit 的圖檔格式 (R:8 bit、G:8 bit、B:8 bit、Alpha:8 bit)
4. 讀檔寫檔相關函數:
● 讀檔:sscanf(buf,"%d %s") 或 sscanf(buf, "%d,%s")
● 寫檔:cstring() 或 sprintf
5. 碰撞偵測通常使用 Bounding Box 來偵測
6. 分時系統使用方式,一般因為 SetTimer 優先權較低,有時易被 Windows 略過,故一般使用 GetTickCount()
7. Alpha Channel 色表做法
● 16 bit:
R:0 ~ 32
G:0 ~ 63
B:0 ~ 32
共需 62 * 32 / 1024 = 2 k 記憶體
● 24 bit:
R:0 ~ 255
G:0 ~ 255
B:0 ~ 255
共需 256 * 256 / 1024 = 64 k 記憶體
8. 一般顯示卡對 16 bit 色彩編碼方式分為以下兩種
● R:G:B = 5:6:5
● R:G:B = 5:5:5
9. DIB (Device Independent Bitmap)
10. template 必須放在 Header File 中
11. 移動記憶體函數的差異
● MoveMemory:記憶體不會重疊
● MemCpy:記憶體會重疊
12. 定義一個 Debug 版本判斷式的方法
#ifdef_DEBUG
ASSERT (count < 10)
#endif
13. 更新速度的基本設定
● 螢幕更新速度:20 fps / sec (1 sec = 1,000 msec = 20 fps => 1fps = 50 ticks)
● 人物速度:20 fps / sec
14. 鍵盤滑鼠事件 Message 的捕捉可用 Hook 去監視
作業:
將所有中文字 Dump 到文字檔 → C++ Source Code And Exe File