Unity使用ECS架構entitas實現ui響應及回放系統
阿新 • • 發佈:2019-01-11
前言
最開始聽說了守望先鋒在開發的時候,使用了一個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))
;
}
}