Created
March 13, 2024 01:53
-
-
Save dorktoast/f7f1295372ff59bcf5a4cc8e2f7897e3 to your computer and use it in GitHub Desktop.
Multi-Tag: A system for tagging objects with multiple tags in Unity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Copyright 2024 Sam Swicegood | |
| * MultiTag.cs | |
| * Created by: Toast <sam@gib.games> | |
| * Created on: 3/11/2023 | |
| * Licensable under MIT | |
| */ | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using UnityEngine; | |
| /// <summary> | |
| /// Contains an array of strings that allow for an object to have more than one tag. | |
| /// </summary> | |
| public class MultiTag : MonoBehaviour, ISerializationCallbackReceiver | |
| { | |
| // A HashSet is used on the backend to improve lookup performance. | |
| private HashSet<string> tags = new HashSet<string>(); | |
| // This list will only be used for serialization purposes. | |
| [SerializeField] private List<string> tagsList = new List<string>(); | |
| private void OnEnable() | |
| { | |
| MultiTagManager.RegisterMultiTag(this); | |
| } | |
| private void OnDisable() | |
| { | |
| MultiTagManager.UnregisterMultiTag(this); | |
| } | |
| public void AddTag(string tag) | |
| { | |
| tag = tag.Trim(); | |
| if (!string.IsNullOrEmpty(tag) && !HasTag(tag)) | |
| { | |
| tags.Add(tag); | |
| tagsList.Add(tag); | |
| } | |
| } | |
| public void RemoveTag(string tag) | |
| { | |
| if (HasTag(tag)) | |
| { | |
| tags.Remove(tag); | |
| tagsList.Remove(tag); | |
| } | |
| } | |
| public bool HasTag(string tag) => tags.Contains(tag); | |
| public void OnAfterDeserialize() | |
| { | |
| // After deserialization, update the HashSet to reflect the serialized List. | |
| tags = new HashSet<string>(tagsList); | |
| } | |
| public void OnBeforeSerialize() | |
| { | |
| // Only necessary if you modify the HashSet during runtime and need to ensure | |
| // the List is synchronized before serialization. | |
| // This assumes 'tags' is your runtime-modifiable HashSet. | |
| if (tags.Count + 1 == tagsList.Count) | |
| { | |
| tagsList.RemoveAt(tagsList.Count-1); | |
| tagsList.Add(""); | |
| return; | |
| } | |
| if (tags.Count != tagsList.Count) | |
| { | |
| tagsList.Clear(); | |
| tagsList.AddRange(tags); | |
| } | |
| } | |
| public IEnumerable<string> GetCurrentTags() | |
| { | |
| return tags; | |
| } | |
| public static void RebuildMultiTags() | |
| { | |
| MultiTagManager.RebuildObjectList(); | |
| } | |
| } | |
| /// <summary> | |
| /// Utilities related to the <see cref="MultiTag"/> behavior. | |
| /// </summary> | |
| public static class MultiTagUtility | |
| { | |
| /// <summary> | |
| /// Returns the first active GameObject in the scene with the corresponding tags, including <see cref="MultiTag"/> objects. | |
| /// Returns null if no matching objects are found. | |
| /// </summary> | |
| /// <param name="tag"></param> | |
| /// <param name="ignoreBuiltIn">Ignore the Built-in Unity tag field.</param> | |
| /// <remarks>If ignoreBuiltIn is false, this will check built-in tag first.</remarks> | |
| public static GameObject FindObjectWithTags(string tag, bool ignoreBuiltIn = false) | |
| { | |
| // Check for objects with built-in tag | |
| if (!ignoreBuiltIn) | |
| { | |
| GameObject result = GameObject.FindWithTag(tag); | |
| if (result != null) return result; | |
| } | |
| // Check for objects with MultiTag | |
| foreach (var multiTag in MultiTagManager.GetAllMultiTags()) | |
| { | |
| if (multiTag.HasTag(tag)) | |
| return multiTag.gameObject; | |
| } | |
| return null; | |
| } | |
| /// <summary> | |
| /// Returns all active GameObjects in the scene with the corresponding tags, including <see cref="MultiTag"/> objects. | |
| /// </summary> | |
| /// <returns>A GameObject array.</returns> | |
| /// <param name="tag"></param> | |
| /// <param name="ignoreBuiltIn">Ignore the Built-in Unity tag field.</param> | |
| /// <remarks>If ignoreBuiltIn is false, this will check built-in tag first.</remarks> | |
| public static GameObject[] FindObjectsWithTags(string tag, bool ignoreBuiltIn = false) | |
| { | |
| return (GameObject[])FindWithTags(tag, ignoreBuiltIn); | |
| } | |
| /// <summary> | |
| /// Returns all active GameObjects in the scene with the corresponding tags, including <see cref="MultiTag"/> objects. | |
| /// </summary> | |
| /// <returns>A GameObject <see cref="IEnumerable"/>.</returns> | |
| /// <param name="tag"></param> | |
| /// <param name="ignoreBuiltIn">Ignore the Built-in Unity tag field.</param> | |
| /// <remarks>If ignoreBuiltIn is false, this will check built-in tag first.</remarks> | |
| public static IEnumerable<GameObject> FindWithTags(string tag, bool ignoreBuiltIn = false) | |
| { | |
| var foundObjects = new List<GameObject>(); | |
| if (!ignoreBuiltIn) | |
| { | |
| foundObjects.AddRange(GameObject.FindGameObjectsWithTag(tag)); | |
| } | |
| // Include objects with MultiTag | |
| foreach (var multiTag in MultiTagManager.GetAllMultiTags()) | |
| { | |
| if (multiTag.HasTag(tag) && (!ignoreBuiltIn || !foundObjects.Contains(multiTag.gameObject))) | |
| { | |
| foundObjects.Add(multiTag.gameObject); | |
| } | |
| } | |
| return foundObjects; | |
| } | |
| /// <summary> | |
| /// Checks if the GameObject is tagged with the target tag, including checking for a <see cref="MultiTag"/>. | |
| /// </summary> | |
| /// <param name="tag">Tags to check for.</param> | |
| /// <remarks>If ignoreBuiltIn is false, this will check built-in tag first.</remarks> | |
| public static bool CompareTags(this GameObject go, string tag, bool ignoreBuiltIn = false) | |
| { | |
| if (!ignoreBuiltIn) | |
| { | |
| if (go.CompareTag(tag)) return true; | |
| } | |
| if (go.TryGetComponent(out MultiTag multiTag)) | |
| { | |
| return multiTag.HasTag(tag); | |
| } | |
| return false; | |
| } | |
| } | |
| /// <summary> | |
| /// Manages registration and tracking of all MultiTag components in the scene. | |
| /// Provides methods to register, unregister, retrieve, and rebuild the list of MultiTag components. | |
| /// </summary> | |
| public static class MultiTagManager | |
| { | |
| private static HashSet<MultiTag> allMultiTags = new HashSet<MultiTag>(); | |
| private static HashSet<string> possibleTags = new HashSet<string> { "EditorOnly, MainCamera, Player"}; | |
| /// <summary> | |
| /// Registers a MultiTag component. If the component is already registered, this method does nothing. | |
| /// </summary> | |
| /// <param name="multiTag">The MultiTag component to register.</param> | |
| public static void RegisterMultiTag(MultiTag multiTag) | |
| { | |
| if (!allMultiTags.Contains(multiTag)) | |
| { | |
| allMultiTags.Add(multiTag); | |
| } | |
| } | |
| /// <summary> | |
| /// Unregisters a MultiTag component. If the component is not currently registered, this method does nothing. | |
| /// </summary> | |
| /// <param name="multiTag">The MultiTag component to unregister.</param> | |
| public static void UnregisterMultiTag(MultiTag multiTag) | |
| { | |
| allMultiTags.Remove(multiTag); | |
| } | |
| /// <summary> | |
| /// Retrieves all registered MultiTag components. | |
| /// </summary> | |
| /// <returns>An IEnumerable of all registered MultiTag components.</returns> | |
| public static IEnumerable<MultiTag> GetAllMultiTags() | |
| { | |
| return allMultiTags; | |
| } | |
| /// <summary> | |
| /// Rebuilds the list of registered MultiTag components, updating it to reflect the current state of the scene. | |
| /// This can be useful after significant changes to the scene's object hierarchy or after loading a new scene. | |
| /// </summary> | |
| public static void RebuildObjectList() | |
| { | |
| allMultiTags.Clear(); | |
| allMultiTags = new HashSet<MultiTag>(Object.FindObjectsOfType<MultiTag>()); | |
| } | |
| public static IEnumerable<string> GetPossibleTags() | |
| { | |
| possibleTags = new HashSet<string> { "EditorOnly, MainCamera, Player" }; | |
| foreach(MultiTag multiTag in allMultiTags) | |
| { | |
| foreach(string s in multiTag.GetCurrentTags()) | |
| { | |
| if(!possibleTags.Contains(s)) | |
| { | |
| possibleTags.Add(s); | |
| } | |
| } | |
| } | |
| return possibleTags; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment