Physics on Unity Pure ECS

If you don´t know, there is no implementation of a physics collision treatment if you are implementing pure ECS at this moment.
But what can we do is create our own collision detection and each kind of physics treatment we need.

Gravity System

For my platform game i need to create an implementation for gravity. A good point of that approach it is that we can create different ways of understand gravity as Mario Galaxy for example.

Im my case i implemented a very simple one


using UnityEngine;
using Unity.Entities;
using Unity.Transforms;

using Unity.Mathematics;

public class RidigBodySystem : ComponentSystem
{
    public struct Data
    {
        public readonly int Length;
        public ComponentDataArray<Position> Position;
        public ComponentDataArray<CollisionComponent> Collision;
        public ComponentDataArray<RigidBodyComponent> RigidBody;

        public ComponentDataArray<Velocity> Velocity;
    }

    [Inject] private Data m_Data;

    protected override void OnUpdate()
    {
        float3 gravity = new float3(0, -20, 0);
        float dt = Time.deltaTime;
        for (int index = 0; index < m_Data.Length; ++index)
        {
            var position = m_Data.Position[index].Value;

            if (m_Data.Collision[index].Value == 0)
            {
                Velocity velocity = new Velocity
                {
                    Value = m_Data.Velocity[index].Value + gravity * dt
                };
                m_Data.Velocity[index] = velocity;

                position += (m_Data.Velocity[index].Value * dt + 0.5f * gravity * dt * dt);
            }

            m_Data.Position[index] = new Position { Value = position };
        }
    }
}

In this code there is a important concept, the injection

public struct Data
    {
        public readonly int Length;
        public ComponentDataArray<Position> Position;
        public ComponentDataArray<RigidBodyComponent> RigidBody;
    }

    [Inject] private Data m_Data;

Unity, has its dependencies injection that based on the struct we create, give us the data of all entities that cointains this components. The beauty of ECS it is that we don´t know which entity we are changing, actualy, that is the main concept, we don´t need to know. The systems must be created thinking in the data. So we design systems that changes data and not managers that change objects as we as used to.

In this example, only the entities that contains the components Position and RigidBody will have its data changed.

The component created for that contains only the mass information and can be used to  identify that the entity falls and in the future create collisions and momentun

public struct RigidBodyComponent : IComponentData
{
    public float Mass;
}

Collision detection

I will initially create just a broadphase physics detection, using a simple AABB

Computing AABB

First of all we calculate the AABB of every entities that contains the component AABB

public struct AABBComponent : IComponentData
{
    public float3 center;
    public float3 halfwidths;
}

using UnityEngine;
using Unity.Entities;
using Unity.Transforms;

using Unity.Mathematics;


public class ComputeColliderAABBSystem : ComponentSystem
{
    public struct Data
    {
        public readonly int Length;
        public ComponentDataArray<Position> Position;
        public ComponentDataArray<AABBComponent> AABB;
    }

    [Inject] private Data m_Data;

    protected override void OnUpdate()
    {
        for (int index = 0; index < m_Data.Length; ++index)
        {
            var position = m_Data.Position[index].Value;
            float size = 2.5f;

            AABBComponent aabb = new AABBComponent
            {
                center = position,
                halfwidths = new float3(size, size, size)
            };
            m_Data.AABB[index] = aabb;
        }
    }
}

The size it is still hardcoded. This values will be tunned further

Calculating broad collision

For a while the collision component it is very simple

public struct CollisionComponent : IComponentData
{
    public float Value;
}

For every entity that contains collision and AABB component we must compare to check if a collision occurred

private bool checkAABB(AABBComponent a, AABBComponent b)
    {
        bool x = Mathf.Abs(a.center[0] - b.center[0]) <= (a.halfwidths[0] + b.halfwidths[0]);
        bool y = Mathf.Abs(a.center[1] - b.center[1]) <= (a.halfwidths[1] + b.halfwidths[1]);
        bool z = Mathf.Abs(a.center[2] - b.center[2]) <= (a.halfwidths[2] + b.halfwidths[2]);

        return x && y && z;
    }

The update method of the system it is a simple comparison

protected override void OnUpdate()
    {
        for (int index = 0; index < m_Data.Length; ++index)
        {
            m_Data.Collision[index] = new CollisionComponent
            {
                Value = 0
            };
            for (int id = 0; id < m_Data.Length; ++id)
            {
                if (index == id)
                    continue;

                if (checkAABB(m_Data.AABB[index], m_Data.AABB[id]))
                {
                    m_Data.Collision[index] = new CollisionComponent
                    {
                        Value = 1
                    };
                }
            }
        }
    }

Applying collision

The only collision we will check for a while it is of players with blocks while the player falls, then the gravity check must have a little changes 

We added a anotation in the class

[UpdateAfter(typeof(BroadphaseSystem))]

Telling the ECS system to run after the collisions were checked in the broadphasesystem.

Another change is in the update method

protected override void OnUpdate()
    {
        float3 gravity = new float3(0, -20, 0);
        float dt = Time.deltaTime;
        for (int index = 0; index < m_Data.Length; ++index)
        {
            var position = m_Data.Position[index].Value;

            if (m_Data.Collision[index].Value == 0)
            {
                Velocity velocity = new Velocity
                {
                    Value = m_Data.Velocity[index].Value + gravity * dt
                };
                m_Data.Velocity[index] = velocity;

                position += (m_Data.Velocity[index].Value * dt + 0.5f * gravity * dt * dt);
            }

            m_Data.Position[index] = new Position { Value = position };
        }
    }

As you can notice there is a comparison

if (m_Data.Collision[index].Value == 0)

To check if the entity was hitted by a collision.

This is a very simple approach of a collision. In future tutorials i will treat it better

References

  • https://forum.unity.com/threads/physics-in-pure-ecs.531716/
  • https://medium.com/@tomasz.piowczyk/https-medium-com-tomasz-piowczyk-part-1-unity-ecs-briefly-about-ecs-c077f90efc9a