How to play audio with DOTS Physics collisions?

I would like to have SFX play when DOTS Physics blocks collide. So far I have an event for each collision-enter, but I don’t know how to play the audio from an ECS system. In the GIF below, you can see that each collision-enter event generates a console message:

6399302--713831--DOTSCollisionAudio.gif

Here is the code:

using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;

[Serializable][GenerateAuthoringComponent]
public struct BlockComponentData : IComponentData
{
    public double   hitTime;

    public float    impulse;
    public float3   position;

    public bool     justEnteredCollision;
}
using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Physics;
using Unity.Physics.Systems;

using Unity.Mathematics;
using Unity.Burst;
using Unity.Collections;

public class ColliderEffectsSystem : JobComponentSystem {

    private BuildPhysicsWorld buildPhysicsWorld;
    private StepPhysicsWorld stepPhysicsWorld;



    [BurstCompile]
    [UpdateAfter(typeof(EndFramePhysicsSystem))]
    private struct CollisionJob : ICollisionEventsJob {

        [ReadOnly]
        public PhysicsWorld physicsWorld;

        [ReadOnly]
        public double       hitTime;

        [ReadOnly]
        public float        minImpulseForEffect; // seconds

        [ReadOnly]
        public float        minRecollideTime; // seconds


        public ComponentDataFromEntity<BlockComponentData> blockComponentEntities;

    

        // EXECUTE
        public void Execute(CollisionEvent collisionEvent) {

            CollisionEvent.Details d = collisionEvent.CalculateDetails(ref physicsWorld);

            Entity entityA = collisionEvent.EntityA;
            Entity entityB = collisionEvent.EntityB;

            if (d.EstimatedImpulse > minImpulseForEffect)
            {
                if      (blockComponentEntities.HasComponent(entityA))
                    ProcessCollisionEntity(entityA, d);
                else if (blockComponentEntities.HasComponent(entityB))
                    ProcessCollisionEntity(entityB, d);
            }
        }



        // PROCESS COLLISION OF ENTITY
        private void ProcessCollisionEntity(Entity entity, CollisionEvent.Details d)
        {
            if (blockComponentEntities.HasComponent(entity))
            {
                BlockComponentData blockData    = blockComponentEntities[entity];

                double timeSinceLastCollision   = hitTime - blockData.hitTime;
                blockData.hitTime = hitTime;
     
                if (d.EstimatedImpulse > 1 && timeSinceLastCollision > minRecollideTime)
                {
                    blockData.justEnteredCollision      = true;
                    blockData.impulse                   = d.EstimatedImpulse;
                    blockData.position                  = d.AverageContactPointPosition;
                }
                blockComponentEntities[entity]          = blockData;
            }
        }
    }




    protected override void OnCreate() {
        buildPhysicsWorld           = World.GetOrCreateSystem<BuildPhysicsWorld>();
        stepPhysicsWorld            = World.GetOrCreateSystem<StepPhysicsWorld>();
    }

    protected override JobHandle OnUpdate(JobHandle inputDeps) {

        CollisionJob collisionJob   = new CollisionJob {
            blockComponentEntities  = GetComponentDataFromEntity<BlockComponentData>(),
            physicsWorld            = buildPhysicsWorld.PhysicsWorld,
            hitTime                 = Time.ElapsedTime,
            minImpulseForEffect     = 1.0f,
            minRecollideTime        = 0.2f
        };

        return collisionJob.Schedule(stepPhysicsWorld.Simulation, ref buildPhysicsWorld.PhysicsWorld, inputDeps);
    }

}




[UpdateAfter(typeof(BuildPhysicsWorld))]
public class ProcessCollisionsSystem : ComponentSystem
{
    protected override void OnUpdate()
    {
        Entities.ForEach((Entity entity, ref BlockComponentData bcd) =>
        {
            if (bcd.justEnteredCollision)
            {
                Debug.Log("JUST ENTERED COLLISION - PLAY AUDIO at: " + bcd.position);

                bcd.justEnteredCollision = false;
            }
        });
    }
}

Am I on the right track here? How can I play a short AudioClip where that Debug.Log message is?

Thanks in advance!

You are on the perfect track regarding the physics stuff. Now I know nothing about audio, so it might be better to ask the question in some non-physics-related subforum, because your physics stuff is completely correct.

1 Like

Also make sure to take a look at CollisionEvent.Details.EstimatedContactPointPositions, the length of this array may give you the info on different audio you might want to play (1 means it’s a vertex collision, 2 for edge, 3 or more for face), so you could play a different sound for each of these. At least that’s what we designed it for. :slight_smile:

3 Likes

Thanks for this feedback, @petarmHavok ! I have been burrowing down the DOTS Physics rabbit hole for a couple weeks now, so as a DOTS beginner, it is encouraging to hear that I am on the right track per physics events – or at least not going completely off the rails!

I am excited about the vertex/edge/face collision info (which I was not aware of) not only for varying the sound files as you suggest, but also for fine tuning dust/debris particle emission. The impulse value will be used to adjust volume and pitch of the audio as well. Very exciting!

From my research so far, it looks like DOTS Audio is in the early stages, so for now I will work on using a hybrid approach by loading and instantiating Prefabs. Here is a first pass at playing an audio source in the position of the collision contact (will add pooling next):

[UpdateAfter(typeof(BuildPhysicsWorld))]
public class ProcessCollisionsSystem : ComponentSystem
{
    private GameObject stoneClunkGO;
 

    protected override void OnCreate()
    {
        stoneClunkGO = Resources.Load("StoneClunk") as GameObject;
    }


    protected override void OnUpdate()
    {
        Entities.ForEach((Entity entity, ref BlockComponentData bcd) =>
        {
            if (bcd.justEnteredCollision)
            {
                Debug.Log("JUST ENTERED COLLISION - PLAY AUDIO at: " + bcd.position);

                // AUDIO
                GameObject clunker = GameObject.Instantiate(stoneClunkGO, bcd.position, Quaternion.identity);
                AudioSource audioSource = clunker.GetComponent<AudioSource>();
                audioSource.pitch = UnityEngine.Random.Range(0.6f, 0.9f);
                audioSource.Play();

                bcd.justEnteredCollision = false;
            }
        });
    }
}

Thanks again for your help, @petarmHavok !

1 Like

I notice that the cube after collision rotates and lies on the ground for a full stop. Probably here an extra noise is needed to simulate the final corner that hits the ground. Since the collider will not detect this final collision, (because is already in a collision state) he-she can detect it by looking at the rotation. If after the collision, is still rotating and at the moment the rotation stop, then play audio. A Second softer source audion can be used.

2 Likes