using System.Text.Json.Serialization; namespace Sandbox; #nullable enable /// /// A serialized reference to a that can be resolved at runtime. /// Can either have a for objects in a scene, or a /// if we're referencing a prefab. /// [ActionGraphExposeWhenCached] internal readonly struct GameObjectReference { private const string ExpectedReferenceType = "gameobject"; /// /// Reference a in a scene by its . /// public static GameObjectReference FromId( Guid id ) => new( ExpectedReferenceType, gameObjectId: id ); /// /// Reference a prefab by its . /// public static GameObjectReference FromPrefabPath( string prefabPath ) => new( ExpectedReferenceType, prefabPath: prefabPath ); /// /// Reference a given . /// public static GameObjectReference FromInstance( GameObject go ) { // If we reference a prefab scene, use the prefab path instead of the ID. // But only if the prefab scene is not the active scene. // If the prefab is the active scene, we can use the ID, as users may want to reference the prefab root from within the prefab scene. if ( go is PrefabScene prefabRoot && go.Scene != Game.ActiveScene ) { Assert.NotNull( prefabRoot.Source, "Prefab root should have a source" ); return FromPrefabPath( prefabRoot.Source.ResourcePath ); } return FromId( go.Id ); } /// /// Expected to be "gameobject" for a game object reference. /// [JsonPropertyName( "_type" )] public string ReferenceType { get; } /// /// If we're referencing an object in a scene, this is its . /// [JsonPropertyName( "go" ), JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )] public Guid GameObjectId { get; } /// /// If we're referencing a prefab, this is its . /// [JsonPropertyName( "prefab" ), JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] public string? PrefabPath { get; } [JsonConstructor] private GameObjectReference( string referenceType, Guid gameObjectId = default, string? prefabPath = null ) { ReferenceType = referenceType; GameObjectId = gameObjectId; PrefabPath = prefabPath; } /// /// Attempt to resolve this reference in the current . Returns if /// the reference couldn't be resolved, and logs a warning. /// public GameObject? Resolve() => Resolve( Game.ActiveScene ); /// /// Attempt to resolve this reference in the given . Returns if /// the reference couldn't be resolved. /// /// Scene to attempt to resolve the reference in. /// If true, log a warning to the console if the reference couldn't be resolved. public GameObject? Resolve( Scene scene, bool warn = false ) { if ( ReferenceType != ExpectedReferenceType ) throw new( $"Tried to deserialize unknown type '{ReferenceType ?? "null"}' as GameObject" ); if ( !string.IsNullOrWhiteSpace( PrefabPath ) ) { var prefabFile = (PrefabFile)GameResource.GetPromise( typeof( PrefabFile ), PrefabPath ); return SceneUtility.GetPrefabScene( prefabFile ); } var go = scene.Directory.FindByGuid( GameObjectId ); if ( !go.IsValid() ) { // Conna: this is okay - we may be deserializing over the network and this is a property // referring to a GameObject that we don't know about. Let's just make the reference null. if ( warn ) Log.Warning( $"Unknown GameObject {GameObjectId}" ); return null; } return go; } }