Often, I want certain GameObjects, which I'll hereafter refer to as "entities", to exhibit the following properties: I want entities to use Unity's physics system for collision with terrain, but to pass through fellow entities (So enemies and the player, which would be entities, can stand on the ground but not on each other, etc.) However, I still want scripts I write to be able to detect when entities overlap (So enemies can turn around when they walk into each other, etc.).
The way I've previously achieved this behavior is by giving each should-be entity two colliders, one with IsTrigger turned on and the other with it turned off (From here on, I'll call the former Collider the "trigger collider" and the latter the "solid collider"). I then give each entity a component I wrote that, on Start(), loops through every GameObject currently in the scene, filters them based on layer or some other predicate to determine whether they're another entity, and calls Physics.IgnoreCollision() on the solid colliders of the object with the component and all the unfiltered objects from the loop. Here's what that code looks like:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
//Has object ignore non-trigger collisions with objects on the given layers.
public class IgnoreSolidColliders : MonoBehaviour
{
//The layers an object has to be on to be affected by this script.
[SerializeField] int[] layers;
// Start is called before the first frame update
void Start()
{
Collider2D solidCollider = new Collider2D();
foreach(Collider2D collider in GetComponents<Collider2D>())
{
if(!collider.isTrigger)
{
solidCollider = collider;
break;
}
}
foreach(GameObject gameObject in GetAllObjectsOnlyInScene())
{
//Check to make sure the object has colliders and is on a targeted layer before wasting any more time on it
if(gameObject.GetComponent<Collider2D>() != null && layers.Contains(gameObject.layer))
{
foreach(Collider2D collider in gameObject.GetComponents<Collider2D>())
{
if(!collider.isTrigger)
{
Physics2D.IgnoreCollision(solidCollider, collider);
}
}
}
}
}
List<GameObject> GetAllObjectsOnlyInScene()
{
List<GameObject> objectsInScene = new List<GameObject>();
foreach (GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[])
{
if (!EditorUtility.IsPersistent(go.transform.root.gameObject) && !(go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave))
objectsInScene.Add(go);
}
return objectsInScene;
}
}
This works, because GameObjects with this script and the two colliders pass through each other, but they don't pass through GameObjects that aren't on one of the targeted layers, and scripts I write can still detect when entities touch using OnTriggerEnter2D(), OnTriggerStay(), etc. However, there are a few problems with this approach:
The most important problem, I'd say, is that since entities only interact through their trigger colliders, and triggers don't have
ContactPoints, it becomes harder to determine things like the normal of collision between entities (which, for traditional collisions between "solid" colliders, can be retrieved usingContactPoint.normal).Having two of the same Component on one object is kind of clunky, in my opinion. For example, whenever I need just the solid or trigger collider on an entity, I have to use
GetComponents<Collider>()and loop through each result to find the Collider I want. Also, whenever I want to change something like the dimensions of an entity's hitbox, either through code or with the Inspector, such that collisions with both entities and non-entities are affected, I have to write the same values into both Colliders.I imagine that, with certain kinds of games where more
GameObjects would be instantiated at once than what I'm currently working on, looping though everyGameObjectin the scene each time an entity is instantiated could become costly. I could have each entity repeatedly callPhysics.OverlapBox()and useIgnoreCollisions()on any entities that are caught by the overlap box, but I'm not sure how performant that approach would be, either.
Here are few ideas I've tried to solve some or all of the problems listed above, and some other notes about the problem:
I can't determine whether a
GameObjectshould be an entity based on whether it's Static or Dynamic, because things like moving platforms should be non-entities.I've tried waiting until two objects collide to determine whether they're both entities and call
IgnoreCollision(), but that allows the entities to physically collide for a moment beforeIgnoreCollision()kicks in. The effects of this delay are very noticeable.I've also tried using the Layer Collision Matrix to disable collisions between layers that would contain only entities, but disables triggers,
OnCollisionEnter(), etc. in addition to automatic collision resolution, so I become unable to detect entity-entity collision through code.
The other reason I'm asking this question is because I imagine this is such commonly-desired behavior that Unity probably has a more convenient and less problematic built-in way to achieve this effect. However, if such a method exists, I've been having too much trouble articulating this problem into Google to find it.
IgnoreCollisionmanually.)