<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>猴子靈藥 [Monkey Potion] &#187; 入門概念</title>
	<atom:link href="http://blog.monkeypotion.net/category/gameprog/beginner/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.monkeypotion.net</link>
	<description>遊戲開發‧遊戲程式‧遊戲設計</description>
	<lastBuildDate>Thu, 02 Feb 2012 11:32:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>MSVC與CRT的恩怨情仇</title>
		<link>http://blog.monkeypotion.net/gameprog/beginner/love-and-hate-between-msvc-and-crt</link>
		<comments>http://blog.monkeypotion.net/gameprog/beginner/love-and-hate-between-msvc-and-crt#comments</comments>
		<pubDate>Sun, 08 Mar 2009 16:26:34 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[入門概念]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=954</guid>
		<description><![CDATA[很久沒有寫程式設計入門知識的相關文章了，這篇文章要來談談程式庫 (Library) 連結，以及關於 MSVC 與 CRT 之間的種種恩怨情仇。 如果你使用的作業系統是 Linux、Mac 或其他非 Windows 平台，你可以忽略這篇文章；如果你使用的作業系統是 Windows 平台，但沒有用 Microsoft Visual Studio C++（以下簡稱為 MSVC）軟體撰寫 C++ 程式的話，這篇文章對你的幫助可能很有限；但如果你的作業系統是 Windows，而且你使用的程式整合開發環境是 MSVC 軟體撰寫 C++ 程式的話，這篇文章應該能夠幫助你釐清一些重要的基礎觀念。 身為程式設計者，在學習程式設計的過程中，你是否曾經遇過某些看起來不知所云的錯誤訊息，卻不知該如何解決？例如當你快快樂樂地寫完程式，並且確認所有的程式碼都能成功通過編譯之後，接著執行「建置方案」(Build Solution) 的步驟，結果卻跑出一堆莫名其妙的錯誤： LIBCMTD.lib(mlock.obj) : error LNK2005: __lock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定義過了 LIBCMTD.lib(mlock.obj) : error LNK2005: __unlock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定義過了 LIBCMTD.lib(crt0.obj) : error LNK2005: _mainCRTStartup 已在 MSVCRTD.lib(crtexe.obj) 中定義過了 ………… LINK [...]]]></description>
			<content:encoded><![CDATA[<p>很久沒有寫程式設計入門知識的相關文章了，這篇文章要來談談程式庫 (Library) 連結，以及關於 MSVC 與 CRT 之間的種種恩怨情仇。</p>
<p>如果你使用的作業系統是 Linux、Mac 或其他非 Windows 平台，你可以忽略這篇文章；如果你使用的作業系統是 Windows 平台，但沒有用 Microsoft Visual Studio C++（以下簡稱為 MSVC）軟體撰寫 C++ 程式的話，這篇文章對你的幫助可能很有限；但如果你的作業系統是 Windows，而且你使用的程式整合開發環境是 MSVC 軟體撰寫 C++ 程式的話，這篇文章應該能夠幫助你釐清一些重要的基礎觀念。</p>
<p>身為程式設計者，在學習程式設計的過程中，你是否曾經遇過某些看起來不知所云的錯誤訊息，卻不知該如何解決？例如當你快快樂樂地寫完程式，並且確認所有的程式碼都能成功通過編譯之後，接著執行「建置方案」(Build Solution) 的步驟，結果卻跑出一堆莫名其妙的錯誤：</p>
<blockquote><p>
LIBCMTD.lib(mlock.obj) : error LNK2005: __lock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定義過了<br />
LIBCMTD.lib(mlock.obj) : error LNK2005: __unlock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定義過了<br />
LIBCMTD.lib(crt0.obj) : error LNK2005: _mainCRTStartup 已在 MSVCRTD.lib(crtexe.obj) 中定義過了</p>
<p>…………</p>
<p>LINK : warning LNK4098: 預設的程式庫 &#8216;MSVCRTD&#8217; 與其他使用的程式庫衝突，請使用 /NODEFAULTLIB:library<br />
LINK : warning LNK4098: 預設的程式庫 &#8216;LIBCMTD&#8217; 與其他使用的程式庫衝突，請使用 /NODEFAULTLIB:library<br />
D:\Workspace\CrtLibTest\Debug\CrtLibTest.exe : fatal error LNK1169: 找到有一或多個已定義的符號
</p></blockquote>
<p>以一般的情況來說，如果在你的程式專案中有使用某些由他人所撰寫的第三方程式庫或是開源專案的程式庫，比較容易會發生上述的錯誤狀況。從上述這些看似離奇而令人摸不著頭緒的錯誤訊息中，我們大概可以猜測問題點應該在於 LIBCMTD.lib 與 MSVCRTD.lib 這兩個程式庫身上。<strong>但到底什麼是 LIBCMTD.lib 和 MSVCRTD.lib？在我們的程式碼中有使用這些程式庫嗎？</strong></p>
<p><span id="more-954"></span></p>
<p>答案是肯定的。</p>
<p>熟悉 C 語言的程式設計者都知道，如果要使用 printf()、scanf() 或者 fopen() 等等 C 語言的基本 I/O 操作函式時，首先必須用 #include 語法將 stdio.h 這個標頭檔納入我們的程式碼中。藉由 stdio.h 中對這些 I/O 操作函式所做出的函式宣告 (function declaration)，編譯器 (Compiler) 才得以確認 printf、scanf 以及 fopen 等等都是合法可用的函式。</p>
<p>而當我們撰寫的程式碼經過編譯器產出 OBJ 形式的檔案之後，需要再經由連結器 (Linker) 的處理程序，將程式碼中全部有使用到的函式定義 (function definition) 連結建置起來，才能夠產生出最後的程式執行檔。問題來了，我們知道 printf、scanf 以及 fopen 的函式宣告存在於 stdio.h 當中，但是這些傢伙的函式定義，也就是真正的實做程式碼，究竟存放在什麼地方呢？</p>
<p><strong>在 C 語言的標準程式庫中。</strong></p>
<p>由 C 語言所制訂的標準程式庫，稱之為<strong>「執行階段程式庫」</strong>，也就是 <strong>C Run-Time Library</strong>，通常可簡稱為 <strong>CRT</strong>。在 C 語言的標準程式庫中，包含了一組常用的基礎函式，例如 I/O 處理與字串操作程序等等，所以只要我們使用 C 語言撰寫程式碼，就一定要將編譯完成後的程式碼 OBJ 檔，連結至 C 語言的執行階段程式庫，才能夠產生出合法的 C 語言程式執行檔。</p>
<p>而 CRT 並非只有單一一種版本存在。事實上，除了可以依「除錯」與「釋出」用途分成兩個版本之外，兩者又可分別衍生分出「靜態連結」與「動態連結」兩種形式：</p>
<blockquote><p>
<strong>靜態連結</strong>：</p>
<ul>
<li>LIBCMTD.lib（除錯版本）</li>
<li>LIBCMT.lib</li>
</ul>
<p><strong>動態連結</strong>：</p>
<ul>
<li>MSVCRTD.lib（除錯版本）</li>
<li>MSVCRT.lib</li>
</ul>
</blockquote>
<p>雖然這四個 CRT 版本的用途與使用方式各不相同，但卻有個共通的特點，就是<strong>它們都是滿足執行緒安全需求，可在多執行緒程式碼中安全使用的程式庫版本</strong>。事實上，在過去 MSVC 6 的版本中，本來還有另外兩個 LIBCD.lib（除錯版本）與 LIBC.lib 程式庫，是專門給單執行緒程式使用的 CRT 版本，但是這兩個選項自 MSVC 2005 開始就從設定選項中被刪除掉了，所以現在大多數程式設計者使用的都是多執行緒的 CRT 版本。</p>
<p>在程式庫連結 (library linking) 的行為中，靜態連結和動態連結的分別，在於使用靜態連結時，會直接將程式庫的函式定義嵌入執行檔之中，而使用動態連結時，程式庫的函式定義則存在於另外的獨立檔案，通常是 DLL 格式的檔案中，然後與程式執行檔一同發佈給使用者。因此在檔案的尺寸上，使用動態連結的執行檔檔案，通常會比使用靜態連結的執行檔檔案來得更小一些。</p>
<p>使用動態連結 CRT 版本的好處，是能夠將經常使用到的標準程式庫們獨立出來，放在 Windows 的系統資料夾中，以減少我們建置出來的執行檔檔案尺寸。但反過來說，<strong>使用動態連結 CRT 版本的缺點也在於這些與執行檔相依為命的 DLL 檔案上</strong>。舉例來說，如果程式以 MSVC 2005 建置出 Debug 組態的執行檔，則此執行檔需要有 msvcr80d.dll 存在才能順利執行；如果是 Release 組態，則相依於 msvcr80.dll。但是如果你把相同的程式碼拿到 MSVC 2008 上建置，產生出來的執行檔則相依於 msvcr90d.dll 與 msvcr90.dll 兩個不同的 DLL 檔案。<strong>不同版本的 MSVC，都會有各自不同的相依 DLL 檔案。</strong></p>
<p>在 MSVC 的程式專案中，如何指定程式碼要使用靜態連結或者動態連結的 CRT 版本？其實很容易，只要在專案屬性的「C/C++」頁面中，選擇「程式碼產生」(Code Generation) 子頁面，其中有個「執行階段程式庫」(Runtime Library) 的項目，也就是專案中用來設定 CRT 連結版本的地方。其中總共有四個選項，正好對應於上述靜態連結與動態連結的四個不同程式庫版本。</p>
<ul>
<li><strong>多執行緒偵錯 (/MTd)</strong>：對應 LIBCMTD.lib</li>
<li><strong>多執行緒 (/MT)</strong>：對應 LIBCMT.lib</li>
<li><strong>多執行緒偵錯 DLL (/MDd)</strong>：對應 MSVCRTD.lib</li>
<li><strong>多執行緒 DLL (/MD)</strong>：對應 MSVCRT.lib</li>
</ul>
<p>如果你沒有做任何設定就開始建置程式的話，MSVC 的預設選項則會使用動態連結的版本。</p>
<div id="attachment_1318" class="wp-caption aligncenter" style="width: 733px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2009/03/c-runtime-library.jpg" alt="c-runtime-library" title="c-runtime-library" width="723" height="249" class="size-full wp-image-1318" /><p class="wp-caption-text">C Runtime Library</p></div>
<p>請注意，以上只是單純 C 語言的程式庫而沒有包含 C++ 語言在內。如果你的程式系統中，有包含 C++ 語言的程式碼的話，那又是另外一回事了。但是在專案屬性的頁面中，為什麼找不到相關的設定選項呢？<strong>因為 MSVC 悄悄地幫程式設計者代勞處理掉了。</strong>只要在程式碼中使用 #include 語法納入任何一個 C++ 的標頭檔，例如 iostream 或 fstream，MSVC 就會在連結器的運作階段中，自動幫我們連結 C++ 的執行階段程式庫。而 C++ 的執行階段程式庫，同樣可分為四個版本：</p>
<blockquote><p>
<strong>靜態連結</strong>：</p>
<ul>
<li>LIBCPMTD.lib（除錯版本）</li>
<li>LIBCPMT.lib</li>
</ul>
<p><strong>動態連結</strong>：</p>
<ul>
<li>MSVCPRTD.lib（除錯版本）：執行檔相依於 MSVCP90D.dll</li>
<li>MSVCPRT.lib：執行檔相依於 MSVCP90.dll</li>
</ul>
</blockquote>
<p>至於程式執行檔使用的是靜態連結或者動態連結的版本，就仰賴於 C 語言的版本設定選項了。舉個例子來說，如果你撰寫了一個 Debug 組態的 C++ 程式，並且保留專案原先預設的建置選項（動態連結），那麼最終建置出來的程式執行檔將會相依於 MSVCR90D.dll 以及 MSVCP90D.dll 兩個 DLL 檔案。如果將相同的程式以 Release 組態建置完成，則會相依於 MSVCR90.dll 以及 MSVCP90.dll 二者。</p>
<div id="attachment_1317" class="wp-caption aligncenter" style="width: 733px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2009/03/standard-cpp-library.jpg" alt="standard-cpp-library" title="standard-cpp-library" width="723" height="173" class="size-full wp-image-1317" /><p class="wp-caption-text">Standard C++ Library</p></div>
<p>剛學習程式設計的入門者，經常會在滿心歡喜地完成一件程式作品並且傳給其他人使用時，卻發現不能在別人的電腦上啟動程式，其實就是陷入了使用者電腦缺少 DLL 檔案而無法執行程式的窘境。有三種方法可以解決這個令人困擾的問題：</p>
<ol>
<li>使用者的電腦，必須先安裝「Visual C++ 可轉發套件」（<a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=A5C84275-3B97-4AB7-A40D-3802B2AF5FC2&#038;displaylang=zh-tw">MSVC 2008</a> 或 <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=200B2FD9-AE1A-4A14-984D-389C36F85647&#038;displaylang=zh-tw">MSVC 2005</a> ）。</li>
<li>將所需的 DLL 檔案，例如 MSVCR90D.dll 與 MSVCP90D.dll，直接附在程式的下載包當中。</li>
<li>以靜態連結方式建置程式執行檔。</li>
</ol>
<p>當你無法確定自己的程式或別人的程式，是否相依於某些特定的 DLL 檔案時，有一個非常好用的免費工具程式 <a href="http://www.dependencywalker.com/">Dependency Walker</a>，可以開啟 EXE 格式的執行檔或者 DLL 格式的動態程式庫，然後詳細地條列出它們所相依的 DLL 檔案。</p>
<p>瞭解了幾種不同的 CRT 版本選項之後，回到最前面的錯誤訊息問題，相信各位現在應該能夠很清楚地理解，原來會發生這些奇怪的錯誤狀況，<strong>是因為程式同時連結了 LIBCMTD.lib 與 MSVCRTD.lib 所以造成函式定義版本衝突</strong>。也就是說，程式連結器已經在其中一個 CRT 的版本中找到所需的函式定義，但此時卻又跳出另外一位 CRT，也給了一份相同函式的實作版本，所以連結器無法判斷應該忽略誰並且選擇誰。</p>
<p>而這個狀況的發生原因，就是你的程式與程式所連結的外部程式庫，使用了不同的 CRT 版本之故。例如，當你的程式使用了 Lua，自然必須連結至 Lua 的程式庫 lua5.1.lib，但如果 lua5.1.lib 是以靜態連結版本的 CRT 建置而成，而你的程式卻是以預設選項，動態連結 CRT 來建置程式執行檔的話，如此一來就會產生上述這些錯誤訊息了。至此，問題的答案已昭然若揭，解決方法有二種：<strong>其一是將 Lua 重新以動態連結 CRT 的方式建置出一個新的程式庫，其二則是將自己的程式專案改成以靜態連結 CRT 方式建置。</strong></p>
<p>換個角度想，當你身為一位程式庫的設計開發者，想要將自己寫的東西分享給其他人，但又不想要完全開放自己撰寫的程式源碼時，至少可以同時提供以下四種版本的程式庫，以妥善滿足使用者的各種不同需求：</p>
<ul>
<li>Debug：動態連結除錯版本</li>
<li>Release：動態連結版本</li>
<li>Debug_Static：靜態連結除錯版本</li>
<li>Release_Static：靜態連結版本</li>
</ul>
<p>然而，有時候世界並不會運作得如此理想。在某些特殊的狀況下，當我們使用他人所寫的第三方程式庫時，有時可能只拿得到其中某個特定的版本，例如 Release_Static 版本時，就很有可能會遇到程式庫衝突的錯誤情形。此時就需要視專案的實際需求而定，可以在專案屬性中指定「忽略特定程式庫」(Ignore Specific Library) 這個選項，讓程式碼連結器忽略某些程式庫，以此化解動靜程式庫或新舊程式庫之間的恩怨衝突。</p>
<p><strong>小測驗</strong>：你所撰寫的程式，必須連結某個以靜態多執行緒 (/MT) CRT 建置而成的程式庫。如果你的程式在 Debug 組態下以多執行緒偵錯 (/MTd) 選項建置，是否會產生衝突？如果你的程式在 Release 組態下以多執行緒 (/MT) 選項建置，是否會產生衝突？是的話，應該如何解決？</p>
<p><strong>延伸閱讀：</strong></p>
<ul>
<li><a href="http://msdn.microsoft.com/en-us/library/abx4dbyh.aspx">[MSDN] Visual Studio 2008: C Run-Time Libraries</a></li>
<li><a href="http://kobyk.wordpress.com/2007/07/20/dynamically-linking-with-msvcrtdll-using-visual-c-2005/">Dynamically linking with MSVCRT.DLL using Visual C++ 2005</a></li>
</ul>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=954&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/beginner/love-and-hate-between-msvc-and-crt/feed</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>物件導向設計新思維：探索Policy-Based Class Design新視界</title>
		<link>http://blog.monkeypotion.net/gameprog/beginner/exploring-the-field-of-policy-based-class-design</link>
		<comments>http://blog.monkeypotion.net/gameprog/beginner/exploring-the-field-of-policy-based-class-design#comments</comments>
		<pubDate>Tue, 22 Apr 2008 16:32:31 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[入門概念]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=112</guid>
		<description><![CDATA[Policy-Based Class Design 到底是什麼樣的東西？又能夠用來解決什麼樣的問題？本文將由一個在程式專案中常見的現實案例，帶領讀者進入 Policy-Based Class Design 的全新思維領域。 學習一項新的思維方法或實作技術，最容易的方法就是從需求面出發。所以在介紹 Policy-Based Design 之前，先來看看一個現實中經常會遇到的程式設計案例：假設在處理 Input/Output 功能的系統層，原來存在著 BinaryReader 與 BinaryWriter 兩個類別，分別用來讀取以及寫入二進位制的資料串流。 現在，在新的專案項目中，我們希望能夠利用這兩個既存的 IO 類別，但是又不希望將檔案的讀取與寫入動作，分成 BinaryReader 與 BinaryWriter 兩個不同的類別進行處理。因此，程式設計者需要設計一個新的 IO 管理者類別，將原有的讀取與寫入功能合併在一起，以達到集中操作以及檔案資源管理的功用。 而為了盡量減少對既存程式碼的修改，其中一種設計方法是使新的 IOManager 類別，同時繼承自 BinaryReader 與 BinaryWriter 類別，然後藉由虛擬函式的宣告，修改專案所需的行為程序： class IOManager : public BinaryReader, public BinaryWriter { public: virtual void Read(); // 覆寫自 BinaryReader virtual void Write(); // 覆寫自 BinaryWriter [...]]]></description>
			<content:encoded><![CDATA[<p><strong>Policy-Based Class Design</strong> 到底是什麼樣的東西？又能夠用來解決什麼樣的問題？本文將由一個在程式專案中常見的現實案例，帶領讀者進入 Policy-Based Class Design 的全新思維領域。</p>
<p>學習一項新的思維方法或實作技術，最容易的方法就是從<strong>需求面</strong>出發。所以在介紹 Policy-Based Design 之前，先來看看一個現實中經常會遇到的程式設計案例：假設在處理 Input/Output 功能的系統層，原來存在著 BinaryReader 與 BinaryWriter 兩個類別，分別用來讀取以及寫入二進位制的資料串流。</p>
<p>現在，在新的專案項目中，我們希望能夠利用這兩個既存的 IO 類別，但是又不希望將檔案的讀取與寫入動作，分成 BinaryReader 與 BinaryWriter 兩個不同的類別進行處理。因此，程式設計者需要設計一個新的 IO 管理者類別，將原有的讀取與寫入功能合併在一起，以達到集中操作以及檔案資源管理的功用。</p>
<p>而為了盡量減少對既存程式碼的修改，其中一種設計方法是使新的 IOManager 類別，同時繼承自 BinaryReader 與 BinaryWriter 類別，然後藉由虛擬函式的宣告，修改專案所需的行為程序：</p>
<pre name="code" class="cpp">
class IOManager
    :
    public BinaryReader,
    public BinaryWriter
{
public:
    virtual void Read(); // 覆寫自 BinaryReader
    virtual void Write(); // 覆寫自 BinaryWriter
}
</pre>
<p>這樣設計的方法確實能夠在不修改原有類別的前提下，達到 IO 系統介面集中的目的。程式系統的撰寫者無須再同時照料 BinaryReader 與 BinaryWriter 兩個類別，只要一個 IOManager 類別就能夠處理 IO 層面的功能操作。</p>
<p><span id="more-112"></span></p>
<p>而除了前例所使用的<strong>多重繼承 (Multiple Inheritance)</strong> 以外，另外一種設計方法則是使用<strong>物件複合 (Object Composition)</strong> 的方式，將 BinaryReader 與 BinaryWriter 類別的物件當做成員變數包含在 IOManager 類別中：</p>
<pre name="code" class="cpp">
class IOManager
{
public:
    void Read()
    {
        pkReader->Read();
    }

    void Write()
    {
        pkWriter->Write();
    }

private:
    BinaryReader* m_pkReader;
    BinaryWriter* m_pkWriter;
}
</pre>
<p>為了將 BinaryReader 與 BinaryWriter 類別所提供的功能當作<strong>黑盒子</strong>組件使用，不論是多重繼承或者物件複合，都是很常見的合理作法。然而，如果在專案開發過程中提出了新的設計需求，需要讀取其他格式的檔案，例如 XML 檔案時，應該要如何整合至 IOManager 類別中？</p>
<p>在程式系統的設計中，大部分的情況下我們會希望在管理者類別中，能夠盡量保持一致的使用介面，例如 IOManager 類別中的 Read() 與 Write() 函式，對於 IO 系統的使用者來說，並不需要去瞭解目前讀取的是 Binary 格式或 XML 格式的資料，只需要直接使用 Read() 與 Write() 函式就能夠達到讀取與寫入檔案的目標。</p>
<p>為了完成這項新的設計需求，程式設計者需要依照前述的作法實作出 XmlReader 與 XmlWriter 類別之後，再以多重繼承或物件複合的方法創建出新的 XmlIOManager 類別：</p>
<pre name="code" class="cpp">
class XmlIOManager
    :
    public XmlReader,
    public XmlWriter
{
public:
    virtual void Read(); // 覆寫自 XmlReader
    virtual void Write(); // 覆寫自 XmlWriter
}
</pre>
<p>如果需要讀取或寫入 Binary 格式的檔案就使用原來的 IOManager；而如果需要讀取或寫入 XML 格式的檔案，就利用新的 XmlIOManager 類別。OK，搞定收工！</p>
<p>等等，如果假設萬一有可能（就是真的會發生）專案需要<strong>以 XML 格式讀取檔案，同時使用 Binary 格式寫入檔案</strong>時，應當如何是好？同樣地，我們可以使用物件複合的方式，創建一個新的（名字很長的） XmlInputBinaryOutputManager 類別：</p>
<pre name="code" class="cpp">
class XmlInputBinaryOutputManager
{
public:
    void Read()
    {
        pkReader->Read();
    }

    void Write()
    {
        pkWriter->Write();
    }

private:
    XmlReader* m_pkReader;
    BinaryWriter* m_pkWriter;
}
</pre>
<p>問題是解決了，但是按照這樣的程式設計與實作方法，很快地就會出現 BinaryInputBinaryOutputManager、XmlInputXmlOutputManager、XmlInputBinaryOutputManager、BinaryInputXmlOutputManager 四個高階功能介面相同（名字同樣很長），但實作細節不同的 IOManager 類別家族。</p>
<p>另外一個比較理想的解決方案，應該是新建一個抽象的 BaseReader 類別以及一個抽象的 BaseWriter 類別，使 BinaryReader 與 XmlReader 衍生自 BaseReader 類別，BinaryWriter 與 XmlWriter 衍生自 BaseWriter 類別，然後在建構 IOManager 類別時，傳入所需的實作版本：</p>
<pre name="code" class="cpp">
class IOManager
{
public:
    IOManager(BaseReader* pkReader, BaseWriter* pkWriter)
    {
        m_pkReader = pkReader;
        m_pkWriter = pkWriter;
    }

    void Read()
    {
        pkReader->Read();
    }

    void Write()
    {
        pkWriter->Write();
    }

private:
    BaseReader* m_pkReader;
    BaseWriter* m_pkWriter;
}
</pre>
<p>不是很直覺的使用方式，但還算是個能夠接受的解決方案。只是，做為程式設計者，是否只能屈服於這樣的設計方法？是否還有其他的可能性存在？</p>
<p>其實，在上述的程式設計案例中，已經點出了一個在專案開發流程中常見的情形：<strong>存在數種不同版本的設計實作方案，每次只從中選擇其一的方案，但是又需要保持切換與擴充的彈性。</strong>設計，本身就是一種<strong>不斷變動</strong>的行為。設計者經常會遇到兩難的情境：一方面既想保持系統的修改彈性，另一方面又要能夠達到理想的執行效能與成果。</p>
<p>在<a href="http://tlsj.tenlong.com.tw/WebModule/BookSearch/bookSearchViewAction.do?isbn=9864212672&#038;sid=15596"><strong>《C++ 設計新思維》</strong></a>(Modern C++ Design) 中的第一章，作者 <a href="http://www.erdani.org/"><strong>Andrei Alexandrescu</strong></a> 為人們展示了一種全新的 Template 程式設計技術，稱之為 <a href="http://en.wikipedia.org/wiki/Policy-based_design"><strong>Policy-Based Class Design</strong></a>。Policy-Based Class Design 同時使用了 Template 以及 Multiple Inheritance 兩項技術，結合兩者的優點，成為一種既新穎又實用的設計方法。<strong>Policy，字面上的意思是：政策、方針，或策略。</strong>所以 Policy-Based Class Design 也就是將原來錯綜複雜的程式系統，拆解成為數個獨立運作的「策略」，然後藉由 Policy 之間的分工合作與交替組合能力，使得以這個設計架構完成的程式系統能夠同時得到擴充性、彈性與易用性等多項優點。</p>
<p>如果使用 Policy-Based Design 方法，重新改寫上述的類別設計範例，可以得到以下的初步結果：</p>
<pre name="code" class="cpp">
template
<
	class ReadingPolicy,
	class WritingPolicy
>
class ResourceIOManager
	:
	public ReadingPolicy,
	public WritingPolicy
{
public:
	void Read()
	{
		ReadingPolicy::Read();
	}

	void Write()
	{
		WritingPolicy::Write();
	}
};
</pre>
<p>請特別注意在這段程式碼中，<strong>同時運用 Template 與 Multiple Inheritance 的作法</strong>。在 Template 中所使用的 ReadingPolicy 與 WritingPolicy 類別，是程式設計者能夠在編譯時期自行決定的一種類別參數，再加上 ResourceIOManager 類別繼承自這兩個當作 Template 建構參數傳入的 ReadingPolicy 與 WritingPolicy 類別，所以在 Read() 與 Write() 函式中，就可以直接呼叫並且委託這兩個 Policy 類別進行讀取與寫入的程序。</p>
<p>而上述兩項需要傳入做為 Template 建構參數的 ReadingPolicy 與 WritingPolicy 類別是什麼？其實也就是之前已經存在的 BinaryReader、BinaryWriter、XmlReader 以及 XmlWriter 類別：</p>
<pre name="code" class="cpp">
class BinaryReader
{
public:
	void Read()
	{
	    // Reading in binary mode
	}
};

class BinaryWriter
{
public:
	void Write()
	{
	    // Writing in binary mode
	}
};
</pre>
<p>在 Policy-Base Class Design 中，ResourceIOManager 類別所扮演的角色為 <strong>Host Class</strong>，而 BinaryReader 與 BinaryWriter 這些不同版本的 Reader 與 Writer 實作類別則稱為 <strong>Policy Class</strong>。只需要切換不同 Policy Class，程式設計者就能夠輕鬆地組合出完全合乎設計需求的類別功能：</p>
<pre name="code" class="cpp">
void main()
{
    // Binary read, binary write
    ResourceIOManager< BinaryReader, BinaryWriter > kBinaryIOMgr;
    kBinaryIOMgr.Read();
    kBinaryIOMgr.Write();

    // Xml read, Xml write
    ResourceIOManager< XmlReader, XmlWriter > kXmlIOMgr;
    kXmlIOMgr.Read();
    kXmlIOMgr.Write();

    // Xml read, Binary write
    ResourceIOManager< XmlReader, BinaryWriter > kXmlBinaryIOMgr;
    kXmlBinaryIOMgr.Read();
    kXmlBinaryIOMgr.Write();
}
</pre>
<p>如上述的使用範例所示，只需要一個 ResourceIOManager 類別，以及四個 Reader 與 Writer 類別，就能夠自由地組合出四種不同實作版本的 IO 程序處理器！未來如果需要加入處理純文字檔案用的 TextReader 與 TextWriter，只要利用相同的方法，同樣能夠輕易地達到擴充實作版本的目標。</p>
<p>然而，上述的實作方法，仍然不是 Policy-Based Design 的正確形式，還沒有將 Policy-Based Design 的威力發揮到極致。在前例中，建構 ResourceIOManager 類別所使用的 BinaryReader 等四個 Policy Class，都只是<strong>一般的類別</strong>。Policy-Based Design 真正厲害之處在於，這些用來建構 Template 的類別參數，<strong>本身亦可以是一個 Tempate 化的類別！</strong></p>
<p>也就是說，我們可以將 BinaryReader 與 BinaryWriter 修改成為 Templated Class：</p>
<pre name="code" class="cpp">
template< class T >
class BinaryReader
{
public:
	void Read()
	{
	    // Reading in binary mode
	}
};

template< class T >
class BinaryWriter
{
public:
	void Write()
	{
	    // Writing in binary mode
	}
};
</pre>
<p>接著，將 ResourceIOManager 修改為接受 Templated Classes 做為建構參數的類別：</p>
<pre name="code" class="cpp">
template
<
	class Subject,
	template< class > class ReadingPolicy,
	template< class > class WritingPolicy
>
class ResourceIOManager
	:
	public ReadingPolicy< Subject >,
	public WritingPolicy< Subject >
{
public:
	void Read()
	{
		ReadingPolicy< Subject >::Read();
	}

	void Write()
	{
		WritingPolicy< Subject >::Write();
	}
};
</pre>
<p>請注意上述程式碼中 <strong>template< class > class ReadingPolicy</strong> 以及 <strong>template< class > class WritingPolicy</strong> 的用法。這樣的程式碼寫法，宣告了傳入 ResourceIOManager 類別的 ReadingPolicy 與 WritingPolicy 類別參數，本身也是一個 Template 化的類別，需要再傳入另一個類別做為兩者的建構參數。而在此，就是由其中的 Subject 類別，扮演這個 <strong>Template Template Parameter（樣版的樣版參數）</strong>的角色。</p>
<p>這個 Subject 類別，做為建構實體物件用的參數傳入 ResourceIOManager 類別，<strong>同時</strong>也做為 ReadingPolicy 以及 WritingPolicy 類別的建構參數。有了 Subject 類別參數之後，就能夠使 Policy Class 更具擴充性與彈性，能夠處理其他類型的實體檔案，例如 Animation、Texture 或 Script 資源等等：</p>
<pre name="code" class="cpp">
void main()
{
	// 管理 Animation 檔案資源
	ResourceIOManager< AnimationResource, BinaryReader, BinaryWriter > kMgr1;
	kMgr1.Read();
	kMgr1.Write();

	// 管理 Texture 檔案資源
	ResourceIOManager< TextureResource, XMLReader, XMLWriter > kMgr2;
	kMgr2.Read();
	kMgr2.Write();

	// 管理 Script 檔案資源
	ResourceIOManager< ScriptResource, TextReader, TextWriter > kMgr3;
	kMgr3.Read();
	kMgr3.Write();
}
</pre>
<p>以上的實作方式，才是 Policy-Based Class Design 真正的設計形式以及使用方法。至此，原先存在的 BinaryReader、BinaryWriter、XMLReader 與 XMLWriter 類別，不再需要綁死於單一的 IOManager 管理者類別之中，而能夠自由組合替換，甚至對不同類型的檔案資源做出特定的處理程序，也完成了兼具修改彈性與執行效能的系統架構設計目標。</p>
<p>更進一步，如果需要在 ResourceIOManager 類別中再加入新的 Policy Class，例如用來處理錯誤狀態的 ErrorHandlingPolicy，同樣也非常容易：</p>
<pre name="code" class="cpp">
template
<
	class Subject,
	template< class > class ReadingPolicy,
	template< class > class WritingPolicy,
	template< class > class ErrorHandlingPolicy
>
class ResourceIOManager
	:
	public ReadingPolicy< Subject >,
	public WritingPolicy< Subject >,
	public ErrorHandlingPolicy< Subject >
{
public:
	void Read()
	{
		if (!ReadingPolicy< Subject >::Read())
		{
			ErrorHandlingPolicy< Subject >::HandleError();
		}
	}

	void Write()
	{
		if (!WritingPolicy< Subject >::Write())
		{
			ErrorHandlingPolicy< Subject >::HandleError();
		}
	}
};
</pre>
<p>在 Policy-Based Class Design 中，可以說<strong>整個 Host Class 幾乎就是 Policy Class 們的集合</strong>。Host Class 本身不經手功能層面的實作細節，而僅訂立出功能運作的骨架流程，真正的運作細節則交由各個不同的 Policy Class 分工進行處理。在 <a href="http://en.wikipedia.org/wiki/Main_Page"><strong>Wikipedia</strong></a> 中對於 <a href="http://en.wikipedia.org/wiki/Policy-based_design">Policy-Based Design</a> 的介紹，有一段很貼切的敘述：</p>
<blockquote><p>
Wherever we would need to make a possible limiting design decision, we should postpone that decision and delegate it to an appropriately named policy.
</p></blockquote>
<p>在程式系統的設計過程中，有時由於時間或資源的因素，所做出的設計決定往往考慮不周全而十分受侷限。因此做為一位程式設計者，應該盡可能延後做出決定的時機，不要過早將設計決定綁死在前期的程式系統設計架構中，才能夠因應未來不斷發生的需求變動以及設計變動，也才能夠真正地達到程式系統組件的<strong>可復用性 (Reusability)</strong>。也正如<strong>《C++ 設計新思維》</strong>中所提到的：</p>
<blockquote><p>
「設計」就是一種「選擇」。大多數時候我們的困難並不在於找不到解決方案，而是有太多解決方案。
</p></blockquote>
<p>而 Policy-Based Class Design 正是為此目的而生。藉由既有技術的巧妙結合，Policy-Based Class Design 為程式設計者帶來前所未有的全新思維模式以及實作技術，使程式設計者能夠在許多不同的解決方案中，自由選擇並且輕鬆組合出最合適的實作版本。</p>
<p>在下篇文章裡，我將會更深入詳細地闡述 Policy-Based Class Design 背後的思維模式，以及更多在遊戲程式系統中的相關應用。</p>
<p>程式碼範例下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=6"><strong>PolicyBasedDesign_SampleCode.zip</strong></a> <small>(下載次數： 465 )</small></p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=112&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/beginner/exploring-the-field-of-policy-based-class-design/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>容器們，奮起吧！—實做 STL Containers 的包裝介面</title>
		<link>http://blog.monkeypotion.net/gameprog/beginner/containers-wrapper-class</link>
		<comments>http://blog.monkeypotion.net/gameprog/beginner/containers-wrapper-class#comments</comments>
		<pubDate>Sun, 24 Feb 2008 16:07:42 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[入門概念]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/gameprog/beginner/containers-wrapper-class</guid>
		<description><![CDATA[做為一個程式設計者，在 C++ 的程式設計領域中，我們無可避免的會經常使用到各種 STL 的容器 (Container)，包括 vector、map、set、list 等等。熟習這些容器的功能特徵與操作行為並且和它們成為好朋友，是每個 C++ 程式設計者必備的基礎知識。在大多數的情況下，我們都能心滿意足地使用著這些前人的心血結晶，不會產生任何的問題。 然而，如果某天，在現存運行的程式專案中，需要將 STL Container 替換成其他實做方案時怎麼辦？本文中將以 STL Container 中，最常用來儲存與索引資料用的 std::map 容器為例，探討容器包裝介面的必要性、實做目標以及各項實做細節，並且提供完整的程式碼範例下載。 在遊戲的程式設計中，map（或 hash_table）可以說是最常使用的資料結構之一，通常會用來存放需要進行快速檢索的資料；例如在 MMO 遊戲中以 Unique ID 做為鍵值，索引至存放玩家資料的物件指標。而對於 map 容器的操作行為，一般來說至少會用到創建、插入、刪除、尋找與巡訪結構的動作，如範例所示： // 創建map變數 std::map< int, std::string > kMap; std::map< int, std::string >::iterator kIter; // 插入value kMap.insert(std::make_pair(11, "a test")); kMap.insert(std::make_pair(12, "the second test")); // 巡訪map for (kIter = kMap.begin(); [...]]]></description>
			<content:encoded><![CDATA[<p>做為一個程式設計者，在 C++ 的程式設計領域中，我們無可避免的會經常使用到各種 <strong>STL 的容器 (Container)</strong>，包括 vector、map、set、list 等等。熟習這些容器的功能特徵與操作行為並且和它們成為好朋友，是每個 C++ 程式設計者必備的基礎知識。在大多數的情況下，我們都能心滿意足地使用著這些前人的心血結晶，不會產生任何的問題。</p>
<p>然而，如果某天，在現存運行的程式專案中，<strong>需要將 STL Container 替換成其他實做方案時怎麼辦？</strong>本文中將以 STL Container 中，最常用來儲存與索引資料用的 <strong>std::map</strong> 容器為例，探討容器包裝介面的必要性、實做目標以及各項實做細節，並且提供<strong>完整的程式碼範例下載</strong>。</p>
<p><span id="more-57"></span></p>
<p>在遊戲的程式設計中，map（或 hash_table）可以說是最常使用的資料結構之一，通常會用來存放需要進行快速檢索的資料；例如在 MMO 遊戲中以 Unique ID 做為鍵值，索引至存放玩家資料的物件指標。而對於 map 容器的操作行為，一般來說至少會用到創建、插入、刪除、尋找與巡訪結構的動作，如範例所示：</p>
<pre name="code" class="cpp">
// 創建map變數
std::map< int, std::string > kMap;
std::map< int, std::string >::iterator kIter;

// 插入value
kMap.insert(std::make_pair(11, "a test"));
kMap.insert(std::make_pair(12, "the second test"));

// 巡訪map
for (kIter = kMap.begin(); kIter != kMap.end(); ++ kIter) {
    // 對每個元素進行所需的動作
}

// 取得map中的value
std::string kValue;
int iKey = 11;

kIter = kMap.find(iKey);
if (kIter != kMap.end()) {
    kValue = kIter->second;
}
</pre>
<p>以上看起來是充滿祥和之氣，完全無害的程式碼。但是日子並不會就這麼順心如意地過下去。有什麼理由與可能性，會需要<strong>更換已經是 C++ 標準函式庫又超級好用的 STL Container？</strong></p>
<blockquote>
<ul>
<li>STL Container 是屬於<strong>泛用型的設計</strong>，實做上存在著某些效能上的妥協，以換取絕大的彈性與泛用能力。如果是對於系統效能要求極高的專案，應該考慮自製 <strong>Domain-Specific 的容器</strong>以提升系統的整體表現。</li>
<li>繪圖引擎或遊戲引擎中，有一組引擎開發者自己實做的 Container，同時使用兩種 Container 可能會面臨資料轉換的問題，<strong>既不便於操作使用也不能保持一致性</strong>，所以必須更換成引擎實做的 Container 版本。</li>
<li>專案的程式 Crash 在一堆莫名其妙看不懂的 STL 程式碼當中，於是有人疾呼：<strong>「STL 這該死的 Bug！」</strong>而這個人又非常恰巧地剛好是你的老闆。所以，你需要負責將 STL Container 整個換掉。</li>
</ul>
</blockquote>
<p>於是，苦命悲情的程式設計者，需要開始<strong>發揮超乎常人的堅持、毅力與勇氣</strong>，嘗過無數編譯器的冷面駁回以及除錯器的慘烈哀嚎之後，才能把原先散落在程式庫各處的 STL Container 改寫成新的語法與操作方式。</p>
<p>（讓我們為這些勇者默禱三分鐘）</p>
<p>為了避免上述的慘案發生在自己身上，如果能在一開始撰寫程式時，就使用經過包裝的 STL Container，保持 Container 介面的一致性，就能夠使未來的改寫與重構任務不再是個令人頭大想撞牆的麻煩事。STL Container 經過了介面類別的包裝之後，容器就可以改頭換面、重新做人，不論以後內部的實做細節如何變動，都不會影響其他外部系統的使用。</p>
<p>實做包裝容器用的 <strong>Wrapper Class</strong>，有三項首要的目標：</p>
<ul>
<li><strong>一致化的介面</strong></li>
<li><strong>簡單易用的操作方法</strong></li>
<li><strong>自動管理 Heap 物件的毀滅</strong></li>
</ul>
<p>前兩項很容易理解，本來就是為了預防慘案發生所需達到的目標，但第三項目標是什麼緣由？</p>
<p>在遊戲專案中以 STL Container 管理各種遊戲物件的時候，常常會需要在存有容器的物件解構時，將容器內部所存放的物件指標也一併解構。但是，程式設計者經常是健忘或者懶惰的，常常只記得要初始化 Container，然後開心地使用便利的操作行為，<strong>卻很容易忘記在清除 Container 內容時將其中的物件指標一一抓取出來解構，於是就造成了程式的 Memory Leak。</strong>上述這種情境，經常是許多新手常犯的無心之過。最好的方法就是<strong>保持自己撰寫程式碼的紀律，在寫出建構與初始化的程式碼區塊之後，立即撰寫相對應的解構程式碼</strong>，才能夠避免發生這樣的糟糕問題。</p>
<p>更進一步，如果能夠將容器中存放的物件指標<strong>解構行為自動化</strong>，將能夠免除許多搭配容器操作 Heap 物件上的負擔與潛在問題。以玩家角色的 Class 為例，如果其中內含著一個 map 容器，用來存放玩家身上的各種裝備物件：</p>
<pre name="code" class="cpp">
class CPlayer
{
private:
    std::map< int, CEquipment* > m_kEquipment;

public:
    CPlayer()
    {
        m_kEquipment.insert(std::make_pair(1, new CEquipment("Head")));
        m_kEquipment.insert(std::make_pair(2, new CEquipment("Chest")));
        m_kEquipment.insert(std::make_pair(3, new CEquipment("Hand")));
    }

    ~CPlayer()
    {
        // 記憶體漏掉了！
        m_kEquipment.clear();
    }
};
</pre>
<p>在以上的程式碼範例中，糟糕的記憶體漏洞問題就這樣輕易地發生了。而 Memory Leak 往往並不是很容易能察覺的一種臭蟲，常需要藉助外部的 Profiling 工具才能找出在龐大程式碼中有所疏漏的部分。更糟的是，在沒有早期發現、早期治療的情況下，在專案開發的越後期，程式庫越加複雜龐大， Memory Leak 的問題就更加不容易發現了。</p>
<p>其實，做為一個程式設計者，我們的心裡只有幾個小小的微薄心願：</p>
<blockquote>
<ul>
<li>程式<strong>不要有 Memory Leak</strong>，只管創建物件不管毀滅物件！</li>
<li>程式<strong>不要有 Memory Overwrite</strong>，記憶體怎麼用都沒問題！</li>
<li>程式<strong>不要有 Runtime Error</strong>，例外狀況通通抓起來做標本！</li>
</ul>
</blockquote>
<p>（我想，可能<strong>中大樂透的機會</strong>或許還大得許多。 XD）</p>
<p>所以如果能在 Container 毀滅時，就自動幫我們把其中存放的所有物件解構掉，就真的是方便多了。再也不用不好意思地搔搔頭說：「拍寫～不小心忘記了。」因此，實做 Container 的目標之一，就是希望能夠自動管理存放在容器中的物件。讓我們開始吧！</p>
<p>首先利用 <strong>Preprocessor Macro</strong> 定義的方式，將資料型態定義在獨立的 Header File 中：</p>
<pre name="code" class="cpp">
// @file DataType.h

#include < vector >
#include < map >
#include < list >
#include < deque >

#define GaocVectorContainer std::vector
#define GaocMapContainer std::map
#define GaocListContainer std::list
#define GaocDequeContainer std::deque
</pre>
<p>將 STL Container 的型別以 Preprocessor Macro 重新定義，有利於容器基本型別的變動與修改。然後就可以在 <strong>Templated 化的 Wrapper Class</strong> 中把 map 的型別以及其他相關的型別定義，以 <strong>typedef</strong> 的方法抽取出來，轉變成簡短易於使用的資料型別。在此我採用 <strong>C#/.NET</strong> 中的容器名稱，將 Wrapper Class 命名為 Dictionary：</p>
<pre name="code" class="cpp">
// @file Dictionary.h

template < class TKey, class TValue >
class Dictionary
{
private:

    typedef GaocMapContainer< TKey, TValue >   MapType;
    typedef typename GaocMapContainer< TKey, TValue >::iterator   MapIter;
    typedef typename GaocMapContainer< TKey, TValue >::value_type   MapValueType;

    MapType    m_kMap;
    MapIter    m_kIter;
};
</pre>
<p>有了基本的容器型別與其他相關型別定義後，就可以開始定義 Wrapper Class 的介面。在容器的 <strong>Public Methods</strong> 中，應該要包含基本的操作行為包括插入、取得、巡訪與刪除函式。這些 Method 大致上會長得像這樣：</p>
<pre name="code" class="cpp">
// @file Dictionary.h

template < class TKey, class TValue >
class Dictionary
{
public:
    // 插入元素
    void SetAt(TKey _kKey, TValue _kValue);
    // 取得元素
    bool GetAt(TKey _kKey, TValue&#038; _rkValue);
    // 巡訪容器
    bool GetFirst(TValue&#038; _rkValue);
    bool GetNext(TValue&#038; _rkValue);
    // 刪除元素
    void RemoveAt(TKey _kKey);
    void RemoveAll();
};
</pre>
<p>注意在所有取值的 Method 中，<strong>並不直接以 return 傳回所要求的元素</strong>。主要有兩個原因，其一是檢查 Boolean 回傳值判斷取值是否成功比較直觀易懂；其次則是如果在容器中找不到要求的元素時，並不容易指定要傳回什麼樣的預設值或是空值，所以使用 Pass By Reference 取得所要求的元素，得以避免空值設定的問題。</p>
<p>接下來看看實做部分的程式碼，插入與取得容器內存元素的操作很簡單：</p>
<pre name="code" class="cpp">
// @file Dictionary.inl

template< class TKey, class TValue >
inline void Dictionary< TKey, TValue >::SetAt(TKey _kKey, TValue _kValue)
{
    m_kMap.insert(MapValueType(_kKey, _kValue));
}

template< class TKey, class TValue >
inline bool Dictionary< TKey, TValue >::GetAt(TKey _kKey, TValue&#038; _rkValue)
{
    m_kIter = m_kMap.find(_kKey);
    if (m_kIter == m_kMap.end()) {
        return FALSE;
    }

    _rkValue = m_kIter->second;
    return TRUE;
}
</pre>
<p>接著，藉由 Wrapper Class 內存的 <strong>Iterator Object 狀態與操作</strong>，能夠讓容器的使用者巡訪 map 中的所有元素：</p>
<pre name="code" class="cpp">
template< class TKey, class TValue >
inline bool Dictionary< TKey, TValue >::GetFirst(TValue&#038; _rkValue)
{
    if (m_kMap.empty()) {
        return FALSE;
    }

    m_kIter = m_kMap.begin();
    _rkValue = m_kIter->second;
    ++ m_kIter;
    return TRUE;
}

template< class TKey, class TValue >
inline bool Dictionary< TKey, TValue >::GetNext(TValue&#038; _rkValue)
{
    if (m_kIter == m_kMap.end()) {
        return FALSE;
    }

    _rkValue = m_kIter->second;
    ++ m_kIter;
    return TRUE;
}
</pre>
<p>有了插入、取值與巡訪的操作方法之後，最後要處理的就是最為關鍵的刪除行為。以 RemoveAll() 函式為例，如果 map 內存的是一般數值或非 Heap 物件，就可以直接呼叫 map::clear() 函式刪除 map 內存的資料。然而，如果內存的元素是屬於 Heap 物件，就需要使用 C++ 的 <strong>operator delete</strong> 將 map 中的物件一一巡訪並且解構：</p>
<pre name="code" class="cpp">
template < class TKey, class TValue >
inline void Dictionary< TKey, TValue >::RemoveAll()
{
    // 如果是內存 Heap 物件
    if (m_bUseHeapObject) {
        // 巡訪整個map
        for (m_kIter = m_kMap.begin(); m_kIter != m_kMap.end(); m_kIter ++) {
            // 使用 C++ 的 operator delete
            delete m_kIter->second;
        }
    }

    // 不是 Heap 物件，只需要直接清除map
    m_kMap.clear();
}
</pre>
<p>嗯，看起來沒有問題，但是這樣的實做方式在以下的使用實例中<strong>無法成功通過編譯器的關卡</strong>：</p>
<pre name="code" class="cpp">
// 宣告一個 Dictionary 容器，內存 Heap 物件，沒問題。
Dictionary< int, CPlayer* > m_kPlayerPool;

// 宣告一個 Dictionary 容器，內存一般型別數值。嗶嗶！編譯錯誤！
Dictionary< int, float > m_kValuePool;
</pre>
<p>這是因為 <strong>delete operator 不能作用在非 Heap 物件的型別上！</strong>不論宣告 <strong>Dictionary</strong> 時，使用的是 Heap 物件或非 Heap 物件，在 RemoveAll() 函式中的每行程式碼，同樣都會經過編譯器的查驗，所以在 <strong>TValue 型別不是 Pointer</strong> 的情況下就會產生編譯錯誤。</p>
<p>因此，為了應付這個問題，在此需要將存放一般非 Heap 物件型別與存放 Heap 物件型別<strong>分開成不同的 Class 實做</strong>。於是重新命名原來的 Dictionary 類別，產生出 <strong>ValueDictionary</strong> 與 <strong>AutoPtrDictionary</strong> 兩個長相雷同但是用途不同的容器 Wrapper Class。多了新的 Class 定義之後，就能夠順利的處理 RemoveAt() 與 RemoveAll() 函式了：</p>
<pre name="code" class="cpp">
//@file ValueDictionary.h
template < class TKey, class TValue >
inline void ValueDictionary< TKey, TValue >::RemoveAll()
{
    m_kMap.clear();
}

//@file AutoPtrDictionary.h
template < class TKey, class TValue >
inline void AutoPtrDictionary< TKey, TValue >::RemoveAll()
{
    for (m_kIter = m_kMap.begin(); m_kIter != m_kMap.end(); m_kIter ++) {
        delete m_kIter->second;
    }
    m_kMap.clear();
}
</pre>
<p>最後在這兩個 Wrapper Class 的<strong>解構式</strong>中呼叫 RemoveAll()，就能夠達成自動清除 map 內容物並且解構 Heap 物件的目標：</p>
<pre name="code" class="cpp">
//@file ValueDictionary.h
template< class TKey, class TValue >
ValueDictionary< TKey, TValue >::~AutoPtrDictionary()
{
    RemoveAll();
}

//@file AutoPtrDictionary.h
template< class TKey, class TValue >
AutoPtrDictionary< TKey, TValue >::~AutoPtrDictionary()
{
    RemoveAll();
}
</pre>
</blockquote>
<p>以下是利用這兩個新建的 Class 的使用範例：</p>
<pre name="code" class="cpp">
// 創建 Container 物件
ValueDictionary< int, float >* pkValueDict = new ValueDictionary< int, float >();
AutoPtrDictionary< int, TestObjectType* >* pkPointerDict = new AutoPtrDictionary< int, TestObjectType* >(true);

// 存放數值資料
pkValueDict->SetAt(1, 1.0f);
pkValueDict->SetAt(2, 2.0f);
pkValueDict->SetAt(3, 3.0f);

// 存放 Heap 物件資料
pkPointerDict->SetAt(123, new TestObjectType());

// 取得對應的 Heap 物件
TestObjectType* pkObject1 = NULL;
if (pkPointerDict->GetAt(123, pkObject1))
{
    std::cout << "TestObjectType object is existing." << std::endl;
}

// 巡訪 Container 物件
float fValue1 = 0.0f;
bool bResult = pkValueDict->GetFirst(fValue1);
while (bResult)
{
    std::cout << fValue1 << std::endl;
    bResult = pkValueDict->GetNext(fValue1);
}

// 毀滅 Container 物件
delete pkPointerDict;
delete pkValueDict;
</pre>
<p>至此，完成了兩個不同的 Class 用以包裝並且取代原有的 <strong>std::map</strong> 容器。只是在 <strong>ValueDictionary</strong> 與 <strong>AutoPtrDictionary</strong> 這兩個 Class 中，絕大部分的程式碼都是相同而且重複的，除了 RemoveAt() 與 RemoveAll() 函式的實做細節有所分別之外，其餘的部分完全都是維持相同的介面與實做，兩者有著如出一轍的程式碼。有沒有什麼方法能夠處理這種<strong>令人有點不舒服</strong>的情形呢？</p>
<p>在<strong>《Refactoring》</strong>書中提到：<strong>「重複的程式碼是一切罪惡的淵藪。」</strong>所以，為了減輕一點點身為程式設計者的<strong>罪惡感</strong>，降低因為胸中鬱悶怒火而造成的<strong>地球暖化</strong>情形，並且能夠達到 Wrapper Class 的有效使用，在次篇文章中，將針對這個議題提出比較進階深入的改善方法。</p>
<p>完整程式碼下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=1"><strong>ContainerWrapperClass_SampleCode.zip</strong></a> <small>(下載次數： 549 )</small></p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=57&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/beginner/containers-wrapper-class/feed</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>商業或自製，引擎大不同！</title>
		<link>http://blog.monkeypotion.net/gameprog/beginner/difference-of-commercial-and-home-made-engines</link>
		<comments>http://blog.monkeypotion.net/gameprog/beginner/difference-of-commercial-and-home-made-engines#comments</comments>
		<pubDate>Thu, 24 Jan 2008 16:44:05 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[入門概念]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/gameprog/beginner/difference-of-commercial-and-home-made-engines</guid>
		<description><![CDATA[在前篇文章「遊戲引擎的層級架構 」中，有位訪客的迴響提到一個很關鍵的問題： 國外的某些商業繪圖引擎並沒有提供 Game 相關的編輯器，為何還是有很多公司購買商業繪圖引擎？ 一言以蔽之，就是 Robust 與否的問題。 Robust：堅固的、剛健的、結實的、強壯的。 什麼樣的引擎才能稱得上是堅固結實呢？所謂的引擎技術，不僅只是在最新的顯示卡與最好的配備上展現出炫麗奪目的效果，就能夠稱得上是優秀的引擎。除了一般的運行狀況之外，更需要周全地考量各種邊界狀況 (Boundary Condition) 的情形，以及如何進行例外處理 (Exception Handling) 的程序。檔案不存在的情形如何處理？記憶體不足無法配置如何處理？檔案讀取當中發生毀損狀況怎麼辦？有沒有可能傳入空指標 (Pointer) 而造成系統當機？還有很多很多不同面向的問題。 真正夠格使用這個字的引擎技術，才能夠成為一個成功的商業引擎。這些動輒數十萬美金至上百萬美金授權費的商業引擎，為何能夠設定這樣高昂的價格？就是因為他們的工程技術，是經過嚴苛的市場所檢驗證明過的。多數製作商業引擎的公司大多有七、八年甚至十年以上的歷史了，其間已經有不計其數的作品與專案面市，如果引擎製作出來的成果，不能夠經得起玩家與消費者的考驗，早就消失在歷史的洪流中了。 在個人電腦平台上開發遊戲，最大的關鍵困難點，相容性 (Compatibility) 的問題就佔了極大的部分。很多的顯示卡，加上不同的顯示卡驅動程式設定，還有其他無數的硬體設備組合，甚至使用者安裝的其他軟體，都可能與遊戲程式發生的衝突。平時開發引擎或遊戲程式，總認為不可能會發生的情形，上市後到了玩家的電腦裡，就是有意想不到的場合，會發生這些百分之一、千分之一，甚至萬分之一機率才會出現的臭蟲與當機問題。 而這些商業引擎，已經製作出非常多的商業遊戲作品，經過數以萬計的玩家操作使用，引擎開發公司本身不斷地接受遊戲開發者與玩家兩個面向的反饋，一步步地改良與加強原有的技術，能夠稱得上是真正千錘百鍊的實做技術。因此，即使這些引擎缺少便利的編輯器或工具，仍然不減他們「很硬」的事實。就如同是一種信譽般，他就是可以拍胸脯掛保證來捍衛遊戲程式，把問題發生的可能性減至最低。這就是商業引擎能夠屹立不搖的原因。 當然，沒有足夠的編輯器與工具，對於遊戲開發來說，真的是會令人吐血不止。通常在這樣的狀況下，至少需要一個有經驗的能手，在熟悉引擎的使用之後，再專注於製作相關工具的工作上。反面來想，如果這些引擎能夠具備完善強大的編輯器與輔助工具，那麼可預想的，這些繪圖引擎很快地就會「轉職」成為遊戲引擎了。原來數十萬美金的身價，也會跟著扶搖直上，擠身百萬引擎之列。果真如此，開發者能不能負擔得起這樣的投資，就是另一個層面的問題了。 我從學生時代起就開始關注 3D 繪圖引擎的發展，找過也看過不論是國內或國外許多的自製引擎，發現一個很有趣的奇妙現象：不管是繪圖引擎還是聲稱的遊戲引擎，常常都搞得像各國間的軍備競賽一樣。其他引擎實做出的功能，自己也不能落人後，於是洋洋灑灑列出引擎所有大大小小的 Feature（特點），展示很多看起來不賴的 Screenshot（截圖），然後這樣那樣，請來點掌聲、請付錢買我，啪啦啪啦啪啦。當然其中也有看起來非常不錯的引擎，因為沒有親身使用過，真的只是看起來不錯而已；但是，心裡一樣有著大大的疑問句油然而生。 相較之下，許多著名的開放原始碼 (Open Source) 引擎，如 Ogre 與 Irrlicht 已經行之有年，也逐漸從原本的小嫩嫩成長茁壯起來了。與自製引擎不同的是，這些開放原始碼的引擎，雖然離商業引擎的程度還有段不短的距離，但是他們擁有許多的開發者與使用者，已經經歷過許多的問題、建議與改進。透過這些活躍的開發者社群與使用者社群相輔相成地投注心力，也能夠逐漸地將引擎拉拔長大。 以上所提的內容，用意不是在貶低引擎技術的發展；事實上，技術的進步與發展對於遊戲開發來說，仍然相當重要而不可忽視。另外，3D 繪圖理論 (3D Graphics) 與相關程式設計，也的確是相當令人著迷的事物。自己也是從第一本 OpenGL 紅皮書開始，就已經深深迷上而不可自拔了。技術與理論，都是非常令人入迷的事物，而能夠將這些書本上的理論，轉化成為酷炫精彩的畫面，更是許多程式設計者投身遊戲業界的動力之一。然而對於遊戲開發製作來說，以遊戲為名，切勿忘記引擎技術僅僅是眾多的環節其中之一而已。 經過三年多的商業引擎使用經驗後，以台灣遊戲界的現況來說，我並不贊成自行開發 3D 引擎。當然這是以業界發展與商業現實的角度來看的。多數用來開發大型遊戲的商業引擎，背後都是一個完整團隊的運作；具有龐大完備的架構、各式開發工具，研發成員幾乎都是專業資深的碩博士，還要有厲害的行銷團隊等等。在製作出賣座的好遊戲這個前提之下，自行開發引擎這個選項，幾乎可以說是不合乎理性與成本的做法。漂亮的畫面很多人都會做，儘管把 Shader 用得天花亂墜、胡說八道，如果沒有真正有趣的內容 (Content) 存在，展現出來的也不過是許多華麗而無意義的三角形堆砌而已。 唯有真切瞭解我們自己的核心優勢而善用之，才有機會能夠打破現狀，發展出我們自己的遊戲產業的路！]]></description>
			<content:encoded><![CDATA[<p>在前篇文章<a href="http://blog.monkeypotion.net/gameprog/beginner/layered-architecture-of-game-engine"><strong>「遊戲引擎的層級架構 」</strong></a>中，有位訪客的<a href="http://blog.monkeypotion.net/gameprog/beginner/layered-architecture-of-game-engine#comment-28">迴響</a>提到一個很關鍵的問題：</p>
<blockquote><p>
國外的某些商業繪圖引擎並沒有提供 Game 相關的編輯器，為何還是有很多公司購買商業繪圖引擎？
</p></blockquote>
<p>一言以蔽之，就是 <strong>Robust</strong> 與否的問題。</p>
<blockquote><p>
<strong>Robust：堅固的、剛健的、結實的、強壯的。</strong>
</p></blockquote>
<p>什麼樣的引擎才能稱得上是堅固結實呢？所謂的引擎技術，不僅只是在最新的顯示卡與最好的配備上展現出炫麗奪目的效果，就能夠稱得上是優秀的引擎。除了一般的運行狀況之外，更需要周全地考量各種邊界狀況 (Boundary Condition) 的情形，以及如何進行例外處理 (Exception Handling) 的程序。檔案不存在的情形如何處理？記憶體不足無法配置如何處理？檔案讀取當中發生毀損狀況怎麼辦？有沒有可能傳入空指標 (Pointer) 而造成系統當機？還有很多很多不同面向的問題。</p>
<p><span id="more-37"></span></p>
<p>真正夠格使用這個字的引擎技術，才能夠成為一個成功的商業引擎。這些動輒<strong>數十萬美金至上百萬美金</strong>授權費的商業引擎，為何能夠設定這樣高昂的價格？就是因為他們的工程技術，是經過嚴苛的<strong>市場</strong>所<strong>檢驗證明</strong>過的。多數製作商業引擎的公司大多有七、八年甚至十年以上的歷史了，其間已經有不計其數的作品與專案面市，如果引擎製作出來的成果，不能夠經得起玩家與消費者的考驗，早就消失在歷史的洪流中了。</p>
<p>在個人電腦平台上開發遊戲，最大的關鍵困難點，<strong>相容性 (Compatibility)</strong> 的問題就佔了極大的部分。很多的顯示卡，加上不同的顯示卡驅動程式設定，還有其他無數的硬體設備組合，甚至使用者安裝的其他軟體，都可能與遊戲程式發生的衝突。平時開發引擎或遊戲程式，總認為不可能會發生的情形，上市後到了玩家的電腦裡，就是有意想不到的場合，會發生這些百分之一、千分之一，甚至萬分之一機率才會出現的臭蟲與當機問題。</p>
<p>而這些商業引擎，已經製作出非常多的商業遊戲作品，經過數以萬計的玩家操作使用，引擎開發公司本身不斷地接受遊戲開發者與玩家兩個面向的反饋，一步步地改良與加強原有的技術，能夠稱得上是真正<strong>千錘百鍊的實做技術</strong>。因此，即使這些引擎缺少便利的編輯器或工具，仍然不減他們<strong>「很硬」</strong>的事實。就如同是一種信譽般，他就是可以拍胸脯掛保證來捍衛遊戲程式，把問題發生的可能性減至最低。這就是商業引擎能夠屹立不搖的原因。</p>
<p>當然，沒有足夠的編輯器與工具，對於遊戲開發來說，真的是會令人吐血不止。通常在這樣的狀況下，至少需要一個有經驗的能手，在熟悉引擎的使用之後，再專注於製作相關工具的工作上。反面來想，如果這些引擎能夠具備完善強大的編輯器與輔助工具，那麼可預想的，這些繪圖引擎很快地就會<strong>「轉職」</strong>成為遊戲引擎了。原來數十萬美金的身價，也會跟著扶搖直上，擠身<strong>百萬引擎</strong>之列。果真如此，開發者能不能負擔得起這樣的投資，就是另一個層面的問題了。</p>
<p>我從學生時代起就開始關注 3D 繪圖引擎的發展，找過也看過不論是國內或國外許多的自製引擎，發現一個很有趣的奇妙現象：不管是繪圖引擎還是聲稱的遊戲引擎，常常都搞得像各國間的<strong>軍備競賽</strong>一樣。其他引擎實做出的功能，自己也不能落人後，於是洋洋灑灑列出引擎所有大大小小的 Feature（特點），展示很多看起來不賴的 Screenshot（截圖），然後這樣那樣，請來點掌聲、請付錢買我，啪啦啪啦啪啦。當然其中也有看起來非常不錯的引擎，因為沒有親身使用過，真的只是<strong>看起來</strong>不錯而已；但是，心裡一樣有著大大的<strong>疑問句</strong>油然而生。</p>
<p>相較之下，許多著名的<strong>開放原始碼 (Open Source)</strong> 引擎，如 <a href="http://www.ogre3d.org/"><strong>Ogre</strong></a> 與 <a href="http://irrlicht.sourceforge.net/"><strong>Irrlicht</strong></a> 已經行之有年，也逐漸從原本的小嫩嫩成長茁壯起來了。與自製引擎不同的是，這些開放原始碼的引擎，雖然離商業引擎的程度還有段不短的距離，但是他們擁有許多的開發者與使用者，已經經歷過許多的問題、建議與改進。透過這些活躍的開發者社群與使用者社群相輔相成地投注心力，也能夠逐漸地將引擎拉拔長大。</p>
<p>以上所提的內容，用意不是在貶低引擎技術的發展；事實上，技術的進步與發展對於遊戲開發來說，仍然相當重要而不可忽視。另外，3D 繪圖理論 (3D Graphics) 與相關程式設計，也的確是相當令人著迷的事物。自己也是從第一本 <a href="http://tlsj.tenlong.com.tw/WebModule/BookSearch/bookSearchViewAction.do?isbn=0321481003&#038;sid=38149"><strong>OpenGL 紅皮書</strong></a>開始，就已經深深迷上而不可自拔了。技術與理論，都是非常令人入迷的事物，而能夠將這些書本上的理論，轉化成為酷炫精彩的畫面，更是許多程式設計者投身遊戲業界的動力之一。然而對於遊戲開發製作來說，<strong>以遊戲為名</strong>，切勿忘記<strong>引擎技術僅僅是眾多的環節其中之一</strong>而已。</p>
<p>經過三年多的商業引擎使用經驗後，以台灣遊戲界的現況來說，<strong>我並不贊成自行開發 3D 引擎</strong>。當然這是以業界發展與商業現實的角度來看的。多數用來開發大型遊戲的商業引擎，背後都是一個完整團隊的運作；具有龐大完備的架構、各式開發工具，研發成員幾乎都是專業資深的碩博士，還要有厲害的行銷團隊等等。在<strong>製作出賣座的好遊戲</strong>這個前提之下，自行開發引擎這個選項，幾乎可以說是不合乎理性與成本的做法。漂亮的畫面很多人都會做，儘管把 <a href="http://en.wikipedia.org/wiki/Shader">Shader</a> 用得天花亂墜、胡說八道，如果沒有真正有趣的<strong>內容 (Content)</strong> 存在，展現出來的也不過是許多<strong>華麗而無意義的三角形堆砌</strong>而已。</p>
<p>唯有真切瞭解我們自己的<strong>核心優勢</strong>而善用之，才有機會能夠打破現狀，發展出我們自己的<strong>遊戲產業的路</strong>！</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=37&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/beginner/difference-of-commercial-and-home-made-engines/feed</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Scripting系統概論與Lua簡介</title>
		<link>http://blog.monkeypotion.net/gameprog/beginner/introduction-of-scripting-system-and-lua</link>
		<comments>http://blog.monkeypotion.net/gameprog/beginner/introduction-of-scripting-system-and-lua#comments</comments>
		<pubDate>Fri, 18 Jan 2008 16:23:58 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[入門概念]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/gameprog/beginner/introduction-of-scripting-system-and-lua</guid>
		<description><![CDATA[基於我很愛 Lua 這個 Scripting Language（劇本描述語言←有點彆扭的翻譯 Orz）的立場，不先來篇介紹實在說不過去啊。不過在介紹 Lua 出場前，要先把老大哥 Scripting Language 推出來暖暖場才行。 一般的程式語言，在程式碼撰寫完畢後都需要經過編譯 (Compile) 這個步驟，交由程式語言工具的編譯器 (Compiler) 進行語法驗證、程式碼連結與機器碼建立，最後才能產生出所謂的可執行檔 (Executable Files)，也就是在 Windows 平台下滑鼠左鍵雙擊就會開始執行的檔案類型。而使用 Scripting Language 編寫好的程式碼，無須經過編譯的步驟就能夠被直譯 (Interpreted) 並且執行。使用 Scripting Language 有點像是使用一組預先定義好的指令集，在直譯器 (Interpreter) 解析到這些指令時，就能夠去執行預先定義好的行為與程式。所以相較於 C/C++ 屬於需要經過編譯才能執行的程式語言 (Compiled Language)，Scripting Language 被稱為是一種直譯式的語言 (Interpreted Language)。 有了基本的概念之後，首先，應該要問的問題是，為什麼要使用 Scripting Language？Scripting 系統與遊戲開發有什麼關連性？關於這兩個問題，這裡訪問到三位不願具名的受訪者： 程式開發者A君：它可以幫助我們節省開發時間，不用每次做個小更動就要等待程式重新編譯連結，也會減少被企畫騷擾的時間。 企畫設計者B先生：終於可以自由地測試各種想法與做法，而不用每天跑去問程式：請問～這個功能可不可以加一下了。（淚） 美術設計者C小姐：聽說可以用來做 GUI（圖形化使用者介面）耶，不過我沒用過。 在一般遊戲開發的過程中，常見的情境是：程式開發者依據企畫設計者所訂定的文件規格，製作完成某功能後，再交由企畫檢視成果與預想的設計是否符合、是否有需要改進之處，然後再將設計交回程式開發者進行修改，修改完畢後再交由企畫設計者檢閱。以上程序會一直跳針，反覆反覆再反覆直到完成為止。設計本身是一種需要反覆迭代進行的程序，但是太頻繁的來往溝通，可能會殺死程式開發者的專注力與工作效率，也無法使企畫設計者的想法得到迅速的反饋。為了能讓程式開發者放下一點點肩上的擔子，將部分權責交由企畫設計者獨立處理，於是有了各種編輯器與 Scripting 系統的發明。 那麼，運用 Scripting 系統與一般的編輯器來開發遊戲有什麼不同？其實兩者各有所長。編輯器之類的工具，通常是一組程式設計者預設好的功能，讓使用者能夠以直覺化的方式去操作各種程序，例如場景、人物與特效編輯等等。而 Scripting 系統，則帶給使用者更大的權力與更多的可能性，可以提供使用者極大的彈性去調整與預覽製作的成果。要做出好學好用的編輯器，通常是相當曠日費時的工作，並且還需要持續地依照企畫設計者的需求增加功能與修補臭蟲。製作編輯器這項工作，可以說是沒有真正完整完成的時候。相較之下，使用 Scripting [...]]]></description>
			<content:encoded><![CDATA[<p><img src='http://blog.monkeypotion.net/wp-content/uploads/2008/01/lua.gif' alt='Lua Programming Language' class='alignleft'/>基於我很愛 <a href="http://zh.wikipedia.org/w/index.php?title=Lua&#038;variant=zh-tw"><strong>Lua</strong></a> 這個 <a href="http://en.wikipedia.org/wiki/Scripting_language"><strong>Scripting Language</strong></a>（劇本描述語言←有點彆扭的翻譯 Orz）的立場，不先來篇介紹實在說不過去啊。不過在介紹 Lua 出場前，要先把老大哥 Scripting Language 推出來暖暖場才行。</p>
<p>一般的程式語言，在程式碼撰寫完畢後都需要經過<strong>編譯 (Compile)</strong> 這個步驟，交由程式語言工具的<strong>編譯器 (<a href="http://en.wikipedia.org/wiki/Compiler">Compiler</a>)</strong> 進行語法驗證、程式碼連結與機器碼建立，最後才能產生出所謂的可執行檔 (Executable Files)，也就是在 Windows 平台下滑鼠左鍵雙擊就會開始執行的檔案類型。而使用 Scripting Language 編寫好的程式碼，<strong>無須經過編譯的步驟</strong>就能夠被<strong>直譯 (Interpreted)</strong> 並且執行。使用 Scripting Language 有點像是使用<strong>一組預先定義好的指令集</strong>，在<strong>直譯器 (<a href="http://en.wikipedia.org/wiki/Interpreter_%28computing%29">Interpreter</a>) </strong>解析到這些指令時，就能夠去執行預先定義好的行為與程式。所以相較於 C/C++ 屬於需要經過編譯才能執行的程式語言 (<a href="http://en.wikipedia.org/wiki/Compiled_language">Compiled Language</a>)，Scripting Language 被稱為是一種<strong>直譯式的語言 (<a href="http://en.wikipedia.org/wiki/Interpreted_language">Interpreted Language</a>)</strong>。</p>
<p><span id="more-30"></span></p>
<p>有了基本的概念之後，首先，應該要問的問題是，<strong>為什麼要使用 Scripting Language？Scripting 系統與遊戲開發有什麼關連性？</strong>關於這兩個問題，這裡訪問到三位不願具名的受訪者：</p>
<blockquote>
<ul>
<li><strong>程式開發者</strong>A君：它可以幫助我們節省開發時間，不用每次做個小更動就要等待程式重新編譯連結，也會減少被企畫騷擾的時間。</li>
<li><strong>企畫設計者</strong>B先生：終於可以自由地測試各種想法與做法，而不用每天跑去問程式：請問～這個功能可不可以加一下了。（淚）</li>
<li><strong>美術設計者</strong>C小姐：聽說可以用來做 <a href="http://en.wikipedia.org/wiki/Gui">GUI</a>（圖形化使用者介面）耶，不過我沒用過。</li>
</ul>
</blockquote>
<p>在一般遊戲開發的過程中，常見的情境是：程式開發者依據企畫設計者所訂定的文件規格，製作完成某功能後，再交由企畫檢視成果與預想的設計是否符合、是否有需要改進之處，然後再將設計交回程式開發者進行修改，修改完畢後再交由企畫設計者檢閱。以上程序會一直跳針，<strong>反覆反覆再反覆直到完成為止</strong>。設計本身是一種需要反覆迭代進行的程序，但是太頻繁的來往溝通，可能會殺死程式開發者的專注力與工作效率，也無法使企畫設計者的想法得到迅速的反饋。為了能讓程式開發者放下一點點肩上的擔子，將部分權責交由企畫設計者獨立處理，於是有了各種<strong>編輯器與 Scripting 系統</strong>的發明。</p>
<p>那麼，運用 Scripting 系統與一般的編輯器來開發遊戲有什麼不同？其實<strong>兩者各有所長</strong>。編輯器之類的工具，通常是一組程式設計者預設好的功能，讓使用者能夠以直覺化的方式去操作各種程序，例如場景、人物與特效編輯等等。而 Scripting 系統，則帶給使用者<strong>更大的權力與更多的可能性</strong>，可以提供使用者極大的彈性去調整與預覽製作的成果。要做出好學好用的編輯器，通常是相當曠日費時的工作，並且還需要持續地依照企畫設計者的需求增加功能與修補臭蟲。製作編輯器這項工作，可以說是沒有真正完整完成的時候。相較之下，使用 Scripting 系統需要企畫設計者更多的程式相關知識，但是卻能夠在耗費較少開發時間的情況下，達到最大的彈性與效益。</p>
<p>瞭解為什麼要使用 Scripting Language 之後，下一個問題是，在這麼多種類的 Scripting Language 中，<strong>為什麼要使用 Lua？</strong>它到底有什麼好處可以勝過其他競爭者？來聽聽官方的說法：</p>
<blockquote>
<ul>
<li><strong>Lua</strong> 既小且快又不用錢。</li>
<li><strong>Lua</strong> 是非常易學易用的的一種語言。</li>
<li><strong>Lua</strong> 與 C/C++ 的整合性很好。</li>
<li><strong>Lua</strong> 的可移植性極高。</li>
</ul>
</blockquote>
<p>Lua 的核心非常緊實且不佔空間，執行速度優於其他 Scripting Language，可擴充性極高。因為 Lua 語言本身是以 <a href="http://en.wikipedia.org/wiki/ANSI_C"><strong>ANSI C</strong></a> 標準寫成，幾乎所有平台上都能夠執行，目前已經有相當多遊戲專案與其他應用程式的<a href="http://www.lua.org/uses.html"><strong>成功實例</strong></a>。我曾經在 <a href="http://en.wikipedia.org/wiki/Linux">Linux</a> 與 <a href="http://en.wikipedia.org/wiki/Mac_OS_X">Mac OS X</a> 平台上，測試 Windows 中寫好的 Lua 程式，在不修改任何 Script 程式碼的情況下，的確能夠順利執行無誤。</p>
<p>談到 Lua，就不能不提這位超級具份量的客戶：<a href="http://www.worldofwarcraft.com/"><strong>魔獸世界 (World of Warcraft)</strong></a>。在這款超級著名的多人線上遊戲中，也借助了許多 Lua 的威力。最為人所熟知的，就是遊戲允許玩家自行製作各式各樣的 UI（使用者介面）；藉由 <strong><a href="http://en.wikipedia.org/wiki/Xml">XML</a> 負責定義外型配置</strong>，而 <strong>Lua 負責執行核心功能</strong>，我們可以看到世界各地玩家成千上萬的自製 UI 模組，很多的成果都不遜於官方原本製作的版本。因為這項讓玩家自製內容的開放功能，大幅增強了魔獸世界遊戲裡，以及遊戲外的社群互動。</p>
<p>另外，魔獸世界中的大型頭目怪物，總是擁有非常多樣化的攻擊模式，這是在其他多數 MMOG 中很少見的遊戲樂趣。我推論其中的<strong>怪物 AI</strong>，同樣也是透過 Lua 的編寫來達成這樣的功能（未證實）。假設，魔獸世界沒有使用 Lua 或其他 Scripting 系統的方式來開發這些 AI 行為，應該還有以下兩種方式，可以達成如此豐富多變的 AI 行為：</p>
<blockquote>
<ul>
<li><strong>交給程式開發者</strong>：每隻頭目都要寫出特定的程式碼，多少頭目就有多少特製化的程式碼，或是複製貼上複製貼上複製複製複製貼上的程式碼。<br />
                             程式開發者A君：………我要換工作（找離職單）。
                     </li>
<li><strong>交給企畫設計者</strong>：需要一張超大的表格，定義所有可能發生的條件與行為，然後每次只取用其中一小部分的欄位使用。<br />
                             企畫設計者B先生：………幫我多拿一張。
                     </li>
</ul>
</blockquote>
<p>回顧歷史，在不很久以前，那段晦暗不明的日子裡，許多開發者為了能得到 Scripting Language 這帖靈藥，於是<strong>自己動手</strong>寫出了各式各樣的 Scripting Language，程式碼的長相有可能類似於這樣：</p>
<pre name="code" class="cpp">
IF player_hp < 100 THEN
    CAST_SPELL(1)
ELSE IF self_hp < 50 THEN
    RUNAWAY
ELSE
    MELEE_ATTACK
END
</pre>
<p>這種自家製造的 Scripting Language，往往陷入太難學習、效能太差與後續維護除蟲的無盡深淵中。終於，在 <strong>Lua</strong>、<a href="http://www.python.org/"><strong>Python</strong></a> 與 <a href="http://www.ruby-lang.org/"><strong>Ruby</strong></a> 三者出現之後，遊戲界就像是現出了三道曙光一樣，再也不必投注人力時程開發不知會不會有問題的語言，也不必擔心開發者離職後的語言維護除錯問題。這三大 Scripting Language 在遊戲業界中都有各自的支持使用者與廣泛的應用；例如在 <a href="http://en.wikipedia.org/wiki/Rpgmaker"><strong>RPGMaker</strong></a> 這套著名的遊戲製作工具中，也能夠使用 Ruby 來撰寫進階的加強功能。</p>
<p>如果你有一種以上的程式語言學習經驗，對於 Lua 肯定能夠輕鬆上手。就算從來沒有學過任何程式語言，Lua 的學習也比其他語言來得容易許多。讀過<a href="http://www.lua.org/"><strong>官方網站</strong></a>幾個章節的<a href="http://www.lua.org/manual/5.1/"><strong>參考手冊</strong></a>之後，就能夠很輕易地開始寫 Lua 程式了。如果對於 Lua 有進一步興趣的話，<a href="http://www.amazon.com/Programming-Lua-Second-Roberto-Ierusalimschy/dp/8590379825/ref=pd_bxgy_b_text_b">《Programming in Lua》</a>是一本必看不可的好書。另外如果想要瞭解 Lua 如何運用在遊戲開發的各種功能上，我會推薦閱讀<a href="http://www.amazon.com/Game-Development-LUA/dp/1584504048">《Game Development with Lua》</a>這本書。</p>
<p>最後，以場景編輯為例，舉個簡單的應用示範 Lua 其中一種使用方式：程式開發者需要先在 C++ 端撰寫好 Lua 端程式碼可呼叫使用的函式，然後在 Lua Script 中就可以自由呼叫使用。首先在 C++ 端的主程式中寫好：</p>
<pre name="code" class="lua">
// 在場景上增加一個物件
static int AddObject(lua_State* _pkState)
{
    string kObjectType;
    // 取出 script 傳來的第一個參數
    g_ScriptManager->GetValueAt(1, kObjectType);

    if (kObjectType == "monster") {
        // 取出後續的參數進行處理
    }
    else if (kObjectType == "light") {
       // 取出後續的參數進行處理
    }

    // 把物件交由場景相關的介面處理
    g_SceneManager->AddObject(kObjectType);
    return 0;
}
</pre>
<p>以上這樣的函式稱為 <strong>Glue Function</strong>，負責把 C++ 與 Lua 兩端<strong>膠黏起來以便於相互溝通</strong>。把這個函式註冊好之後，在 Lua 端裡就可以這樣使用：</p>
<pre name="code" class="lua">
-- 這是 Lua 中的註解

-- 增加一隻怪物在 (10, 20, 5) 的位置
AddObject("monster", 10, 20, 5)
-- 增加一個白色燈光在 (0, 50, 0) 的位置
AddObject("light", 0, 50, 0, 255, 255, 255)
-- 增加一個寶箱在 (-51, 512, 3) 的位置
AddObject("treasure", -51, 512, 3)
</pre>
<p>對於場景上各種物件的增加與刪減，從此不用透過 C++ 端程式碼的修改，能夠直接利用 Lua 端的 Script 做出更加動態與彈性的關卡設計。在下篇<strong>進階技術</strong>的文章中，會以小型商業遊戲中應用的實例，來看 Lua 真正使用在遊戲中的實做細節，敬請期待囉。</p>
<p>最後，讓我們一起大呼：<strong>葡萄牙月亮萬歲～！</strong> XD</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=30&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/beginner/introduction-of-scripting-system-and-lua/feed</wfw:commentRss>
		<slash:comments>21</slash:comments>
		</item>
		<item>
		<title>遊戲引擎的層級架構</title>
		<link>http://blog.monkeypotion.net/gameprog/beginner/layered-architecture-of-game-engine</link>
		<comments>http://blog.monkeypotion.net/gameprog/beginner/layered-architecture-of-game-engine#comments</comments>
		<pubDate>Tue, 15 Jan 2008 16:04:54 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[入門概念]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/gameprog/beginner/layered-architecture-of-game-engine</guid>
		<description><![CDATA[在遊戲程式的領域中，最常聽到的專有名詞，可以說是非 Game Engine（遊戲引擎）莫屬了。聽起來是個很炫很酷的名詞，但其實遊戲引擎一詞經常被過度泛稱與誤用。所謂的遊戲引擎架構，由低階 (Low-Level) 至高階 (High-Level) 可細分為以下三個層級 (Layer)： 繪圖 API（例如：DirectX 與 OpenGL） 繪圖引擎（例如：OGRE、Renderware 與 Gamebryo）與其他引擎 遊戲引擎（例如：Unreal 與 Torque） 繪圖 API，負責掌管程式與硬體間的溝通，將硬體層的功能與特徵抽象化，提供一組標準化的介面供程式設計者使用。目前 DirectX 與 OpenGL 已成為業界兩大標準。此層級屬於繪圖底層的規格化與標準化，有利於引擎與遊戲開發者以及整個業界的發展，使開發者可以專注在更具體與遊戲相關的引擎架構上，而不會受制於各家廠商不同硬體實做內容所產生的限制。 繪圖引擎，將底層的繪圖 API 包裝成與實做無關的介面，甚至能夠提供數種不同平台的繪圖 API 以供跨平台開發使用，更進一步的為程式設計者帶來許多的功能性以及便利性。使用繪圖引擎對於開發者來說最大的益處，就是可以使用以繪圖 API 建構起來的各種繪圖架構與技術，例如 Scene Graph 架構、空間分割、資源管理、光影處理等等。 遊戲引擎，則是一組完整的解決方案，能夠在保持一定彈性的原則下，提供最大程度的功能性與便利性。除了包含繪圖引擎的功能之外，可能也會包含播放音樂音效的音效引擎、判斷物理碰撞行為的物理引擎等其他功能面的元件。相較於單純的繪圖引擎，一個完整的遊戲引擎，更需要提供許多的編輯器與工具，例如地形編輯器、人物動作編輯器等等。而遊戲引擎與美術設計軟體（例如 3ds Max）的整合性也相當重要；如果引擎內含強大的編輯工具與外掛輸出程式，不僅能為程式開發者節省時間，為企畫與美術設計者帶來便利性，更能夠降低人為錯誤的發生率，進一步加速遊戲的開發流程。 除了上述的繪圖 API、繪圖引擎與遊戲引擎層級之外，還有一個稱為遊戲框架 (Game Framework) 的層級。在軟體開發的領域中，所謂的 Framework 是指一個在軟體系統中可重複利用的設計。與遊戲相關的著名框架系統有用來開發 Xbox360 遊戲的 XNA Framework 與微軟大力推行的 .NET Framework。框架系統需要能在提供現成實做版本的情況下，同時保留具有彈性且可擴充的介面，以期達到框架的可重複利用性。 此外，由於遊戲產業的蓬勃發展，專注於開發繪圖引擎與遊戲引擎的公司也越來越多，於是出現了一個新興的專有名詞稱之為 Middleware（中介軟體）。中介軟體是用來提供某特定層面的功能性元件，目的在於節省遊戲開發的時程與風險，包含像是繪圖引擎、物理引擎、人工智慧引擎、音效引擎等等都可以算是中介軟體的一種。目前也有中介軟體開發廠商，提供包含伺服器端與客戶端程式在內，開發 MMO 遊戲的完整解決方案。 [...]]]></description>
			<content:encoded><![CDATA[<p>在<strong>遊戲程式</strong>的領域中，最常聽到的專有名詞，可以說是非 <strong><a href="http://en.wikipedia.org/wiki/Game_engine">Game Engine</a>（遊戲引擎）</strong>莫屬了。聽起來是個很炫很酷的名詞，但其實遊戲引擎一詞經常被過度泛稱與誤用。所謂的<strong>遊戲引擎架構</strong>，由低階 (Low-Level) 至高階 (High-Level) 可細分為以下三個<strong>層級 (Layer)</strong>：</p>
<ul>
<li><strong>繪圖 <a href="http://en.wikipedia.org/wiki/Api">API</a></strong>（例如：<a href="http://en.wikipedia.org/wiki/Directx">DirectX</a> 與 <a href="http://en.wikipedia.org/wiki/Opengl">OpenGL</a>）</li>
<li><strong>繪圖引擎</strong>（例如：<a href="http://www.ogre3d.org/">OGRE</a>、<a href="http://en.wikipedia.org/wiki/RenderWare">Renderware</a> 與 <a href="http://www.emergent.net/">Gamebryo</a>）<strong>與其他引擎</strong></li>
<li><strong>遊戲引擎</strong>（例如：<a href="http://www.unrealtechnology.com/">Unreal</a> 與 <a href="http://www.garagegames.com/">Torque</a>）</li>
</ul>
<p><span id="more-27"></span></p>
<p>繪圖 API，負責掌管程式與硬體間的溝通，將硬體層的功能與特徵抽象化，<strong>提供一組標準化的介面</strong>供程式設計者使用。目前 DirectX 與 OpenGL 已成為業界兩大標準。此層級屬於繪圖底層的規格化與標準化，有利於引擎與遊戲開發者以及整個業界的發展，使開發者可以專注在更具體與遊戲相關的引擎架構上，而不會受制於各家廠商不同硬體實做內容所產生的限制。</p>
<p>繪圖引擎，將底層的繪圖 API 包裝成與實做無關的介面，甚至能夠提供數種不同平台的繪圖 API 以供跨平台開發使用，更進一步的為程式設計者帶來許多的功能性以及便利性。使用繪圖引擎對於開發者來說最大的益處，就是可以使用<strong>以繪圖 API 建構起來的各種繪圖架構與技術</strong>，例如 <a href="http://en.wikipedia.org/wiki/Scene_graph">Scene Graph</a> 架構、空間分割、資源管理、光影處理等等。</p>
<p>遊戲引擎，則是<strong>一組完整的解決方案</strong>，能夠在保持一定彈性的原則下，提供最大程度的功能性與便利性。除了包含繪圖引擎的功能之外，可能也會包含播放音樂音效的音效引擎、判斷物理碰撞行為的物理引擎等其他功能面的元件。相較於單純的繪圖引擎，一個完整的遊戲引擎，更需要提供許多的<strong>編輯器與工具</strong>，例如地形編輯器、人物動作編輯器等等。而遊戲引擎與美術設計軟體（例如 <a href="http://www.autodesk.com/3dsmax">3ds Max</a>）的整合性也相當重要；如果引擎內含強大的編輯工具與外掛輸出程式，不僅能為程式開發者節省時間，為企畫與美術設計者帶來便利性，更能夠降低人為錯誤的發生率，進一步加速遊戲的開發流程。</p>
<p>除了上述的繪圖 API、繪圖引擎與遊戲引擎層級之外，還有一個稱為<strong>遊戲框架 (Game Framework)</strong> 的層級。在軟體開發的領域中，所謂的 <a href="http://en.wikipedia.org/wiki/Software_framework">Framework</a> 是指一個<strong>在軟體系統中可重複利用的設計</strong>。與遊戲相關的著名框架系統有用來開發 <a href="http://en.wikipedia.org/wiki/XBox360">Xbox360</a> 遊戲的 <a href="http://en.wikipedia.org/wiki/Microsoft_XNA">XNA Framework</a> 與微軟大力推行的 <a href="http://en.wikipedia.org/wiki/.net_framework">.NET Framework</a>。框架系統需要能在提供現成實做版本的情況下，同時保留具有彈性且可擴充的介面，以期達到框架的可重複利用性。</p>
<p>此外，由於遊戲產業的蓬勃發展，專注於開發繪圖引擎與遊戲引擎的公司也越來越多，於是出現了一個新興的專有名詞稱之為 <strong><a href="http://en.wikipedia.org/wiki/Middleware">Middleware</a>（中介軟體）</strong>。中介軟體是用來提供某特定層面的功能性元件，目的在於節省遊戲開發的時程與風險，包含像是繪圖引擎、物理引擎、人工智慧引擎、音效引擎等等都可以算是中介軟體的一種。目前也有中介軟體開發廠商，提供包含伺服器端與客戶端程式在內，開發 MMO 遊戲的<a href="http://www.bigworldtech.com/index/index.php">完整解決方案</a>。</p>
<p>在遊戲程式開發中，最困難的關鍵點往往在於各層級間的橋接與溝通部分。對於各個層級，開發者需要盡可能使它們達到彼此獨立不相依的情況，減少各層級之間的耦合性，在這樣的設計模式之下，才有利於達到元件化、層級化的高度可再利用遊戲引擎。簡而言之，也就是將<strong>介面與實做分離</strong>的設計概念。在最理想的情況下，希望能夠將<strong>引擎框架的泛用程式碼</strong>與<strong>和特定遊戲相關的程式碼</strong>兩部分完全分離而治，以期達到在引擎相關程式碼變動最少的情況之下，能夠提供給不同的遊戲程式碼來使用。</p>
<p>遊戲引擎所提供的功能，就像是玩樂高 (<a href="http://en.wikipedia.org/wiki/Lego">LEGO</a>) 玩具一樣，我們可以使用積木箱裡各種不同形狀的<strong>基本積木</strong>，組合出夢想藍圖中的巨大模型。這些基本積木就像是<strong>遊戲引擎中的各種元件</strong>，可以根據不同遊戲專案的不同需求，取出合適的積木元件拼湊組合成為一個完整的遊戲。而這個從積木組成模型的步驟，則需要一個規格化、標準化的程序，使得<strong>「組裝」</strong>這個動作，變得很直覺易懂，無論未來積木的內容材料是否有改變，都不會對使用者造成太大的影響。積木的「組裝」動作就像是程式的<strong>「介面」</strong>一樣，任意變動積木的組合方法或是介面的使用方法，都會造成使用者的困擾與不便，並且大幅降低複製使用經驗的可能性。</p>
<p>然而在真正的開發過程中，我們可能會發現，要達成各層獨立、介面實做分開是非常不容易的任務。特別是在程式架構不良的情況下，會使得日漸龐大的遊戲程式碼層層交疊、錯綜盤根，就像是地基不穩卻又拼命往上加蓋的高樓一樣搖搖欲墜。因此正如 <a href="http://en.wikipedia.org/wiki/Design_Patterns">《Design Patterns》</a> 書中的設計思維所述：應該著力於<strong>將「變動」與「不變動」的部分抽離分開</strong>，分別封裝「變動」與「不變動」的概念，才能夠將進行變動時所需耗費的心力降至最低。</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=27&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/beginner/layered-architecture-of-game-engine/feed</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
	</channel>
</rss>

