2010-02-07 58 views
2

我正在開發Java Android遊戲。這裏的遊戲通常需要使用內存池來避免垃圾回收。結合具有不同領域效率的子類?

爲了討論的方便,說我有以下類:

// Enemy in the game 
class Enemy { int x; int y; abstract void update(); } 
// Enemy that just bounces around 
class Roamer extends Enemy { int direction; ... } 
// Enemy that chases a player 
class Chaser extends Enemy { Player target; ... } 
// maybe 20 more enemy types... 

利用子類是一種痛苦使用內存池的時候,因爲你需要如說你想要多少個漫遊者和追蹤者物體,而不是你可能需要多少敵人物體。此外,虛擬功能調用更新可能會很慢。

一個選擇,我認爲是所有這些類只是合併成一個如

class Enemy 
{ 
    int direction; 
    Player target; 
    int type; // i.e. ROAMER_TYPE, CHASER_TYPE 
} 

然後,我會有一個更新函數,檢查「類型」變量,並相應地更新。這顯然是更類似C的方法。雖然因爲例如漫遊者敵人將有一個它從未使用的「目標」變量。我一次不可能在內存中擁有超過100個敵人,儘管如此,這確實不是一個明智的記憶。我只想在漂亮的代碼和速度之間做出妥協。

有沒有人有任何意見如何最好的結構呢?合併這樣的類太過分了嗎?這是一個好主意嗎?我應該只使用普通班級樹嗎?

我知道有沒有正確的答案,但我想有些不同的觀點。

+0

如果採用第二種方法,請務必使用枚舉而不是整數,以確保類型安全。 – 2010-02-08 01:26:09

+0

枚舉嚴格不建議在android http://developer.android.com/guide/practices/design/performance.html#avoid_enums下。我真的不喜歡這種方法,因爲這意味着我必須輸入文件的名稱兩次(一次用引號,一次用於常量)。 – 2010-02-08 01:31:25

回答

0

您是否確實已經確認沒有內存池會出現性能問題?

假設你需要使用一個內存池,爲什麼不能有一個對每種類型的對象,讓他們「成長」爲需要的對象的數量?如果對象類型之間的平衡隨着時間的推移而劇烈變化,那麼效率就會很低,但是您描述的方法也會效率低下(未使用的字段),並且看起來好像不太好玩。

+0

>你是否真的證實你會遇到沒有內存池的性能問題? 如果您經常取消分配工具,Android GC可以隨機運行約300ms,因此如果您需要一致的例如內存池,則大多數遊戲都可以使用內存池。 30幀/秒。 >如果物體類型之間的平衡隨着時間的推移劇烈變化,但效果不佳,但是您描述的方法效率不高 嗯。我需要與虛擬函數調用的速度平衡。 – 2010-02-07 23:12:28

1

此外,虛擬函數調用,更新可能會很慢。

廢話!進行虛函數調用的成本是JIT編譯應用程序中的幾條指令。即使在解釋模式下,我也只希望它能夠更多地使用一打原生指令。這是在噪音下降......除非你每秒鐘撥打這些電話數百萬次。

我會第二個@勞倫斯答案的要點。只有通過驗證(即通過測量)纔會將性能問題花費在性能問題上,那麼確實是需要解決的性能問題。如果你忽略這一點,你可能會冒險讓你的代碼比需要的更加複雜和難以維護。你可能會讓你的表現更糟糕,特別是在Android JIT編譯器變得更好的時候。

+1

Android僅在最近的版本2.0中獲得了JIT編譯。即使在該版本中,它也沒有爲普通用戶啓用。這只是開發人員測試的一個選項。無論如何,許多手機永遠不會升級。 也就是說,Google I/O Real-Time Games談話中有一個圖表,虛擬函數調用並沒有那麼糟糕。 JNI和通過接口調用的情況要糟得多: http://code.google.com/events/io/2009/sessions/WritingRealTimeGamesAndroid.html – 2010-02-08 01:23:56

+1

「製作虛擬函數調用的代價是編譯JIT時的幾條指令應用。」 而且還有可能是一個緩存未命中,這在性能上比僅僅是一些額外的指令糟糕得多。 – munificent 2010-02-08 19:19:29

2

在這裏組合會比繼承工作更好嗎?例如,如下所示:

class Enemy { 
    int direction; 
    Player target; 
    Strategy updater; 
} 
interface Strategy { 
    void update(Enemy state); 
} 
class Roamer implements Strategy { 
    public void update(Enemy state) { 
     //Roamer logic. 
    } 
} 
class Chaser implements Strategy { 
    public void update(Enemy state) { 
     //Chaser logic. 
    } 
} 

然後,您只需要每個特定策略的一個實例。例如,當前充當漫遊者的池中的所有敵方物體將使用單個漫遊者實例來進行更新。這可以防止必須將所有邏輯保存在一個類中,這可能會變得異常複雜。

如果這不是一款Android遊戲,那麼你可以在Enemy類中放置一張地圖,以保持一個策略需要的敵人狀態,但另一個則不會。因爲我們必須避免分配,這可能會觸發垃圾收集器,但是,最好讓所有敵人級別的成員像你說的那樣。

你可以在Enemy類中使用預先分配的數組作爲狀態。例如,追逐者將始終保持對正在追逐狀態數組的特定索引的玩家的引用。然而,這變得非常複雜。

0

你可以複製內存池是內部的,從這裏的框架(見池,池化,PoolableManager,池,FinitePool,SynchronizedPool):

https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util

如果OU這樣做,一定要把它們放在你自己的包名字空間中。

爲了實現對象池,不需要丟失對象方向。如果代碼仍然如此通用以至於讓你感到害怕,請使用相同的想法爲對象製作簡化的通用池。