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;
}
}