當前位置: 華文問答 > 遊戲

如何評價【守望先鋒】架構設計?

2018-04-02遊戲

如果你看不明白原帖中的ECS(似乎很多人有這個反應),我覺得其實是很正常的,尤其是從開始寫程式碼到今天只接觸過OOP的人,而OW本身是一個很宏大的專案,你沒法很好的去理解這個例子,假如你真的有興趣了解ECS,那麽我給你們舉個很簡單的例子,以及說一下ECS的基本Motif(調性):

首先是規則區別,我還是要重復一個Key(我記得似乎在別的貼文說過):

1,OOP的核心思想是:我是什麽——我是一個角色物件,我是一個子彈物件。

2,ECS的核心思想是:我有什麽——我有render,我有move,我有motion。

就拿子彈和角色來說,如果我們是一個很簡單的射擊遊戲,或者是Top-Down-Shooter的,或者更簡單直白的,FC上的坦克相信都知道吧?

如果用OOP的方式做,它裏面有角色(坦克)物件和子彈物件,偽代碼

class tank {

//這裏僅說一些有代表性的,就不細節設計了

public tank()

public Vector2 move(direction) //有一個move方法來移動坦克,在這個方法裏面,我們根據面向、速度等因素來讓坦克的座標發生位移,同時我們讓坦克的動作發生了變化。註意:我們把坦克的渲染其實也放在了這裏,所謂的渲染包括了移動時候播放的動畫等。

public bullet fire() //我們透過這個來讓坦克開火,產生子彈

public void kill() //我們透過這個來讓坦克在HP小於等於0的時候死亡。

}

class bullet {

public bullet(){}

public Vector2 move(direction) //子彈也會根據方向移動,這毫無疑問。註意,我們把子彈的渲染也放在了這裏,所謂渲染包括了子彈移動的時候貼出他的位置。

public void hit() //子彈命中了什麽東西之後要呼叫這個,用來消除子彈。

}

我相信不用寫game,大多人也知道這個game要怎麽寫,畢竟這麽簡單的遊戲。


那麽我們來看看ECS是怎麽做這個的?偽代碼

moveComponent{

enum direction //移動方向,不做進一步解釋了

float moveSpeed //移動速度,我想你也明白

}

positionComponent{

float x; //位於地圖的座標,不解釋了

float y;

}

function moveSystem(entities){ //這個system只關心帶有positionComponent和moveComponent的entity,所以entities裏面的所有entity都必然有個moveComponent和positionComponent

for (entity in entities){

這裏就不詳細寫了,執行的事情就是根據moveComponent中的direction和moveSpeed,為positionComponent中的x,y重新賦值。

}

}

renderComponent{

string resource; //貼圖的資訊,可以是資源名,等等,老做遊戲了你應該理解這個

}

function renderSystem(entities){

//這個系統的工作,就是根據自己關心的帶有positionComponent和renderComponent的物件,來進行渲染工作。

}

collisionComponent{

rect body; //FC的坦克大戰中,所有的子彈和坦克,其實都是矩形的。

int side; //可以理解為陣營,同一陣營的互相忽略碰撞,當然寫在這裏面並不是最好的方案,具體要看需求,目前的需求下這還算湊合。

hitEntities: array<entity>; //用於記錄碰撞到的entity

}

tankComponent{

//證明這是一個tank,並且記錄坦克需要的數據

int hp

}

bulletComponent{

//證明這是一個子彈,並記錄子彈需要的資訊

int power

bool hitSomeThing

}

function collisionSystem(entities){

//這個system關心的是collisionComponent和positionComponent,他的工作就是根據position和collisionComponent.rect來判斷2個entity是否碰撞,如果碰撞,則將彼此加入到對方的collisionComponent.hitEntities陣列中去。

}

function damageSystem(entities){

//這是一個很有意思的system,他只關心帶有collisionComponent,然後會根據collisionComponent.hitEntities中的entity提供的資訊配合被捕捉的entity的其他component(比如tankComponent和bulletComponent)進行特殊處理。

//這個system的工作之一:如果這個entity並沒有tankComponent,同時還沒有bulletComponent,for迴圈執行continue,檢查下一個entity。

//如果這個entity擁有tankComponent,證明這是坦克,則遍歷collisionComponent.hitEntities,根據其中帶有bulletComponent的entity進行處理,你可以同時銷毀他們全部,並且都對這個坦克進行傷害,也可以根據被認為是子彈(帶有bulletComponent的entity)的個數特殊處理,比如被3個子彈命中則hp提高10000等,根據設計需求來實作。

//值得註意的是:即使entity.tankComponent.hp<=0,也不由我來進行下一步操作,我就是一個damageSystem,我負責的就是damage(造成傷害),造成傷害的後果,管我鳥事兒。

}

function tankDestroySystem(entities){

//我只關心帶有tankComponent的entity,我的任務是如果entity.tankComponent.hp<=0,則將他們殺死。

}

function bulletDestroySystem(entities){

//和tankDestroySystem差不多,但我只負責對bulletComponent.hitSomeThing==true的物件進行dispose()

}

這就是一個典型的ECS的遊戲寫法,他沒有坦克物件,子彈物件,但是他有坦克和子彈

tankEntity = entities.new()

.add(new positionComponent(x,y))

.add(new renderComponent(xxxx))

.add(new collisionComponent(rect(x,x,x,x), y)

.add(new tankComponent(x))

bulletEntity = entities.new()

.add(new positionComponent(x,y))

.add(new renderComponent(xxxx))

.add(new collisionComponent(rect(x,x,x,x), y)

.add(new bulletComponent(x))

你會發現,子彈和坦克唯一的區別就是一個是子彈,一個是坦克。你可能要問,為什麽沒有moveComponent?因為moveComponent只有在移動的時候才需要添加上去,當移動完畢,就可以把它移除掉,這對於效率來說,還算是一種最佳化。


到這裏,我們是不是可以思考另外一個問題——在一個3歲小孩子眼裏,畫出來的羊和活著的羊有什麽區別?區別就是:活著的羊會動,他有motionComponent和moveComponent。當然話題收回來,現在,我們發現遊戲中還沒有設計地形,對,沒有地形玩什麽?

OOP方式:

class ObstacleGrid{

//tilebased遊戲,地圖格當然不能沒有,當然這種遊戲需要的只是會阻擋的地形格子資訊而已。

string resource; //貼圖資源

bool breakable; //能否被子彈擊穿

Vector2 position; //位置

.....

public void break() //被子彈銷毀了

}

看起來加一個物件不復雜,但是當地圖上有了這些obstacleGrids之後,你要在game中coding什麽呢?很多,比如坦克移動的時候,你不能穿越他,再比如子彈命中他的時候……等等等等。


ECS方式,我們要做的事情很清晰:

wallComponent{

//證明我是阻擋地形,別在意名字的細節……

bool breakable;

}

function obstacleSystem(entities){

//這個system關心的,只有帶有wallComponent和collisionComponent的entity,根據collisionComponent.hitEntities進行對應的處理,相信你動動腦子能想明白。

//這裏有一個獨特與oop的地方,如果我(墻壁entity)阻擋了坦克或者子彈,我要做的是對對方entity.remove(moveComponent),導致對方不能發生移動。

}

阻擋地形:

obstacleGrid = entities.new() .add(new positionComponent(x,y)) .add(new renderComponent(xxxx)) .add(new collisionComponent(rect(x,x,x,x), y) .add(new wallComponent(x))

看到這裏,發現為啥他和子彈和坦克如此接近?是不是能突然冒出一個有意思的想法?如果策劃說了,我的阻擋地形也會移動!是不是OOP很懵逼?但ECS就很好處理了?


以上是一個簡單的思路上的例子,區別了ECS和OOP,ECS中你說一點耦合不存在可能嗎?是可能的,system之間是否必須耦合(其實必須優先執行aSystem再執行bSystem就可以認為是耦合)取決於你的programming和對業務的理解能力。當然,我想說的是,請看


是不是符合Component中沒有任何行為?no functions

是不是符合沒有狀態?任何system都不關心遊戲狀態

第三個、第四個因為業務簡單就沒有體現出來

最後一個是不是system之間互不關心?當然!


所以,請認真的去理解ECS的特性和好處,不要輕易的做出危險的類比!


算了,吐槽就去掉吧,還是講講道理。