Skip to content

Instantly share code, notes, and snippets.

@hariedo
Created March 29, 2024 15:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hariedo/808d6bc0aa4f35d2eae9d94c3ea8a2a5 to your computer and use it in GitHub Desktop.
Save hariedo/808d6bc0aa4f35d2eae9d94c3ea8a2a5 to your computer and use it in GitHub Desktop.
// Thing.cs
// Automatically indexed MonoBehaviour for searching by type.
//
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Things
{
// A Thing is a MonoBehaviour which can be found or iterated by type.
//
// Every Thing is registered on Awake and unregistered on Destroy.
// Finding all instances of a specific class of Thing in an area
// is quicker than finding them using FindObject calls.
//
public abstract class Thing: MonoBehaviour
{
public virtual void Awake()
{
Remember(this.GetType());
}
public virtual void OnDestroy()
{
Forget(this.GetType());
}
// In-Memory registry of all Things by type.
// If Cow : Mammal : Animal : Thing, then we register
// a Cow instance under each of Cow, Mammal and Animal.
//
private static Dictionary<Type,HashSet<Thing>> allThings;
private void Remember(Type type)
{
if (type == null || type == typeof(Thing))
return;
if (allThings == null)
allThings = new Dictionary<Type,HashSet<Thing>>();
if (!allThings.ContainsKey(type))
allThings[type] = new HashSet<Thing>();
allThings[type].Add(this);
Remember(type.BaseType);
}
private void Forget(Type type)
{
if (type == null || type == typeof(Thing))
return;
if (allThings == null)
return;
if (!allThings.ContainsKey(type))
return;
allThings[type].Remove(this);
Forget(type.BaseType);
}
// Find things by Type only.
//
public static IEnumerable<T> FindThings<T>()
where T: Thing
{
if (allThings == null)
yield break;
Type type = typeof(T);
if (!allThings.ContainsKey(type))
yield break;
foreach (Thing thing in allThings[type])
yield return thing as T;
}
// Find things by Type, Proximity, Field Angle, and/or Exclusion.
//
// source: object positions measured relative to this transform
// range: objects outside this distance ignored
// fov: objects outside this wedge from source.forward ignored
// exclude: objects inside this transform hierarchy are ignored
//
public static T FindNearestThing<T>(Transform source,
float range=float.PositiveInfinity,
float fov=360f,
Transform exclude=null)
where T: Thing
{
if (source == null)
return null;
Thing best = null;
float closestSq = float.PositiveInfinity;
Vector3 position = source.position;
float rangeSq = range*range;
float halfFov = 0.5f*fov;
foreach (Thing thing in allThings[type])
{
if (thing == null)
continue;
if (exclude != null && thing.transform.IsChildOf(exclude))
continue;
Vector3 direction = thing.transform.position - position;
float angle = Vector3.Angle(direction, source.forward);
if (angle > halfFov)
continue;
float dSq =
(thing.transform.position - position).sqrMagnitude;
if (dSq >= closestSq)
continue;
if (dSq > rangeSq)
continue;
best = thing;
closestSq = dSq;
}
return best as T;
}
}
}
@hariedo
Copy link
Author

hariedo commented Mar 29, 2024

When I first started Unity, years ago, I kinda assumed that since there was a FindObjectsByType function, that Unity was surely indexing things behind the scenes. Not a chance. (I also thought that Unity was surely indexing things by layer, and by tag, but those apparently aren't true, either.)

However, it's so useful to have some of your classes found by their type, that I immediately added this class to my toolbox. I don't use this abstract base class for everything in my game, but I do use it a LOT instead of tags. Want to find all Vehicles? Want to find the closest Interactive lever or doorknob or fruit in front of the player? Want to iterate through every Enemy? Want to apply damage to all Damageables in a given radius? These are much easier when you index your types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment