- Home /
Collision sounds doubling up.
I have a physics playground with a pile of poker chips and a sound plays when they collide.
The issue is, the sound is played for each gameObject so the collision sound plays twice and creates a terrible reverb-like effect. I have tried a random bool but sometimes no sound plays.
I can't think of any logic that would solve this problem.
void OnCollisionEnter(Collision collision)
{
if (Time.timeSinceLevelLoad > 0.2f && gameObject.GetComponent<Rigidbody>().velocity.magnitude > 0.75f && transform.position.y < 5.0f)
{
bool altPlay = (Random.value > 0.5f);
audioSource.clip = chipHit;
audioSource.volume = (rBody.velocity.magnitude / 5) * audioSpeedModifier;
audioSource.pitch = Random.Range (0.6f , 1.0f);
if (altPlay == true)
{
audioSource.PlayOneShot(chipHit);
}
}
}
Perhaps you could have a static int representing the amount of frames since the last "collision sound" was played, and only play the sound if the static variable is above a certain threshold (say 5 frames).
Alternatively, assu$$anonymous$$g both collisions happen in the same frame, just have a static Bool indicating whether the sound was already played on that frame. The poker chip that first "notices" the collision will set the Bool to true, then play the sound. The other poker chip will avoid playing the sound, since the Bool is already true. Finally, the Bool can be reset to false on the next frame.
Thanks for the suggestions but I'm not sure it could work. Sometimes a chip will collide multiple times in the same frame, especially in the beginning when all the chips are dumped on the table. I can't filter by name or tag because each chip needs to produce a sound at some point.
Answer by trapazza · Sep 10, 2018 at 10:54 PM
Right from the top of my head:
Remove the sound logic from the entity.
Instead, write some kind of SoundManager where you'd have some method called "PlayCollisionSound(Collision a, Collision b)".
When called, register all pairs of colliders and actually play a sound.
Ignore collisions of already registered pairs of colliders during the current frame.
Clear registered pairs at the beginning of the next frame.
Edit: Updated. Removed dictionary and coroutine, based on trapazza Hashset code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Chip_Coll : $$anonymous$$onoBehaviour
{
void OnCollisionEnter(Collision collision)
{
if (Time.timeSinceLevelLoad > 0.2f && gameObject.GetComponent<Rigidbody>().velocity.magnitude > 0.75f && transform.position.y < 6.0f)
{
GameObject.Find("Sound$$anonymous$$anager").GetComponent<sound$$anonymous$$anagerScript>().PlayCollisionSound(GetComponent<Collider>(), collision.collider, GetComponent<Rigidbody>());
}
}
}
..and the sound manager script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class sound$$anonymous$$anagerScript : $$anonymous$$onoBehaviour
{
public AudioClip chipHit;
private HashSet<Collider> _collisions = new HashSet<Collider>();
public void Update()
{
_collisions.Clear();
}
public void PlayCollisionSound(Collider colla, Collider collb, Rigidbody rbody)
{
if (_collisions.Contains(colla) && _collisions.Contains(collb))
{
return;
}
_collisions.Add(colla);
_collisions.Add(collb);
AudioSource audioSource = GetComponent<AudioSource>();
audioSource.clip = chipHit;
audioSource.volume = (rbody.velocity.magnitude / 5);
audioSource.pitch = Random.Range (0.7f , 1.0f);
audioSource.PlayOneShot(chipHit);
}
}
I'm confused about needing a co-routine for this.
Every time OnCollisionEnter is called you're creating a coroutine that doesn't behave as such. It's called like a normal function since you're yielding on exit. Also on every call you're creating a local dictionary that will only contain a single element, so iterating through it makes no sense. I mean, you could get rid of the whole coroutine thing (no IEnumerable, no yield, no dictionary).
I'm very surprised it's working, but if it is I doubt it's because of the reason you think it is.
The idea is to keep track of all pairs of game objects that have collide during a frame. This could be a starting point. Note that I'm now using a HashSet ins$$anonymous$$d of a Dictionary.
// untested code
public class sound$$anonymous$$anagerScript : $$anonymous$$onoBehaviour
{
public AudioClip chipHit;
private HashSet<Collider> _collisions = new HashSet<Collider>();
public void Update()
{
_collisions.Clear();
}
public void PlayCollisionSound(Collider colla, Collider collb, Rigidbody rbody)
{
// have this pair of colliders already made a sound?
if( _collisions.Exists(colla) && _collisions.Exists(collb) )
return;
// nop, register both colliders (one of them might have been registered already)
_collisions.Add( colla );
_collisions.Add( collb )
// play a single sound
AudioSource audioSource = GetComponent<AudioSource>();
audioSource.clip = chipHit;
audioSource.volume = (rbody.velocity.magnitude / 5);
audioSource.pitch = Random.Range (0.7f , 1.0f);
audioSource.PlayOneShot(chipHit);
}
}
I have updated the code based on this and it seems to be working. A little more variation (like different hit sounds) and this should be good. Thanks for your help on this.
Yes, you could add a lot of variation depending on what's colliding. Also, you'd probably want to change the condition and use '||' in place of '&&', to let a single chip to make a sound when colliding with several others.
Your answer
Follow this Question
Related Questions
Distribute terrain in zones 3 Answers
Yet another Audio on Collision issue 1 Answer
Audio on Collision not working C# 0 Answers
Sound plays right at the beginning 2 Answers
Sounds triggering too fast 4 Answers