1. 程式人生 > >Unity使用ECS架構entitas實現ui響應及回放系統

Unity使用ECS架構entitas實現ui響應及回放系統

前言

最開始聽說了守望先鋒在開發的時候,使用了一個ECS的框架,就非常感興趣。
ECS是一個可以基於現有的遊戲引擎開發的架構,它是Entity、Component、System的集合簡稱,在遊戲開發中,component僅用來儲存資料,多個component可組成一個entity,而system多用於操作component,真正做到了以資料驅動遊戲。
說到資料驅動的好處,就不得不提守望先鋒的回放系統,在遊戲中可謂是相當驚豔的功能,說得通俗一點,只要儲存了資料,就能在你想看的時候,再重現一遍。

Entitas

在瞭解一些ECS架構的特點就希望自己能使用實際操作一下,然後在網上找到了entitas架構,然後還發現實際上早在unite2015和2016上,都有過相關的演示演講,只能說自己孤陋寡聞了。
在github的介紹,Entitas是針對c#和unity實現ECS快速架構的解決方案。這就很完美的了,不需要重複造輪子,自然省下不少時間。

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity

效能對比

演講中還提到了與unity的MonoBehaviour的效能對比
這裡寫圖片描述

HelloWorld

  • 下載entitas,並匯入到unity工程中
  • 在unity中,選擇Tools/Entitas/Preferences,全部設定為Everything,因為是簡單demo,Contexts也只設置為Game,在前面的版本Contexts其實叫pool,所以也可以理解為物件池,所有的Entity都是從這裡取的
    這裡寫圖片描述
  • 按下Shift+ctrl+G,開始編譯
  • 新建一個指令碼GameController,並開始實現簡單的System新增
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Entitas;

public class GameController : MonoBehaviour {

    Systems _systems;

    // Use this for initialization
    void Start () {
        var contexts = Contexts.sharedInstance;

        _systems = CreateSystems(contexts);

        _systems.Initialize();
    }

    // Update is called once per frame
void Update () { _systems.Execute(); _systems.Cleanup(); } private void OnDestroy() { _systems.TearDown(); } Systems CreateSystems(Contexts contexts) { return new Feature("Root System") ; } }
  • GameController繫結在場景的物體的中,執行就能看見場景的System與GameContext

回放系統

  • 準備
    • 在看了entitas在unite2016演示後,然後就打算重現一下他演示的demo。
    • 首先說一下思路,demo以計數幀來驅動遊戲的,然後記錄下在某一幀的重要操作,然後在實現回放系統的時候,只需要從0開始重新運算,並在記錄幀更新響應的資料,就實現了回放的系統,就這麼簡單,一方面這只是一個demo,一方面記錄資料量不大,在實際中肯定還需要做一些優化計算的,不過還是大致思路還是一樣的。
    • 這裡有一個技巧就是,為了方便我們操作回放系統的資料,我們可以將回放系統的相關Systems,作為一個變數存在Component中。
  • 實現一個簡單的ui介面
  • 為我們所需要的資料定義Component,這時候我們只需要新建一個類繼承至IComponent即可,然後為其新增相應的變數,比如當前幀,能量值,消耗能量,是否為暫停。其中標記Game表示你所對應的Contexts,Unique表示唯一Entity
using Entitas;
using Entitas.CodeGeneration.Attributes;

[Game,Unique]
public class TickComponent:IComponent
{
    //當前幀
    public long currentTick;
}
using Entitas;
using Entitas.CodeGeneration.Attributes;

[Game,Unique]
public class ElixirComponent:IComponent
{
    //能量值
    public float amount;
}
using Entitas;

[Game]
public class ConsumeElixirComponent:IComponent
{
    //消耗的能量值
    public int amount;
}
using Entitas;
using Entitas.CodeGeneration.Attributes;

[Game,Unique]
public class PauseComponent:IComponent 
{

}
  • 在unity中按下Shift+Ctrl+G開始編譯
  • 然後編寫自己的TickUpdateSystem,IInitializeSystem是初始化執行的介面,IExecuteSystem是Unity的Update或者FixedUpdate執行的介面
using Entitas;
//更新幀計數
public class TickUpdateSystem : IInitializeSystem, IExecuteSystem
{
    readonly GameContext _context;
    public TickUpdateSystem(Contexts contexts)
    {
        _context = contexts.game;
    }

    public void Initialize()
    {
        _context.ReplaceTick(0);
    }

    public void Execute()
    {
        if (!_context.isPause)
            _context.ReplaceTick(_context.tick.currentTick + 1);
    }
}
  • 然後將TickUpdateSystem,新增到最開始的Root System,在GameContext下就可以看著幀數的變化
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Entitas;

public class GameController : MonoBehaviour {

    Systems _systems;

    // Use this for initialization
    void Start () {
        var contexts = Contexts.sharedInstance;

        _systems = CreateSystems(contexts);

        _systems.Initialize();
    }

    // Update is called once per frame
    void Update () {
        _systems.Execute();
        _systems.Cleanup();
    }

    private void OnDestroy()
    {
        _systems.TearDown();
    }

     Systems CreateSystems(Contexts contexts)
    {
        return new Feature("Root System")
        .Add(new TickUpdateSystem(contexts))
            ;
    }
}

演示

這裡寫圖片描述