I have a NPC with colliders on each head, body and 2 legs. When i instantiate a bullet it often hits 2 colliders. A leg and body or head and body. I have different animations for all the body parts. I am getting weired reacting because of multiple collisions. What can be done?
Any help would be appreciated.
You could place a guard that stops the subsequent collisions from being handled while the animation is playing.
void OnCollisionEnter(Collision c)
{
if (animationIsPlaying)
return;
// Rest of your code.
}
However if you are looking for something more complex, perhaps you can draw ideas from this:
NPCBodypart.cs
using UnityEngine;
// Placed on each body part
public class NPCBodypart : MonoBehaviour
{
public NPCScript npc;
// Route all collisions from body part to main NPC script for processing.
private void OnCollisionEnter(Collision collision)
{
npc.OnChildCollisionEnter(this, collision);
}
}
NPCScript.cs
using UnityEngine;
// Placed on npc root
public class NPCScript : MonoBehaviour
{
// Define for how long you want to ignore collisions from the same object after
// a collision has registered.
public float ignoreTimer = 0.2f;
private ExpirySet<Collider> ignoredColliders = new ExpirySet<Collider>();
private void Awake()
{
foreach (var child in GetComponentsInChildren<NPCBodypart>())
child.npc = this;
// Every so often, we prune the ExpirySet to reclaim memory.
// It could be called more often for more precise Expiry,
// but it is probably not necessary.
InvokeRepeating("Prune", ignoreTimer, ignoreTimer);
}
private void Prune()
{
ignoredColliders.Prune();
}
// Called from NPCBodypart.OnCollisionEnter
public void OnChildCollisionEnter(NPCBodypart child, Collision collision)
{
if (ignoredColliders.Accept(collision.collider, GetExpireTime()))
{
// Process collision...
// Multiple calls to OnChildCollisionEnter won't accept the same collider to enter
// this block until the expiry condition is met and the set has been pruned.
// An idea would be to call back to child to respond to collision.
}
}
private float GetExpireTime()
{
return Time.time + ignoreTimer;
}
}
ExpirySet.cs
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class ExpirySet<T>
{
private readonly List<ExpiryPair> list = new List<ExpiryPair>();
public bool Accept(T value, float time)
{
if (list.Any(ExpiryPair.WithKey(value)))
{
return false;
}
else
{
list.Add(new ExpiryPair(value, time));
return true;
}
}
public void Prune()
{
list.RemoveAll(ExpiryPair.HasExpired);
}
private class ExpiryPair
{
private readonly T key;
private readonly float time;
public ExpiryPair(T key, float time)
{
this.key = key;
this.time = time;
}
public static bool HasExpired(ExpiryPair expiry)
{
return expiry.time < Time.time;
}
#region Predicate helpers, not thread safe
// This approach was chosen to avoid generating memory at runtime.
// The downside is that it is not thread safe and subsequent calls to WithKey
// will cause results from previous calls to compare key with the most recent call.
// It's a code design choice the author chose to take.
private static T keyToCompare;
private static Func<ExpiryPair, bool> keyCompare =
expiry => ReferenceEquals(expiry.key, keyToCompare);
/// <summary>
/// Predicate to test if ExpiryPair has key
/// </summary>
/// <param name="key">The key to text</param>
/// <returns>Predicate that returns true if ExpiryPair has the specified key.
/// Warning: Do not cache result from this call, use result immediately.</returns>
public static Func<ExpiryPair, bool> WithKey(T key)
{
keyToCompare = key;
return keyCompare;
}
#endregion
}
}