Skip to content
chaolun edited this page Aug 14, 2019 · 2 revisions

Entity Component System

  • As a Entity you need to add a EntityBehaviour Component on GameObject

  • You can use right click in Project Window Select Create -> Custom Script -> ComponentBehaviour Installer Create a Component script

  • You can use right click in Project Window Select Create -> Custom Script -> SystemBehaviour Installer Create a System script

  • For Example :

    [Entity]

    Scene :
        Level(GameObject) : [Transform|SceneContext|SceneInstaller(MonoInstaller)]
            Entity(GameObject) : [Transform|EntityBehaviour|HealthComponent(ComponentBehaviour)]
            System(GameObject) : [Transform|HealthSystem(SystemBehaviour)]
    

    [Component]

    using UniEasy.ECS;
    
    public class HealthComponent : ComponentBehaviour
    {
        public float CurrentHealth;
        public float StartingHealth;
    }
    

    [System]

    using UniEasy.ECS;
    using System;
    using UniRx;
    
    public class HealthSystem : SystemBehaviour
    {
        public IGroup Healths;
    
        public override void Initialize (IEventSystem eventSystem, IPoolManager poolManager, GroupFactory groupFactory, PrefabFactory prefabFactory)
        {
            base.Initialize (eventSystem, poolManager, groupFactory, prefabFactory);
    
            Healths = this.Create (typeof(HealthComponent));
        }
    
        public override void OnEnable ()
        {
            base.OnEnable ();
    
            Healths.OnAdd().Subscribe (entity =>
            {
                var healthComponent = entity.GetComponent<healthcomponent> ();
                healthComponent.CurrentHealth = healthComponent.StartingHealth;
            }).AddTo (this.Disposer);
        }
    }
    

ProjectContext

ECSInstaller

  • Add ECSInstaller Component to ProjectContext prefab(Resources/ProjectContext.prefab)

  • Drag ECSInstaller Component to ProjectContext Installers ReorderableList

ProjectContext will call ECSInstaller InstallBindings() function when before scenes loaded

ECSInstaller will create and binding all ECS framework needed core classes

ProjectInstaller

  • Add ProjectInstaller Component to ProjectContext prefab(Resources/ProjectContext.prefab)

  • Drag ProjectInstaller Component to ProjectContext Installers ReorderableList

ProjectContext will call ProjectInstaller InstallBindings() function when before scenes loaded

ProjectInstaller will instantiate, binding and inject all gameObjects from Resources/Kernel folder

SceneInstaller

  • Create a gameObject named Level in the root directory of the scene

  • Set other gameObjects transform parent were Level gameObject

  • Add SceneContext Component to Level gameObject

  • Add SceneInstaller Component to Level gameObject

  • Drag SceneInstaller Component to SceneContext Installers ReorderableList

SceneContext will called SceneInstaller's InstallBindings() function when Awake, so SceneInstaller will inject all SystemBehaviour

For Performance we don't want to SceneInstaller GetComponents<SystemBehaviour> When Awake(), So we need to pre GetComponents<SystemBehaviour> and save them in array or list, we did it, too

You can see a Auto Update toggle and Force Update button on SceneInstaller Inspector panel

If set Auto Update toggle is true SceneInstaller will auto GetComponents<SystemBehaviour> and save them in Systems List when you save scene

Click Force Update button SceneInstaller will GetComponents<SystemBehaviour> and save them in Systems List immediately

If you also want this system can be binding and as a single system, you can add it to Binding Systems List by your hand

GroupFactory

Use GroupFactory.Create (Type[] types) you can get the group(entities) that contain all of these types

You also can use this.Create (Type[] types) get the group when you are in the SystemBehaviour class

WithPredicate (GroupFactory Extend)

Now we add WithPreficate to the group, so we can do more cool things like this :

public class ActiveComponent
{
    public BoolReactiveProperty IsActive;
}
public class ActiveSystem : SystemBehaviour
{
    public IGroup Actives;

    public override void Initialize (IEventSystem eventSystem, IPoolManager poolManager, GroupFactory groupFactory, PrefabFactory prefabFactory)
    {
        base.Initialize (eventSystem, poolManager, groupFactory, prefabFactory);

        Actives = GroupFactory.AddTypes (new Type[] {
            typeof(EntityBehaviour),
            typeof(ActiveComponent),
        }).WithPredicate ((entity) =>
        {
            var activeComponent = e.GetComponent<ActiveComponent> ();

            activeComponent.gameObject.ObserveEveryValueChanged (go => go.activeSelf).Subscribe (b => {
                activeComponent.IsActive.Value = b;
            }).AddTo (activeComponent.Disposer);
            return activeComponent.IsActive;
        }).Create ();
    }

    public override void OnEnable ()
    {
        base.OnEnable ();

        Actives.OnAdd ().Subscribe (entity => {
            Debug.Log ("every time the gameObject is set for active will be called");
        }).AddTo (this.Disposer);

        Actives.OnRemove ().Subscribe (entity => {
            Debug.Log ("every time the gameObject is set for disactive will be called");
        }).AddTo (this.Disposer);
    }
}

As Single System

For Performance for a high frequency used group you can choose to create a class that inherit from Group for him, then bind and inject it into every system that needs to be used, for example :

public class DeadEntities : Group
{
    public override void Initialize (IEventSystem eventSystem, IPoolManager poolManager)
    {
        Components = new Type[] { typeof(HealthComponent) };

        Func<IEntity, ReactiveProperty<bool>> checkIsDead = (e) =>
        {
            var health = e.GetComponent<HealthComponent> ();

            health.CurrentHealth.Value = health.StartingHealth;

            var isDead = health.CurrentHealth.DistinctUntilChanged ().Select (value => value <= 0).ToReactiveProperty();
            return isDead;
        };

        Predicates.Add(checkIsDead);
        base.Initialize (eventSystem, poolManager);
    }
}
public class GroupsInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<DeadEntities>().To<DeadEntities>().AsSingle();
    }
}

Then add the GroupsInstaller component to the SceneContext List

PrefabFactory

If you want to dynamic create an entity GameObject, you need to use PrefabFactory.Instantiate() method

public class ExampleSystem : SystemBehaviour {

    public GameObject Prefab;
    public Transform Parent;
    public bool WorldPositionStays = true;

    public override void Initialize (IEventSystem eventSystem, IPoolManager poolManager, GroupFactory groupFactory, PrefabFactory prefabFactory)
    {
        base.Initialize (eventSystem, poolManager, groupFactory, prefabFactory);

        if (Prefab != null)
        {
            var go = PrefabFactory.Instantiate (Prefab, Parent, WorldPositionStays);
            var entity = (go.GetComponent<EntityBehaviour>() ?? go.AddComponent<EntityBehaviour>()).Entity;      
        }
    }
}

EntityBehaviour

For Performance we don't want to EntityBehaviour GetComponents<Component> When Awake(), So we need to pre GetComponents<Component> and save them in array or list, we did it, too

You can see a Auto Update toggle and Force Update button on EntityBehaviour Inspector panel

If set Auto Update toggle is true EntityBehaviour will auto GetComponents<Component> and save them in Components List when gameObject's components count changed

Click Force Update button EntityBehaviour will GetComponents<Component> and save them in Components List immediately

Runtime Components

Runtime component does not contain references to Objects or Transforms, so it can makes components more single and more clear

You can found it on EntityBehaviour and it is a ReorderableList named Runtime Components [0]

You can use ReorderableList's + Button to select and add a runtime component to EntityBheaviour

// Use UniEasy.ContextMenuAttribute can easy to pack runtime components into groups
// You can see them at drop down context menu when you click `Runtime Components [0]` ReorderableList's `+` Button
[UniEasy.ContextMenu ("Test/HealthComponent")]
public class HealthComponent : RuntimeComponent
{
    public float CurrentHealth;
    public float StartingHealth;
}

Scriptable Components

The object you want to add scriptable components must be prefab or asset!

Because ScriptableComponent inherit from ScriptableObject and we used AssetDatabase.AddObjectToAsset() function to save the ScriptableObject

Note that your data will be modified and saved, whether you are playing or editing mode. Of course, we will disable edit of data at run time. But you still can modify it use scripts at run time

// Use UniEasy.ContextMenuAttribute can easy to pack scriptable components into groups
// You can see them at drop down context menu when you click `Scriptable Components [0]` ReorderableList's `+` Button
[UniEasy.ContextMenu ("Test/HealthComponent")]
public class HealthComponent : ScriptableComponent
{
    public float CurrentHealth;
    public float StartingHealth;
}

Feature (Runtime Systems)

If you want to create a group system, you must inherit from the Feature instead of using it directly !

You can use Runtime Systems [0] ReorderableList's + Button to select and add a runtime system to the group system inherited from the Feature

Divide the systems with similar functions into a group, this can better help you manage complex systems, like I made the Console Systems !

public class ConsoleSystems : Feature {}
[UniEasy.ContextMenu ("Console/DebugSystem")]
public class DebugSystem : RuntimeSystem
{
    ...
}
[UniEasy.ContextMenu ("Console/ConsoleSystem")]
public class ConsoleSystem : RuntimeSystem
{
    ...
}

Create a gameObject and add ConsoleSystems component and click ConsoleSystems's Runtime Systems [0] ReorderableList's + Button in Inspector panel

Then select and add DebugSystem and ConsoleSystem in drop down context menu, Save the scene or make the gameObject a prefab and drag the prefab to the Resources/Kernel/... folder (The Console Systems in this folder will be automatically injected and bindings as a single)

Serialize/Deserialize

Base on WriterExtensions

#if Serialize entity all can be serialize components
string data = entity.Serialize ();
#elif Only Serialize include in the includeTypes components
Type[] includeTypes = Type[] { typeof(SerializeComponent) }
string data = entity.Serialize (includeTypes);
#elif Serialize but ignore include in the ignoreTypes components
Type[] ignoreTypes = Type[] { typeof(NonSerializeComponent) }
string data = entity.Serialize (null, ignoreTypes);
#endif
entity.Deserialize (data);

Clone this wiki locally