iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 21
0
自我挑戰組

寫遊戲初體驗系列 第 21

Day 21 ECS intro

  • 分享至 

  • xImage
  •  

前言

以前呢,讀過Game Programming Patterns這本書,讀完真的是受益良多阿~~。
以前學過C++的繼承之後,在寫大型一點的專案的時候,就會開始無腦的繼承,接著就會遇到循環繼承問題,又或著是繼承鍊長到不行,根本無法維護。
當時就在想,一個大型的遊戲到底是怎麼設計的,一定不可能無腦繼承阿,於是乎讀了這本書之後,才發現原來還有這種方法阿。這幾天就來稍微講講遊戲的實體(Entity)到體是怎麼實現的。

遊戲的本質

遊戲的本質其實就是大量實體(Entity)的行為以及它們之間的交互(Manager)。

但顯然遊戲裡的物件不會只有少少幾個。隨著物件邏輯的膨脹,我們會將相同邏輯的部分進行拆分,而拆分的方法有很多種。

邏輯拆分

繼承

最直觀的寫法就是繼承,當我們的實體需要哪些邏輯的部分,我們就繼承那個部分。

例子

  • 考慮遊戲中
    • decoration(裝飾): 不能互動(physis),可視物品(graphic)
    • prop 物件: 可互動,可視物品
    • zone 區域: 可互動,不可視物品(當作 trigger)

除了菱形繼承的問題,我們說過,遊戲裡的物件不只有那麼少,當遊戲物件多起來邏輯多起來,如果用繼承來解,祖譜肯定是很可觀的。

組件

比起繼承,組件的靈活度更高。當我們需要那部分邏輯時,我們不再繼承他,而是讓物件擁有他。
這樣不但沒有宏偉的繼承樹,要讓各個實體溝通也就方便許多。

這裡我喜歡稱這些邏輯為component

遊戲一般到這裡就很OK了,當然偶爾也會用到繼承,但這裡的繼承通常只會讓你更方便(多個虛構層)。在利用組件模式設計時,大概只有在實體間互相邏輯會費較多心思。

我相信利用組件模式寫一些課堂上小專案的作業肯定綽綽有餘。不過你可能還是會遇到一些問題。

  • 也許你實作實體間交互時,你為各個組件間寫了個manager,接著你會思考,同一段邏輯究竟要放在component,還是放在manager
  • 當實作完,你的Entity以及Game loop87%長這樣
class Entity {

public:

    void update() {
    
        physis->update();
        transform->update();
        graphic->update();
    }

private:

    Physis* physis;
    Transform* transform;
    Graphic* graphic;
};

GameLoop() {

    while(running) {
    
        for(int i = 0 ; i < MAX_ENTITES ; i++)
            entityList->update();
    }
}

這裡要說的問題是,這種方法對緩存不有好,CPU在從記憶體抓取資料時會一次抓取一組資料,稱為cache line這樣當CPU處裡完你當前要他處理的資料,要去做下一個時,他會從cache line找,如果找到,就不必浪費時間再去記憶體找。

我們的遊戲存了每個實體的pointer,實體存著每個component的pointer,這樣在遍歷時,會有很多很多的cache miss,這樣會花費很多額外的時間。一個遊戲注重的,除了遊戲本身好不好玩,另外一個就是效能了。大家都知道遊戲的卡頓才是造成人類暴力的原因。

那剛剛提到的兩個問題有甚麼解決方法呢,就是以下的這個方法囉

Entity Component System(ECS)

Entity Component System,簡稱ECS,這裡是指我們會有3個東西-Entity, Component, System-而不是 "Entity Component" System XD。

先上圖了解一下ECS的架構

你問Component去哪了呢?其實就是Data

我來說明一下以上的東西

  • Data: 其實就是Component,不一樣的地方在於,這次他不會存在任何邏輯,他存的僅僅只有data
struct Transform {

    float posX_;
    float posY_;
    float scale_;
}
  • System: 放邏輯的地方,所有的邏輯都放在這裡
  • Entity: DataLogic都發出去了那還剩甚麼,Entity還真的甚麼都沒有,他僅僅是一個ID,想想看,我們一群component要怎麼知道我們屬於哪個Entity?ID就好了。所以Entity其實就是Data溝通的橋樑,常常Entity只是一個int而已
using Entity = std::size_t;

在實作的過程,會將資料緊密的包在連續記憶體裡,為了確保不會發生太多的cache miss

What is next

接下來我會給大家看看我自己時做的component patternECS(參考)以及別人做的ECS系統Entt

Reference


上一篇
Day 20 Renderer 2D
下一篇
Day 22 Component Pattern
系列文
寫遊戲初體驗30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言