<?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/concept/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.monkeypotion.net</link>
	<description>遊戲開發‧遊戲程式‧遊戲設計</description>
	<lastBuildDate>Mon, 06 Sep 2010 01:52:21 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>實作Lua Closure二合一洗牌發牌機</title>
		<link>http://blog.monkeypotion.net/gameprog/concept/lua-closure-random-shuffle-deck</link>
		<comments>http://blog.monkeypotion.net/gameprog/concept/lua-closure-random-shuffle-deck#comments</comments>
		<pubDate>Fri, 23 Oct 2009 16:30:32 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[觀念技巧]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=1086</guid>
		<description><![CDATA[亂數 (Randomness)，是每一位程式設計者們熟悉又親切的好朋友，無論我們開發製作的是哪一種類型的遊戲，亂數機制總是在遊戲設計程序與撰寫程式碼的過程中，扮演著不可或缺的重要角色。亂數最重要的用途，就是提供遊戲世界中必要的「不確定性」，只要能夠將這份「不確定性」的設計機制運用得宜，就可以為玩家們帶來許多驚喜感與樂趣元素。 然而，有許多時候，亂數的產生並不只是使用 rand() 函式般單純容易而已。在某些遊戲機制裡，無邊無際的亂數數值，並不能滿足程式設計者或企畫設計者所想達到的目標——我們需要的是「亂中有序」——能夠在某個限定範圍之內產生亂數。 何謂「亂中有序」？撲克牌遊戲就是一個最好的實例。撲克牌由 4 種花色與 13 種數字，組合成 52 張「牌組」。而「洗牌」(Shuffle) 動作可以定義為：在一組有限的集合元素內，進行亂數排列的程序。只要將牌組洗完之後，就可以按照牌堆的排列順序，開始一張張地進行「發牌」動作了。 對於程式設計者來說，不論是使用哪一種程式語言，要實作出洗牌與發牌的功能都不是件太困難的事情。假設，我們可以使用 Lua 語言來實作洗牌發牌機，是否能夠創造出什麼樣有趣的變化？ 由於 Lua 是一種無須宣告變數資料型別的程式腳本語言，所以在 Lua 中我們可以非常便利地對任意數量、任意類型的「資料集合」進行洗牌的動作。下列的 Lua 函式 RandShuffle()，接受一個 table 型別的參數做為洗牌用的「牌組」： function RandShuffle(deck) -- 首先確認傳入的參數型態是 table assert(type(deck) == "table"); -- 將 deck 的所有元素複製至暫用的 clone 中 local clone = {}; for k, v in pairs(deck) do clone[k] = v; end -- [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_1541" class="wp-caption alignright" style="width: 338px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2009/10/dice-triangle.jpg" alt="dice-triangle" title="dice-triangle" width="328" height="328" class="size-full wp-image-1541" /><p class="wp-caption-text">（圖片來源：maniacworld.com）</p></div>
<p><strong>亂數 (Randomness)</strong>，是每一位程式設計者們熟悉又親切的好朋友，無論我們開發製作的是哪一種類型的遊戲，亂數機制總是在遊戲設計程序與撰寫程式碼的過程中，扮演著不可或缺的重要角色。亂數最重要的用途，就是提供遊戲世界中必要的「不確定性」，只要能夠將這份「不確定性」的設計機制運用得宜，就可以為玩家們帶來許多驚喜感與樂趣元素。</p>
<p>然而，有許多時候，亂數的產生並不只是使用 rand() 函式般單純容易而已。在某些遊戲機制裡，無邊無際的亂數數值，並不能滿足程式設計者或企畫設計者所想達到的目標——我們需要的是「亂中有序」——能夠在某個限定範圍之內產生亂數。</p>
<p>何謂「亂中有序」？撲克牌遊戲就是一個最好的實例。撲克牌由 4 種花色與 13 種數字，組合成 52 張「牌組」。而<strong>「洗牌」(Shuffle)</strong> 動作可以定義為：在一組有限的集合元素內，進行亂數排列的程序。只要將牌組洗完之後，就可以按照牌堆的排列順序，開始一張張地進行「發牌」動作了。</p>
<p>對於程式設計者來說，不論是使用哪一種程式語言，要實作出洗牌與發牌的功能都不是件太困難的事情。假設，我們可以使用 Lua 語言來實作洗牌發牌機，是否能夠創造出什麼樣有趣的變化？</p>
<p><span id="more-1086"></span></p>
<p>由於 Lua 是一種無須宣告變數資料型別的程式腳本語言，所以在 Lua 中我們可以非常便利地對任意數量、任意類型的「資料集合」進行洗牌的動作。下列的 Lua 函式 RandShuffle()，接受一個 table 型別的參數做為洗牌用的「牌組」：</p>
<pre name="code" class="lua">
function RandShuffle(deck)
    -- 首先確認傳入的參數型態是 table
	assert(type(deck) == "table");

    -- 將 deck 的所有元素複製至暫用的 clone 中
	local clone = {};
	for k, v in pairs(deck) do
		clone[k] = v;
	end

	-- 取得元素集合的大小
	local range = table.maxn(deck);

	-- 巡訪 deck 結構
	for k, v in pairs(deck) do
    	-- 藉由亂數機制，決定要取出的 clone 索引值
		local index = math.random(1, range);

	    -- 在 clone 中移除該元素，塞回原來的 deck 中
		deck[k] = table.remove(clone, index);

		-- 將亂數取值的範圍減1
		range = range - 1;
	end
end
</pre>
<p>無論 deck 結構中的資料，是數字型別、字串型別或甚至另一個 table 結構，只要經過 RandShuffle() 程序的操作，就可以得到一副經過亂數排列，也就是洗牌後的 deck 牌組：</p>
<pre name="code" class="lua">
-- 數字型別的牌組
NumberDeck = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
-- 洗牌
RandShuffle(NumberDeck);

-- 字串型別的牌組
StringDeck = { "Apple", "Banana", "Cherry", "Lemon", "Orange" };
-- 照樣洗牌
RandShuffle(StringDeck);

-- table結構的牌組
ComplexDeck =
{
	{ 1, Monday, "Apple" },
	{ 2, Tuesday, "Banana" },
	{ 3, Wednesday, "Cherry" },
	{ 4, Thursday, "Lemon" },
	{ 5, Friday, "Orange" },
};
-- 洗！洗！洗！
RandShuffle(ComplexDeck);
</pre>
<p>但是到目前為止，我們只是得到了一副洗過牌的牌組，接下來該如何進行「發牌」動作呢？</p>
<p>站在使用者的立場思考，我們會希望發牌功能可以一次發出一張牌，並且在整副牌組全部使用過後，自動重新進行洗牌。所以，在此需要另一個輔助函式 OutputCard() 來完成發牌程序：</p>
<pre name="code" class="lua">
-- 發牌計數器
g_Counter = 0;

function OutputCard(deck)
    -- 紀錄目前的發牌數
	g_Counter = g_Counter + 1;

    -- 如果發牌數大於總牌數
	if (g_Counter > table.maxn(deck)) then
		-- 重新洗牌
		RandShuffle(deck);
		-- 重設發牌計數器
		g_Counter = 1;
	end

	-- 回傳一張牌
	return deck[g_Counter];
end
</pre>
<p>實作出「洗牌機」RandShuffle() 與「發牌機」OutputCard() 後，就可以很便利地使用：</p>
<pre name="code" class="lua">
-- 牌組
MyDeck = { ... };

-- 先將 g_Deck 洗牌
RandShuffle(MyDeck);

-- 發牌
MyCards = {};
-- 第1張牌
MyCards[1] = OutputCard(MyDeck);
-- 第2張牌
MyCards[2] = OutputCard(MyDeck);
-- 第3張牌
MyCards[3] = OutputCard(MyDeck);
-- 第4張牌
MyCards[4] = OutputCard(MyDeck);
-- 第5張牌
MyCards[5] = OutputCard(MyDeck);
</pre>
<p>但光是這樣還不夠完善。假設我們需要一次操作兩副以上相同的牌組怎麼辦？有沒有其他方法，可以不使用全域變數 g_Counter 做為發牌的計數器？在 Lua 缺少物件導向設計機制特性的情形下，能不能將「洗牌機」與「發牌機」合而為一，讓使用者得以更直覺地操作洗牌與發牌程序？</p>
<p>答案就是 <a href="http://www.lua.org/pil/6.1.html">Lua Closures</a>。利用 Lua 語言提供的 Closure 特性，就可以幫助我們輕巧且優雅地達成目標：</p>
<pre name="code" class="lua">
function RandDeck(t)
	assert(type(t) == "table");

    -- Closure所使用的upvalues
	local pool = t;
	local counter = table.maxn(t);

    -- 回傳一個匿名函式
	return function()
		counter = counter + 1;

		if (counter > table.maxn(pool)) then
			RandShuffle(pool);
			counter = 1;
		end

		return pool[counter];
	end
end
</pre>
<p>上列函式 RandDeck() 的回傳值是一個「匿名函式」，而在這個匿名函式中所做的事情，與先前在 OutputCard() 函式中進行的程序相同。根據 Lua Closures 的功能規範，在 RandDeck() 中宣告為區域變數的 pool 與 counter，被稱為 external local variable 或 upvalue。</p>
<p>藉由 upvalue 的特性，程式設計者得以在函式之間保存變數值，並且讓底下的匿名函式進行讀寫動作。對於全域或 RandDeck() 函式外部的程式碼來說，pool 與 counter 是完全不可見的變數；但是對於底下的匿名函式來說，pool 與 counter 的功用就像是物件導向語言中的「成員變數」(member variables) 一樣。</p>
<p>而 RandDeck() 的使用方式也很簡單明瞭，以 52 張撲克牌為例：</p>
<pre name="code" class="lua">
-- 撲克牌牌組
--   花色：c, d, h, s 分別代表 club, diamond, heart, spade 四種花色
--   數字：1 至 13
Poker =
{
	"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13",
	"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10", "d11", "d12", "d13",
	"h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "h9", "h10", "h11", "h12", "h13",
	"s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "s12", "s13",
};

-- 創造一副牌組
g_Deck = RandDeck(Poker);

MyCard = {};
-- 呼叫 g_Deck() 發第1張牌
MyCard[1] = g_Deck();
-- 呼叫 g_Deck() 發第2張牌
MyCard[2] = g_Deck();
-- 呼叫 g_Deck() 發第3張牌
MyCard[3] = g_Deck();

-- 創造多副不同的牌組
g_Deck1 = RandDeck(Poker);
g_Deck2 = RandDeck(Poker);
g_Deck3 = RandDeck(Poker);

MyCard = {};
-- 使用 g_Deck1() 發第1張牌
MyCard[1] = g_Deck1();
-- 使用 g_Deck2() 發第2張牌
MyCard[2] = g_Deck2();
-- 使用 g_Deck3() 發第3張牌
MyCard[3] = g_Deck3();
</pre>
<p>結合之前的 RandShuffle() 與 RandDeck() 寥寥可數的幾行程式碼，就完成了雙效合一的洗牌發牌機。</p>
<p>最後，可以再進一步修改 RandDeck() 函式，讓它能夠一次發出複數張的牌：</p>
<pre name="code" class="lua">
-- 發牌機加強版
function RandMultiDeck(t)
	assert(type(t) == "table");

	local pool = t;
	local counter = table.maxn(t);

	return function(num)
		num = num or 1;

		if (num == 1) then
			counter = counter + 1;

			if (counter > table.maxn(pool)) then
				RandShuffle(pool);
				counter = 1;
			end

			return pool[counter];
		else
			if (counter + num > table.maxn(pool)) then
				RandShuffle(pool);
				counter = 1;
			end

			local result = {};
			for index = counter, counter + num - 1 do
				table.insert(result, pool[index]);
			end

			counter = counter + num;

			return result;
		end
	end
end

-- 創造一副牌組
g_Deck = RandMultiDeck(Poker);

-- 一次發出5張牌！
MyCard = g_Deck(5);
</pre>
<p>在這篇文章裡，雖然只介紹了非常簡單的洗牌與發牌應用，但應該不難看出 Lua Closures 的威力所在。對於習慣使用 C/C++ 或 Java 語言的程式設計者來說，一開始可能會比較難以理解 Lua Closures 的概念，但只要熟悉了它的運作方式，就可以使用 Lua 做到許多目前 C++ 語言無法達成的事情。</p>
<p>關於洗牌與發牌，你是否有更好更巧妙的作法？關於 Lua Closures，你有更棒更實用的應用嗎？歡迎各位給予指教及討論。</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=1086&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/concept/lua-closure-random-shuffle-deck/feed</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>遊戲專案的目錄結構規劃</title>
		<link>http://blog.monkeypotion.net/gameprog/concept/directory-structure-planning-for-game-project</link>
		<comments>http://blog.monkeypotion.net/gameprog/concept/directory-structure-planning-for-game-project#comments</comments>
		<pubDate>Sun, 11 Jan 2009 16:12:29 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[觀念技巧]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=922</guid>
		<description><![CDATA[不知道各位如何規劃遊戲專案的目錄結構？各位所使用的是專案管理體系的正規方法，或者只是隨意地將檔案與資料夾散佈在電腦中的各處？在一個遊戲專案的開發過程裡，重要的資產除了程式碼檔案之外，還包括了設計文件、美術素材、音效檔案，以及最終的執行檔等等。該如何妥善管理這些不同類別的項目，以達到良好的成效？在此將以我個人目前使用的方法，提出一套目錄結構的規劃架構，與各位一同進行交流討論。 有些人喜歡將許多各式各樣不同用途的資料夾，直接放置在電腦的「桌面」上，最後經常會使得整個桌布，幾乎被各種不同圖示與資料夾所淹沒。這樣的作法，在一時的使用上或許非常方便，但是如果沒有善加整理歸檔，未來很容易會找不到想尋找的目標文件或檔案。不論是公司的正式專案，或是自己在工作之餘開發的個人專案，都能夠從良好的目錄結構規劃中，得到易於管理與變動的益處。 首先，我習慣在電腦硬碟的作業系統槽（通常是 C 槽）之外，選擇一個空間足夠的磁碟槽，在根目錄下開啟幾個資料夾進行專案的管理： Codespace：專案區，只存放由 CVS 或 SVN 等版本控制系統取出的各項專案。 Workspace：工作區，存放各項測試中的程式、待完成的工作項目，以及各種經常需要查閱使用的文件及資源。 Temp：暫存區，存放各種暫時物件，包括從網路上下載的安裝檔，以及檔案解壓縮後的資料夾等等。 專案區中的資料，應該經常與版本控制系統保持同步化，盡可能減少修改後的版本存在於自己電腦中的時間。即使個人的工作硬碟不幸遭遇毀損的狀況，也能夠由版本控制系統的伺服器中，重新取回整個專案的資料。而工作區，則用來存放各項試驗中的專案；例如在剛開始接觸 Boost.Pool 函式庫時，我就會在工作區裡開設一個測試資料夾命名為 TestBoostPool 進行各項測試，等到成果達到一定程度後，再將專案提交至版本控制系統以及專案區中存放。最後，在暫存區中的資料，則只是便利暫時性的使用，可以隨時將全部的內容清空刪除。 確立了磁碟槽中的工作目錄結構之後，接下來的關鍵要點就是在於遊戲專案的目錄結構規劃。最終規劃完畢的專案目錄結構，一定要達到便利而易用的目標，最好不要產生太深太多層的目錄，但又要能夠維持各個目錄之間的獨立性。一開始，先按照美術設計、企畫設計與程式設計的工作產物，大致上將目錄分為三項： Asset：遊戲素材區，存放美術設計者製作的各種素材，以及音效、音樂、字型等等遊戲資源檔案。 Document：遊戲文件區，存放各項設計文件，包含企畫設計、美術設計以及程式架構的相關文件在內。 Source：遊戲程式碼區，存放遊戲專案的程式碼，其中再依不同子專案，分成不同的子目錄。 然後在最外層的資料夾，應該以遊戲專案的名稱，或者某個獨特的專案代號做為命名。按照上述的架構，所規劃出來的專案目錄結果如下所示： ＜ProjectName＞/ Asset/ Model/ Script/ Shader/ Sound/ Texture/ Document/ Art/ Design/ Programming/ Reference/ Source/ ＜Project#1＞/ ＜Project#2＞/ ＜Project#3＞/ 為了便利於遊戲版本的發佈與包裝作業，最好能夠將完整可執行的遊戲資料，包含遊戲執行檔、函式庫檔與動態連結檔等，放置在一個獨立的資料夾中。所以在＜ProjectName＞目錄下，我們需要再新增一個 Build 目錄。這個目錄中的資料，是由專案執行流程所動態產生出來的結果，其中包含 Debug 與 Release 兩個子目錄分別存放兩種版本的資料。另外，為了預防兩種版本的執行檔被搞混，程式設計者可以在原來的執行檔名稱後，多加一個「_D」做為識別 Debug 版本的方式，例如將 Debug 版與 Release 版的執行檔分別命名為 Game_D.exe 與 Game.exe。 有些人或許會有疑問：「如果執行檔在 [...]]]></description>
			<content:encoded><![CDATA[<p>不知道各位如何規劃遊戲專案的目錄結構？各位所使用的是專案管理體系的正規方法，或者只是隨意地將檔案與資料夾散佈在電腦中的各處？在一個遊戲專案的開發過程裡，重要的資產除了程式碼檔案之外，還包括了設計文件、美術素材、音效檔案，以及最終的執行檔等等。該如何妥善管理這些不同類別的項目，以達到良好的成效？在此將以我個人目前使用的方法，提出一套目錄結構的規劃架構，與各位一同進行交流討論。</p>
<p>有些人喜歡將許多各式各樣不同用途的資料夾，直接放置在電腦的「桌面」上，最後經常會使得整個桌布，幾乎被各種不同圖示與資料夾所淹沒。這樣的作法，在一時的使用上或許非常方便，但是如果沒有善加整理歸檔，未來很容易會找不到想尋找的目標文件或檔案。不論是公司的正式專案，或是自己在工作之餘開發的個人專案，都能夠從良好的目錄結構規劃中，得到易於管理與變動的益處。</p>
<p>首先，我習慣在電腦硬碟的作業系統槽（通常是 C 槽）之外，選擇一個空間足夠的磁碟槽，在根目錄下開啟幾個資料夾進行專案的管理：</p>
<ul>
<li><strong>Codespace</strong>：專案區，只存放由 CVS 或 SVN 等版本控制系統取出的各項專案。</li>
<li><strong>Workspace</strong>：工作區，存放各項測試中的程式、待完成的工作項目，以及各種經常需要查閱使用的文件及資源。</li>
<li><strong>Temp</strong>：暫存區，存放各種暫時物件，包括從網路上下載的安裝檔，以及檔案解壓縮後的資料夾等等。</li>
</ul>
<p>專案區中的資料，應該經常與版本控制系統保持同步化，盡可能減少修改後的版本存在於自己電腦中的時間。即使個人的工作硬碟不幸遭遇毀損的狀況，也能夠由版本控制系統的伺服器中，重新取回整個專案的資料。而工作區，則用來存放各項試驗中的專案；例如在剛開始接觸 Boost.Pool 函式庫時，我就會在工作區裡開設一個測試資料夾命名為 TestBoostPool 進行各項測試，等到成果達到一定程度後，再將專案提交至版本控制系統以及專案區中存放。最後，在暫存區中的資料，則只是便利暫時性的使用，可以隨時將全部的內容清空刪除。</p>
<p><span id="more-922"></span></p>
<p>確立了磁碟槽中的工作目錄結構之後，接下來的關鍵要點就是在於遊戲專案的目錄結構規劃。<strong>最終規劃完畢的專案目錄結構，一定要達到便利而易用的目標，最好不要產生太深太多層的目錄，但又要能夠維持各個目錄之間的獨立性。</strong>一開始，先按照美術設計、企畫設計與程式設計的工作產物，大致上將目錄分為三項：</p>
<ul>
<li><strong>Asset</strong>：遊戲素材區，存放美術設計者製作的各種素材，以及音效、音樂、字型等等遊戲資源檔案。</li>
<li><strong>Document</strong>：遊戲文件區，存放各項設計文件，包含企畫設計、美術設計以及程式架構的相關文件在內。</li>
<li><strong>Source</strong>：遊戲程式碼區，存放遊戲專案的程式碼，其中再依不同子專案，分成不同的子目錄。</li>
</ul>
<p>然後在最外層的資料夾，應該以遊戲專案的名稱，或者某個獨特的專案代號做為命名。按照上述的架構，所規劃出來的專案目錄結果如下所示：</p>
<blockquote>
<pre>
＜ProjectName＞/

    Asset/
        Model/
        Script/
        Shader/
        Sound/
        Texture/

    Document/
        Art/
        Design/
        Programming/
        Reference/

    Source/
        ＜Project#1＞/
        ＜Project#2＞/
        ＜Project#3＞/
</pre>
</blockquote>
<p>為了便利於遊戲版本的發佈與包裝作業，最好能夠將完整可執行的遊戲資料，包含遊戲執行檔、函式庫檔與動態連結檔等，放置在一個獨立的資料夾中。所以在＜ProjectName＞目錄下，我們需要再新增一個 <strong>Build</strong> 目錄。這個目錄中的資料，是由專案執行流程所動態產生出來的結果，其中包含 Debug 與 Release 兩個子目錄分別存放兩種版本的資料。另外，為了預防兩種版本的執行檔被搞混，程式設計者可以在原來的執行檔名稱後，多加一個「_D」做為識別 Debug 版本的方式，例如將 Debug 版與 Release 版的執行檔分別命名為 Game_D.exe 與 Game.exe。</p>
<p>有些人或許會有疑問：「如果執行檔在 Build 目錄下，要怎麼順利存取 Asset 目錄中的遊戲素材？」其實很簡單，以 MSVC 的專案為例，只要在專案屬性的「除錯」頁面中，將「工作目錄」設定為 Build 所在的目錄，接著在程式中設定存取素材的檔案路徑，這樣一來，當程式設計者將專案建置完畢按下 F5 鍵時，就能夠正確無誤地執行程式了。</p>
<p>在開發與除錯階段時，將執行檔以及遊戲素材分開置放在 Build 與 Asset 目錄中，的確可以簡化一些繁瑣的資料搬移步驟，但是對於正式釋出的遊戲版本，程式設計者是否需要重新設定執行檔與素材檔案的相對路徑？並不需要。只要利用以下的方式，就可以一次滿足 Debug 與 Release 兩種建置版本的需求：</p>
<pre name="code" class="cpp">
#ifdef _DEBUG
    // Path settings for debug build
    static const std::string ASSET_PATH = "../../Asset/";
    static const std::string MODEL_PATH = "../../Asset/Model/";
    static const std::string SCRIPT_PATH = "../../Asset/Script/";
#else
    // Path settings for release build
    static const std::string ASSET_PATH = "./Data/";
    static const std::string MODEL_PATH = "./Data/Model/";
    static const std::string SCRIPT_PATH = "./Data/Script/";
#endif
</pre>
<p>到目前為止，看起來一切都還不錯，但如果只是把遊戲專案分成 Asset、Build、Document 與 Source 四個目錄，仍然不足以達到最佳的專案目錄規劃成效。</p>
<p>除了內部自行開發的程式碼以外，很多時候在遊戲專案中，也會使用第三方團體所提供的程式碼，為了不與我們自己所寫的程式碼專案搞混，以利後續進行各種維護或者升級程序，所以應該將這些程式碼或函式庫獨立出來，在＜ProjectName＞目錄下增加一個 <strong>ThirdParty</strong> 子目錄放置這些專案。在個別的專案目錄下，可以再進一步分為 bin、include 與 lib 三個子目錄，分別存放可執行檔、表頭檔以及函式庫檔案。</p>
<p>另外一種處理第三方團體程式碼的方式，是直接安裝在系統槽的目錄，例如 C:\Program Files\ 之中。就我而言，如果像是 Boost C++ Libraries 或者 Lua 這一類比較固定不變，程式設計者也不會自行修改的函式庫，我就會直接安裝在系統槽目錄中；而如果是為了個別專案的特殊需求，有自行做出修改的第三方函式庫，就適合置放在專案資料夾的 ThirdParty 目錄中。</p>
<p>在 Visual Studio 的預設設定中，會將專案的 Project 檔 (*.vcproj) 存放在與原始碼檔案相同的目錄下。然而這樣的做法，對於具有跨平台需求或者更換程式環境需求的專案來說，可能會造成一些額外的困擾。因此我們可以把與程式專案工具相關的 Solution 檔 (*.sln) 與 Project 檔，分離到獨立的目錄中，在＜ProjectName＞目錄下新增 <strong>Project</strong> 子目錄，依不同的程式環境分成不同子目錄，例如 Msvc80、Msvc90、CodeBlocks 與 XCode 等等，就能夠非常便利地增加新的設定了。</p>
<p>有時候，我們會需要在不開啟程式專案工具的情況下，重新建置整個遊戲專案。為了要達到這樣的目標，會需要撰寫一些命令列指令、Shell Script，或是其他專門的建置工具語言。所以同樣可以在＜ProjectName＞目錄下，新增一個 <strong>Script</strong> 目錄，其中再依不同的作業系統平台，例如 Win32、Linux 與 Mac，分成不同的子目錄。</p>
<p>將專案的 Project 檔與建置 Script 抽離了以後，Source 目錄就會變得非常單純，依照不同的子專案分成數個子目錄，其中僅僅存有 *.h、*.cpp 以及 *.inl 檔案而已。另外，還可以將程式專案的「中介檔案目錄」指定到 Build 目錄中，因為建置過程中產生的 *.obj 等相關檔案，不需要保存下來，所以可以置放在動態產生的 Build 目錄中，更進一步保持 Source 與 Project 目錄的簡化與純淨。</p>
<p>最後，在＜ProjectName＞目錄下新增 <strong>Tool</strong> 子目錄，將一些經常會用到的工具程式，例如遊戲編輯器、角色檢視器、資料轉換器等等，置放於其中以利使用。</p>
<p>遊戲專案目錄規劃的最終版本如下：</p>
<blockquote>
<pre>
＜ProjectName＞/

    Asset/
        Model/
        Script/
        Shader/
        Sound/
        Texture/

    Build/
        Debug/
        Release/

    Document/
        Art/
        Design/
        Programming/
        Reference/

    Project/
        Codeblocks/
        Msvc80/
        Msvc90/
        Xcode/

    Script/
        Linux/
        Mac/
        Win32/

    Source/
        ＜Project#1＞/
        ＜Project#2＞/
        ＜Project#3＞/

    ThirdParty/
        ＜Library#1＞/
        ＜Library#2＞/
        ＜Library#3＞/
            bin/
            include/
            lib/
            source/

    Tool/
        Converter/
        Editor/
        Viewer/
</pre>
</blockquote>
<p>只要按照上述的步驟執行，完整的遊戲專案目錄就大功告成囉！一套簡單且易於使用的遊戲專案目錄結構，不只能夠省去許多手動搬移檔案以及刪除檔案的程序，更能協助我們把各種資料妥善分類，將它們分配存放到正確的歸所。<strong>請不要再讓你的文件、檔案、資料夾與專案，走失在電腦裡的某個角落了！</strong></p>
<p>你覺得這樣的規劃如何？合乎你的使用需求以及專案現況嗎？或者還有什麼可以修改之處？歡迎提出來一起討論喔～ ：D</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=922&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/concept/directory-structure-planning-for-game-project/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>表頭檔要不要？拿速度來換！</title>
		<link>http://blog.monkeypotion.net/gameprog/concept/header-files-inclusion-or-not</link>
		<comments>http://blog.monkeypotion.net/gameprog/concept/header-files-inclusion-or-not#comments</comments>
		<pubDate>Sun, 30 Mar 2008 16:51:39 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[觀念技巧]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/gameprog/concept/header-files-inclusion-or-not</guid>
		<description><![CDATA[本文要提出的內容，是一個非常基礎的程式觀念：表頭檔 (Header Files) 與編譯依存關係的課題。雖然對於許多程式設計者來說，這個觀念技巧應該是相當基本而且必備的基礎知識，但在現實世界中，我卻時常看到許多人忽略了這個問題的重要性，因而造成專案開發流程中的重大災難。 來看看實際的範例。例如在專案中有一個 GameObject 類別，用來實作遊戲物件的相關操作行為： // @ GameObject.h #include < string > class GameObject { public: int GetType(); std::string&#038; GetName(); private: int m_iType; std::string m_kName; }; 有了基礎的遊戲物件類別之後，自然會陸續建構並且實作出其他的類別。當 GameObject 需要使用或包含其他的類別時，可能會在類別中定義一個該類別的成員變數，以結合 GameObject 類別進行更複雜的操作行為。例如，可能會在 GameObject 類別中，加入一個 ComponentA 類別的物件定義遊戲物件的基本組件： // @ GameObject.h #include < string > #include "ComponentA.h" class GameObject { public: int GetType(); std::string&#038; GetName(); ComponentA&#038; [...]]]></description>
			<content:encoded><![CDATA[<p>本文要提出的內容，是一個非常基礎的程式觀念：<strong>表頭檔 (Header Files) 與編譯依存關係的課題。</strong>雖然對於許多程式設計者來說，這個觀念技巧應該是相當基本而且必備的基礎知識，但在現實世界中，我卻時常看到許多人忽略了這個問題的重要性，因而造成專案開發流程中的重大災難。</p>
<p>來看看實際的範例。例如在專案中有一個 GameObject 類別，用來實作遊戲物件的相關操作行為：</p>
<pre name="code" class="cpp">
// @ GameObject.h
#include < string >

class GameObject
{
public:
    int GetType();
    std::string&#038; GetName();

private:
    int m_iType;
    std::string m_kName;
};
</pre>
<p>有了基礎的遊戲物件類別之後，自然會陸續建構並且實作出其他的類別。當 GameObject 需要使用或包含其他的類別時，可能會在類別中定義一個該類別的成員變數，以結合 GameObject 類別進行更複雜的操作行為。例如，可能會在 GameObject 類別中，加入一個 ComponentA 類別的物件定義遊戲物件的基本組件：</p>
<pre name="code" class="cpp">
// @ GameObject.h
#include < string >
#include "ComponentA.h"

class GameObject
{
public:
    int GetType();
    std::string&#038; GetName();
    ComponentA&#038; GetComponentA();

private:
    int m_iType;
    std::string m_kName;
    ComponentA m_kCompA;
};
</pre>
<p><span id="more-89"></span></p>
<p>隨著專案的開發與需求的增加，程式碼的營養越來越好、越長越胖，同時與 GameObject 緊緊相依、難分難捨的類別們也會越來越多。於是，同樣地一一把它們加入 GameObject 的表頭檔宣告中：</p>
<pre name="code" class="cpp">
// @ GameObject.h
#include < string >
#include < vector >
#include "ComponentA.h"
#include "ComponentB.h"
#include "ComponentC.h"
#include "ComponentD.h"

class GameObject
{
public:
    int GetType();
    std::string&#038; GetName();
    ComponentA&#038; GetComponentA();

private:
    int m_iType;
    std::string m_kName;
    ComponentA m_kCompA;
    ComponentB m_kCompB;
    ComponentC m_kCompC;
    std::vector< ComponentD > m_kCompDList;
};
</pre>
<p>除了萬能的 GameObject 類別之外，或許在其他的類別，例如 GameEffect 類別中，也會需要使用 ComponentA、ComponentB、ComponentC 與 ComponentD 這些類別。既然在許多地方都會需要使用到這些類別，<strong>何不將這些 #include 的表頭檔全部放在一個檔案中？</strong>所以聰明的程式設計者想出了一個超級好辦法，就是把所有需要的表頭檔全部丟進同一個表頭檔 GameInclusions.h 中：</p>
<pre name="code" class="cpp">
// @ GameInclusions.h
#include < string >
#include < vector >
#include "ComponentA.h"
#include "ComponentB.h"
#include "ComponentC.h"
#include "ComponentD.h"
</pre>
<p>然後在有需要的地方，只要輕鬆簡單地 #include 這個檔案，就可以自由使用檔案中所包含的任何類別：</p>
<pre name="code" class="cpp">
// @ GameObject.h
#include "GameInclusions.h"

class GameObject
{
public:
    int GetType();
    std::string&#038; GetName();
    ComponentA&#038; GetComponentA();

private:
    int m_iType;
    std::string m_kName;
    ComponentA m_kCompA;
    ComponentB m_kCompB;
    ComponentC m_kCompC;
    std::vector< ComponentD > m_kCompDList;
};
</pre>
<p>不管是 GameEffect 類別、GameRule 類別、GameFactory 類別還是 GameBalabala 類別，所有需要的類別都可以無差別地使用：</p>
<pre name="code" class="cpp">
// @ GameEffect.h
#include "GameInclusions.h"

class GameEffect
{
public:
    // Member functions
private:
    ComponentA m_kCompA;
    ComponentC m_kCompC;
};
</pre>
<p>啊哈！從此再也不需要麻煩地去區分，在這個類別中只使用到 ComponentB 與 ComponentC 類別，而另一個類別只使用到 ComponentD 類別等等許多不同的情況，反正把它們像模組一樣通通包起來，一包就能解決，一次搞定！輕鬆愉快又自然簡單！</p>
<p>可惜的是，程式設計者的日子，從來就沒這麼好過。</p>
<p>在上述的範例中所使用<strong>「在表頭檔中 #include 其他表頭檔」</strong>的用法，會造成程式碼檔案之間嚴重的<strong>編譯依存關係 (Compilation Dependency)</strong> 而使專案建置與編譯的速度大幅下降。什麼是編譯依存關係？請看以下這個例子：</p>
<pre name="code" class="cpp">
// @ Example.cpp
#include "GameObject.h"

GameObject   var;
</pre>
<p>當你在程式碼檔案中寫出上述的變數宣告敘述句，不論程式碼是位於 .h 表頭檔或是 .cpp 原始檔，就是告訴編譯器要依據 GameObject 類別，產生出一個名為 var 的物件變數。為了要讓編譯器能夠配置物件的記憶體，程式設計者必須要<strong>盡告知的義務</strong>，通知編譯器在哪個檔案中可以找到 GameObject 的相關資訊；因此，也就是需要使用每個程式設計者都很熟悉的行為：在程式碼檔案中撰寫對應的 #include 語法。</p>
<p>而後每次在 Example.cpp 內容有所變動的時候，編譯器需要對這個檔案進行重新編譯的工作。編譯器首先會編譯 Example.cpp 中所 #include 的檔案，確認 GameObject.h 檔案的內容通過語法檢測並且沒有任何問題，才能夠對我們所需要的 GameObject 物件做出正確的記憶體配置行為，也才會繼續編譯 Example.cpp 檔案。因此，正如每個程式設計者所熟知的基本知識，使用到什麼類別，就要 #include 相對應的表頭檔，這是再自然也不過的事情了！</p>
<p>那麼，在表頭檔中 #include 其他表頭檔會造成什麼問題？按照上述的說法，也就是<strong>每次在表頭檔內容有所變動的時候，都需要重新編譯這個檔案中所有 #include 的表頭檔！</strong>換言之，在前述的 GameObject 與 Component 類別的例子中，只要在 GameObject.h 中新增或修改任何的程式碼，而需要對 GameObject.h 進行重新編譯時，就會使得編譯器需要先行編譯 ComponentA.h、ComponentB.h、ComponentC.h 以及 ComponentD.h 四個檔案，最後才對 GameObject.h 進行編譯。如果 GameObject.h 只被 GameObject.cpp 所 #include 就不會造成太多的問題；但是如果 GameObject.h 被許多其他的表頭檔與原始檔所 #include，就會<strong>在程式專案中造成指數倍增的檔案依存關係！</strong></p>
<p>在這個例子中，只要使用<a href="http://en.wikipedia.org/wiki/Forward_declaration"><strong>前置宣告 (Forward Declaration)</strong></a> 的方式，並且將原來的實名物件轉換成<strong>物件指標</strong>的宣告方式，就能夠避免在表頭檔中 #include 其他表頭檔而造成的編譯依存關係。</p>
<pre name="code" class="cpp">
// @ GameObject.h
#include < string >
#include < vector >

// 前置宣告
class ComponentA;
class ComponentB;
class ComponentC;
class ComponentD;

class GameObject
{
public:
    int GetType();
    std::string&#038; GetName();
    ComponentA&#038; GetComponentA();

private:
    int m_iType;
    std::string m_kName;
    ComponentA* m_pkCompA; // 轉換成 ComponentA 類別的物件指標
    ComponentB* m_pkCompB; // 轉換成 ComponentB 類別的物件指標
    ComponentC* m_pkCompC; // 轉換成 ComponentC 類別的物件指標
    std::vector< ComponentD* > m_kCompDList; // 轉換成 ComponentD 類別的物件指標
};
</pre>
<p>在<a href="http://tlsj.tenlong.com.tw/WebModule/BookSearch/bookSearchViewAction.do?isbn=9861543554&#038;sid=31211"><strong>《Effective C++》</strong></a>中的第 31 項條款，對於編譯依存關係的問題，刻下每位程式設計者都應該謹記於心的三條戒文：</p>
<blockquote>
<ul>
<li><strong>如果 object references 或 object pointers 可以完成任務，就不要使用 objects。</strong></li>
<li><strong>如果能夠，盡量以 class 的宣告取代 class 的定義。</strong></li>
<li><strong>不要在表頭檔中 #include 其他表頭檔，除非你的表頭檔不這樣就無法編譯。</strong></li>
</ul>
</blockquote>
<p>謹遵這三項條例，才能夠最小化程式專案中的編譯依存關係。在程式檔案中，應該小心謹慎地使用 #include 語法，一個不少，同時也一個都不能多。這是做為一位程式設計者所應該具備的程式撰寫基本素養。</p>
<p>有時候為了減少編譯依存關係，而使用前置宣告的情況會比較不那麼顯而易見；像是我們為了避免 <strong>Pass-by-Value</strong> 所造成的物件複製問題，所以在成員函式的參數列中會盡量使用 object pointer 或 object reference 的方式傳遞物件。另外，有時候為了解決表頭檔間的<strong>循環依存 (Circular Dependencies)</strong> 問題，也就是 ComponentA.h 中 #include 了 ComponentB.h，而在 ComponentB.h 中也 #include 了 ComponentA.h 的情況，所以使用前置宣告也是必須的作法。然而，能夠有意識地選擇使用前置宣告的撰寫方法，對於程式設計者來說，仍然是非常重要的觀念與技巧。</p>
<p>在<strong>《Effective C++》</strong>中，作者也另外提出了一個設計方法，能夠隱藏類別中的實作細節，而達到減少編譯依存關係的效果：</p>
<pre name="code" class="cpp">
// @ GameObject.h

class GameObjectImp;

class GameObject
{
public:
    void SetCompAType(int _iType);
    ComponentB* MakeCompB();
    void ProcessCompC(ComponentC* _pkCompC);

private:
    GameObjectImp* m_pkImp;
};
</pre>
<p>如程式碼所示，在此新增一個 GameObjectImp 類別，然後在 GameObject 的實作程式碼中，將與其他物件相關的實作細節全權委託給 GameObjectImp 類別處理：</p>
<pre name="code" class="cpp">
// @ GameObject.cpp
#include "GameObject.h"
#include "GameObjectImp.h"

void GameObject::SetCompAType(int _iType)
{
    m_pkImp->SetCompAType(_iType);
}

ComponentB* GameObject::MakeCompB()
{
    return m_pkImp->MakeCompB();
}

void GameObject::ProcessCompC(ComponentC* _pkCompC)
{
    m_pkImp->ProcessCompC(_pkCompC);
}
</pre>
<p>使用了上述的設計方法後，在 GameObject.cpp 中僅需要 #include 唯一的 GameObjectImp.h 檔案而完全無須使用其他的表頭檔，能夠大幅消除 GameObject 與其他類別檔案之間的編譯依存關係。然而這個實作方法的缺點，在於需要多產生出一個與原類別相對應的 Imp 類別，並且在每次使用成員函式時，額外增加了一次函式呼叫的成本。所以還是需要視專案的實際情況，決定是否運用這個方法降低編譯依存關係才會比較恰當。</p>
<p>雖然使用前置宣告與物件指標是非常有用的技巧，但是在某些情況中，也會受到限制而無法發揮作用：</p>
<ul>
<li>在 Inline Memeber Functions 中使用其他類別。</li>
<li>在 Templated Class 中使用其他類別。</li>
</ul>
<p>在 C++ 中我們經常會利用<strong>行內成員函式 (Inline Member Functions)</strong> 來降低函式呼叫的成本，但是如果在函式中呼叫了其他物件的成員函式，編譯器就需要真正獲得該類別函式的宣告，而必須直接 #include 相對應的表頭檔：</p>
<pre name="code" class="cpp">
// @ GameObject.h
#include < string >
#include < vector >

// 必須使用 #include 而非前置宣告
#include "ComponentA.h"

class GameObject
{
public:
    inline void SetCompAType(int _iType)
    {
        m_pkCompA->SetType(_iType);
    }

private:
    ComponentA* m_pkCompA;
};
</pre>
<p>另外一個狀況則是宣告<strong>樣版類別 (Templated Class)</strong> 時，與行內成員函式的情境相似，由於樣版類別的函式定義，需要撰寫在表頭檔或是 .inl 檔案中，所以在樣版類別的宣告檔案中，同樣也無法使用前置宣告來降低檔案間的編譯依存關係。所以在使用這兩項技術的同時，也等於是無可避免地增加了程式碼檔案間的編譯依存關係。</p>
<p>雖然利用<a href="http://en.wikipedia.org/wiki/Precompiled_header"><strong>先行編譯表頭檔 (Precompiled Header)</strong></a> 對於專案整體的建置效能能夠有顯著的提升，但是仍然會有力有未逮之處，同時在實際應中也有許多要注意的地方，並不是一股腦地把表頭檔往裡塞就能夠得出良好的編譯效能。關於先行編譯表頭檔的使用，可以參考<a href="http://www.gamesfromwithin.com/articles/0504/000086.html">「The Care and Feeding of Pre-Compiled Headers」</a>這篇很棒的文章。</p>
<p>另外有一些商業軟體工具如 <a href="http://www.xoreax.com/">IncrediBuild</a>，能夠在區域網路中利用多台電腦的串連，以分散式運算的方法進行程式專案的編譯工作，進而大幅提昇編譯建置的速度。然而這些工具大多需要付出可觀的授權費用，並不是每間公司或每個專案都能夠承受這樣的支出項目。如果只是因為程式設計者們的不負責任態度或錯誤習慣，而造成專案的編譯速度低落，並不值得以這樣的金錢投資換取較佳的工作效能，應該要解決問題的產生根源才是真正的長久之道。</p>
<p>以我親身經歷過的慘痛經驗來說，利用前置宣告與先行編譯表頭檔，重構整個專案中的程式檔案依存關係，真的能夠達到不輸於使用昂貴輔助工具的效果。唯一的缺點就是程式設計者的「等待專案建置時間」變少了，而原本很充裕的喝咖啡時間、打屁聊天時間，以及發呆晃神的時間也通通變少了許多。所以如果做為一個專案管理者的角色，我應該會要求<strong>「在表頭檔中撰寫 #include 語法」這件事，需要經過監督者的蓋章許可才能使用！</strong></p>
<p>在小型的的專案中，使用這些技巧或許看不出太明顯的速度差異，但是當專案規模越來越大，成長到上百甚至上千個表頭檔的程度時，編譯依存性的問題就會開始顯露出可怕的負面作用力，逐漸影響開發流程的工作效率。因此，<strong>從一開始就養成並且保持良好的程式寫作習慣</strong>是非常重要的事情，因為做為一個程式設計師所做的多數行為，都是沒有經過有意識思考，而是以直覺做出反應與決定的行為。習慣會決定我們多數日常生活的行為，當然也包括工作的行為在內。</p>
<p>關於習慣的重要性，可以參考看看<strong>比爾大叔</strong>的說法：</p>
<blockquote><p>
Bill Gates says that any programmer who will ever be good is good in the first few years. After that, whether a programmer is good or not is cast in concrete.
</p></blockquote>
<p>如果僅以編譯依存關係與專案編譯速度的角度來看，在 <strong>C#</strong> 中沒有了表頭檔與原始檔的區別，類別宣告與定義都寫在同一個檔案中，也不需要在檔案之間使用 #include 以參照其他類別的宣告定義，還真的是對於程式設計者的一大福音。然而在 C++ 的真實世界與開發環境中，還是請自行維持良好的<strong>程式寫作紀律</strong>吧。</p>
<p>所以做為程式設計者，請從現在開始就養成良好的習慣，不要只是想著<strong>讓所有事情變得簡單</strong>，否則到最後，你將會擁有一個包含所有其他檔案的巨大表頭檔，以及許多不可預期的悲慘後果！</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=89&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/concept/header-files-inclusion-or-not/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>程式碼的版面配置與風格樣式</title>
		<link>http://blog.monkeypotion.net/gameprog/concept/coding-layout-and-style</link>
		<comments>http://blog.monkeypotion.net/gameprog/concept/coding-layout-and-style#comments</comments>
		<pubDate>Sun, 17 Feb 2008 16:08:23 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[觀念技巧]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/gameprog/concept/coding-layout-and-style</guid>
		<description><![CDATA[什麼是程式碼的版面配置 (Layout) 與風格樣式 (Style)？用一句話簡單說明就是：程式碼呈現出來的樣貌。 在程式碼的文字檔案中，除了有意義（或混亂）的程式碼與註解文字之外，佔據其他空間的正是所謂的版面配置元素；也就是程式碼中的空格、空行、斷行、縮排、括弧等元素。 正所謂「人要衣裝，程式要風格」，利用這些版面配置元素將程式碼好好打扮裝飾一番，不僅能夠讓程式碼看起來賞心悅目，使閱讀程式碼的人功力大增，更能夠讓程式碼的邏輯與結構更加清楚顯著地展現出來。可說是一舉數得、好處多多，真是「居家寫 Code，出門上班」的最佳良伴啊～ 版面配置是對於程式結構的有用指引線索。良好的配置方案能夠使程式的視覺化結構與邏輯結構相符合，傳達給人類與電腦相同意義的訊息。在此要再次引用《Refactoring》書中所寫的一句經典： Any fool can write code that a computer can understand. Good programmers write code that humans can understand. 在撰寫程式的過程裡，較少部分的工作成效，是為了使得電腦能夠瞭解程式碼；而較多部分的努力，是為了使得其他的人類能夠讀懂程式碼。在這個前提之下，程式碼如果能夠達到良好的視覺化配置，就可以恰如其份地展現出一個程式的邏輯結構。程式碼的配置和風格，不會影響到外在可見的因素，例如執行速度、記憶體使用等等，而是對於易讀性、可修改性有很大的影響。並且必須在程式專案初始建構的時期，就將這個議題納入考量。 在《Code Complete, Second Edition》中，特別將 Layout and Style 這個主題獨立成一個章節來詳細闡述，可見得程式碼的版面配置，在軟體建構的程序中也有相當重要而不可忽視的地位。 程式碼版面配置的目的，在於達到程式碼邏輯結構的準確性、一致性，並且增進可讀性與可修改的能力。在討論應該使用的格式時，記得將這些需要達到的目標列入首要的考量因素。看起來好看只是其次的條件；達到其他目標，配置就會看起來好看。 保持一致性 (Consistency) 可說是撰寫程式這項任務中的首要之事；除了維持設計邏輯思維上的一致性，能夠使得閱讀者更容易瞭解程式整體架構的脈絡體系之外，達到程式碼外觀的一致性，也能夠大幅幫助閱讀程式碼的人更容易理解程式區塊中的邏輯結構。 對於一個團隊合作開發的程式專案來說，需要在專案一開始的時候，就以清楚的文件以及簡單的範例，訂立出詳細的程式碼配置準則與規定。如此所有團隊成員都能夠按照著規則撰寫程式碼，而不致產生選擇程式碼配置樣式上的困惑。同時也有利於幫助新進的團隊成員，更快速地瞭解龐大的程式庫細節。另外要注意的是，盡可能不要使這些準則規定太過於複雜，否則可能會降低程式撰寫者遵行規則的意願，而使原先訂立規則的良好用意變成徒勞無功了。 如果沒有訂立出明確的準則，容易產生模糊不清的狀況；程式碼撰寫者各自使用自己所習慣或喜愛的格式，對於其他閱讀者來說，可能會造成不小的困擾與疑惑。對於像遊戲製作這樣曠日費時的專案來說，從一開始就訂立出規則，並且在開發過程中時時保持一致性，更是相當重要而不可忽視的程序。如果在專案開始建構之前，忽略了這種看似小事的準則規範，將會在開發過程中帶來許多不必要的麻煩與痛苦。 將邏輯的呈現列為優先考量，通常不會產生醜陋的程式碼，除非是程式碼本身的邏輯就很醜陋。能夠使好的程式碼看起來好看，壞的程式碼看起來難看，比讓所有程式碼看起來都同樣好看來得更加有用。 在除錯中容易忽略的問題，通常是因為我們自覺「認得」某部分的程式碼，而非真正的去閱讀程式碼而發現錯誤。而良好的版面配置，可以幫助我們更迅速地察覺出有問題的程式碼。版面配置，實際上就是提供了一種視覺上的提示 (Visual Hint) 作用，使得閱讀者能夠從視覺所取得的資訊中，立即做出直覺的反應與迅速的判斷。例如在程式編輯器中，常會使用不同的顏色標示關鍵字、註解、變數與字串等等不同作用的內容，也是為了提供程式撰寫者視覺上的提示與便利性。 版面配置是一種邏輯，同時也是一種美學上的議題，使得程式格式間的爭論，比較像是宗教信仰戰爭而非哲理上的探討。 事實上，相較於固執堅持特定結構化方法的種種細節，更重要的是維持一套一致性遵循的結構。不論是什麼樣的準則規範，只要能夠確實遵行，就能夠遠遠勝過沒有使用任何準則的程式專案。大部分的情況下，各持己見、爭執不同的程式撰寫標準，都是沒有必要的情形。 優秀的程式設計師，應該要對於自己的程式碼配置實例保持開放的心態，並且能夠接受經過實證後確實比較良好的配置方式。 除了維持程式碼的準確性與一致性之外，做為一個程式設計者，也要能夠保持著開放的心胸學習更佳的配置方式，並且接受程式碼外觀的演化能力。以我自己的例子來說，起初剛開始學習程式寫作時，我會盡量少用空白格與空行，想要使程式碼看起來簡潔有力： for(int i=0; i]]></description>
			<content:encoded><![CDATA[<p>什麼是程式碼的<strong>版面配置 (Layout)</strong> 與<strong>風格樣式 (Style)</strong>？用一句話簡單說明就是：<strong>程式碼呈現出來的樣貌</strong>。</p>
<p>在程式碼的文字檔案中，除了有意義（或混亂）的程式碼與註解文字之外，佔據其他空間的正是所謂的<strong>版面配置元素</strong>；也就是程式碼中的<strong>空格、空行、斷行、縮排、括弧</strong>等元素。 正所謂<strong>「人要衣裝，程式要風格」</strong>，利用這些版面配置元素將程式碼好好打扮裝飾一番，不僅能夠讓程式碼看起來賞心悅目，使閱讀程式碼的人功力大增，更能夠讓程式碼的邏輯與結構更加清楚顯著地展現出來。可說是一舉數得、好處多多，真是「居家寫 Code，出門上班」的最佳良伴啊～</p>
<p>版面配置是對於程式結構的有用指引線索。良好的配置方案能夠使程式的<strong>視覺化結構與邏輯結構相符合</strong>，傳達給人類與電腦相同意義的訊息。在此要再次引用<strong>《Refactoring》</strong>書中所寫的一句經典：</p>
<blockquote><p>
<strong>Any fool can write code that a computer can understand. Good programmers write code that humans can understand.</strong>
</p></blockquote>
<p>在撰寫程式的過程裡，較少部分的工作成效，是為了使得電腦能夠瞭解程式碼；而較多部分的努力，是為了<strong>使得其他的人類能夠讀懂程式碼</strong>。在這個前提之下，程式碼如果能夠達到良好的視覺化配置，就可以恰如其份地展現出一個程式的邏輯結構。程式碼的配置和風格，不會影響到外在可見的因素，例如執行速度、記憶體使用等等，而是對於易讀性、可修改性有很大的影響。並且必須在程式專案初始建構的時期，就將這個議題納入考量。</p>
<p><span id="more-54"></span></p>
<p>在<strong>《Code Complete, Second Edition》</strong>中，特別將 <strong>Layout and Style</strong> 這個主題獨立成一個章節來詳細闡述，可見得程式碼的版面配置，在軟體建構的程序中也有相當重要而不可忽視的地位。</p>
<blockquote><p>
程式碼版面配置的目的，在於達到程式碼邏輯結構的<strong>準確性</strong>、<strong>一致性</strong>，並且增進<strong>可讀性</strong>與<strong>可修改</strong>的能力。在討論應該使用的格式時，記得將這些需要達到的目標列入首要的考量因素。看起來好看只是其次的條件；達到其他目標，配置就會看起來好看。
</p></blockquote>
<p>保持<strong>一致性 (Consistency)</strong> 可說是撰寫程式這項任務中的首要之事；除了維持設計邏輯思維上的一致性，能夠使得閱讀者更容易瞭解程式整體架構的脈絡體系之外，達到程式碼外觀的一致性，也能夠大幅幫助閱讀程式碼的人更容易理解程式區塊中的邏輯結構。</p>
<p>對於一個團隊合作開發的程式專案來說，需要在專案一開始的時候，就以<strong>清楚的文件</strong>以及<strong>簡單的範例</strong>，訂立出詳細的程式碼配置準則與規定。如此所有團隊成員都能夠按照著規則撰寫程式碼，而不致產生選擇程式碼配置樣式上的困惑。同時也有利於幫助新進的團隊成員，更快速地瞭解龐大的程式庫細節。另外要注意的是，盡可能不要使這些準則規定<strong>太過於複雜</strong>，否則可能會降低程式撰寫者遵行規則的意願，而使原先訂立規則的良好用意變成徒勞無功了。</p>
<p>如果沒有訂立出明確的準則，容易產生模糊不清的狀況；程式碼撰寫者各自使用自己所習慣或喜愛的格式，對於其他閱讀者來說，可能會造成不小的困擾與疑惑。對於像遊戲製作這樣曠日費時的專案來說，從一開始就訂立出規則，並且在開發過程中時時保持一致性，更是相當重要而不可忽視的程序。如果在專案開始建構之前，忽略了這種看似小事的準則規範，將會在開發過程中帶來許多不必要的麻煩與痛苦。</p>
<blockquote><p>
將邏輯的呈現列為優先考量，通常不會產生醜陋的程式碼，除非是程式碼本身的邏輯就很醜陋。能夠<strong>使好的程式碼看起來好看</strong>，<strong>壞的程式碼看起來難看</strong>，比讓所有程式碼看起來都同樣好看來得更加有用。
</p></blockquote>
<p>在除錯中容易忽略的問題，通常是因為我們自覺「認得」某部分的程式碼，而非真正的去閱讀程式碼而發現錯誤。而良好的版面配置，可以幫助我們更迅速地察覺出有問題的程式碼。版面配置，實際上就是提供了一種<strong>視覺上的提示 (Visual Hint)</strong> 作用，使得閱讀者能夠從視覺所取得的資訊中，立即做出直覺的反應與迅速的判斷。例如在程式編輯器中，常會使用不同的顏色標示關鍵字、註解、變數與字串等等不同作用的內容，也是為了提供程式撰寫者視覺上的提示與便利性。</p>
<blockquote><p>
版面配置是一種邏輯，同時也是一種美學上的議題，使得程式格式間的爭論，比較像是<strong>宗教信仰戰爭</strong>而非哲理上的探討。
</p></blockquote>
<p>事實上，相較於固執堅持特定結構化方法的種種細節，<strong>更重要的是</strong>維持一套一致性遵循的結構。不論是什麼樣的準則規範，只要能夠確實遵行，就能夠遠遠勝過沒有使用任何準則的程式專案。大部分的情況下，各持己見、爭執不同的程式撰寫標準，都是沒有必要的情形。</p>
<blockquote><p>
優秀的程式設計師，應該要對於自己的程式碼配置實例保持<strong>開放的心態</strong>，並且能夠接受經過實證後確實比較良好的配置方式。
</p></blockquote>
<p>除了維持程式碼的準確性與一致性之外，做為一個程式設計者，也要能夠保持著開放的心胸學習更佳的配置方式，並且接受程式碼外觀的<strong>演化能力</strong>。以我自己的例子來說，起初剛開始學習程式寫作時，我會盡量少用空白格與空行，想要使程式碼看起來簡潔有力：</p>
<pre name="code" class="cpp">
for(int i=0; i<10; i++) {
    if (ThisIsTrue) MyFunction();
}
</pre>
<p>在逐漸累積程式撰寫的經驗後，才發現這樣的配置方式不只是眼睛看起來會比較疲累，也容易造成程式碼修改時的無心之過，或是程式除錯時的困擾。於是轉換風格為：</p>
<pre name="code" class="cpp">
for ( int i = 0; i < 10; i ++ )
{
    if ( ThisIsTrue == true )
    {
        MyFunction();
    }
}
</pre>
<p>現在由於各種程式碼工具的發展與協助，使得版面配置與視覺提示的功能越來越強大且便利，所以我又轉換撰寫風格成：</p>
<pre name="code" class="cpp">
for (int i=0; i<10; i++)
{
    if (ThisIsTrue)
    {
        MyFunction();
    }
}
</pre>
<p>以上並不是所謂的<strong>最好的配置風格</strong>，而是目前在我的使用經驗與習慣上認為<strong>比較合適的配置風格</strong>，將來同樣會不斷地改進並且演化。如果想要比較各種不同配置方式的優劣勝敗之處，在<strong>《Code Complete, Second Edition》</strong>書中關於<strong>程式區塊 (Code Block)</strong> 的各種風格形式與意義內涵，有深入且詳細的探討，是相當值得細讀的章節。</p>
<p>除了自己撰寫的程式碼之外，不同的程式語言、程式庫與遊戲引擎，也會有不盡相同的撰寫風格與版面配置。一般來說，<strong>遵守程式語言的慣例</strong>，能夠使熟悉相同程式語言的閱讀者快速地瞭解程式。而對於其他的第三方程式庫或遊戲引擎，是否要遵行它們所使用的準則規定？如果同時使用多種不同風格的程式庫要怎麼取決？對於程式庫、框架與引擎的規則標準，通常需要<strong>視專案的實際狀況而定</strong>，並沒有所謂「一定如此」或「絕對不能這樣做」的規範。以下列出幾項需要考慮的基本項目：</p>
<ul>
<li>是否要使用匈牙利命名法標示變數的資料型態？</li>
<li>是否要使用 Scoping Prefix 例如 g_ 與 m_ 來區別變數的 Scope？</li>
<li>控制結構的前括號要放在同一行，還是自成一行？</li>
<li>Inline 的程式碼定義要直接寫在 .h 中，還是分開寫在 .inl 檔案中？</li>
<li>如果單行程式碼的長度過長，要怎麼樣斷行？</li>
</ul>
<p><strong>Layout</strong> 與 <strong>Style</strong> 的配置使用，是在程式撰寫活動中，與<strong>「人」</strong>最為相關的議題。我們寫程式的同時，也要閱讀很多的程式，不論是自己的、團隊成員的或外部第三方的程式碼。閱讀可以說是一種<strong>單向的溝通方式</strong>，閱讀者無法從撰寫者處得到立即的回應，只能夠使用自己的理解力盡可能瞭解撰寫者想要傳達的各種想法與意涵，所以如果寫出了高深莫測、難以維護的程式碼，並不能稱得上是值得驕傲的事。在程式撰寫的領域中，可不適合用什麼隱喻的手法來表現出自我的思維能力。如何能將程式碼中的意涵<strong>正確而完善地傳達給閱讀者</strong>，甚至是未來的<strong>自己</strong>，才是在撰寫程式時最最需要謹記於心並且著力實現的關鍵要點。</p>
<p>最後要提的是，由於部落格<strong>版面格式限制的緣故</strong>，所有的文章裡如果有寫到程式碼的部分，並不會嚴格遵行我個人的版面配置準則，而是<strong>以節省空間與不破壞文字版面為最大原則</strong>，以避免過長的程式碼會顯示在不正常的位置等等奇怪的情況出現。我在平常的程式撰寫中，習慣使用 <strong>4 個空格</strong>做為自動縮排的數量，但是在部落格的文章裡，為了節省每行的空間，我只會使用 <strong>2 個空格</strong>做為縮排格式，也可能會把原先單行的程式碼切斷成兩行以上；而平常寫程式我會使控制結構的<strong>前括號獨立成行</strong>，但是在部落格裡就會視情況而定，未必每次都會使用相同的規則囉。</p>
<p>好啦，不管你目前所參與的專案，或過往所寫的程式碼外表看起來有多醜，從現在開始，就幫它們好好的打扮打扮吧。未來的某天，會有某人非常感激你所做出的努力；而那個人，很可能就是你自己呢。</p>
<p>（<strong>2008/02/24 Updated</strong>：更換了展示程式碼用的 Plugin。從現在開始，文章內的程式碼會回復成以 4 個空格做為自動縮排的數量，程式碼的長度也比較不會影響到部落格的版面囉！：D）</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=54&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/concept/coding-layout-and-style/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>
