Detecting collisions

Updated on 2019.06.19

Posted by h4ri on May 17, 2019 · 9 mins read

Hi there! If you haven’t seen my previous post (moving with Unity Physics), then here it is, or here are the very beginning.

I’m working with Unity for some years now, and I used to the way how it’s operating. I thought if there’s a Raises Collision Events checkbox on the PhysicsShape, then I can subscribe to it somewhere. Maybe in the future, but not now. I tried several ways to solve the problem, but I couldn’t find a way, so I started to examine the Unity.Physics package. Although it’s in the Project window in the Editor, opening from there has some limitations in Visual Studio. Right click -> Show in Explorer on the package, and I copied the packages to a new project and boom, the magic F12 (Go to definitions) ready to serve. After some digging, I found the DebugDisplay folder, where some cool stuff hiding. Enough history, let’s see some code!

ComponentData


// Assets/Scripts/Physics/CollisionData.cs

using Unity.Entities;

public struct CollisionData : IComponentData
{
	public Entity CollidedEntity;
}

The base idea, that two collider is colliding, then I add to them this ComponentData, and after that I iterate the entities what has it. To achieve this, we need two systems as follows.

Systems


// Assets/Scripts/Physics/CollisionEventsSystem.cs

using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Physics;
using Unity.Physics.Systems;

[UpdateAfter(typeof(StepPhysicsWorld)), UpdateBefore(typeof(EndFramePhysicsSystem))]
public class CollisionEventsSystem : JobComponentSystem
{
	private EndSimulationEntityCommandBufferSystem _entityCommandBuffer;
	private BuildPhysicsWorld _buildPhysicsWorldSystem;
	private StepPhysicsWorld _stepPhysicsWorldSystem;
	private EndFramePhysicsSystem _endFramePhysicsSystem;

	protected override void OnCreate()
	{
		_entityCommandBuffer = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
		_buildPhysicsWorldSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
		_stepPhysicsWorldSystem = World.GetOrCreateSystem<StepPhysicsWorld>();
		_endFramePhysicsSystem = World.GetOrCreateSystem<EndFramePhysicsSystem>();
	}

	private struct CollisionEventsJob : ICollisionEventsJob
	{
		[ReadOnly] public EntityCommandBuffer CommandBuffer;
		[ReadOnly] public PhysicsWorld PhysicsWorld;

		public unsafe void Execute(CollisionEvent collisionEvent)
		{
			RigidBody bodyA = PhysicsWorld.Bodies[collisionEvent.BodyIndices.BodyAIndex];
			RigidBody bodyB = PhysicsWorld.Bodies[collisionEvent.BodyIndices.BodyBIndex];

			bool AreCollisionEventsEnabled(Collider* collider)
			{
				return ((ConvexColliderHeader*)collider)->Material.EnableCollisionEvents;
			}

			if (AreCollisionEventsEnabled(bodyA.Collider))
			{
				CommandBuffer.RemoveComponent(bodyA.Entity, typeof(CollisionData));
				CommandBuffer.AddComponent(bodyA.Entity, new CollisionData { CollidedEntity = bodyB.Entity });
			}
			if (AreCollisionEventsEnabled(bodyB.Collider))
			{
				CommandBuffer.RemoveComponent(bodyB.Entity, typeof(CollisionData));
				CommandBuffer.AddComponent(bodyB.Entity, new CollisionData { CollidedEntity = bodyA.Entity });
			}
		}
	}

	protected override JobHandle OnUpdate(JobHandle inputDeps)
	{
		inputDeps = JobHandle.CombineDependencies(inputDeps, _buildPhysicsWorldSystem.FinalJobHandle, _stepPhysicsWorldSystem.FinalSimulationJobHandle);

		JobHandle collisionEventsJob = new CollisionEventsJob
		{
			CommandBuffer = _entityCommandBuffer.CreateCommandBuffer(),
			PhysicsWorld = _buildPhysicsWorldSystem.PhysicsWorld
		}.Schedule(_stepPhysicsWorldSystem.Simulation, ref _buildPhysicsWorldSystem.PhysicsWorld, inputDeps);

		_endFramePhysicsSystem.HandlesToWaitFor.Add(collisionEventsJob);

		_entityCommandBuffer.AddJobHandleForProducer(collisionEventsJob);

		return collisionEventsJob;
	}
}

Because the unsafe keyword, we have to check the Allow ‘unsafe’ Code in the Player Settings/Other Settings in Unity!

This system mostly copied from the Packages/Unity Physics/Unity.Physics.Hybrid/Utilities/DebugDisplay/DisplayCollisionEventsSystem.cs. It’s ordered by the [UpdateAfter(typeof(StepPhysicsWorld)), UpdateBefore(typeof(EndFramePhysicsSystem))]. After the physics world is calculated, we process the collisions, and after all the physics related system we do the result what we want. But now, we get the collided entities, and if they Raises Collision Event’s is checked, then we attach our CollisionData to them. Somewhere on a forum I found a recommendation from a Unity staff (unfortunately I haven’t found it), that it’s the good way and they will optimize for that approach.

Edit (2019.06.19): after the Unity.Physics update (0.0.2 -> 0.1.0) there are some changes. We got a new interface, the ICollisionEventsJob, witch is calls Execute() for every collision event produced by the solver, so we can simplify our code a little bit.

Right now I don’t know is there any way for a oneliner ComponentData update. There’s a SetComponent, but it’s throw an exception if it’s already exists (update: in a recent version it was updated in the case when no difference is found, but in our case it’s not enough), so we must remove the component if exists, and then add to the entity.


// Assets/Scripts/Physics/CollisionResultSolverSystem.cs

using Unity.Entities;
using Unity.Physics.Systems;

[UpdateAfter(typeof(EndFramePhysicsSystem))]
public class CollisionResultResolverSystem : ComponentSystem
{
	private EndSimulationEntityCommandBufferSystem _entityCommandBuffer;
	private EntityQuery _query;

	protected override void OnCreate()
	{
		_entityCommandBuffer = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
		_query = GetEntityQuery(ComponentType.ReadOnly<CollisionData>());
	}

	protected override void OnUpdate()
	{
		EntityCommandBuffer commandBuffer = _entityCommandBuffer.CreateCommandBuffer();

		Entities.With(_query).ForEach((Entity entity, ref CollisionData collisionData) =>
		{
			if (EntityManager.HasComponent<PlayerData>(entity) && EntityManager.HasComponent<EnemyData>(collisionData.CollidedEntity)) commandBuffer.DestroyEntity(collisionData.CollidedEntity);
			else if (EntityManager.HasComponent<EnemyData>(entity) && EntityManager.HasComponent<PlayerData>(collisionData.CollidedEntity)) commandBuffer.DestroyEntity(entity);

			commandBuffer.RemoveComponent<CollisionData>(entity);
		});
	}
}

It’s time to talk about the EntityManager a little bit. You can perform actions on entities like create, update and destroy. It’s “smarter”, then a CommandBuffer, but we can’t use it in a Job unfortunately, because it’s a class. This is the reason that this system is a ComponentSystem. In the OnUpdate we iterate through all entities that has a CollisionData on it and check if it a player or an enemy, and according to what is the collided entity, we destroying the entity if needed. After this we remove the CollisionData from the examined entity. Because we are in a ComponentSystem running on the main thread, these modifications are immediate. We are in the SimulationSystemGroup, so in this frame in the PresentationSystemGroup the destroyed entities won’t be there.

Time for play and watch the awsomeness. Now if an enemy hit the player, will vanish.