2017-03-18 55 views
0

爲了學習Java,我製作了一個經典的「在太空中飛行」2D遊戲。除了玩家對象之外,還存在衆多的敵人/障礙(拾荒者,獵人,彗星和小行星),每個敵人都擁有自己的類來擴展GameObject類。由於可以有幾個清道夫,彗星等這些存儲在arraylists。但是,由於每個對象都可以相互交互,所以存在很多循環和重複代碼,例如每個外來人員根據彗星陣列列表中的對象,小行星陣列列表等進行交互。遊戲設計:組織對象陣列列表

在我的遊戲更新功能,我有:

public void update() { 

    ArrayList<Rock> rockList = rock.getRockList(); 
    ArrayList<Scavenger> scavengerList = scavenger.getScavengerList(); 
    ArrayList<Hunter> hunterList = hunter.getHunterList(); 
    .... 
    npc.update(player, scavengerList, hunterList, rockList); 
    ... 

}

,並在我的NPC類(擴展了遊戲對象類)

public void update(Player player, ArrayList<Scavenger> scavengerList, ArrayList<Hunter> hunterList, ArrayList<Rock> rockList) { 

    for(int i = 0; i < scavengerList.size(); i++) {   
     scavengerList.get(i).update(player,scavengerList, ,hunterList rockList); 
    } 
    for(int i = 0; i < hunterList.size(); i++) {    
     hunterList.get(i).update(player,scavengerList, hunterList, rockList); 
    } 
... 
} 

最後我有一個更新功能在我的清道夫級,我的獵人級等等,如

public class Hunter extends NPC{ 
... 
public void update(Player player,ArrayList<Scavenger> scavengerList, ArrayList<Hunter> hunterList, ArrayList<Rock> rockList) { 
"update all hunter objects according to the player, scavengers, rocks etc" 
} 

這種方法似乎相當麻煩,並且隨着更多類的創建,需要解析和循環的數字或陣列列表已經失控。 任何人都可以推薦一個更好的方式來做到這一點? 我想明顯的方​​法是有一個列表包含所有NPC對象,然後跟蹤他們的類類型並相應地更新。 這是一個更好的方式,或者任何人都可以指向正確的方向嗎?

+0

傳遞'World'實例不是更容易嗎?你可以基本上做'for(updatableThing:updatableThings){updatableThing.update(world); }' – plalx

+0

也許我不完全理解(仍在學習),但是World實例仍然會包含一個包含並更新所有對象的列表,或者我誤解了一些東西? – user2913053

回答

2

是的,有更好的方法。

對於遊戲中的每種類型的對象,計算出它需要展示的一組行爲/特徵。這些行爲應該被定義爲接口。然後,處理行爲/特徵的代碼可以使用該接口,而不必知道關於實際類的任何信息。

例如,如果一些物體移動根據自己當前的速度每轉一圈,可以潛在的與其他物體發生碰撞則可能是一種接口:

public interface Moving { 
    void move(); 
    boolean hasCollided(Shape shape); 
    void handleCollision(Collision collision); 
} 

是移動,然後將實現該接口的任何類。然後World對象可以有一個List<Moving> movingObjects,然後使用:

movingObjects.forEach(Moving::move); 

在它的update方法。

List<Collision> collisions = getAllCollisions(movingObjects); 
for (Collision collision: collisions) { 
    for (Moving element: collision.getCollidingObjects) { 
     element.handleCollision(collision); 
    } 
} 

如果實現該接口的幾個類使用類似的機制來移動自己,那麼你應該是邏輯轉移到一個單獨的類:

要動你可能會碰到這樣的後處理衝突

class Inertia implements Moving { 
    private Velocity velocity; 

    @Override 
    public void move(Shape shape) { 
     velocity.applyTo(shape); 
    } 

    @Override 
    public void applyForceFrom(Position position) { 
     velocity.accelerateAwayFrom(position); 
    } 
} 

你的世界中的物體,然後可以委託自己的遷移行爲這一類:

class Asteroid implements Moving { 
    private final Inertia inertia; 
    private Shape shape = new Circle(radius); 

    @Override 
    public void move() { 
     inertia.move(shape); 
    } 

    @Override 
    public boolean hasCollided(Shape other) { 
     return this.shape.intersects(other); 
    } 

    @Override 
    public void handleCollision(Collision collision) { 
     intertia.applyForceFrom(collision.getCentreOfMass()); 
    } 
} 

這似乎是一種不必要的間接方式,但經驗表明,從長遠來看這是值得的。有關更多詳細信息,請參閱Delegation Pattern

如果每個對象的運動不同(例如一些受重力影響,一些受AI等控制),或者一個類可以在其move(例如重力和慣性)或一個類中應用多個代表如果其行爲是唯一的,則可以實施其自己的move。所有這一切都可以發生,不需要World需要知道任何關於該對象的類正在調用move

作爲一般規則,儘量避免使用extends來繼承超類的行爲。你的獵人擴展NPC擴展GameObject的結構將會很方便,直到你意識到你還希望獵人擴展敵人或AIControlled或其他東西。經驗表明,OO編碼人員認爲這些類型的層次結構最初看起來明智而優雅,但隨着您添加更復雜的功能變得難以管理。

若要更進一步並隱藏所有對象實現哪些行爲接口從World的所有詳細信息,您可能需要查看Visitor Pattern。這將允許世界對象作爲移動者訪問所有遊戲對象,然後作爲AIAgent,然後作爲用戶控制對象等,而不必知道他們在訪問期間做了什麼(或者他們什麼都可以做)。如果使用得當,它功能非常強大,但需要一些習慣。

最後,有一個非常常見的架構模式,被遊戲編寫者稱爲Entity Component System。如果你剛剛學習Java,我暫時忽略這一點,但如果你成爲一個認真的遊戲開發人員,你可能會發現這是對上述架構的改進。

我已經明顯離開了的例子很多細節(如ShapeCirclePositionVelocityCollision等的定義),但這是一般的想法。這還有很多,值得一看關於面向對象設計的書籍或教程,以便更深入地研究。

+0

非常詳盡的答案,謝謝。我可以看到如何避免在類似的類中重複代碼。但是,如果物體運動依賴於彼此,會發生什麼。岩石可以相互碰撞(從而改變方向),獵人可以躲避岩石或追求玩家。這將要求移動功能知道所有對象並相應地計算移動。移動函數是否知道哪個對象調用了它,然後循環移動列表,正如我在原始文章中提到的那樣,基本上我所有的當前列表都加入了一個列表中? – user2913053

+0

我會在我的答案中提供碰撞的通用模型,讓您知道如何處理這個問題。類似的模型應該適用於其他示例。 – sprinter

+0

好吧,我的答案現在變得很可笑了。如果它有幫助,那麼請接受它,然後問另一個問題,如果有什麼具體的你需要幫助。 – sprinter