2009-09-22 68 views
9

我有下面的代碼,認爲簡單的射擊遊戲在C++:建模遊戲對象層次結構的最優雅和最有效的方法是什麼? (設計困擾)

// world.hpp 
//---------- 
class Enemy; 
class Bullet; 
class Player; 

struct World 
{ 
    // has-a collision map 
    // has-a list of Enemies 
    // has-a list of Bullets 
    // has-a pointer to a player 
}; 

// object.hpp 
//----------- 
#include "world.hpp" 

struct Object 
{ 
    virtual ~Object(); 
    virtual void Update() =0; 
    virtual void Render() const =0; 

    Float xPos, yPos, xVel, yVel, radius; // etc. 
}; 

struct Enemy: public Object 
{ 
    virtual ~Enemy(); 
    virtual void Update(); 
    virtual void Render() const; 
}; 

// Bullet and Player are similar (they render and update differently per se, 
/// but the behavior exposed to World is similar) 

// world.cpp 
//---------- 
#include "object.hpp" 

// object.cpp 
//----------- 
#include "object.hpp" 

兩個問題:

1,遊戲對象知道關於其他遊戲對象。

必須有一個知道所有物體的設施。它可能或可能不需要公開所有對象,它必須公開一些,取決於參數(查詢者的位置)。這個設施應該是世界。

對象必須知道他們所在的世界,以查詢碰撞信息和其他對象。

這引入了一個依賴關係,其中Object和World的實現都必須訪問對象的頭部,因此World不會直接包含它自己的頭部,而不是包含object.hpp(其中包含world.hpp)。這讓我感覺不舒服 - 我不覺得world.cpp應該在對object.hpp進行更改後重新編譯。世界並不覺得它應該與Object一起工作。這感覺像不好的設計 - 它如何被修復?

2,多態 - 能否也應該用於除代碼重用和遊戲實體邏輯分組以外的任何事情?

敵人,項目符號和玩家會更新和渲染不同,當然,因此虛擬Update()和Render()功能 - 相同的界面。它們仍然保存在單獨的列表中,其原因是它們的交互取決於兩個交互對象是哪個列表 - 兩個敵人彼此反彈,子彈和敵人相互破壞等。

這是功能這超出了敵人和子彈的實施;這不是我關心的地方。我覺得除了它們相同的界面之外,還有一個因素可以區分敵人,子彈和玩家,這可以並且應該以其他方式表達,允許我爲它們創建一個列表 - 因爲它們的界面是相同的!

問題是如何判斷包含在同一列表中的對象類別。 Typeid或其他形式的類型識別是不可能的 - 但接下來還有什麼其他方式可以做到這一點?或者,這種方法沒有錯?

回答

5

這可能是我在設計類似程序時遇到的最大問題。我已經解決的方法是意識到一個對象實際上並不關心它的絕對位置。它所關心的只是它周圍的事物。結果,World對象(並且我更喜歡對單身人士的對象方法,因爲可以在Internet上搜索的許多好理由)保留了所有對象的位置,並且他們可以問世界哪些對象在附近,其他位置對象是與它有關的,等等。World不應該關心Object的內容;它將持有幾乎任何東西,而且它們自己將定義它們如何相互交互。事件也是處理對象交互的好方法,因爲它們爲World提供了一種方式,告知Object發生了什麼,而不關心Object是什麼。

Object隱藏信息有關所有對象是一個好主意,不應該與隱藏有關任何Object信息混淆。從人的角度來看 - 對於你來說,瞭解和保留關於許多不同人的信息是合理的,儘管你只能通過遇到他們或讓其他人告訴你這些信息來找到這些信息。

編輯再次:

好的。我很清楚你真正想要什麼,這就是多重​​調度 - 能夠在函數調用的多個參數類型上多態地處理一個情況,而不僅僅是一個。不幸的是,C++本身不支持多次調度。

這裏有兩個選項。您可以嘗試使用雙派或副訪問者模式重現多個派遣,也可以使用dynamic_cast。你想使用哪個取決於具體情況。如果你有很多不同的類型可以使用它,dynamic_cast可能是更好的方法。如果你只有幾個,雙重派遣或訪問者模式可能更合適。

+0

謝謝你前兩段;這讓我感覺很有意義,也有助於清除我的頭腦。我已經重寫了我的問題,以澄清我想用多態性做什麼。 – zyndor 2009-09-23 09:37:30

+0

訪客模式很棒,謝謝指出。我猜OnHit()方法類似於模式的Visit()方法,而Objects就是他們自己的訪問者,對吧? – zyndor 2009-09-24 15:59:18

+1

選擇一個答案接受爲最好的答案是困難的,因爲他們都有很好的積分,但沒有一個是全能的。我覺得關於對象 - 世界交互的設計大綱是最有用的一點信息,並且與我的問題最相關。 - – zyndor 2009-10-14 08:00:38

6

我想你會想把對世界的引用交給遊戲對象。這是一個非常基本的原則Dependency Injection。對於碰撞,世界可以使用Strategy pattern來解析特定類型的對象如何相互作用。

這樣可以將主要對象之外的不同對象類型的知識保存在對象中,並將其封裝在具有特定於交互的知識的對象中。

+0

這是一個解決方案(OO設計和性能方面)太糟糕了,而不是每次將參考傳遞給World,只是將一個靜態成員指針存儲到World,可供所有對象訪問? (不是以原始公共形式提交的,也許 - 可能是一個受保護的指針和一個用於設置它的公共靜態方法。) – zyndor 2009-09-24 15:53:46

+0

如何存儲對World對象的引用取決於您。由於這些對象可能不會同時在多個世界中實例化,因此在類上存儲引用可能沒問題。 關鍵的想法是,世界仍然在實例化時被提供給單個對象,所以如果你想在不同世界中擁有兩個相同類型的對象,那麼實例API不會改變,唯一需要重構的重構是在對象本身的實現範圍內。 – 2009-09-24 20:51:05

3

對象必須瞭解世界 他們所在,來查詢碰撞 信息和其他對象。

總之 - 不,他們不。像世界這樣的類可以處理大部分的事情,當世界告訴它發生了碰撞時,對象所要做的就是適當的行爲。

有時您可能需要Object才能擁有某種上下文才能做出其他類型的決定。您可以在需要時傳遞World對象。但是,我不想傳遞世界,最好傳遞一些更小,更相關的對象,這取決於實際執行哪種查詢。很有可能您的World對象執行多種不同的角色,對象只需要對這些角色中的一個或兩個角色進行臨時訪問。在這種情況下,將World對象拆分爲子對象是很好的做法,或者如果這樣做不切實際,可以讓它實現幾個不同的接口。

0

給它左思右想,我意識到,儘管對象DO需要訪問一個世界,它是世界的責任就可以提供附近的對象到對象。

這就是我的競技場非對象(從未實例化過的所有靜態成員,包含所需對象類別列表 - 子彈,敵人,視覺效果等)。)的,但它推出了類似的依存結構爲具有類別列表作爲世界的一部分(這就是爲什麼它不覺得自己是一個很好的解決方案):

// object.hpp 
//----------- 
#include "world.hpp" 

// NOTE: the obvious virtual and pure virtual methods are omitted in the following code 
class Object 
{...}; 

class Enemy: public Object 
{...}; 

class Bullet: public Object 
{...}; 

class Player: public Object 
{...}; 

// arena.hpp 
//----------- 
#include "object.hpp" 

struct Arena 
{ 
    // has-a lists of object categories and a (pointer to a) player 
} 

// object.cpp 
//----------- 
#include "arena.hpp" // for the damn dependencies 

// arena.cpp 
//----------- 
#include "arena.hpp" 

因此,解決方案,或好像有什麼在這一點上,是讓整個對象(分類)不是一個編譯級別高於或低於對象的聲明,而是在相同級別上的

// object.hpp 
//----------- 
#include "world.hpp" 

class Object 
{ 
    static World *pWorld; 
    ... 
}; 

class Enemy: public Object 
{ 
    typedef std::list<Enemy*> InstList; 
    static InstList insts; 
    ... 
}; 

class Bullet: public Object 
{ 
    typedef std::list<Bullet*> InstList; 
    static InstList insts; 
    ... 
}; 

class Player: public Object 
{ 
    static Player *pThePlayer; 
    ... 
}; 

// object.cpp 
//----------- 
#include "object.hpp" 

即使有對專業的敵人,子彈等他們(和其他人的)另外的標題類別名單完全爲他們提供通過包括object.hpp他們顯然必須反正。

關於多態性位以及爲什麼不同類別保存在單獨的列表中:對象類別的基類(Bullet,Enemy,Player)可以提供「事件處理程序」來觸擊某些類型的對象;但是,它們不是在對象級別上而是在類別級別上聲明的。 (例如,我們不關心子彈VS子彈的碰撞,他們沒有檢查,沒有處理。)

// object.hpp 
//----------- 
#include "world.hpp" 

class Object 
{ 
    static World *pWorld; 
    ... 
}; 

class Bullet: public Object 
{ 
    typedef std::list<Bullet*> InstList; 
    static InstList insts; 
    ... 
}; 

class Player: public Object 
{ 
    static Player *pThePlayer; 

    void OnHitBullet(const Bullet *b); 
    ... 
}; 

class Enemy: public Object 
{ 
    typedef std::list<Enemy*> InstList; 
    static InstList insts; 

    virtual void OnHitBullet(const Bullet *b); 
    virtual void OnHitPlayer(const Player *p); 
    ... 
}; 

編輯,爲了完整性:

// game.hpp 
//----------- 
#include "object.hpp" 

struct Game 
{ 
    static World world; 

    static void Update(); // update world and objects, check for collisions and 
         /// handle them. 
    static void Render(); 
}; 
相關問題