using System.Collections.ObjectModel; using System.Text.Json.Nodes; namespace Sandbox; public partial class GameObject { /// /// We are cloned from a prefab. Stop that. /// public void BreakFromPrefab() { if ( !IsOutermostPrefabInstanceRoot ) return; PrefabInstanceData.ConvertTopLevelNestedToFullPrefabInstances( this ); ClearPrefabInstance(); } public void UpdateFromPrefab() { if ( !IsPrefabInstance ) return; if ( !IsPrefabInstanceRoot ) { Clear(); return; } PrefabInstance.UpdateGameObjectFromPrefab( this ); } /// /// Get the GameObject of a prefab from file path /// public static GameObject GetPrefab( string prefabFilePath ) { var prefabFile = ResourceLibrary.Get( prefabFilePath ); if ( prefabFile is null ) return default; return SceneUtility.GetPrefabScene( prefabFile ); } public string PrefabInstanceSource { get { return PrefabInstance?.PrefabSource; } } /// /// This GameObject is part of a prefab instance. /// public bool IsPrefabInstance { get { if ( IsPrefabInstanceRoot ) return true; if ( Parent is null ) return false; return Parent.IsPrefabInstance; } } /// /// This GameObject is the root of a prefab instance. /// Returns true for regular instance roots and nested prefab instance roots. /// public bool IsPrefabInstanceRoot => _prefabInstanceData is not null && !IsPrefabCacheSceneRoot; /// /// Get the root of the prefab instance this GameObject is part of. /// internal GameObject PrefabInstanceRoot { get { return IsPrefabInstanceRoot ? this : Parent?.PrefabInstanceRoot; } } /// /// Get the outermost prefab instance root of this GameObject. /// This is the root of the prefab instance this GameObject is part of, or the root of the prefab instance this GameObject is nested in. /// internal GameObject OutermostPrefabInstanceRoot { get { Assert.True( IsPrefabInstance ); if ( Parent == null ) return this; // Case 1: GameObject is inside a prefab but isn't the root if ( IsPrefabInstance && !IsPrefabInstanceRoot ) return Parent.OutermostPrefabInstanceRoot; // Case 2: GameObject is a prefab root but is nested inside another prefab if ( IsPrefabInstanceRoot && Parent.IsPrefabInstance && IsNestedPrefabInstanceRoot ) return Parent.OutermostPrefabInstanceRoot; // Otherwise, we've found the outermost prefab instance root return this; } } /// /// This GameObject is the root of a prefab instance and is not nested inside another prefab instance. /// internal bool IsOutermostPrefabInstanceRoot => IsPrefabInstanceRoot && OutermostPrefabInstanceRoot == this; /// /// This GameObject is the root of a nested prefab instance. /// internal bool IsNestedPrefabInstanceRoot => IsPrefabInstanceRoot && PrefabInstance.IsNested; /// /// Is this PrefabRoot the root of a prefab cache scene? /// internal bool IsPrefabCacheSceneRoot => this is PrefabCacheScene; /// /// The filename of the map this object is defined in. /// private string MapSource { get; set; } // This should never have been public (maybe we did it for easy access from the editor?), // It is super easy to mess stuff up when using this incorrectly, even worse now with the new prefab system. [Obsolete( "Stop using this, you will most likely mess something up." )] public void SetPrefabSource( string prefabSource ) { InitPrefabInstance( prefabSource, false ); } /// /// Initializes the instance data. /// internal void InitPrefabInstance( string prefabSource, bool isNested ) { if ( string.IsNullOrEmpty( prefabSource ) ) { _prefabInstanceData = null; return; } // Added 12th Dec 2023 prefabSource = prefabSource.Replace( ".object", ".prefab", StringComparison.OrdinalIgnoreCase ); _prefabInstanceData = new PrefabInstanceData( prefabSource, this, isNested ); } internal void ClearPrefabInstance() { _prefabInstanceData = null; } internal void SetMapSource( string mapSource ) { MapSource = mapSource; } internal bool IsMapInstanceRoot => MapSource is not null; /// /// Access point for all prefab instance related data. /// Can be accessed on both instance root and children contained within the instance. /// For outermost prefab instances this will contain a patch and guid mappings. /// For nested prefab instances this will just contain the prefab source. /// internal PrefabInstanceData PrefabInstance { get { return IsPrefabInstanceRoot ? _prefabInstanceData : Parent?.PrefabInstance; } } PrefabInstanceData _prefabInstanceData = null; /// /// Defines objects within a scene hierarchy we want to track for prefab diffing and patching. /// internal static HashSet DiffObjectDefinitions = [ Json.TrackedObjectDefinition.CreatePresenceBasedDefinition( type: "GameObject", requiredFields: [JsonKeys.Id, JsonKeys.Children, JsonKeys.Components, JsonKeys.Flags], idProperty: JsonKeys.Id, parentType: "GameObject", allowedAsRoot: true, ignoredProperties: [ JsonKeys.EditorPrefabInstanceNestedSource, JsonKeys.EditorSkipPrefabBreakOnRefresh ] ), // Prefab Instances are GameObjects as well but have a different structure (children and components array can be omitted) Json.TrackedObjectDefinition.CreatePresenceBasedDefinition( type: "GameObject", requiredFields: [JsonKeys.Id, JsonKeys.PrefabInstanceSource], idProperty: JsonKeys.Id, parentType: "GameObject", allowedAsRoot: false, atomic: true ), Json.TrackedObjectDefinition.CreatePresenceBasedDefinition( type: "Component", requiredFields: [Component.JsonKeys.Id, Component.JsonKeys.Type, Component.JsonKeys.Enabled], idProperty: Component.JsonKeys.Id, parentType: "GameObject", allowedAsRoot: true ), ]; }