2011-02-09 20 views
16

我正在研究用Java編寫的小型遊戲(但問題與語言無關)。由於我想探索各種設計模式,所以我掛斷了Composite pattern/Entity系統(我最初的讀法是關於herehere),作爲典型的深層次繼承的替代方案。複合模式/實體系統和傳統OOP

現在,寫了幾千行代碼後,我有點困惑。我認爲理解模式,我喜歡使用它。我認爲這非常酷,而星巴克就是這樣,但它認爲它提供的好處是短暫的,而且(最讓我感到厭煩的)嚴重依賴於你的粒度。

這裏是從上面的第二條圖片: enter image description here

我愛對象的方式(遊戲實體,或任何你想打電話給他們)有一組最少的組件和推斷的想法是,你可以編寫代碼,看起來像:

BaseEntity Alien = new BaseEntity(); 
BaseEntity Player = new BaseEntity(); 

Alien.addComponent(new Position(), new Movement(), new Render(), new Script(), new Target()); 
Player.addComponent(new Position(), new Movement(), new Render(), new Script(), new Physics()); 

..這將是非常好的......但在現實中,代碼最終看起來像

BaseEntity Alien = new BaseEntity(); 
BaseEntity Player = new BaseEntity(); 

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer()); 

看來,我最終得到了一些非常專業的組件,這些組件由較少的組件組成。很多時候,我必須製造一些依賴於其他組件的組件。畢竟,如果你沒有位置,你如何渲染?不僅如此,最終渲染玩家與外星人與手榴彈的方式可能會有根本的不同。除非你製作了一個非常大的組件(在這種情況下......你爲什麼要使用複合圖案?)

給出另一個真實的例子。我的角色可以裝備各種裝備。裝備一件裝備時,會更改一些統計數據以及視覺顯示內容。下面是我的代碼看起來像現在:

billy.addControllers(new Movement(), new Position(), new CharacterAnimationRender(), new KeyboardCharacterInput()); 

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY); 
billy.get(CharacterAnimationRender.class).setFace(FACE.BLUSH_FACE); 
billy.get(CharacterAnimationRender.class).setHair(HAIR.RED_HAIR); 
billy.get(CharacterAnimationRender.class).setDress(DRESS.DRAGON_PLATE_ARMOR); 

以上CharacterAnimationRender.class隻影響什麼直觀地顯示出來。所以我顯然需要製造另一個處理齒輪數據的組件。但是,我爲什麼會做這樣的事情:

billy.addControllers(new CharacterStatistics()); 

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY); 
billy.get(CharacterStatistics.class).setBodyStats(BODY_STATS.NORMAL_BODY); 

當我只能做一個CharacterGearStuff控制器/組件,用於處理統計雙方的分佈以及視覺的變化?底線,我不知道這應該如何幫助生產力,因爲除非你想手動處理所有的東西,你仍然必須創建依賴2+組件的「元組件」(並修改/交叉組件)修改所有子組件 - 將我們帶回OOP)。或者,我正在考慮完全錯誤。我是嗎?

回答

5

這聽起來像是你稍微誤解了組件模式。

組件僅爲數據,無代碼。如果你的組件中有代碼,它不再是一個組件 - 它更復雜一些。

所以,舉例來說,你應該能夠平凡分享您的CharacterAnimationRender和CharacterStatistics,如:

CharacterStats { int BODY } 
CharacterGameStats { ...not sure what data you have that affects gameplay, but NOT the rendering... } 
CharacterVisualDetails { int FACE, int HAIR } 

...但無需這些要知道每個的存在其他。當你談論組件之間的「依賴關係」時,我承認你已經迷路了。一個ints結構如何「依賴於」另一個ints結構?他們不能。他們只是大量的數據。

...

再回到您的問題在一開始,你最終用:

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer()); 

...這是完美的。假設你已經正確地編寫了這些組件,你已經將數據拆分成易於讀取/調試/編輯/編碼的小塊。

但是,這是猜測,因爲您沒有指定那些組件內部的內容......例如, AlienAIMovement - 這是什麼?通常情況下,我希望你有一個「AIMovement()」,然後編輯它成爲一個外星人的版本,例如,改變該組件中的一些內部標誌來指示它使用AI系統中的「外來」功能。

1

我認爲你在這裏使用了一個錯誤的方法。模式應該被採納以適應您的需求,而不是相反。簡單總是比複雜更好,如果你覺得有些東西不能正確工作,這意味着你應該退後幾步,或許從一開始就開始。

對於我來說,這有一個代碼味道已經:

BaseEntity Alien = new BaseEntity(); 
Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 

我希望面向對象的代碼,這個看起來是這樣的:

Alien alien = new AlienBuilder() 
    .withPosition(10, 248) 
    .withTargetStrategy(TargetStrategy.CLOSEST) 
    .build(); 

//somewhere in the main loop 
Renderer renderer = getRenderer(); 
renderer.render(alien); 

當您使用泛型類所有實體,您將擁有一個非常通用且難以使用的API來處理您的對象。

此外,將位置,移動和渲染器等功能置於同一個組件的保護傘下感覺不對。職位不是一個組件,而是一個屬性。運動是一種行爲,渲染器是一種與你的領域模型完全無關的東西,它是圖形子系統的一部分。組件可以是汽車的輪子,身體部位和槍支的外星人。

遊戲開發是一件非常複雜的事情,從第一次嘗試就很難做到。重寫你的代碼,從錯誤中吸取教訓,感受你在做什麼,而不是試圖從某篇文章中調整模式。如果你想在模式上變得更好,你應該嘗試一些比遊戲開發更多的東西。例如編寫一個簡單的文本編輯器。

+5

我不認爲遊戲實體/組件本來就是面向對象的,它們是爲了數據驅動的。至少從我讀到的第一篇文章上面看到的。我還閱讀了很多實現了實體系統/組件模式(http://gamadu.com/temp/es.zip,http://code.google.com/p/spartanframework/等)的開源代碼),似乎我正在以同樣的方式實施它。 – 2011-02-09 16:08:47

+0

「重寫你的代碼」幾乎總是很糟糕的建議。同樣清楚你不熟悉ECP模式。 – vaughandroid 2015-08-31 18:53:25

2

David,

首先感謝您的完美問題。

我瞭解您的問題,並認爲您沒有正確使用該模式。請閱讀這篇文章:http://en.wikipedia.org/wiki/Composite_pattern

例如,如果你不能實現一般的類運動,並需要AlienAIMovement和KeyboardMovement,你可能應該使用Visitor模式。但在開始重新構建數千個代碼行之前,請檢查您是否可以執行以下操作。

是否有機會寫類接受BaseEntity參數的類運動?運動的所有實現之間的區別可能僅僅是一個參數,旗幟或者其他嗎?在這種情況下,您的代碼如下:

Alien.addComponent(new Position(), new Movement(Alien), new Render(Alien), new Script(Alien), new Target());

我認爲這是沒有那麼糟糕。

如果是不可能的,嘗試創建使用工廠實例,所以

Alien.addComponent(f.createPosition(), f.createMovement(Alien), f.createRender(Alien), f.createRenderScript(Alien), f.createTarget());

我希望我的建議幫助。

+0

我喜歡「新運動(外星人)」的想法,但我認爲這樣做會打破實體系統模式,至少從閱讀上面的第一篇文章和評論。 – 2011-02-09 16:09:28

2

Ents似乎被設計來做你想要的。如果你仍然想要自己的圖書館,至少可以從它的設計中學習。

以前的答案看起來都很笨拙,並創建了大量不必要的對象IMO。