Have you made any useful utilities with Odin?
Login and submit your creations hereGameObjects 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