插件及文檔:https://github.com/sschmid/Entitas-CSharp/wiki/Home
資料:
什麼是Entitas
Entitas是一個運行效率高的輕量級C# Entity-Component-System(ECS)框架,專門爲unity訂製。提供內部緩存和快速的組件訪問。它經過精心設計,可以在垃圾收集環境中發揮最佳作用。
## 優缺點:
來自大佬的點評
優點:
- 遵循這個框架的規則去寫代碼,代碼結構清晰。
- ECS這種模式,耦合度就很低,所有的遊戲物體都是組件的組合而已,可擴展性強,通過合理組合component就能配置出一個新的遊戲物體。
- 很方便的管理所有實體狀態,entitas提供了類似狀態機的功能,當感興趣的某個屬性發生變化時,能在System中很方便的做出響應,不管什麼狀態,都能很方便的做出對應處理。
- unity本身的開發模式就類似ECS,unity2018更是推出了最新的ECS框架,entitas很符合這種開發模式
- entitas自開源以來,一直在更新維護,並受到了unity官方的認可,在unite大會上都有提到。所以,這個框架還是很靠譜的。
缺點:
- 國內資料少,上手難度高。國內用這個框架開發的特別少,遇到問題需要自己爬坑。
- 不適合小項目。小項目用entitas反而麻煩
- entitas更新太快,官方wiki文檔更新沒有跟上,比如,我在看官方Demo-MatchOne的時候,有個Event的Attribute, wiki上暫時還沒有這個的
- 代碼熱更方面是個問題, entitas基本對unity開發定了一套完整的規則,特別是有Code Generate,如果項目發佈後想要更新加入新的代碼會很麻煩,官方對此也沒有說明,目前好像也沒有人分享在entitas中加入lua熱更的功能
編程思想
面向對象思想強調對象,通過對象自身屬性等完成具體實現。ECS則強調過程,通過並無實際意義的實體來收集作爲數據的容器來完成具體實現。
- E:Entity無實際意義,僅作爲收集組合C的容器。
- C:Component包含數據的組件,無方法實現。
S:處理數據的系統,自身無任何數據,通過方法來實現。
Entitas基本概念
+------------------+
| Context |
|------------------|
| e e | +-----------+
| e e---|----> | Entity |
| e e | |-----------|
| e e e | | Component |
| e e | | | +-----------+
| e e | | Component-|----> | Component |
| e e e | | | |-----------|
| e e e | | Component | | Data |
+------------------+ +-----------+ +-----------+
|
|
| +-------------+ Groups:
| | e | Subsets of entities in the context
| | e e | for blazing fast querying
+---> | +------------+
| e | | |
| e | e | e |
+--------|----+ e |
| e |
| e e |
+------------+
Entity
entity是一個存儲數據的容器,用以表現程序中存在的對象。你可以添加,替換和移除數據通過IComponent。Entitas也有相應的事件event來通知你這些變化。
Entitas通過代碼生成器可以自然的產生很多易讀的代碼如下文中的方法便是代碼生成器自動產生的API調用。
entity.AddPosition(3, 7);
entity.AddHealth(100);
entity.isMovable = true;
entity.ReplacePosition(10, 100);
entity.ReplaceHealth(entity.health.value - 1);
entity.isMovable = false;
entity.RemovePosition();
var hasPos = entity.hasPosition;
var movable = entity.isMovable;
Context
Context是一個讓你可以創建和銷燬實體entities的工廠。通過它可以過濾感興趣的實體。
// Contexts.game is kindly generated for you by the code generator
var gameContext = Contexts.game;
var entity = gameContext.CreateEntity();
entity.isMovable = true;
// Returns all entities having MovableComponent and PositionComponent.
// Matchers are also generated for you.
var entities = gameContext.GetEntities(Matcher<GameEntity>.AllOf(GameMatcher.Movable, GameMatcher.Position));
foreach (var e in entities) {
// do something
}
Group
通過組Group,可以對上下文中的實體進行超快速過濾。 當實體更改時,它們會不斷更新,並且可以立即返回實體組。 想象一下,您有成千上萬個實體,而您只需要那些具有PositionComponent的實體-只需詢問該組Group的上下文,它的結果就已經被篩選完成。
gameContext.GetGroup(GameMatcher.Position).GetEntities();
Group和獲取的entities都被緩存下來,所以該方法運行速度非常高。儘可能的優先使用Group。gameContext.GetEntities(GameMatcher.Moveble)同樣可以內部地使用groups。
Groups有OnEntityAdded,OnEntityRemoved,OnEntityUpdated來對group變化作出響應。
gameContext.GetGroup(GameMatcher.Position).OnEntityAdded += (group, entity, index, component) => {
// Do something
};
如果你想彙總和處理這些變化,可以使用Collector
Collector
Collector提供了便捷的方法來對group的變化作出反應。比如你想彙總和處理所有添加或替換PositionComponent的實體entities。
var group = gameContext.GetGroup(GameMatcher.Position);
var collector = group.CreateCollector(GroupEvent.Added);
接下來
foreach (var e in collector.collectedEntities) {
// do something with all the entities
// that have been collected to this point of time
}
collector.ClearCollectedEntities();
停用collector可以方便的結束監視
collector.Deactivate();
Matcher
Matcher匹配器由代碼生成器生成,可以組合。匹配器通常用於從感興趣的上下文中獲取實體組。需要在匹配器前加上你感興趣的上下文名稱(例如GameMatcher, InputMatcher等)。
System
entitas中有四種Systems:
- IInitializeSystem: 只執行一次 (system.Initialize())
- IExecuteSystem: 每幀執行 (system.Execute())
- ICleanupSystem: 在其他系統完成後每一幀執行(system.Cleanup())
- ReactiveSystem: 當觀察的group改變時執行(system.Execute(Entity[]))
public class MoveSystem : IExecuteSystem {
public void Execute() {
// Do sth
}
}
public class CreateLevelSystem : IInitializeSystem {
public void Initialize() {
// Do sth
}
}
public class RenderPositionSystem: ReactiveSystem<GameEntity> {
public RenderPositionSystem(Contexts contexts) : base(contexts.Game) {
}
protected override Collector<GameEntity> GetTrigger(IContext<GameEntity> context) {
return context.CreateCollector(GameMatcher.Position);
}
protected override bool Filter(GameEntity entity) {
// check for required components (here it is position and view)
return entity.hasPosition && entity.hasView;
}
protected override void Execute(List<GameEntity> entities) {
foreach (var e in entities) {
// do stuff to the matched entities
e.view.gameObject.transform.position = e.position.position;
}
}
}
最後需要注意的是,需要創建一個管理System的System,因爲一個遊戲開發過程中,不可能只有一個System的,爲了方便管理,便有了[Feature]System的概念。這個類要繼承Feature,在構造器裏Add所有System進去。Feature就像一個管理System的SystemManager。
var systems = new Systems(contexts)
.Add(new CreateLevelSystem(contexts))
.Add(new UpdateBoardSystem(contexts))
.Add(new MoveSystem(contexts))
.Add(new RenderPositionSystem(contexts));
// Call once on start
systems.Initialize();
// Call every frame
systems.Execute();
Hello World (欲入我門,必先Hello World)
創建Component
作爲數據容器,存儲我們需要輸出的字符串信息。同理如果是位置信息,則需要我們自己創建x,y,z座標信息。這裏的Game
namespace JunMoxiao
{
/// <summary>
/// 打印消息的組件
/// </summary>
[Game]//Entitas中Atrribute,方便標記組件所屬entities,提高內存效率
public class LogComponent : IComponent
{
/// <summary>
/// 打印信息
/// </summary>
public string message;
}
}
Entitas中的Atrributes
Code Generator(代碼生成器)目前支持與類、接口和結構一起使用的以下特性:
- Context: 可以使用此特性使組件僅在指定的context中可用;例如 [MyContextName], [Enemies], [UI]....提高內存效率。它還可以創建組件。
- Unique: 代碼生成器將提供額外的方法,以確保最多存在一個具有該組件的實體,相當於單例。
- FlagPrefix:僅可用於支持標記組件的自定義前綴。
- PrimaryEntityIndex: 可用於將實體限制爲唯一的組件值。
- EntityIndex: 可用於搜索具有組件值的實體。
- CustomComponentName: 爲一個類或接口生成具有不同名稱的多個組件。
- DontGenerate]: 代碼生成器不會使用此屬性處理組件。
- Cleanup: 代碼生成器將生成刪除組件或銷燬實體的系統。
System
LogSystem
namespace JunMoxiao
{
public class LogSystem : ReactiveSystem<GameEntity>
{
public LogSystem(Contexts contexts) : base(contexts.game)
{
}
/// <summary>
///執行
/// </summary>
/// <param name="entities"></param>
protected override void Execute(List<GameEntity> entities)
{
foreach (GameEntity gameEntity in entities)
{
Debug.Log(gameEntity.junMoxiaoLog.message);
}
}
/// <summary>
/// 篩選器
/// </summary>
protected override bool Filter(GameEntity entity)
{
return entity.hasJunMoxiaoLog;
}
/// <summary>
/// 觸發器
/// </summary>
protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
{
return context.CreateCollector(GameMatcher.JunMoxiaoLog);
}
}
}
InitSystem
namespace JunMoxiao
{
/// <summary>
/// 初始化系統
/// </summary>
public class InitSystem : IInitializeSystem
{
private readonly GameContext _gameContext;
public InitSystem(Contexts contexts)
{
_gameContext = contexts.game;
}
public void Initialize()
{
_gameContext.CreateEntity().AddJunMoxiaoLog("hello world!");
}
}
}
Feature
entitas爲提供了Features來組織你的system。使用Features將相關system組合在一起。這有一個額外的好處,就是可以在Unity層次結構中爲你的system分離可視化調試對象。現在可以在邏輯組中檢查它們,而不是一次檢查所有。
Feature還可以幫助你在項目中執行更廣泛的範例規則。功能的執行順序由添加它們的順序決定,然後按照這個順序初始化它們,確保遊戲邏輯不會被幹擾。
Feature要求實現構造函數。使用Add()方法向Feature添加system。這裏添加它們的順序定義了它們在運行時的執行順序。可以在Controller中使用Feature將systems組實例化。
namespace JunMoxiao
{
/// <summary>
/// 將創建的系統添加到框架內
/// </summary>
public class AddGameSystem : Feature
{
public AddGameSystem(Contexts contexts) : base("AddGameSystem")
{
Add(new LogSystem(contexts));
Add(new InitSystem(contexts));
}
}
}
Controller
namespace JunMoxiao
{
public class HelloWorldController : MonoBehaviour
{
private Systems _systems;
void Start()
{
var context = Contexts.sharedInstance;
_systems=new Feature("Systems").Add(new AddGameSystem(context));
_systems.Initialize();
}
void Update()
{
_systems.Execute();
_systems.Cleanup();
}
}
}
運行
/ 未完待續