發表文章

目前顯示的是 4月, 2009的文章

從WIN32到iPhone移植good

圖片
good到iPhone的移植的作業雖不能說100%達成目標,但也有點小成果。這篇文章就來把整個過程大致的描述一下,這樣也能對移植過程有個大概的了解。 建立開發環境 要將一個程式從一個平台移植到另一個平台,第一個步驟就是先建立新平台的工作環境。要建立可以開發iPhone程式的環境的過程就不多說了,我只能建議說可能的話還是弄台Mac電腦省的不必要的麻煩。這算是和其它平台比較起來,一個較大的門檻。 有了可以跑Mac的電腦後,再來就是去下載 Xcode ,接著就是 iPhone SDK 。 玩玩Sample 要了解一個新的平台,對我而言最容易上手的方式就是去玩玩它的範例,而且只挑作移植時需要看的就夠了。我首先要看的當然是怎麼畫圖,其它都不需要了。只要能畫圖我就可以初步知道我作的東西對不對。 我先找到一個非常簡單的範例,它只在畫面上貼二張靜態的2D圖形。透過這個簡單的範例再加上大概研究一下Objective-C的語法,就可以開始作點簡單的測試,玩玩怎麼畫圖。 決定測試移植的程式 在iPhone上的繪圖程式主要還是透過OpenGL ES作畫,所以我很快的決定拿 25940p 來作移植測試。 主要的原因是25940p已完成過跨平台移植測試(PSP),所以現在要移植到iPhone上,相對也會容易多。程式本身和平台無關的部份完全可以一次移植上去,不必作任何修改。而25940p的繪圖部份原本是使用OpenGL作簡單的幾何圖形表示,所以換成OpenGL ES也不會太難。 ...(略) 準備移植 開始移植前,首先確認一下要移植的程式使用了那些模組,這些模組是否有支援目標平台。good使用了三個第三方程式庫,分別是 SDL 、 Lua 及 zlib 。Lua及zlib基本上和任何平台無關,可以直接跨過去。比較有問題的是SDL。 雖然SDL本身有跨了包含了Mac的許多平台,但仔細確認後發現SDL是不支援iPhone的。搜尋一下後,發現有人作了iPhone的移植,但礙於iPhone的保密協議不能開放。這就麻煩了,要解決這個問題有三個方法。第一是自己移植SDL到iPhone上,第二是另外找一個代替品,第三是自己來。 稍微評估了第一個方法,結論是對我太難了,就算我能作也不是一天二天的事情。所以換第二個方案,開始上網找找有沒有替代品。 結果沒能很快找到合適的,最後還是決定用自己以前實作的一個小繪圖程式

色塊背景物件

圖片
新增了一個純色塊背景物件類別。底下是一個簡單的測試抓圖。 為了提供這個功能,有幾個地方需要跟著作修改。首先當然是核心部份要加上新的物件定義,再來是編輯器部份要可以作新增,最後是執行時期要可以畫出這樣的物件,以及新增幾條Script呼叫。 核心的支援 這部份其實也沒什麼,主要就是在原本的物件類別上新增一個色塊背景的子類別項目。同時還需要新增二個屬性來支援這個新類別,分別是物件的大小以及背景顏色。 物件大小是一個有四個數字欄位的複合屬性,分別是左上位置以及寬高,目前只用到其中的寬高二個屬性。 編輯器的修改 編輯器裡主要需要作修改的是關卡編輯器。在工具列上新增一個鈕,用來在關卡裡新增一個色塊背景。 可以加入後,還要在View裡把它畫出來。除了畫出來之外,還要處理它的HitTest、PropView的支援等等。 執行時期 執行時期的支援最單純,只是作畫而已。 新增Script呼叫 對應新物件,新增了四條Script API。分別是Good.GetDim、Good.SetDim、Good.GetBgColor及Good.SetBgColor。 SetDim及GetDim是用來存取物件的範圍屬性,目前只使用到寬高二個值來決定色塊的大小。SetBgColor及GetBgColor則是用來存取色塊顏色值。 另外Good.GenObj的定義也稍作調整。GenObj的第二個參數如果是指定一個小於等於0的值,也就是一個無效的值,則表示要建立一個色塊背景物件。 範例 大概說明一下上面看到的抓圖範例的程式。首先在關卡初始化時建立一堆測試物件。 math.randomseed(os.clock()) for i = 1,2000 do   local cb = Good.GenObj(-1, 0, 'TestBlock') end 這段Lua程式碼用來建立2000個用TestBlock控制行為的色塊物件(因為第二個參數不指定資源所以自動成為色塊物件)。 TestBlock定義如下。 TestBlock = {} TestBlock.OnCreate = function(param)   if (math.random(2) == 1) then     param.dirx = -1 * math.random(1,5)   else     param.dirx = 1 *

About 25940p

圖片
25940p 其實是我自己蠻喜歡的一個小作品,雖然說似乎不怎麼受歡迎的樣子。因為我原來就特別喜歡縱向捲軸射擊遊戲的緣故,才作了相關的研發作了這個作品。話雖說是小品,不過我後來實在退步太多了,自己也很難過關。 線上玩 這個小遊戲花了我大約一個星期的時間製作,主要的時間都花在關卡編輯上。整個遊戲都是用script編輯出來的,所謂的整個,除了彈幕外、敵機什麼時候出來怎麼移動、粒子特效、選單、背景多重星空等等,全都是用script編輯出來的。所以其實說花了一星期製作是假的,我花了更多時間在底層的開發上,為了這個小遊戲我花了幾個月時間,設計開發了專門用來製作射擊遊戲的stge語言和虛擬機器。 本來我作小遊戲花的時間並不多,不過從 鋤草機 開始,我已經開始有那種花很多時間來製作一款可以在很短時間內破關的小遊戲的傾向。比如說鋤草機大概花一個月時間製作,但15分鐘可以過關。25940p也差不多15至20分鐘可以全破,但斷斷續續至少用半年以上時間研發製作。 + + + 為了開發一套專門給射擊遊戲使用的script,我花了很多時間在設計上。完成後,首先是使用C++實作虛擬機器的部份,因為這樣至少可以先進入測試階段。虛擬機器的核心部份以smallworld2的ObjectPool模組為中心管理所有資源,把所有東西都當作粒子作為基本概念,不論是子彈或是敵機等等都用同樣的方式對待。然後所有粒子都能掛上一個可以控制它動作的緒,再加上一些和射擊相關的屬性支援,就可以運作動起來了。 Parser的部份一開始原本是想使用Lex/Yacc來作製作,但後來決定使用 Boost::Spirit ,因為沒碰過可以順便學習新東西,結果還出乎意料外的容易使用,缺點是Complie時間大增。接著再用WTL寫個簡單的工具程式,可以直接在上面編寫script並以視覺化的方式立即檢視執行結果。 最後才是撰寫遊戲程式本身。基本上遊戲本身的主要工作就是畫圖,把所有粒子根據遊戲自己的定義畫出來。子彈粒子就畫成子彈,爆炸碎片粒子就畫成爆炸碎片等等。成像部份使用OpenGL來作,因為也是順便學學OpenGL。 剛好因為成像是用OpenGL的緣故,所以在有支援OpenGL的平台上要作Porting就容易了。這也是為什麼我可以很容易的把25940p移植到PSP和iPhone上。因為除了成像,還有像是IO等和系統比較有

如何在 Objective-C 中使用 C/C++ 的程式碼

這幾天在玩Mac。按照慣例,每次遇到新平台都會先透過HelloWorld範例作個初步入門。這個步驟花了我一個早上的時間,實際上大部份的時間都在學習 Objective-C 的語法。 第一眼看到Objective-C的時候,感覺實在很彆扭,不過透過網路上找到的資料,雖然不致於說己經會寫Objective-C的程式,但至少也看的懂它的語法。 有了基本知識後,接下來當然是直接進入Porting的動作。Porting的項目我選了 25940p ,因為這個小遊戲的成像是使用OpenGL,而且也有成功移植到PSP的經驗,所以感覺上應該也可以很容易的移植到 iPhone 上去。 結果想不到一開始卡關了差不多三個小時左右,全卡在不知道怎麼從Objective-C裡叫用我原來寫的C++程式碼(25940p是用C++寫成的)。可是我看到的Sample裡,明明混用了C和C++的程式碼,問題不知道出在那邊。 就這樣花了大概三小時才摸出一個所以然來。知道問題出在那裡後,很快的就把25940p的碼再加上使用到的部份smallworld2的碼在一行不改的情況下整合進去,接下來的就只有剩下成像和輸入的部份需要作點調整。 + + + 其實要解決我遇到的問題非常簡單,重點就是 Objective-C不能直使用C++的碼,但可以直接使用C的碼。所以解決辨法很明顯了,使用間接的方法來使用C++的碼就行了。從Objective-C使用C,再透過C來使用C++就能達到目的。 底下用個十分簡單的例子來表達這個概念。 test.h裡定義了些東西,比如說有個Test類別。 ... class Test { ... }; ... 接著在你的Objective-C定義檔main.m裡,無論是用#import "test.h"或#include "test.h"都會產生錯誤。我們需要另外產生一個.h,再定義一些純C的API,透過這些純C的API來使用test.h裡的東西,例如 Test類別。 如下,定義個test_wrap.h。 ... void UseTest1(); void UseTest2(); ... 並且實作這些API,然後在main.m裡import或include test_wrap.h並呼叫純C的API,這樣就不會有什麼問題了。

薩爾達傳說:夢見島資源

圖片
薩爾達傳說:夢見島的地圖是由160乘128個Tile所組成,每個畫面大小是10乘8個Tile,也就是說完整的地圖是由16乘16共256個畫面組成。每次當林克移動到畫面邊緣的時候,整個畫面會往林克前進的方向捲動,一次捲動一整個畫面。 地圖的每個Tile大小是16乘16Pixel。下面是我千辛萬苦才找到的Tileset,實在是太感謝提供這張圖的善心人士。找到這張圖的時候,差點就沒哭出來。 有了Tileset之後就可以開始編輯地圖了,所以我又繼續上網尋找遊戲地圖,最後好不容易也讓我找到了。 參考這張全圖再加上Tileset就可以使用地圖編輯器"慢慢"畫了。作這事真的是需要些耐心,好在我是邊開發編輯器邊慢慢編輯地圖,還不會覺得太難受。最後的結果是完成了大約90%以上的地圖,因為有些部份少了Tile,有些部份不知怎麼編輯,所以就暫時先這樣。 + + + 除了地圖Tileset之外,我也收集到一些Sprite,只不過這些Sprite資源沒辨法很簡單的直接使用,主要原因是Tileset裡面的元素不像地圖Tileset每一格都是對齊的,像是下面這張圖。 不過還好我還沒有真的要把夢見島完整的實作出來,只作了個小小的測試Demo,自己用小畫家拆幾塊出來拼倒是還好。 下次再來看看怎麼實作這個Demo。

實作鋤草機GamePlay

圖片
設定物件類別 good的GamePlay使用 Lua 實作,所以繼續閱讀以下內容之前,我會先假設你已俱備使用 Lua 的基本能力,當然這也表示你對什麼是程式設計有基本的概念。 首先開啟編輯好一個關卡的鋤草機專案,接著在資源樹上點擊關卡開啟關卡編輯器。 在屬性檢視器裡,原本空白的Script欄位要填入個物件類別名稱。當我們執行遊戲時,RunTime除了會根據關卡資源建立所有物件外,如果物件有指定物件類別旳話,則會另外建立一個物件類別的Instance關聯到這個物件,然後在執行時期執行這個物件類別所定義的Script。物件類別對應於物件類別的Instance的關係,就相當於資源對應於物件。 物件類別 如果你OOP的經驗的話會更容易了解些。在good裡面的一個物件類別,相當於OOP裡的一個Class。它可以有Data Member,也可以有Member Function。所以在前面才會說,當物件建立時,假如有指定物件類別的話,會另外建立一個物件類別的Instance關聯到這個物件,這個意思和我們在作OOP的概念是一樣的,是Class和Instance的關係。 good的物件類別是以Lua的Table實作的。下面的範例使用Lua的語法建立一個空的類別,也就是一個空的Table。雖然沒什麼功能,不過這樣子就能使用了,可以把它填入Script欄位。 Level = {} 現在Level是個空類別,為了使它擁有GamePlay我們要為它加上處理Event的能力。 Level.OnStep = function(param) end 我們替Level類別加上了一個叫作OnStep的事件處理函式。OnStep是個特殊的函式名稱,這個名稱被good使用來作為一個Event通知函式。每一個Frame這個函式會先被呼叫一次,也就是說假如FPS是60的話,那麼OnStep函式每秒鐘會被呼叫60次。當然前題是你指定給物件的類別必需要有提供了OnStep的實作。 OnStep這個函式有個叫作param的參數。這個param參數傳來的就是前面提到過的物件的Instance。 除了OnStep事件之外,目前還支援的事件有OnCreate以及OnDestroy事件,分別對應到物件建立完成後以及物件被刪除前的通知。這二個事件也同樣有一個param參數,功能和OnStep事件的param參數一模一樣。 鋤

最早的測試地圖

圖片
最早最早good在開發的時候是先從地圖編輯器開始的,所以需要編個東西作測試,不過當時拿來作測試用的地圖並不是 鋤草機 的地圖,而是 薩爾達傳說:夢見島 的地圖。 夢見島是我最愛的遊戲之一,本來除了想編輯鋤草機的一個關卡之外,還有另一個希望可以達成的目標,那就是我也想要使用good來製作夢見島。會選擇夢見島除了說這是我的最愛之外,另一個主要原因是我沒有美術支援,只好上網找現成的資源,也剛好讓我找到夢見島的一些圖形資源,所以很快的就決定是它了。 只不過後來目標又稍微改變了一下,變成先不要完整實作夢見島,改成實作一個使用夢見島的地圖還有武器系統的小型MMORPG,可以讓大家在地圖上拿著各種武器亂鬥,只不過這個計劃目前也還遙遙無期中。 至少我也好不容易把夢見島的世界地圖編的差不多了,有圖為證。另外也用Script寫了一個超小Demo,這個Demo下次再來作介紹。

good程式設計:資源 vs 物件

資源和物件(指RunTime物件)的差別,以最簡單的方法作解釋的話,可以這樣子說。所有在編輯器裡面編輯的東西都叫作資源,而所有在你點擊了Play後執行起來的遊戲的畫面上看到(包含看不到)的東西都叫作物件。 關於資源的部份用這樣子作解釋應該不會有什麼問題,只有一個小地方要再注意一下。在關卡資源裡擺設的東西,我們也叫作物件(關卡物件)。不過這點也不會有太大衝突,只要記住一點,因為我們是在編輯器裡編輯這個關卡,所以這關卡是個資源,而關卡裡的東西也同樣都是資源。 資源的類別主要有四類:貼圖、地圖、精靈和關卡。 + + + 至於在RunTime時期,所有東西都是物件這件事應該也不難理解。既然說所有東西都是物件,自然也包含了關卡本身。 RunTime物件會在建立時根據它所參考資料類別作初始化。例如說參考了一個精靈資源來建立一個物件,RunTime就會建立出一個精靈物件給你。而參考了一個關卡物件,RunTime就會建立一個關卡物件給你,當然也連帶的將關卡底下的所有子物件一起建立起來。 物件的類別對應到資源的類別,主要也有四種:貼圖物件、地圖物件、精靈物件和關卡物件。 + + + 最後再補充一點。在RunTime時期關卡物件只能有唯一個,而其它類別的物件則數量不定,且它們的Parent都是那個唯一的關卡物件。

good程式設計:ID

在進入good的程式設計之前,有一些基本的觀念有必要先了解,否則在實作GamePlay時遇到了某些問題,可能會莫名其妙而且陷入一直找不到問題所在的困境。 這篇文章內容要介紹在good裡面非常重要的ID的概念。 所有東西都有ID 在前面編輯器的簡單介紹裡面可以發現到,所有資源不論是什麼樣的種類,在屬性檢視器裡面都可以看到有個ID屬性。除此之外,在關卡資源裡的所有物件也都擁有自己的ID。(ID是個數字) 每一個ID都是唯一 所有使用到的ID,無論是資源項目的ID或者是物件的ID,每一個都是獨一無二的。也就是說如果有個精靈資源的ID是12,那麼12這個ID就會只有一個,不會再有另一個資源或是物件的ID也是12。 ID可以重覆使用 good裡所使用的ID都是由編輯器或是RunTime維護及分配使用,當一個資源或物件被刪除時,這個資源或物件原來使用的ID就會被回收。而被回收的ID並不一定會被立即重覆使用,有可能在你作了幾次新增動作之後才發現到,新增的資源或物件被分配到一個被回收的ID。當然也有可能在你刪除了一個資源或物件之後,被回收的ID立刻就被重覆使用在下一個新增的資源或物件上。 以上這三點是對於good編輯器在ID上的使用需要了解的基本知識。 +++ 那麼RunTime呢? 當你在編輯器工具列上點擊Play鈕時,good RunTime會載入第一個關卡資源,根據第一個關卡資源的內容,建立起所有的物件,這些物件包含貼圖物件、地圖物件和精靈物件等。good RunTime會根據在關卡編輯器裡所編輯的物件次序和類別以及其相關屬性,一個一個把這些物件建立起來。 其中最重要的是,當我們使用關卡編輯器作編輯時,關卡中的每一個物件都會自動分配到一個ID。而RunTime在建立這些物件時,同樣的也會分配一模一樣的ID給這些RunTime物件。 因為在執行過程中的ID使用原則一樣遵守上面所列的三項原則,所以動態刪除的物件的ID會被回收,而動態產生出來的新物件的ID也可能會是重覆使用舊ID。