發表文章

目前顯示的是 2009的文章

good基礎教學

圖片
這篇文章要來一步步製作一個彈跳球的範例,完成後相信對good會有一個基本的認識。 ; 1, 首先打開good編輯器,這時專案是空白的,按下Ctrl+S將空白專案儲存為bounceball.txt。 2, 接著在工具列上點擊NewTexture或選單Project->NewTexture加入用來作為彈跳球的face.png。 會使用png格式的圖檔的目的是,因為我們這張圖會用到鏤空效果,png格式可以帶alpha,我們可以很容易利用png的alpha來作alpha鏤空。 * 所有會使用到的圖形,我們都需要以加入貼圖的方式事先加入到good專案內才能夠使用。 3, 再用同樣的方式加入一張準備用來作為背景的圖形lace_0103.gif。 到此先按Ctrl+S存檔。(隨時存檔是個需要養成的好習慣) 4, 接下來在工具列上點擊NewSprite或選單Project->NewSprite加入一個精靈。一般在遊戲中,在畫面上會動的人物怪物等等我們把它叫作精靈(Sprite)物件,以我們要製作的範例來說,我們現在要建立一個表示彈跳球的精靈。 我們前面共加入了二張圖,要用來作為彈跳球的圖是tex1,這個名稱我們也可以在加入圖形時自己命名。這張圖的大小是107x107,所以我們在TileWidth/Height上填入了107和107,表示要使用整張圖。同時我們在name欄位自己填入了ball這個名字,當然也可以像加入圖形時保留空白,讓編輯器為我們自動加上名字。 完成後要加入一個空白的精靈,我們需要加入影格才能在畫面上顯示東西。因為我們已經事先設定好要使用整個圖形的大小,所以可以看到在右下角的貼圖檢視區上選取了整張的圖(透明紅色),接著按下貼圖上方的新增影格鈕加入影格。這樣就完成了精靈的設定,再按下Ctrl+S存檔。 5, 接下來在工具列上點擊NewLevel或選單Project->NewLevel新增一個空白關卡,準備來建立我們的遊戲場景。 good的關卡是我們實際執行遊戲時會呈現在畫面上的東西,我們可以建立很多不同的關卡,但同一時間只會有一個正在執行。我們可以在關卡上擺放各種物件,像是背景地圖或精靈等等,這些物件在關卡執行時都會顯示在畫面上。 首先我們加入一個背景圖,在關卡編輯器上方的工具列點擊NewTextureBg選擇tex2也就是我們事先加入的背景貼圖,完成

Stge基礎教學

這篇文章作為stge script的入門教學,會告訴你如何撰寫stge script來描述簡單的彈幕效果並整合到good裡,透過good的顯示功能呈現到畫面上。要補充說明的是,雖然stge script最初是為了射擊遊戲而設計的,但因為它也有基本足夠的彈性,所以也能夠作為粒子效果來應用。 ; 1, 首先打開good Game Editor,按下Ctrl+S儲存空白專案,命名為StgeTest1.txt。 2, 接著在工具列上點擊新增空白關卡(New Level),或點擊選單Project->New Level...加入空白關卡。 3, 在新關卡(level1)的屬性檢視器上的ClearColor,開啟顏色選擇對話盒並設定成黑色。 4, 在工具列上點擊新增空白腳本(New Script),或點擊選單Project->New Script...加入空白腳本,檔案名稱填StgeTest1.lua。 5, 在資源樹上點擊level1回到空白關卡1的屬性檢視器,在Script欄位上填入Level。 6, 按下Ctrl+S儲存檔案。 現在已建立基本的資源,接下來全部使用script來建立我們要的功能。首先撰寫一些good script作出基本框架。 Level= {} Level.OnCreate = function(param)   Stge.RunScript('StgeTest1') end Level.OnNewParticle = function(param, particle)   local obj = Good.GenObj(-1, -1)   Good.SetDim(obj, 0,0, 3, 3)   Good.SetBgColor(obj, 0xffff0000)   Stge.BindParticle(particle, obj) end Level.OnKillParticle = function(param, particle)   Good.KillObj(Stge.GetParticleBind(particle)) end 如上,我們有個叫作Level的table,並加入了三個空白的function,這三個function是good預先定義的event。OnCreate是當物件建立時會被呼叫,並且只會被呼叫一次,

究極超人

圖片
有圖為證!

關於C/C++的指標

我想應該還有不少人在使用指標上有些地方觀念不大清楚,比如說下面二個函式,那個是正確的?為什麼?像這樣的問題如果弄不清楚,寫出來的程式一定非常危險。 // 為簡化忽略檢查 void alloc_mem(char* p) // 版本1 {   p = new char[100]; } void alloc_mem(char** p) // 版本2 {   *p = new char[100]; } 如上,這個函式要配置大小是100個字元的記憶體並從傳入的參數p回傳,這二個版本除了輸入參數不一樣外大致上是一樣的;從第一個版本來看,參數是一個字元指標,記憶體配置出之後直接傳給p,如果觀念正確的人一定可以馬上指出這樣的寫法是錯誤的,第二個版本才是正確能work的。 現在就來說明為什麼,在這之前先要了解在C/C++中,函式的參數是如何傳遞的,在C/C++中函式的呼叫所傳入的參數是透過堆疊(Stack) 來傳入函式的,不懂什麼是堆疊也沒關係,就把它看成是另外一塊記憶體也行,當在程式中呼叫某個函式時,傳入的參數會先被複製到這塊記憶體中,當在函式中要使用這些參數時再從堆疊中去取出來。 以版本1的例子來說明,如下在程式中大概會這樣呼叫。 char* pp = NULL; alloc_mem(pp); pp一開始的初值是NULL,當呼叫alloc_mem時,pp的值會被複製到堆疊中(傳址),這種情況和以下的code事實上是對等的,只不過 p的值一開始被初始化成和pp的值一樣,p就好像一個區域變數一樣,一離開函式後這個變數就無效了,所以在外面的pp的值永遠都不會改變,同時在涵式中 new出來的記憶體也lost掉了。 void alloc_mem() {   char* p = new char[100]; } 再來看版本2,它的參數是一個指標的指標,這是什麼意思,我們先從實際使用上來看,如下。 char* pp = NULL; alloc_mem(&pp); 這次我們把pp這個變數的位址傳入涵式,所以在涵式中所得到的是pp這個變數的位址,在函式中p所含的內容是pp的位址,pp是一個char*形態的變數,p是一個指標它的內容是char*的形態,現在p已經指向pp了,所以對p的內容作改變,相對的pp的值也會跟著改變。 現在來看另外一個類似例子,會比較清楚些。 void change_v

分散式的線上遊戲伺服器

smallworld是smallworld2網路架構的第三層(應用層)。 smallworld2網路架構分成四個階層,最底層是串流層(Stream),負責提供最基本的TCP/IP串流封裝及連線管理。第二層為封包層(Network),負責提供格式化的封包支援以及完整的斷線處理機制。第三層為應用層(Smallworld),提供動態可擴展的分散式網路架構。第四層為遊戲應用層,提供和線上遊戲一般應用邏輯相關支援。 設計smallworld應用層最大的困難在於,必須讓使用者也就是應用程式的開發者,能夠以開發單一伺服器應用的單純方式,來開發一個分散式架構的多人連線應用程式,所有複雜的細節都需由底層處理掉。smallworld建立了二個概念來達成這個目標,分別是Scope及VirtualConnection。 對於伺服器S而言,所有訊息都是透過一條Connection傳送出去的。Connection的另一個端點可能是一個Client,也可能是另一個Server。而這個端點可能是與伺服器S有實際建立連線,也可能是間接和伺服器S建立連線。假如這個Connection與伺服器S間有實體連線,則伺服器S就能直接把訊息傳送給對方,否則就以間接的方式轉送過去。無論這個Connection是直接或間接的連線,對於伺服器S來說,是不必關心的事情,底層自動會想辨法把訊息傳送給這條Connection的對應的端點上去。所以對伺服器S而言,Connection是虛擬的。 Connection的取得一律透過定義Scope作為Filter來獲得。以線上遊戲為例。當一個玩家登入遊戲後,就會有許多個Scope和他建立關係,例如這個玩家的可見視野、玩家加入的組隊、公會、聊天室、P2P交易、商店等等。這些全都是Scope,概念一樣,只是定義不同。透過定義好的Scope,再拿這個定義作為Filter由可以到達的在線上的伺服器收集符合的Connection,之後就可以對這些Connection作操作。 以上是構成smallworld的二個重要Concept。 ------------------ 套用smalllworld的框架就可以很容易建立可以動態擴展的線上遊戲架構,不過在實際應用上還是會有其它問題。舉個例子說明:假如我以單一伺服器的方式實作了一支Server程式,這支程式可以處理完整的虛擬世界。伺服器執行起來後

C++ Const

作個小實驗。 void func_a(const int& a) {   int& b = const_cast<int&>(a);   b = 200;   cout << a << ", " << &a << endl; } int main() {   const int a = 100;   cout << a << ", " << &a << endl;   func_a(a);   cout << a << ", " << &a << endl; } ------------------------------------------------- 100, 0012FED4 200, 0012FED4 100, 0012FED4 ------------------------------------------------- 可以看到,在func_a內,明明a的值已經被改成200了(可以由變數位址確認)。為了確認這一點,加上了第四個cout,如下。 cout << *(int*)&a << ", " << &a << endl; ------------------------------------------------- 100, 0012FED4 200, 0012FED4 100, 0012FED4 200, 0012FED4 ------------------------------------------------- 可以看到第四個cout果然印出a的內容已改變為200。但是第三個cout卻是印出原來的值100,這是為什麼?答案在將程式反組譯,從組合語言的層面來看就很明顯了。 cout << a << ", " << &a << endl; 0041E6B2 push offset std::

自動解壓縮程式原理

圖片
許多朋友來信詢問關於編輯器 CreateExecutable 的運作原理,所以這篇文章就來講講背後的技術原理。 底下提供一個簡單的方法,也是編輯器使用的方法,而這個方法只能適用在Windows作業系統,因為它使用了Windows系統的資源機制。Windows提供了一系列的API可以讓你對 PE 執行檔內的程式資源作存取。利用這點,只要我們作點變化稍加利用就能夠達到我們的需求。 + + + 先說明good編輯器產生獨立的單一可執行檔的操作過程。 1,開啟舊專案 2,執行選單Project->Create Executable 3,選擇輸出目標 4,按下儲存 整個動作完成後,會產生一個不必編輯器就可以獨立執行遊戲執行檔。其實這個動作就相當於,編輯器會幫你產生一個內建了遊戲資源的播放器(Player)。 那麼這個播放器是從那來的?答案是它是事先建立好,然後被藏在編輯器裡。當使用者執行了Create Executable操作時,編輯器會從自己身上取出這個Player,然後再把專案所有相關資源,當成資料又全塞到Player裡,全部掺在一起打包成一個獨立的執行檔。當執行這個執行檔時,Player會先從自己身上找到遊戲資源,解開,載入遊戲,然後開始執行。 要完成上述的功能,主要有二件事情。第一就是如何把資料藏到執行檔內,第二就是載入藏在執行檔內的資料,以下分別說明。 + + + 在VS.NET裡執行Add Resource功能時可以選擇要加入的資源類型,這些類型是Windows預設的,例如Bitmap/Cursor/Dialog/Menu/String Table等等。假如要加入的不是內建的資源類型,你也能在AddResource對話盒點選Custom鈕加入自訂資源。自訂資源用一個自訂的字串來作區別,例如"GameData"。如下圖所示,加入了一個"TEST"類型的資源,ID是IDR_TEST1,對應到外部檔案res\test1.bin。 以上是用工具的方式來加入自訂資源,只要是能夠編輯程式資源的工具都能拿來應用,而不限只能用VS.NET。接著我們來看怎麼用程式來加入資源。 Windows提供了 BeginUpdateResource , UpdateResource 及 EndUpdateResource 三個API來讓

C++的靜態多型

class Foo : public Base<Foo> { }; 在上例中,類別Foo繼承自類別Base,類別Base有一個Template參數,而在此例中是以類別Foo作為參數傳入。以上這個宣告是合法的,根據C++的標準規格,當類別Foo宣告之後,Foo這個名字就算是已被定義了,所以能夠立即被拿來餵給類別Base作為參數使用。 template<class T> class B { public: void sayHello() { T* pThis = static_cast<T*>(this); pThis->hello(); } // 可被子類別覆寫(overridable) void hello() { cout << "B::hello" << endl; } }; class D1 : public B<D1> { }; class D2 : public B<D2> { public: void hello() { cout << "D2::hello" << endl; } }; int main() { D1 d1; D2 d2; d1.sayHello(); // 印出 "B::hello" d2.sayHello(); // 印出 "D2::hello" return 0; } 在上面的範例中我們宣告了一個類別B,還有二個繼承類別B的類別D1和類別D2。類別D1和類別D2以上面所提到的方式,以自己為參數繼承template類別B。類別B中有個sayHello公開函式,它會呼叫一個叫作hello的函式。hello函式在類別B中有一個預設的實作,會在畫面上列印出"B::hello"的文字訊息,而這個函式hello可以被子類別所覆寫。如果子類別有覆寫hello函式實作自己的版本,則在呼叫sayHello時子類別的實作就會被呼叫到,否則就會呼叫到預設的類別B的版本。 這裡使用到的關鍵技巧就在於static_cast<T*>(this)這

PNG秀圖的處理

圖片
首先看看底下這張PNG格式圖檔,正確顯示結果應該如下原圖所示。 載入後以API AlphaBlend 作Per-Pixel Alpha Blend如下所示,結果是不正確。 檢查AlphaBlend規格發現,以AlphaBlend作Per-Pixel Alpha Blend時,會以底下公式計算每一個像素的值。 Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red; Dst.Green = Src.Green + (1 - Src.Alpha) * Dst.Green; Dst.Blue = Src.Blue + (1 - Src.Alpha) * Dst.Blue; 要作正確PNG秀圖(或者說有帶Alpha Channel的圖),底下列出虛擬碼。 if (255 == Src.Alpha) { Dst.Red = Src.Red; Dst.Green = Src.Green; Dst.Blue = Src.Blue; } else if (0 != Src.Alpha) { Dst.Red = Src.Red * Src.Alpha + (1 - Src.Alpha) * Dst.Red; Dst.Green = Src.Green * Src.Alpha + (1 - Src.Alpha) * Dst.Green; Dst.Blue = Src.Blue * Src.Alpha + (1 - Src.Alpha) * Dst.Blue; } ; AlphaBlend測試代碼: BLENDFUNCTION bf; bf.BlendOp = AC_SRC_OVER; // 目前僅支援此操作. bf.BlendFlags = 0; // 必須為0 bf.SourceConstantAlpha = 255; // 使用Per-Pixel Alpha bf.AlphaFormat = AC_SRC_ALPHA; // 使用Per-Pixel Alpha dc.AlphaBlend(0, 0, sz.cx, sz.cy, memdc, 0, 0, sz.cx, sz.cy, bf);

實作組合語言除錯器

圖片
這幾天寫了個簡易版的組合語言除錯器。原因是當我在整理並重新組譯之前寫的一個組合語言小程式時,找到了一個小Bug。Debug的過程花了點時間,因為手上還沒有Debugger可用。所以就動手把之前就已計劃有需要製作的除錯器實作了一個可用的簡單版本出來,目前俱備基本的Memmory Dump, Unassemble, Breakpoints 等除錯功能。 這張圖裡面可以看到的指令好像跟x86指令集裡的有點不大一樣,暫存器看起來更不同。沒錯,因為這是我設計的虛擬機器指令集,我把它命名為 jaja XD

實驗中國象棋 for Windows Mobile Port

圖片
花了一點時間把以前作的實驗象棋植移上去,純粹當作睡前消磨時間的小玩意。這次還新增了簡單的開局資料,至少能避免每次都走出一模一樣的棋局。 PS:WM PC 模擬器在有Focus的情況下PrintScr鍵似乎無效。

good sample list

在一邊開發good編輯器及核心功能的時候,也同時會製作使用到對應功能的小專案來作測試。目前總共有8個範例,有的是很小的測試程式,也有的是具體而微的小遊戲,下面作個整理及簡單介紹。 weeder 鋤草機 這是最早作的範例,因為 鋤草機 這個小遊戲感覺起來最單純,所以就拿它作為一個標的,以把它用good實作出來為目標來開發good相關功能。一開始只編輯了一個關卡,後來花了點時間,將原作裡12個關卡全部編輯出來,作成完整版。 zelda 薩爾達 這是最早開發good的起因,為了重製薩爾達作為練習。一開始重點放在地圖編輯器上,也花了不少時間編輯世界地圖。整合了 Lua 後,再加上一小段程式碼,讓林克可以在地圖上走動。不過和最終的目標還相差太遠,只能在地圖上走走路,作些簡單的碰撞,只好等有機會再繼續未完成的工作。 mmc 魔法寶石方塊 為了驗證good也能製作出這種類型的遊戲,花了幾天時間編輯實作這個小遊戲的雛型出來,效果還不錯。 texture 貼圖物件 加上這個功能後,才能以更簡單的方法製作拼圖遊戲。沒有這個功能,也能作出拼圖這樣的遊戲,只不過是需要使用較曲折麻煩的方法作到。 color 色塊物件 有了色塊物件後,就不必以填貼圖的方式來填色,省圖又方便。 stge 粒子系統 很早就規劃打算要整合stge模組。雖然stge本來是專門設計來製作彈幕射擊遊戲,但因為它本身的設計就是一個以粒子為核心的系統,所以不一定只能被用來製作射擊遊戲,也能夠作為一般用途的粒子系統。目前還不算100%整合完成,但已經可以拿來作應用。等到更完整的整合完成後,就能用good來重製 25940p 。 puzzle 拼圖 在貼圖物件功能加入後,就能很簡單的製作出這類型的應用。 mario 瑪莉歐 這是拿來作gameplay練習的小品,實際上寫的不是很好,細節部份還有許多加強的空間。

Win32 拖曳Tree-View項目

處理Win32的Tree-View Item主要分為三個步驟。 開始拖曳通知。 拖曳中處理。 拖曳結束。 底下對拖曳程序的每個階段作個簡介,並提供實作範例。 開始拖曳通知 當使用者開始拖曳(Drag)Tree-View Item時,Tree-View元件會經由WM_NOTIFY訊息發送一個TVN_BEGINDRAG通知給父視窗,這時應用便可以以這個通知得知使用者從什麼位置開始拖曳那一個Item,並開始進入拖曳程序。 case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case TVN_BEGINDRAG: // ...由此進入拖曳程序 Main_OnBeginDrag(); break; } break; Main_OnBeginDrag是應用程式定義的一個函式,主要的任務是設定好相關狀態表示進入拖曳程序,並由Tree-View取得一個暫時的ImageList元件,可以用來畫出拖曳中的Item。 void Main_OnBeginDrag(HWND hwndTV, LPNMTREEVIEW lpnmtv) { // 取得暫時的ImageList. HIMAGELIST himl = TreeView_CreateDragImage(hwndTV, lpnmtv->itemNew.hItem); // 開始進入拖曳. ImageList_BeginDrag(himl, 0, 0, 0); ImageList_DragEnter(hwndTV, lpnmtv->pt.x, lpnmtv->pt.x); SetCapture(GetParent(hwndTV)); g_fDragging = TRUE; } 拖曳中處理 當使用者以滑鼠左鍵開以拖曳物件時,Tree-View送出一個TVN_BEGINDRAG,接著應用程式需要處理WM_MOUSEMOVE訊息來反應使用者拖曳的動作,直到使用者將滑鼠按鍵放開產生一個WM_LBUTTONUP訊息時,才結束拖曳動作。附帶一提,如果使用者是以滑鼠右鍵開始拖曳物件,則產生的通知是TVB_BEGINRDRAG,並以對應的WM_RBUTTONUP訊息作結束訊號。 void Main_OnMouseMove(HWND hwndParent, HWND hwn

good spec

程式語言 程式語言:C/C++ 編輯器 系統平台:Windows 視窗框架: WTL 執行時期 系統平台:Windows,MacOS/iPhone 程式庫:smallworld2,stge 第三方程式庫: Lua / SDL / zlib / Boost.Spirit 繪圖(gx) GDI SDL OpenGL 編輯器使用WTL作視窗框架實作,所以主要是以C++ Template實作。而Runtime部份則整個使用C++ Template實作,主要區分成三大模組:good、ed和rt。另外還有個gx模組,簡單定義了圖形介面讓其它模組使用來作圖形繪製。 good模組提供了資源管理的功能,ed模組繼承good模組再擴充編輯功能,而rt模組繼承good模組實作Runtime及提供Script擴充介面。其實正確來說,rt模組並非繼承good模組,而是包含good模組,透過包含及使用good模組作為資源管理。 good模組是個狀態管理器,只能載入資源和讀取內容。ed模組則繼承good模組提供編輯資源內容的功能,可以修改資源並存檔。而rt模組透過good模組載入資源,再經由RunTime及Script將資源和物件關聯起來,並付予物件動作邏輯,最後再透過gx模組將結果呈現在畫面上。

good::gx介面

good::gx是負責繪圖的介面。它很簡單,至少目前來說還很簡單。只有二個介面:Image及Graphics。 Image介面對貼圖作一層薄薄的包裝,good執行的時候,所有使用到的貼圖的地方全都透過這一層介面作存取。下面是Image介面定義。 template<class ImgT> class Image { public: bool isValid() const; int getWidth() const; int getHeight() const; bool hasKeyColor() const; int getKeyColor() const; void setKeyColor(int keycolor); }; Graphics介面提供了很少的功能,目前只有貼圖和畫色塊二個功能,因為目前good的成像目前只需要這二個功能就足夠畫出所有東西。下面是Graphics介面定義。 template<class ImgT> class Graphics { public: bool drawImage(int x, int y, ImgT const& img, int srcx, int srcy, int srcw, int srch); bool drawImage(int x, int y, ImgT const& img) { return drawImage(x, y, img, 0, 0, img.getWidth(), img.getHeight()); } bool fillSolidColor(int left, int top, int width, int height, int color); }; + + + 實際應用的時候,只需要針對不同繪圖平台作不同實作品的提供,對於good內部而言,幾乎不必作任何修改。目前good::gx有四種實作品,GDI、SDL、imgp及OpenGL。

OpenGL 畫色塊

圖片
前幾天用OpenGL glDrawArrays方法來畫色塊時遇到問題。色塊是畫出來了,不過另外一個不使用顏色的貼圖方塊也被疊上了和色塊一樣的顏色,真是莫名其妙。 原來,畫色塊的時候使用glColor,而且當畫貼圖方塊時,glColor指定的顏色也會被使用到,所以要再以glColor指定一個白色的顏色值進去(0xffffffff),這樣畫出來的結果才會正確。 看起來好像對了,不過還是不對勁,因為我畫的是一個RGB(0x88ffff00)的黃色色塊,可是這顏色怪怪的。經過幾次測試發現這顏色和畫色塊之前所畫的一個貼圖方塊的顏色有關係。 經過研究後,發現問題還是出在狀態上,畫色塊的時候除了要以glDisableClientState關掉GL_TEXTURE_COORD_ARRAY狀態外,也還要再以glDisable把GL_TEXTURE_2D狀態關閉,這樣子就得到正確的結果了。 + + + 換說回來,雖然作過幾個3D應用的專案,甚至還參與過3D引擎的實作,我還是不懂3D。往好處想,這表示我還有很大的成長空間~

編輯魔法寶石遊戲資源

圖片
底下是J2ME版小香咪咪方塊的寶石方塊圖形資源,直接可以拿來使用,有這張圖就足夠來作個魔石寶石小遊戲了。 全部共有六種不同顏色的寶石方塊,再加上一個威力方塊P,不過這次我們只會用到那六種不同顏色的方塊。 + + + 首先開個新專案,視窗解析度設成和J2ME版小香咪咪方塊的大小一樣,176x208,其它保留預設值,然後專案存檔。 接著把圖形資源加入,每個Tile大小是16x16。一一把六種不同顏色的方塊和它對應的同色爆炸方塊加入製成精靈資源。在加入方塊精靈資源時,有個小地方特別注意的是,先加入一個顏色的方塊後,接著再加入這個顏色方塊的爆炸版本。依照這個規則把所有方塊加入,共12個精靈。 如上圖,接著再加入一個地圖資源。大小是11x13,每個Tile是16x16。上面缺的一塊是故意保留,而不是畫到一半就抓圖下來。缺的這一塊到時候會在關卡編輯器裡增加一個色塊來填,為了作出方塊是由正上方落下來的效果,我們會拿一個色塊來作遮擋。 接下來如上圖所示新增一個關卡資源,大小設定成和視窗一樣,指定黑色作為背景清除顏色。加入事先編輯好的地圖資源,一個遮擋用的色塊,還有三個作了提示下一回合要出現的方塊。以上完成所有遊戲資源編輯,剩下的就是用Lua編寫遊戲邏輯,下回再繼續。

魔法寶石消除演算法

圖片
魔法寶石類遊戲的消除演算法主要有二種,一種我稱為米字形規則,另一種我稱它為十字形規則。 米字形規則 米字形規則是以一個方塊為中心,假如在這個方塊的上下左右或斜向的方向上包含自己本身有超過三個以上相同顏色的方塊存在,則這個方塊就可以被消除。 如上圖中間的區塊部份,1號方塊周圍用紅色線框框起來的四個方向,垂直(A)、水平(B)及二條斜向(C及D)。以此規則,上圖右側區塊部份中標示1到5的五個橘色方塊可以被消除。 檢查的時候,每一條方向是各自獨立的,也就是說上述超過三個以上相同顏色的方塊這個限制必須是在同一方向上才能成立。只要其中一條方向消除條件成立,剩下的方向就可以略過不作檢驗。同理在同一個方向上只要檢查出有三個以上相同方塊存在,剩下的方塊就不必再檢查。 以垂直方向為例。 首先檢查1號方塊上方的方塊是否和1號方塊是相同顏色,假如不同的話再往上一個方塊也沒必要再作檢查。假如相同的話就再檢查最上面那個A方塊,假如也是一樣的話,那剩下的下面二個A方塊就沒必要再檢驗了,因為已經有三個一樣的方塊存在了。 假如最上面的A方塊和1號方塊不同的話,則再檢查1號方塊下方的A方塊。假如這個方塊和1號方塊不同的話,那最下面的A方塊也沒必要再作檢查了。反過來說,假如這個方塊和1號方塊是一樣顏色的話,再加上1號方塊上面那塊就有三個一樣顏色的方塊。 同理可以推出反向及其它方向的檢驗規則。 十字形規則 十字形規則是以一個方塊為中心,如果包含自身在這個方塊的上下左右的相隣方塊中有四個以上相同顏色的方塊存在,則這個方塊就可以被消除。 如上圖中,可以看到要判定1號方塊是否可以消除的範圍比米字形規則大的多,依據十字規則的檢驗,最後可以找出1到4號的四個黃色方塊可以被消除。 十字形規則的檢查方法,其實就是一個暴力搜尋法。關鍵原理是建立一個表,一開始把要檢查是否可消除的方塊(X)加入這個表。接著以如下的迴圈不斷的檢查,直到結果出來為止。 如果檢查表為空,則結束迴圈並返回X不可消除。 從表中取出一個要檢查的方塊(Y),並檢查Y的上下左右的四個方塊是否和X相同: 如果Y(上)和X相同,則計數加1同時把Y(上)加入檢查表。如果計數值為3,則跳出迴圈返回X可消除。 如果Y(下)和X相同,則計數加1同時把Y(下)加入檢查表。如果計數值為3,則跳出迴圈返回X可消除。 如果Y(左)和X相同,則計數加1同時把Y(左)加入檢查表

good的資料格式

自從使用 Ini 之後我就不再碰 XML 了,在任何情況下,只要情況允許我都會儘可能的使用Ini作為資料格式或設定檔案。雖然Ini不像XML那樣天生就能表達結構化的資料,但也不是完全不行,至少應用在good上就沒什麼問題。 good專案資料格式使用Ini,再透過smallworld2的Ini模組就能很簡單的操作讀寫。 + + + 專案檔頭 每個good專案檔裡,至少會有個名稱叫作good的Section,這相當於檔案的Header。這個Section包含了整個專案的內容資訊,是個總表。底下是個典型的範例。 [good] version=0.3 name=mmc window=176 208 texs=1 maps=2 3 sprites=4 5 6 7 8 9 10 11 12 13 14 15 levels=18 如上。格式版本是0.3;專案名稱是mmc;視窗的大小或解析度是176X208;有一個ID為1的貼圖資源;有二個ID分別是2和3的地圖資源;有12個精靈資源;以及一個ID為18的關卡資源。 多麼簡單一目了然,這就是我喜歡使用Ini的原因。 Script資源 接著有個叫scripts的Section,這個Section的內容是Script資源列表,和其它類型的資源比較起來是比較特別的,因為它不是在Section good裡面的一個項目,而是獨立出來一個Section。底下是個範例。 [scripts] 16=./mmc.lua 在scripts Section裡,每一個項目表示一個Script資源,項目的Key值表示為這個Script資源的ID,而項目的Value值表示為Script資源的檔案路徑。在good裡面的所有牽涉到檔案路徑的欄位,內容一律都是相對於專案檔本身的相對路徑。 貼圖資源 在專案檔頭裡列出的貼圖資源,每一個項目都對應到一個貼圖資源Section,底下是個簡單的範例。 [tex5] name=tileset fileName=./weeder.bmp hasKeyColor=1 keyColor=253 0 255 貼圖資源Section的名稱固定以tex開始,後面的數字表示這個資源的ID。其它不同類型的資源也是以同樣的規則定義,所以你大概可以猜到Section map2就是ID為2的地圖資源。 上面的例子可以清楚的看到,這個貼圖的ID為5;

實作迷你薩爾達傳說

圖片
夢見島的地圖是由160乘128個Tile所組成,每個畫面大小是10乘8個Tile,也就是說完整的地圖是由16乘16共256個畫面組成。每次當林克移動到畫面邊緣的時候,整個畫面會往林克前進的方向捲動,一次捲動一整個畫面。 先以編輯器編輯好遊戲資源。使用地圖TileSet編輯好地圖資源,並開一個新關卡資源加地圖加入。然後再加入幾個簡單的精靈資源,主要是林克的4個行走方向,還有幾朵不同顏色的花。接著就可以開始用 Lua 來實作Gameplay。 + + + 因為這是個簡單的Demo,主要要實作的功能是要可以在地圖上行走,也要可以作簡單的碰撞,當走到地圖邊緣的時候要能捲動到下一張地圖。 為了簡單起見,我把它劃分為二個類別:Game及Link。Game類別只負責作地圖捲動的工作。而Link類別則負責處理使用方向鍵輸入在地圖上行走,行走的時候作碰撞簡單,以及走到地圖邊緣時觸發事件讓Game類別作地圖捲動。 另外在也圖上常常可以見到一些會動的小花,假如要在關卡編輯器裡面,一個一個用精靈物件的方式種上去太費時費力了。所以需要使用動態生成的方式,在每次地圖捲動後把可見範圍內的小花動態產生出來,同時釋放捲出畫面範圍外的小花物件。 所以最後整個關卡裡只需放二個物件,一個是地圖,一個是林克(在靠畫面中央小屋前的小傢伙)。然後在關卡的Script欄位填上Game,林克的Script欄位填上Link。而視窗的大小設定為一張小地圖的大小,160x128。 + + + 在關卡上的每一個物件都有個位置,可以透過Good.GetPos和Good.SetPos來作存取。而關卡本身也有位置屬性,一樣可以透過Good.GetPos和Good.SetPos來作存取。 改變關卡的位置,可以製作出地圖捲動的效果。當然也可以去改變地圖物件的位置來作出捲動效果,但是使用這個方法的話,所有相對於地圖的物件,像是林克或地圖上的小花也都要一起移動。雖然可以把這件物件的父親都設成地圖,這樣只要移動地圖就好,但還是比不上移動關卡來的直覺容易,畢盡關卡是所有物件的最上層父物件,只要移動它所有底下的物件都可以全跟著動起來。 Game.OnCreate = function(param) local idLvl = param._id scrolling = false Good.SetPos(idLvl, 320, 1280

分解字串成Token

偶爾我們會需要自己將字串分解成一個個的Token,對於簡單的需求我們通常都自己來,而不特別使用Tokenizer。使用C語言的話,我們會用strtok這個函式來完成我們的需求,不過我比較偏好C++的作法。 string s("this is a string"); vector<string> v; v.assign(   istream_iterator<string>(stringstream(s)),   istream_iterator<string>() ); 如上所示,執行後v的內容會包含4個Token:this, is, a, string。 有一點要提醒的是,因為stringstream的Ctor會對s作copy的動作而不是直接使用s作為來源,所以當s的是一個很大的字串的話,在效能上會受到影響。 上面的範例在分解字串時是以空白字元作分隔,那假如要使用其它不同的字元作分隔符號該怎麼作呢? getline可以提供這基本的需求。 stringstream ss(s); string token; for (;;) {   getline(ss, token, ',');   if (ss.fail())     break;   cout << token << endl; } 上面這個範例示範使用getline以','字元作分隔符號將字串分解。 + + + 反向的操作有很多作法,底下舉一個STL的作法。 stringstream ss; copy(   v.begin(), v.end(),   ostream_iterator<string>(ss, " ") ); string s(ss.str()); stringstream的str方法也會產生一份新的string,所以當字串很大時效能也會受到影響。

從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。

很讚的遊戲編輯器

圖片
簡介 good是一個輕量的2D遊戲編輯器,中文名稱叫作很讚的遊戲編輯器! 剛開始的時候其實也沒有想的太多,只想到要作到可以用來編輯 鋤草機 這個遊戲的一個關卡就夠了。而現在也的確達到了這個目標。廢話不多說,我們直接開始,透過編輯一個簡單的遊戲關卡來看看good能作到什麼,也等於對good作一個很簡單的介紹。 開新專案 首先把good執行起來,可以看到如下圖所示的畫面。 點擊一下左側上半部的資源樹上的Project項目。可以看到資源樹下方的屬性檢視器跟著產生變化。 在Name欄位填上專案的名稱:鋤草機,其它欄位保留預設值不動。你可以發現到編輯器視窗的標題列產生了變化,標題變成了good - (鋤草機),同時工具列上的存檔鈕也致能了。點擊存檔鈕,給一個檔名把專案存到一個你想要的地方。你可以發現,編輯器的標題又改變了,這次加上了專案檔的路徑及檔案名稱。 新增圖形資源 接著我們要新增 鋤草機 需要使用到的圖形資源,一共是二張圖。點擊工具列上的新增圖形資源鈕叫出視窗介面。 Name欄位填不填無所謂,重點是File欄位。點擊File欄位最右側的鈕叫出開啟舊檔對話盒視窗,挑選我們想要加入的圖形檔案加入到專案裡。 點擊資源樹上的tex2,再點擊一下屬性檢視器上的KeyColor欄位。點擊KeyColor欄位後,在欄位的右側會出現一個小鈕,再點擊這個小鈕叫出顏色選擇介面,接著選擇RGB值為R:253 G:0 B:255的顏色值後按下確定。 以上的動作是因為我們的tex2需要使用到鏤空色的功能而作的鏤空色設定。而tex1(如下圖)只作為靜態背景圖,就不需要作鏤空色的設定。 這裡補充說明一下,每一個資源項目無論是那種類別都會有幾個共同的屬性。從屬性檢視器裡面可以看到最上面的Id以及Name屬性就是共用的屬性。Id是用來辨別不同資源的,每一個Id都是唯一的,對於Id這個屬性以後我們還會再提到。再來是Name屬性,這是個可有可無的欄位,也因此這個欄位也不需保證是唯一的。 編輯地圖 接下來點擊工具列上的新增地圖鈕叫出介面視窗。 我們現在要新增一個6X6個Tile的地圖,所以在Width及Height各填上6。每一個Tile的大小是32X32,這剛好是預設值。最後在Tileset的Texture欄位最右側點擊選擇圖形鈕叫出介面視窗來,選取剛剛加入的tex2圖形資源作為我們用來編輯地圖的Tiles