DOTS physics for 2D games: Apply constraint on z axis?

I am trying determine the feasibility of using DOTS physics for a 2D side scrolling game. Is there a way to apply a constraint on the z-axis so that Rigidbodies don’t fall off the edge?

1 Like

Yup, check out the ‘4b. Limit DOF’ demo. Also, see this post for a little background history: Unity Physics Discussion page-7#post-4460308

5 Likes

For those who want to do this via code (instead of the editor), here is how you can create a joint for a rigidbody entity that will apply constraints against the z-axis for both rotation and translation:

public static Entity CreateLockedZAxisJointEntity(EntityManager entityManager, Entity entity)
{
    var worldFromA = new RigidTransform(
        entityManager.GetComponentData<Rotation>(entity).Value,
        entityManager.GetComponentData<Translation>(entity).Value
    );
    RigidTransform bFromA = math.mul(math.inverse(RigidTransform.identity), worldFromA);
    BlobAssetReference<JointData> jointData = CreateLockedZAxisJoint(bFromA);

    var componentData = new PhysicsJoint
    {
        JointData = jointData,
        EntityA = entity,
        EnableCollision = 1,
    };

    Entity jointEntity = entityManager.CreateEntity(typeof(PhysicsJoint));
    entityManager.AddComponentData(jointEntity, componentData);

#if UNITY_EDITOR
    entityManager.SetName(jointEntity, $"Joining {entityManager.GetName(entity)} + PhysicsWorld");
#endif

    return jointEntity;
}

public static BlobAssetReference<JointData> CreateLockedZAxisJoint(RigidTransform offset)
{
    Constraint[] constraints = new Constraint[2] {
        new Constraint
        {
            ConstrainedAxes = new bool3(false, false, true),
            Type = ConstraintType.Linear,
            Min = 0,
            Max = 0,
            SpringFrequency = Constraint.DefaultSpringFrequency,
            SpringDamping = Constraint.DefaultSpringDamping
        },
        new Constraint
        {
            ConstrainedAxes = new bool3(false, false, true),
            Type = ConstraintType.Angular,
            Min = 0,
            Max = 0,
            SpringFrequency = Constraint.DefaultSpringFrequency,
            SpringDamping = Constraint.DefaultSpringDamping
        }
    };

    return JointData.Create(
        new Math.MTransform(float3x3.identity, float3.zero),
        new Math.MTransform(offset),
        constraints
    );
}

Usage is to simply pass in your entityManager and the entity you wish to apply the constraint against to this function:

CreateLockedZAxisJointEntity(entityManager, entity);
4 Likes

When I try to use the above code, Unity crashes (no error given). This is how I’ve tried to implement it:

using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Physics;
using Unity.Collections;

public sealed class Bootstrap : MonoBehaviour
{
    public int count;
    private EntityManager entityManager;
    public GameObject cubePrefab;

    private void OnEnable()
    {
     
    }

    private void Start()
    {
        if (!enabled) return;

        entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

        var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
        Entity cubePrefabEntity = GameObjectConversionUtility.ConvertGameObjectHierarchy(cubePrefab, settings);

        CreateLockedZAxisJointEntity(entityManager, cubePrefabEntity);

        var positions = new NativeArray<float3>(count, Allocator.Temp);

        int yPos = 2;
        for (int i = 0; i < positions.Length; i++) {
            // Instantiate a bunch of cubes on top of each other.
            positions[i] = new float3(UnityEngine.Random.Range(0f, 1.5f), yPos, UnityEngine.Random.Range(0f, 1.5f));
            var instance = entityManager.Instantiate(cubePrefabEntity);
            entityManager.SetComponentData(instance, new Translation { Value = positions[i] });
            yPos += 2;
        }
        positions.Dispose();
    }

    public static Entity CreateLockedZAxisJointEntity(EntityManager entityManager, Entity entity)
    {
        var worldFromA = new RigidTransform(
            entityManager.GetComponentData<Rotation>(entity).Value,
            entityManager.GetComponentData<Translation>(entity).Value
        );
        RigidTransform bFromA = math.mul(math.inverse(RigidTransform.identity), worldFromA);
        BlobAssetReference<JointData> jointData = CreateLockedZAxisJoint(bFromA);

        var componentData = new PhysicsJoint
        {
            JointData = jointData,
            EntityA = entity,
            EnableCollision = 1,
        };

        Entity jointEntity = entityManager.CreateEntity(typeof(PhysicsJoint));
        entityManager.AddComponentData(jointEntity, componentData);

#if UNITY_EDITOR
        entityManager.SetName(jointEntity, $"Joining {entityManager.GetName(entity)} + PhysicsWorld");
#endif

        return jointEntity;
    }

    public static BlobAssetReference<JointData> CreateLockedZAxisJoint(RigidTransform offset)
    {
        Constraint[] constraints = new Constraint[2] {
            new Constraint
            {
                ConstrainedAxes = new bool3(false, false, true),
                Type = ConstraintType.Linear,
                Min = 0,
                Max = 0,
                SpringFrequency = Constraint.DefaultSpringFrequency,
                SpringDamping = Constraint.DefaultSpringDamping
            },
            new Constraint
            {
                ConstrainedAxes = new bool3(false, false, true),
                Type = ConstraintType.Angular,
                Min = 0,
                Max = 0,
                SpringFrequency = Constraint.DefaultSpringFrequency,
                SpringDamping = Constraint.DefaultSpringDamping
            }
        };

        return JointData.Create(
            new Math.MTransform(float3x3.identity, float3.zero),
            new Math.MTransform(offset),
            constraints
        );
    }
}

If I don’t use CreateLockedZAxisJointEntity the prefab cubes (fitted with Physics Body and Shape) instantiate properly. Any idea what’s going on?

I am not familiar with using Entities as prefabs, and tend avoid the GameObjectConversion workflow process in favor of building my game via code (gives me more control and dramatically simplifies content creation on complex projects for me).

However, instead of applying the joint constraint on the prefab entity, try to instead to apply it to the instance inside the for loop that you created, like so:

int yPos = 2;
for (int i = 0; i < positions.Length; i++) {
    // Instantiate a bunch of cubes on top of each other.
    positions[i] = new float3(UnityEngine.Random.Range(0f, 1.5f), yPos, UnityEngine.Random.Range(0f, 1.5f));
    var instance = entityManager.Instantiate(cubePrefabEntity);
    entityManager.SetComponentData(instance, new Translation { Value = positions[i] });
    CreateLockedZAxisJointEntity(entityManager, instance);
    yPos += 2;
}

Also, it’s important that the joint is created after the translation is set. Doing it before the translation would not necessarily cause unity crash, but more so that any updates to the Translation component after a joint has been created will be ignored. Well not exactly ignored, more so the physics joint system will override Translation component with the connected position (relative to the connected body) of the joint. So the creation process is like so:

- Create Rigidbody Entity
- Set Translation
- Create Joint entity (Use current Translation and Rotatation of RigidbodyEntity to set the connected position on the joint)
- Phsysics Joint Systems OnUpdate: Update Translation of RigidbodyEntity with connected position from the Joint Entity.
2 Likes

Makes perfect sense. I’m trying to transition into a more code-centric process, but I’m admittedly a weak programmer.

And this did the trick! Thank you.

I found another solution that works for me. Set infinity inertia tensor for x and y axis like that:

em.AddComponentData(entity, PhysicsMass.CreateDynamic(new MassProperties {
        Volume = ...,
        MassDistribution = new MassDistribution {
                Transform = ...,
                InertiaTensor = new float3(float.PositiveInfinity, float.PositiveInfinity, 1),
        }
}, 1));
2 Likes

^ Oh man, that one is great. Thanks for sharing!

This seems to work for the rotation but the Z translation is not fixed and slightly moves away from 0 over time.
My (hacky feeling) approach is to have a system running after ExportPhysicsWorld which sets the Z translation, X rotation, Y rotation as well as the linear velocity in Z direction and the angular velocity in X and Y directions to 0 every frame:

using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.Transforms;

[UpdateAfter(typeof(ExportPhysicsWorld))]
public class Physics2dSystem : JobComponentSystem
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        return Entities
            .WithBurst()
            .ForEach((
                ref PhysicsVelocity velocity,
                ref Translation position,
                ref Rotation rotation) =>
            {
                velocity.Linear = new float3(velocity.Linear.x, velocity.Linear.y, 0);
                velocity.Angular = new float3(0, 0, velocity.Angular.z);
                position.Value = new float3(position.Value.x, position.Value.y, 0);
                rotation.Value = quaternion.Euler(0, 0, rotation.Value.ToEuler().z);
            })
            .Schedule(inputDeps);
    }
}
1 Like

@BenjaminBachman if you want to constrain a body around all axes, you can just set all axes of the InertiaTensor to Infinity

But wouldn’t that also fix the rotation around the Z axis?
Maybe I misunderstood OP. In my example I try to constraint the physics to the XY plane for an “Asteroid” type game. So only allow rotation around the Z axis but restrict the Z translation to 0. Like this:
5287932--530454--upload_2019-12-16_17-27-37.png
Can this even be expressed with a float3 inertia tensor?

Setting InertiaTensor to Infinity doesn’t really lock rotation around an axis. It just prevents the simulation from modifying the angular velocity around the axis. You can still set the PhysicsVelocity to whatever you want.

Bump. I wonder what is the right way to do the same in ECS physics 1.0.

3 Likes