Moving with Unity.Physics

Hi there! If you haven’t seen the previous post (follow player with camera), then here it is, or here are the very beginning.

Some info, before we continue: When I started to play around the physics, I forgot to disable my non-physics EnemyMovementSystem, and I didn’t understand, why my new codes doesn’t have any effect. That was because it was executed along with my new System and overrided the behaviour of the enemies. So now start a new project or call Disable = false; in the old movement system’s OnCreate or comment out the whole script.

Before we begin to write any code, we must attach to our objects two scripts. PhysicsShape and PhysicsBody responsible for the entity’s physics handling. In the “default” Unity system is a good practice to layering our GameObjects and set up the Collision matrix, so everything collide only those objects what needs to. In the new physics system we must create a PhysicsCategoryNames asset for this. I created the Player, Enemy and Projectile (more on that in a later post) categories. On the PhysicsShape we can set the Belongs To dropdown to Player for the player, Enemy for the enemy and Collides With dropdown to Enemy for the player and Player, Enemy, Projectile for the enemy. Also I checked in the Raises Collision Events both of them. We’ll need to handle this manually later.

The player

ComponentData


// Assets/Scripts/Player/PlayerData.cs

using Unity.Entities;
using Unity.Transforms;

public struct PlayerData : IComponentData
{
  public float Speed;
  public Translation Position;
}

Same as before, expanded with a speed variable.

PlayerProxy


// Assets/Scripts/Player/PlayerProxy.cs

using Unity.Entities;
using UnityEngine;

public class PlayerProxy : MonoBehaviour, IConvertGameObjectToEntity
{
#pragma warning disable 0649
  [SerializeField] private float _speed;
  [SerializeField] private CameraFollow _camera;
#pragma warning restore 0649

  public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
  {
    dstManager.AddComponentData(entity, new PlayerData()
    {
      Speed = _speed
    });
    _camera.SetEntityToFollow(entity);
  }
}

Nothing chaned. The CameraFollow and _camera.SetEntityToFollow is optional (more on that in the previous post).

System


// Assets/Scripts/Player/PlayerMovementSystem.cs (rename it, if you working in the same project)

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

[UpdateBefore(typeof(BuildPhysicsWorld))]
public class PlayerMovementSystem : ComponentSystem
{
  private EntityQuery _query;

  protected override void OnCreate()
  {
    _query = GetEntityQuery(
      ComponentType.ReadWrite<PlayerData>(),
      ComponentType.ReadOnly<Translation>(),
      ComponentType.ReadWrite<PhysicsVelocity>());
  }

  protected override void OnUpdate()
  {
    float deltaTime = Time.fixedDeltaTime;

    Entities.With(_query).ForEach(
      (Entity entity,
      ref PlayerData playerData,
      ref Translation position,
      ref PhysicsVelocity velocity) =>
    {
    float
      horizontal = Input.GetAxis("Horizontal") * deltaTime * playerData.Speed,
      vertical = Input.GetAxis("Vertical") * deltaTime * playerData.Speed;

      velocity.Linear = new float3(horizontal, .0f, vertical);
      velocity.Angular = float3.zero;

      playerData.Position.Value = position.Value;
    });
  }
}

You may noticed the [UpdateBefore(typeof(BuildPhysicsWorld))]. It’s ensures we calulate our physics movement before Unity builds up the physics simulations result. With the new components the player entity have some new physics related ComponentDatas (you can see them in the Entity Debugger). We’ll use the PhysicsVelocity to set the force what affect the entity. I moving my player on the XZ axises, so I set this two values. I also didn’t want to the player to rotate, so I zeroed out the angular velocity. After that, you should move you player around with physics. Awesome. Enemies next.

The enemies

ComponentData


// Assets/Scripts/Enemy/EnemyData.cs

using Unity.Entities;

public struct EnemyData : IComponentData
{
  public float Speed;
}

Nothing chanded.

EnemyProxy


// Assets/Scripts/Enemy/EnemyProxy.cs

using Unity.Entities;
using UnityEngine;

public class EnemyProxy : MonoBehaviour, IConvertGameObjectToEntity
{
#pragma warning disable 0649
  [SerializeField] private float _speed;
#pragma warning restore 0649

  public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
  {
    dstManager.AddComponentData(entity, new EnemyData() { Speed = _speed });
  }
}

Also the same.

System


// Assets/Scripts/Enemy/EnemyMovementSystem.cs

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;
using Unity.Transforms;
using UnityEngine;

[UpdateBefore(typeof(BuildPhysicsWorld))]
public class EnemyMovementSystem : JobComponentSystem
{
  [BurstCompile]
  private struct GetPlayerPositionJob : IJobForEach<PlayerData>
  {
    public NativeArray<PlayerData> PlayerData;

    public void Execute([ReadOnly] ref PlayerData playerData)
    {
      PlayerData[0] = playerData;
    }
  }

  [BurstCompile]
  private struct EnemyMovementJob : IJobForEach<EnemyData, Translation, PhysicsVelocity>
  {
    [ReadOnly] public NativeArray<PlayerData> PlayerData;
    public float DeltaTime;

    public void Execute([ReadOnly] ref EnemyData enemyData, [ReadOnly] ref Translation position, ref PhysicsVelocity velocity)
    {
      float3 dir = PlayerData[0].Position.Value - position.Value;
      dir = math.normalize(dir) * DeltaTime * enemyData.Speed;

      velocity.Linear = new float3(dir.x, .0f, dir.z);
      velocity.Angular = float3.zero;
    }
  }

  protected override JobHandle OnUpdate(JobHandle inputDeps)
  {
    NativeArray<PlayerData> playerData = new NativeArray<PlayerData>(1, Allocator.TempJob);

    JobHandle getPlayerPositionJob = new GetPlayerPositionJob()
    {
      PlayerData = playerData
    }.Run(this, inputDeps);

    getPlayerPositionJob.Complete();

    JobHandle enemyMovementJob = new EnemyMovementJob()
    {
      PlayerData = playerData,
      DeltaTime = Time.fixedDeltaTime
    }.Run(this, getPlayerPositionJob);

    enemyMovementJob.Complete();
    playerData.Dispose();

    return enemyMovementJob;
  }
}

Nothing new here also. We get the PlayerData, and moving the enemies towards it, only here we do it via the PhysicsVelocity.

Now if you hit play, the enemies will do what’s expected, colliding with the player and with each other. Now we have something what at least looks nice and have some wow factor. Have fun and experimenting with the numbers and speeds!