Community Made Tools

Have you made any useful utilities with Odin?

Login and submit your creations here

UnityID - GUIDs for Unity

Authored by Krechevskoy
Shared 24-07-2024

GameObjects often need unique IDs which persist across scenes, compilation, and runtime sessions. This is particularly useful for saving/loading data and other forms of persistence.

Normally you would use System.Guid for these kinds of application, but Unity doesn't understand how to serialize them. This is a quick/clean fix for that particular problem.

Usage

Once in the inspector, you can right-click the ID to copy, clear, or regenerate it

public class Example : MonoBehaviour { public UnityID id1; // Declare with no value public UnityID id3 = new Guid(); // Declare with a .NET GUID public UnityID id2 = UnityID.New(); // Declare with a random value }

UnityID.cs

using System; using Sirenix.OdinInspector; using UnityEngine; /// <summary> /// A globally unique identifier (GUID) for Unity. /// It is serializable and persists across scenes and runtime sessions. /// </summary> [Serializable, HideLabel, InlineProperty] public struct UnityID : IEquatable<UnityID> { [ShowInInspector, LabelText("@$property.Parent.NiceName")] [CustomContextMenu("Generate GUID", nameof(GenerateGUID))] [CustomContextMenu("Clear GUID", nameof(ClearGUID))] public readonly string HexString => ToString(); [SerializeField, HideInInspector] private uint Part1, Part2, Part3, Part4; public static UnityID Empty => new(0, 0, 0, 0); public readonly bool IsEmpty => this == Empty; public UnityID(uint part1, uint part2, uint part3, uint part4) { Part1 = part1; Part2 = part2; Part3 = part3; Part4 = part4; } public UnityID(Guid guid) { byte[] bytes = guid.ToByteArray(); Part1 = BitConverter.ToUInt32(bytes, 0); Part2 = BitConverter.ToUInt32(bytes, 4); Part3 = BitConverter.ToUInt32(bytes, 8); Part4 = BitConverter.ToUInt32(bytes, 12); } public override readonly string ToString() { return $"{Part1:X8}-{Part2:X8}-{Part3:X8}-{Part4:X8}"; } public static UnityID Parse(string hexString) { // Remove hyphens and convert to lowercase hexString = hexString.Replace("-", "").ToLower(); if (hexString.Length != 32) return Empty; return new UnityID( Convert.ToUInt32(hexString[0..8], 16), Convert.ToUInt32(hexString[8..16], 16), Convert.ToUInt32(hexString[16..24], 16), Convert.ToUInt32(hexString[24..32], 16) ); } public readonly Guid ToGuid() { byte[] bytes = new byte[16]; BitConverter.GetBytes(Part1).CopyTo(bytes, 0); BitConverter.GetBytes(Part2).CopyTo(bytes, 4); BitConverter.GetBytes(Part3).CopyTo(bytes, 8); BitConverter.GetBytes(Part4).CopyTo(bytes, 12); return new Guid(bytes); } public static UnityID New() => Guid.NewGuid(); public static implicit operator UnityID(Guid guid) => new(guid); public static implicit operator Guid(UnityID unityId) => unityId.ToGuid(); public static bool operator ==(UnityID left, UnityID right) => left.Equals(right); public static bool operator !=(UnityID left, UnityID right) => !left.Equals(right); public override readonly bool Equals(object obj) { return obj is UnityID unityId && Equals(unityId); } public readonly bool Equals(UnityID other) { return Part1 == other.Part1 && Part2 == other.Part2 && Part3 == other.Part3 && Part4 == other.Part4; } public override readonly int GetHashCode() { return HashCode.Combine(Part1, Part2, Part3, Part4); } private void ClearGUID() => this = Empty; private void GenerateGUID() => this = New(); }

UnityIDValidator.cs

This is an optional validator that can flag empty IDs.

#if UNITY_EDITOR using Sirenix.OdinInspector.Editor.Validation; using Sirenix.OdinInspector; [assembly: RegisterValidationRule(typeof(UnityIDValidator), Name = "Check UnityIDs")] public class UnityIDValidator : ValueValidator<UnityID> { [EnumToggleButtons] public ValidatorSeverity Severity = ValidatorSeverity.Warning; protected override void Validate(ValidationResult result) { if (Value.IsEmpty) { result .Add(Severity, "UnityID is empty. Are you sure this is correct?") .WithFix(() => Value = UnityID.New()); } } } #endif