SECS is a lightweight, C# engine-independent Entity Component System framework based on structs
Entity is an int
ID that serves as a container for components.
// Creating entity
int entity = world.NewEntity();
// Deleting entity
world.DelEntity(entity);
Container for holding data. Must be struct
that implements IEcsComponent
interface
public struct PlayerCmp : IEcsComponent
{
public float speed;
}
World is a place that holds all the data about filters, entities and their components. Provides API for getting pools, filters, adding or deleting entities.
Matcher is a container that used for defining matching conditions for the filter.
// Only entities that have InputCmp will be present in the filter
EcsMatcher inputMatcher = EcsMatcher.Include(typeof(InputCmp)).End();
EcsFilter inputFilter = world.GetFilter(inputMatcher);
//Only alive enemies (without IsDeadCmp) that have HealthCmp will be present in the filter
EcsMatcher aliveEnemyMatcher = EcsMatcher
.Include
(
typeof(HealthCmp),
typeof(EnemyCmp)
)
.Exclude
(
typeof(IsDeadCmp)
)
.End();
EcsFilter aliveEnemiesFilter = world.GetFilter(aliveEnemyMatcher);
Filter is a container that keeps entities always up to date that meet matcher requirements.
public sealed class MovePlayerSystem : IEcsRunSystem
{
private readonly EcsFilter _playerFilter;
private readonly EcsPool<PlayerCmp> _playerPool;
private readonly EcsPool<VelocityCmp> _velocityPool;
public MovePlayerSystem(EcsWorld world)
{
// Only entities with PlayerCmp and VelocityCmp will be present in the filter
EcsMatcher playerMatcher = EcsMatcher.Include
(
typeof(PlayerCmp),
typeof(VelocityCmp)
)
.End();
_playerFilter = world.GetFilter(playerMatcher);
_playerPool = world.GetPool<PlayerCmp>();
_velocityPool = world.GetPool<VelocityCmp>();
}
public void OnRun()
{
//Iterating through all entities that have PlayerCmp and VelocityCmp
foreach(var playerEntity in _playerFilter)
{
ref PlayerCmp playerCmp = ref _playerFilter.GetComponent(playerEntity);
ref VelocityCmp velocityCmp = ref _velocityPool.GetComponent(playerEntity);
//Launching player to the space
velocityCmp.velocity += Vector3.up * playerCmp.speed * Time.deltaTime;
}
}
}
Is a container for specific components. Provides an API for managing entity components.
// Getting pools form the world
EcsPool<IsHappyCmp> isHappyCmpPool = world.GetPool<IsHappyCmp>();
EcsPool<HealthCmp> healthCmpPool = world.GetPool<HealthCmp>();
EcsPool<ApplyDamageCmp> applyDamageCmpPool = world.GetPool<ApplyDamageCmp>();
EcsPool<IsHungryCmp> isHungryCmpPool = world.GetPool<IsHungryCmp>();
// Gettings components from the entity
ref HealthCmp healthCmp = ref healthCmpPool.GetComponent(playerEntity);
ref ApplyDamageCmp applyDamageCmp = ref applyDamageCmpPool.GetComponent(playerEntity);
// Mutating component's data
healthCmp.currentHealth -= applyDamageCmp.damage;
// Deleting component from the entity
applyDamageCmpPool.DelComponent(playerEntity);
// Adding component to the entity
isHappyCmpPool.AddComponent(playerEntity);
if(isHungryCmpPool.HasComponent(playerEntity))
Print("Player is hungry :(");
Note that there is also possibility to work with non generic API as well. But keep in mind that it is slower than working with generic methods and should be avoided when possible.
Here is same example but with non generic API
EcsPool<IsHappyCmp> isHappyCmpPool = (EcsPool<IsHappyCmp>)world.GetPool(typeof(IsHappyCmp));
EcsPool<HealthCmp> healthCmpPool = (EcsPool<HealthCmp>)world.GetPool(typeof(HealthCmp))
EcsPool<ApplyDamageCmp> applyDamageCmpPool = (EcsPool<ApplyDamageCmp>)world.GetPool(typeof(ApplyDamageCmp));
EcsPool<IsHungryCmp> isHungryCmpPool = (EcsPool<IsHungryCmp>)world.GetPool(typeof(IsHungryCmp))
// Gettings components from the entity. Note that in that case you can not get component by reference
// and have to manually cast to correct component type.
HealthCmp healthCmpCopy = (HealthCmp)healthCmpPool.GetComponentCopy(playerEntity);
ApplyDamageCmp applyDamageCmpCopy = (HealthCmp)applyDamageCmpPool.GetComponent(playerEntity);
// Mutating component's data (Don't forget to set component back in order to apply changes)
healthCmpCopy.currentHealth -= applyDamageCmp.damage;
healthCmpPool.SetComponent(playerEntity, healthCmpCopy);
// Deleting component from the entity
applyDamageCmpPool.DelComponent(playerEntity);
// Adding component to the entity
IsHappyCmp isHappyCmp = new IsHappyCmp();
isHappyCmpPool.AddComponent(playerEntity, isHappyCmp);
if(isHungryCmpPool.HasComponent(playerEntity))
Print("Player is hungry :(");
As you can see such approach is less convenient and has less compile time safety. So try to avoid it when possible.
Is a container for logic for processing filtered entities.
// Creating container for systems
EcsSystems updateSystems = new EcsSystems(world);
// Adding systems
updateSystems.Add(new SpawnPlayerSystem(_world))
.Add(new ReceiveInputSystem(_world))
.Add(new MovePlayerSystem(_world));
// Firing system logic
updateSystems.FireInitSystems();
updateSystems.FireRunSystems();
updateSystems.FireDisposeSystems();
Type of systems that are meant to be executed first.
public sealed class InitializePlayerSystem : IEcsInitSystem
{
public void OnInit()
{
// Initialize logic
}
}
Type of systems that are should be executed each frame of the game. Often contains main logic for processing filtered entities.
public sealed class MovePlayerSystem : IEcsRunSystem
{
public void OnRun()
{
// Move logic
}
}
Type of systems that are meant to be executed last.
public sealed class DeletePLayer : IEcsDisposeSystem
{
public void OnDispose()
{
// Player deletion logic
}
}
Different system types also can be combined together
public sealed class PlayerGodSystem : IEcsInitSystem, IEcsRunSystem, IEcsDisposeSystem
{
public void OnInit()
{
}
public void OnRun()
{
}
public void OnDispose()
{
}
}
public sealed class EcsSetup : MonoBehaviour
{
private EcsWorld _world;
private EcsSystems _updateSystems;
private void Awake()
{
// Initialize world
_world = new EcsWorld();
// Initialize systems container
_updateSystems = new EcsSystems(_world);
//Add systems
_updateSystems.Add(new SpawnPlayerSystem(_world))
.Add(new ReceiveInputSystem(_world))
.Add(new MovePlayerSystem(_world));
}
private void Start()
{
//Fire logic
_updateSystems.FireInitSystems();
}
private void Update()
{
//Fire logic
_updateSystems.FireRunSystems();
}
private void OnDestroy()
{
//Fire logic
_updateSystems.FireDisposeSystems();
}
}