進入這一個章節的最後一部份
一口氣就介紹三小節StateCompositeCommand
不過不用太擔心,我想這篇文章會很短
因為...我懶得打那麼多的字~
前兩篇文章不斷提到觀念,也是這個章節的中心思想
當你遇到複雜的巢狀判斷式,或是冗長的判斷式時
請毫不猶豫的簡化它!
所以延續前面的主題,另外有三個Pattern可以幫助你簡化
也就是StateCompositeCommand
講到State,就不能不提到目前的AI State machine架構
沒錯,它就是典型的State Pattern
AI利用FSMActor來操縱State的轉移
來達成不同階段AI的行為
再來是Composite
看完書中的範例,我腦中只想到一個架構:AI Parser
其實就是XML Parser
利用Composite的架構來解析複雜的語法標籤
真是再合適不過了
至於Command,我並不是很熟這個架構
但是我直覺性的聯想到Debug Tool裡面的Command Mode
因為Debug Tool是會無限延伸的
定死了架構到後面一定會很悽慘
Command在這時候就是很好的選擇
把各種不同類型指令放到底下的Class去解析,去處理
只訂定共通的操縱介面
如此一來Debug Tool的Parser就會很乾淨,也容易管理和擴充
以上,就是Simplification最後一個部分~

kgsprogrammer 發表在 痞客邦 留言(3) 人氣()

這次來比較兩個有趣的東西:StrategyDecorator
同樣是巢狀邏輯判斷式的寫法
改法不止限於上次提到的Composed
StrategyDecorator也是不錯的選擇
艾倫史密斯在互動元件那邊做了一個示範
詳細內容我不贅述了,有興趣可以去找找看
總之是在處理主角吸上互動元件後
怎麼對主角施予重力和建立Joint
讓主角不會掉下來,並且可以做有限制的移動
第一個碰到的問題是
當主角踩在互動元件上面的那一瞬間
要怎麼去判斷他踩到的是什麼呢?
想當然爾,碰撞會有碰撞訊息
於是以下的Code就會出現:
switch (pMsg->m_uiType)
{
    case MST_TRIGGER_COLLISION:
    {
        ksMsgActor *pObj = pMsg->m_pTouchAI;
        if ( NiIsKindOf(ksObjA, pObj) || NiIsKindOf(ksObjB, pObj) )
            // 建立第一種Joint
        else if ( NiIsKindOf(ksObjC, pObj) || NiIsKindOf(ksObjD, pObj) )
            // 建立第二種Joint
        else if ( NiIsKindOf(ksObjE, pObj) )
            // 建立第三種Joint
        else if ( NiIsKindOf(ksObjF, pObj) )
            // 建立第四種Joint
        break;
    }
}
很好,決定了Joint的種類
接下來主角開始移動,但是磁力物件千百種
有的直有的彎,怎麼知道主角該不該轉彎呢?
主角吸在物件上面,所以會有Trigger的訊息
於是下列的程式碼出現:
switch (pMsg->m_uiType)
{
    case MST_TRIGGER_ON_STAY:
    {
        ksMsgActor *pObj = pMsg->m_pTouchAI;
        if ( NiIsKindOf(ksObjA, pObj) || NiIsKindOf(ksObjB, pObj) )
            // 走在直線型的物件上
        else if ( NiIsKindOf(ksObjC, pObj) || NiIsKindOf(ksObjD, pObj) )
            // 走在圓弧形的物件上
        break;
    }
}
如果你以為艾倫史密斯是這種寫法,那你就錯了
要知道,他可是史密斯
寫法當然要不一樣,所以他採取的是Strategy
一種抽象到不行的寫法,害我每次Trace Code都很痛苦...
不過這真的是物件抽象的極致
搞到最後他也不知道是碰到哪一個種類的物件~XD
使用Strategy,可以大大減少主角這邊判斷的機制
我只需要呼叫統一的介面,就可以輕鬆吸上磁力物件
就像這樣:
ksTerrainDestKit* pKit = (ksTerrainDestKit*)pAI->GetKit(TERRAIN_DEST_KIT);
pKit->CreateJoint();
這裡的CreateJoint裡面包著就是Strategy function
同理,主角移動時也用類似的方式來取得資料:
pKit->AddForce();
剩下的由物件自己去處,而ksTerrainDestKit就是Strategy委託者
那麼Decorator何時會使用呢?
對我來說,如果Strategy是橫向的封裝
那Decorator就是縱向的封裝
Strategy會依據種類來決定使用哪一種function
但是Decorator是一層一層的
封裝多少層,就會經過多少個function
兩個Pattern詳細比較在豬屎排程寫的比較多
但是真正要使用的時刻,怎麼判斷用哪一個
還真的是靠個人修行!
講了那麼多,其實兩種Pattern的比較只講了一點點...
好吧,我承認自己修行不夠,沒法寫出精闢的解析
所以就讓我繼續下一章節吧!XD

kgsprogrammer 發表在 痞客邦 留言(1) 人氣()


 
最近玩了不少 Web game,不過都大同小異,沒有吸引我的
不過前幾天發現了這個,把我嚇到了(http://az4.webgame.com.cn/)
它是第一個即時對戰 PK 的 Web Game
這是什麼意思?
目前大家所熟悉的 Web RPG Game 的移動的方式大多是用點的(點移動的地區)
雖然這個也是用點的,不過它所呈現出來的是會動態移動你要到的位置
看起來和 MMORPG 的移動方式一樣
下面是他其它的介紹
一般的 Web RPG Game 按熱鍵喝水放技能算啥
它能直接在地圖上點選,並用攻擊技能打怪,或者放輔助技能幫人補血  
一般 MMORPG 遊戲所具備的功能它都有
排行榜 & 任務
商店
 
人與人間的互動都有的也都有 怪物血條、聊天頻道、工會組織  

kgsprogrammer 發表在 痞客邦 留言(1) 人氣()

結束了創建(Creation)的部分,接下來則是簡化(Simplification)
 
我想又臭又長的程式碼是沒有人可以接受的

kgsprogrammer 發表在 痞客邦 留言(2) 人氣()

http://www.gametrailers.com/player/38353.html
超搞笑的自創角色
第一對的快打旋風的倩咪和布蘭卡
第二對是春麗和貝卡
我還在網路上看過魯夫與麥當勞姐姐
真是超酷的啦
改天我也要自創一個角色來玩玩

kgsprogrammer 發表在 痞客邦 留言(0) 人氣()

最近在GameTrailers.com看到一段影片
http://www.gametrailers.com/player/37719.html
裡面是比較劍魂4的XBOX360版與PS3版的畫面比較
首先看到的是戰鬥前的In-Game明顯的不同,不僅僅是腳色的動畫就連運鏡也都有不一樣的手法,雖然大同小異,但是還是覺得做的很用心。不會是因為是做跨平台就搞同一套,有一種量身打造的感動。
再來就是腳色的細節,PS3版表現上細節比較看的出來,膚色與膚質較真實。XBOX360版看起來就像充氣娃娃,膚質太過光滑了(請看蘇菲亞的那段In-Game)。而且可以仔細看到PS3版乳搖程度比較劇烈,這應該是因為PS3版的玩家大部分是日本人的關係,NBGI比較照顧日本。可惡!!應該買PS3版的。
最後就是直接拼接兩個畫面來比較,仔細一看,兩個畫面對不太起來。這可能有兩個原因,第一個就是由於In-Game的鏡頭表現不一樣,所以運鏡時不是很match。第二個就是原生解析度兩個版本不同。再仔細一看,PS3版比較暗,XBOX360版比較亮,這應該是HDR參數調整的關係。兩個版本因為硬體性能的關係,對後製的調整參數多少都會不同,要調整到一樣的品質也是很難。除此之外,PS3版的鋸齒狀很嚴重,我記得以前看過一篇文章上面提到PS3的HDR與反鋸齒只能選一個開啟,原因好像是Render Buffer的關係,詳細情形還要去找找那篇文章。除了鋸齒,連model好像都有做的不一樣,仔細一看,PS3版的女性腳色眼睛比較大比較有神,XBOX360版的女性腳色兩眼都無神。這樣一來我覺得應該就是版本區域不同所做的特殊設計,為了因應市場考量,日本人總是比較喜歡大眼看起來可愛的女性,而歐美比較喜歡冷艷酷酷的女性。
這樣看了之後,總歸一句話
XBOX360在畫面上略勝一籌,原因可以歸因於XBOX360在開發上可能比較佔優勢畢竟已經開發多款遊戲而且硬體性能上比較平易近人。但是PS3版的Model個人感覺比較好,比例和表現方式我個人比較喜歡,再加上乳搖效果比較強烈,而且運鏡感覺有魄力。
可惜我已經先買XBOX360版了,不然我就買PS3版了。

kgsprogrammer 發表在 痞客邦 留言(0) 人氣()


每個人在寫文章時都有自己的風格,這會依據自己的文學造詣和思考邏輯而有不同。想想看,以前在聯考或是在作文課時,明明就是大家的題目都一樣,可是卻有人寫的好有人寫的差。而且是不可能會有相同的文章出現。但是照樣造句的話呢?那就會有可能出現一樣的造句,因為大家是在同一個規範之下寫簡短的句子(可能是抄參考書的)。
把相同的情形拿到寫程式這件事來看。在一個團隊中每個人的程度都不同,就算不會因為程度的關係,但是寫出來的程式碼卻會因為自己的習慣而大不相同。如果把相同的工作交待給兩個人同時去執行,姑且不論寫好寫壞,也不論完成的時間點與效果。這兩個人所設計出來的資料結構,類別的命名,再者介面與註解等等,都不會一樣。
這樣看來,相同的工作都會有不同的程式碼出現,那不相同的就更不可能會有機會一樣了(除非是用copy的)。
這麼說,一個專案的程式碼就像一本散文集。
其實散文集也沒有什麼不好,就當做看一篇一篇的短篇故事吧。
但是!
散文裡有參雜火星文或是古文就不好了吧!
有人或許會問
為什麼要擔心風格問題?
如果程式能夠Run的話,誰在乎裡面是什麼?
讓程式碼看起來漂亮不是很花時間嗎?
這些規則是不是太武斷?
事實證明,會有這種想法的人不適合當Programmer。(因為一定會被公幹到死)
寫的好的程式碼比較易於閱讀與了解,而且幾乎是錯誤比較少,並且比漫不經心又未修飾過的程式碼來的小又快。
想想看,在一個看似功能正常的程式,展開來的程式碼卻是一坨屎。
雜亂無章,演算法邏輯有問題,多做了重複的事情等等。
如果你看到了這樣的程式碼,心中一定會有一團無名火從"懶趴"那裏衝上來,甚至會嚴重的影響到你工作的情緒與進度。
許多Game Programmer都會埋怨美術或是企劃資料沒有用好。
批評美術為什麼要在很遠的地方放一個幾千面幾萬面的物件,批評企劃設計不好或是資料沒填正確。但是卻忽略了自己程式碼的品質與風格帶給團隊的殺傷力。
這遠遠比美術與企劃資料有錯來的難查,因為美術和企劃的資料都是把問題清楚的攤開來讓大家檢驗,惟獨程式碼是沒辦法被大家檢驗的,也只有Programer可以來審視,但監守自盜總是不好的吧,如果沒有把好的習慣養成,實在是很難說服別人自己的程式碼是沒有問題的。
命名
命名通常可區分為變數的命名與函式的命名,想像一下就跟你寫句子要有主詞與動詞一樣,主詞就是變數,動詞就是函式。以下是命名時的習慣與比較好的建議。
1.  全域變數在字首加上g_,成員變數在字首加上m_,靜態成員變數在字首加上ms_,區域變數也可加上l_或是不加,變數的型態是指標時加上p,例如g_pScene, ms_pSingleton, m_pNode, pAI。class和struct在字首加上C或S,或是由團隊共同指定字首(通常會利用專案或是公司的縮寫)。
變數的型態是int時加上i, unsigned int 加上ui, float 加上f, double 加上 d, char加上c, char * 加上pc。加上這些最主要是能夠快速辨識變數的型別,以免還要回到宣告的地方才能知道型別定義。如果是自己宣告的struct或是class通常不會加上型別識別,而是直接宣告描述性意思與strcut或class的名稱,例如有一個class叫做Node, 宣告一個m_SceneNode。
2. 全域變數(global)和成員變數(member)使用描述性名稱,區域變數(local)使用簡短的名稱。
3. const的變數使用大寫,例如:const float PI = 3.1415962f;。define的巨集或是常數也用大寫。
4. 維持一致性,給相關的事物取相關的名字,這些名字要能顯示之間的關係並指出彼此的差異。例如:
class CUserQueue 
{
public:
    int m_iNoOfItemInQ, m_iFrontofThequeue, m_iQueueCapacity;
};
[queue]這個字分別以Q, Quene, queue三種形式出現。然而變數只能經由UserQueue來存取,所以成員根本不需要提及[Queue]。而且一次出現三種形式反而更讓人混淆其意義。以下版本會比較好一點。
class CUserQueue
{
public:
    int m_iItems, m_iFront, m_iCapacity;
};
5. 函式使用主動名稱。函式名稱應該以主動動詞為基礎,後面可以加上名詞。例如float GetTime();。但是會傳回bool的函式名稱,就應該以可以從函式名字上看出傳回值所代表的意義。例如:bool CheckOctal(int iNumber);,並沒有指明哪個值代表true哪個值代表false。而bool IsOctal(int iNumber);,就清楚多了,因為引數是八進位就回傳true,否則就回傳false。
6. 名稱要精確。名稱不指示標籤,還會傳達資訊給讀者。一個誤導的名字會引發奇怪的臭蟲。例如:
bool InTable(Object * pObj)
{
    int j = this.getIndex(pObj);
    return (j == m_iTableCount);
}
如果發現物件的話,getIndex會回傳一個0到m_iTableCount-1的索引值,如果沒有則回傳m_iTableCount。因此InTable回傳的值和這個名稱所代表的意思正好相反。這樣會讓其他使用的人產生困擾,甚至要花一些時間來除錯,最後才發現產品外觀與內容不符。

kgsprogrammer 發表在 痞客邦 留言(4) 人氣()

最後一個是為了確保Factory唯一性所做的機制
也就是DesignPatterns裡面的Singleton
相信很多人都有使用過的經驗
但是書中提到的卻是使用過度Singletonitis(指沈迷於
Singleton)
裡面講了很多不需要使用Singleton的情形
它最大的問題就是製造了全域的變數
而且還是一個工廠(Factory)的大小!
並不是說不要使用Singleton
而是使用它之前一定要慎重的思考其必要性
到最後得到一個結論:
『當你可以設計出一種避免使用Singleton的方法時

就不要使用它』

再回到AI的建立
假設今天要建立物理的Shape
而且一隻AI可能需要多個Shape
可能會有這樣的設計:
class ShapeFactory
{
public:
    static
ShapeFactory * GetInstance()
    {
        static
ShapeFactory *ms_pFactory = NULL;
        if (
ms_pFactory == NULL)
           
ms_pFactory = new ShapeFactory();
        return
ms_pFactory;
    };
    NxShape *CreateShape(iType, vExtend, kTransform)
    {
        switch (iType)
        {
            case NxBoxShape:
                return ksPhysX::Get()->CreateBoxShape(
vExtend, kTransform);
            case NxSphereShape:
                return ksPhysX::Get()->CreateSphereShape(
                                   vExtend, kTransform);
            case NxCapsultShape:
                return ksPhysX::Get()->CreateCapsultShape(
                                   vExtend, kTransform);
        }
    };
private:
   
ShapeFactory();
    ~
ShapeFactory();
};
class PhysXKit
{
public:
    NxShape *CreateShape(iType, vExtend, kTransform);
    {
        return 
ShapeFactory::GetInstance()->CreateShape(
                        iType, vExtend, kTransform);
    }
};
嗯,我需要建立很多不同種類的Shape
所以我建立了一個Shpae Factory來處理這件事情
而且還是一個Singleton的Factory
但是有這個必要嗎?
事實上這個Factory只會被
PhysXKit使用
卻被建立為全域的Singleton Instance
理論上它應該被合併到
PhysXKit裡面,如下:
class PhysXKit
{
public:
    NxShape *CreateShape(iType, vExtend, kTransform)
    {
        switch (iType)
        {
            case NxBoxShape:
                return ksPhysX::Get()->CreateBoxShape(
vExtend, kTransform);
            case NxSphereShape:
                return ksPhysX::Get()->CreateSphereShape(
                                          vExtend, kTransform);
            case NxCapsultShape:
                return ksPhysX::Get()->CreateCapsultShape(
                                          vExtend, kTransform);
        }
    };
};
這就是書上提到的Inline Singleton(Singleton內置化)
如果再看詳細一點,會發現ksPhysX似乎也有同樣的問題
如此一直改下去,就能夠把不該存在的Singleton消除了

kgsprogrammer 發表在 痞客邦 留言(1) 人氣()

何謂[遊戲鐵三角]? 
每個遊戲創作都會有所謂的遊戲鐵三角,他們分別是企劃、程式、美術,這三大類型的人又被分作許多細類型,這群奇葩在遊戲工作中分工著不同的事務,也在不同企業文化的公司有著不同的稱號...

比如說:企劃會稱作編導、製作人(掛著好聽的名字依然是作相同的事情 :p),還有許多分身被稱作執行企劃、創意企劃、關卡企劃、系統企劃等等;而程式也有不同的稱號與專業分工人稱:程序、編程或工程師,也有依照「使用功能」被分作搞系統的、搞規劃的、成像類的、資料庫的、搞網路的、還有寫3D的,當然也會有萬能的神(........經病),自以為是萬能的「神」想要把所有的事情都覽在自己身上(也有一種說法是因為本身能力不足,在工作上沒有辦法作出適度的分工與切割);而美術分類就更為廣泛,大分為2D3D,在對岸則是稱作二維與三維(搞曖昧的詞),兩大類之後又要分作造型設定、人物、場景、物件...公司規模越大他們ㄉ名目就會被分的更多更細,其中的派閥就會相對的增加,有趣的戲碼就會上演的更加厲害...

 

一舉成名天下知 一夜成屍無人問 
這三大類族群的人,大多躲在陰暗的角落裡(也有人稱為象牙塔)研發著不為人知的驚世駭俗的大作,個個期望一舉成名,但是彼此之間經常因為理念、立場、風格而產生不同的派系,彼此之間的合縱連橫,天天上演著「如何有效的將責任推卸給別人之後享受更多的權力()」的戲碼,但是螳螂與蟬的殊死戰中往往不自知最大隻的麻雀通常就是公司的決策者與殘酷且現實的玩家與市場。
雖然現實總是殘酷的,但是過程比結果重要的論點之下,讓遊戲研發人個個化悲憤為力量,又再一次的往下一套美麗的虛幻之火中投身而去,而前仆後繼樂此不疲,直到看破之後離開卻又遠遠的躲在一旁看笑話...

kgsprogrammer 發表在 痞客邦 留言(4) 人氣()

今日是公司聚餐...
我們這一桌在等待甜點來臨
正覺得無聊時
不知道是哪個該死的提議要玩數支遊戲
於是就拿起桌上的菜當作懲罰
第一輪是軟殼蟹
由艾文同學中獎
也激起了大家繼續玩的慾望
於是繼續下一輪
第二輪是湯裡面的丸子一顆
經過一番廝殺後
由傑斯特同學中獎
但是他心有不甘,決定要上訴
於是第三輪追加六顆丸子
中的人兩顆,下一個人四顆
這輪中獎的是川董
但重點是
下一個人是傑斯特兄
於是他就有了五顆丸子
傑兄不信邪,決定要賭到底
於是最後一輪是三隻軟殼蟹
中獎人的前一個後一個都要吃
結果,想當然爾的......他連莊三次
川董是他上一個,中了一隻
連我都被牽連到...
結算~
艾文:軟殼蟹一隻
李奧:軟殼蟹一隻
川董:軟殼蟹一隻 + 兩顆丸子
傑斯特:

kgsprogrammer 發表在 痞客邦 留言(5) 人氣()

延續上一次的主題,再來看看Factory內部的建立流程
一個AI需要載入Model,需要建立FSM,需要建立Physics...
所以流程大概會有:
// Load model and create callback function
m_pAM = NiActorManager::Create( sFileName );
m_pkAnimCallback = NiNew AnimCallbackObject;
m_pAM->SetCallbackObject(m_pkAnimCallback);
// create state machine and some state node
m_pFSM = NiNew  ksFSM( iAgentType );
ksBaseStateNode *pIdleState = NiNew ksBaseStateNode(
                                                                                  IdleStateID );
m_pFSM->AddState(pIdleState);
ksBaseStateNode *pMoveState = NiNew ksBaseStateNode(
                                                                                      MoveStateID );
m_pFSM->AddState(pMoveState);
// create NxShape and NxActor for physcis
NxShapeDesc *pShapeDesc = ksPhysX::Get()->CreateBoxShape(
                                                                shapeflag, extension, transform);
m_pPhysXActor = ksPhysX::Get()->GetPhysXScene()->createActor(

                                        pShapeDesc);
嗯,看完頭也昏了,所以這也不是個好的建立方式
這時候Builder就出現了
這也是一種對於建構子的封裝方式
因為使用者建立AI的時候,通常不想知道太詳細的建立方式
而且寫太多雜七雜八很容易搞混或是漏寫
既然我們知道可以分成三大部分,就可以把建立的步驟拆解
每一個步驟都封裝成為function,只留下簡單的interface
首先把Builder建立出來:
class ksAIBuilder : public NiRefObject
{
public:
    ksAIBuilder();
    ~ksAIBuilder();
    // Load model and create callback function
   
NiActorManager *CreateActorManager();
    // create state machine and some state node
    ksFSM *CreateFSM();
    // create NxShape and NxActor for physcis
    NxActor *CreatePhysics();
};
建立完之後再來就是使用了,於是AI的建立流程就變成:
ksAIBuilder *pBuilder = NiNew ksAIBuilder();
m_pAM =
pBuilder->CreateActorManager();
m_pFSM =
pBuilder->CreateFSM();
m_pPhysXAcotr =
pBuilder->CreatePhysics();
這樣一來要建立AI就簡單多了,變化性又高
當我想要一個沒有物理的AI,只要不呼叫
CreatePhysics就可以了
既直覺又不容易出錯

kgsprogrammer 發表在 痞客邦 留言(1) 人氣()

首先要道歉一下,我看書的速度真的太慢了...
總之目前進入了第一大單元 - 創建(Creation)
顧名思義的它就是要教你怎麼用好的方式去建立一個Class
由於裡面內容牽扯到大量豬屎排程(Design Patterns)
不熟的人還請搭配書本服用
在我們建立一系列有繼承關係的Class,例如State Node
會很直覺的想要建立一個Factory來統一建立的介面
這樣做除了可以統一Client端呼叫建立的介面之外
維護起來也會比較簡單(不用到處東找西找)
但是怎麼建立那個Factory是個大學問
首先是Factory的介面
一般來說,以建立AI的例子來講,如下:
ksAI *pAI = AIFactory->CreateAI();
假如今天想要建立不同種類的怪物AI呢?
ksAI *pAI = AIFactory->CreateAI( iAIType );
複雜一點,建立AI的時候必須給他一些初始值:
ksAI *pAI = AIFactory->CreateAI( iAIType, iInitHP );
好像不夠,產生器需要生產的種類跟速度,有些怪物一開始要隱形
有些怪物需要Ragdoll Model:
ksAI *pAI = AIFactory->CreateAI( iAIType, iInitHP,
                            iChildType, fGeneraeSpeed, bVisiable,
                            sRagdollModelName...... );
是不是開始覺得複雜起來?因為初始值的種類太多了
當然你可以把初始值的資料包裝成Class或是Structure
但是總覺得包裝起來很麻煩,還要多建立一個物件,是否有其他方式呢?
書上提供一種Creation Method的方法
簡單來說就是對建構子(Constructor)作封裝的動作
例如我們確定了怪物的大分類,可以把介面再細分一下:
ksAI *pNormalAI = AIFactory->CreateNormalAI( iAIType,
                                           iInitHP, bVisiable);
ksAI *pGeneratorAI = AIFactory->CreateGeneratorAI( iAIType,
                                                iInitHP, iChildType, fGeneraeSpeed);
ksAI *pRagdollAI = AIFactory->CreateRagdollAI( iAIType, iInitHP,
                                            sRagdollModelName);
這樣一來一方面你可以很清楚知道你建立了哪個種類的AI
另一方面還可以簡化和控制傳入的參數數量
並且把子類別的建構子也封裝了起來

kgsprogrammer 發表在 痞客邦 留言(1) 人氣()

« 1 2 3 4 5
Blog Stats
⚠️

成人內容提醒

本部落格內容僅限年滿十八歲者瀏覽。
若您未滿十八歲,請立即離開。

已滿十八歲者,亦請勿將內容提供給未成年人士。