Part 1: Unity ECS - briefly about ecsPart 2: Unity ECS - project designPart 3: Unity ECS - operations on EntitiesPart 4: Unity ECS - ECS and Jobs
Just before we start, I just want to mention this is very simple way of thinking (beginner-friendly) and doesn't cover any pattern for creating systems, just an example thinking scheme.
How to think in ECS?
You probably were afraid of how to design your game.. These components, systems, entities seems be hard to think about them. Well, it's not. Actually it's pretty simple.
Entities:
What do we need to make game alive? Obviously, some entities that will do something. This is trivial, just think what Entity do we need? Some Baloon? What components needs baloon? Position, Renderer, maybe something more but we can live with just these two.
Components:
We have very simple data container which have just some data. What parameters we need to make game? Position? DamageValue? Health? Probably we will check something with booleans, hey, they all are just primitives, nothing scary. Let's talk about systems.
Systems:
Think about their behaviour like that: do the same as you've done before with monobehaviours with just one difference. The difference is loop, nothing really more (maybe just injection). We loop over all entities that match your group, work as your worked before in MonoBehaviour.Update Let's take example of baloons that float up
// We are in MonoBehaviour script
private void Update()
{
// We can cache this Vector on Awake, but still, every script has new vector
// So it's 12 bytes (3 floats each 4 bytes) PER SCRIPT from vector
transform.position += new Vector3(0, 1, 0);
}
// We are in ComponentSystem
struct Group
{
public readonly int Length
public ComponentDataArray<Position> Positions;
}
[Inject] Group data;
protected override void OnUpdate()
{
for (int i = 0; i < data.Length; i++)
{
// When Unity uses ref returns from C#7 it'll make the extra assignment redundant.
var position = data.Positions[i];
// We can cache float3 so we'll have only one variable
// If so, in contrast to MonoBehaviour - it's just 12 bytes OVERALL - we have it cached in system
position += new float3(0, 1, 0);
data.Positions[i] = position;
}
}
It seems a much more work? It just looks like that. We've got much more efficient code. We take care of data and we're cache friendly. Say for example we have 10000 baloons. Each has monobehaviour, and its Update that's so expensive, isn't it? (Will it even run? Haha) 120000 bytes from just vectors. With ECS that's just piece of cake. We still have just 12 bytes from our float3 (used as vector)
Okey let's take a look at sample project, how can we structure it?
Duck Shooter Example
Fantastic small game - we'll just shoot to angry ducks, that's all. What do we need?
The simple thinking scheme:
Components:Just data container
Position - { float2 Pos } - must have for ducks and our crosshair to place them where we want
MouseInput - { bool Fire, int2 Pos } - we will check where is mouse position and when fire button was pressed
Health -{ float Value } - some ducks you can oneshot, others can be stronger
MoveSpeed - { float Value } - some ducks can be faster than the others
DuckTag - { } - empty struct, just for proper filtering
PlayerTag - { } - empty struct, just for proper filtering
SharedComponents: shared component between entities, mainly "readonly" (See docs THERE)
SpriteRenderer { Material mat, Image img } - we don't want to shoot to invisible ducks, do we?
Systems:operate on all entities matching the given group of components (each entity has each component from group)
InputSystem (operates on group of { MouseInput } )
(Behaviour => MouseInput = Input.Mouse, Fire = Input.ButtonDown)
CrosshairSystem (operates on group of { MouseInput, Position, PlayerTag } )
(Behaviour => Position = MouseInput)
MoveSystem (operates on group of { Position, MoveSpeed } )
(Behaviour => Position += MoveSpeed)
ShootSystem (operates on group1 of { Position, Health, DuckTag }
+ group2 of { Position, MouseInput, PlayerTag } )
(Behaviour => if(group2.MouseInput.Fire && group2.Position == group1.Position) group1.Health -= 1)
DuckDeathSystem (operates on group of { Health } )
(Behaviour => if(health <= 0) Destroy(thisEntity))
GameConditionSystem (operates on group of { DuckTag } )
(Behaviour => [Always update] (attribute that prevent group from being disabled it there is no matching entity); if(group.Length <= 0) GameOver())
Entities: just index
(Player(Crosshair)) - has components { PlayerTag, Position, MouseInput }
What's next?Part 3: Unity ECS - operating on Entities
Give me some feedback in comment section, don't worry I won't hate you if you show me some "anomaly" in my article.
If you enjoy my article - like it and follow me. It'll motivate me to write more articles. See you.
Hi, I must say this post is awesome.
I like your example, but I think it might need some example code for those how are just getting into ECS. So, I made one and it can be found at https://github.com/theodor349/Duck-Shooter