using System; namespace Editor; public static partial class EditorUtility { public static partial class Prefabs { /// /// Returns the name of the prefab file that this GameObject or Component is an instance of. /// public static bool IsOuterMostPrefabRoot( object obj ) { var go = ResolveGameObject( obj ); if ( !go.IsValid() ) return false; if ( !go.IsPrefabInstance ) return false; return go.IsOutermostPrefabInstanceRoot; } /// /// Returns the name of the prefab file that this GameObject or Component is an instance of. /// public static string GetOuterMostPrefabName( object obj ) { var go = ResolveGameObject( obj ); if ( !go.IsValid() ) return null; if ( !go.IsPrefabInstance ) return null; var prefabName = System.IO.Path.GetFileNameWithoutExtension( go.OutermostPrefabInstanceRoot.PrefabInstanceSource ); return prefabName.ToTitleCase(); } /// /// /// public static bool IsPropertyOverridden( SerializedProperty prop ) { var obj = prop.Parent; if ( obj is null ) return false; var go = obj.Targets.Select( ResolveGameObject ).FirstOrDefault(); var owner = obj.Targets.FirstOrDefault(); if ( !go.IsValid() ) return false; if ( !go.IsPrefabInstance ) return false; if ( !prop.IsEditable ) return false; return go.OutermostPrefabInstanceRoot.PrefabInstance.IsPropertyOverridden( owner, prop.Name, go.IsPrefabInstanceRoot ); } /// /// /// public static bool IsGameObjectAddedToInstance( GameObject go ) { if ( !go.IsValid() ) return false; if ( !go.IsPrefabInstance ) return false; if ( go.IsOutermostPrefabInstanceRoot && go.Parent is not null && go.Parent.IsPrefabInstance ) { return go.Parent.OutermostPrefabInstanceRoot.PrefabInstance.IsAddedGameObject( go ); } return go.OutermostPrefabInstanceRoot.PrefabInstance.IsAddedGameObject( go ); } /// /// /// public static bool IsComponentAddedToInstance( Component comp ) { if ( !comp.IsValid() ) return false; if ( !comp.GameObject.IsPrefabInstance ) return false; return comp.GameObject.OutermostPrefabInstanceRoot.PrefabInstance.IsAddedComponent( comp ); } /// /// /// public static bool IsInstanceModified( GameObject prefabInstance ) { if ( !prefabInstance.IsValid() ) return false; if ( !prefabInstance.IsPrefabInstanceRoot ) return false; return prefabInstance.IsOutermostPrefabInstanceRoot && prefabInstance.OutermostPrefabInstanceRoot.PrefabInstance.IsModified(); } /// /// /// public static bool IsGameObjectInstanceModified( GameObject go ) { if ( !go.IsValid() ) return false; if ( !go.IsPrefabInstance ) return false; return go.OutermostPrefabInstanceRoot.PrefabInstance.IsGameObjectModified( go, go.IsOutermostPrefabInstanceRoot ); } /// /// /// public static bool IsComponentInstanceModified( Component comp ) { if ( !comp.GameObject.IsValid() ) return false; if ( !comp.GameObject.IsPrefabInstance ) return false; return comp.GameObject.OutermostPrefabInstanceRoot.PrefabInstance.IsComponentModified( comp ); } /// /// Returns true if the owning GameObject is part of a prefab instance. /// public static bool IsComponentPartOfInstance( Component comp ) { if ( !comp.IsValid() ) return false; if ( !comp.GameObject.IsValid() ) return false; if ( !comp.GameObject.IsPrefabInstance ) return false; return comp.GameObject.OutermostPrefabInstanceRoot.IsPrefabInstance; } /// /// /// public static void RevertPropertyChange( SerializedProperty prop ) { var obj = prop.Parent; if ( obj is null ) return; var go = obj.Targets.Select( ResolveGameObject ).FirstOrDefault(); var owner = obj.Targets.FirstOrDefault(); if ( !go.IsValid() ) return; if ( !go.IsPrefabInstance ) return; go.OutermostPrefabInstanceRoot.PrefabInstance.RevertPropertyChange( owner, prop.Name ); } /// /// /// public static void ApplyPropertyChange( SerializedProperty prop ) { var obj = prop.Parent; if ( obj is null ) return; var go = obj.Targets.Select( ResolveGameObject ).FirstOrDefault(); var owner = obj.Targets.FirstOrDefault(); if ( !go.IsValid() ) return; if ( !go.IsPrefabInstance ) return; // Store go id we use it to restore inspector // Need to use id to restore inspector because prefab refresh may destroy references var goId = go.Id; go.OutermostPrefabInstanceRoot.PrefabInstance.ApplyPropertyChangeToPrefab( owner, prop.Name ); UpdatePrefabAfterModification( go.OutermostPrefabInstanceRoot.PrefabInstanceSource ); EditorUtility.InspectorObject = SceneEditorSession.Active.Scene.Directory.FindByGuid( goId ); } /// /// /// public static void RevertComponentInstanceChanges( Component comp ) { if ( !comp.IsValid() ) return; if ( !comp.GameObject.IsPrefabInstance ) return; comp.GameObject.OutermostPrefabInstanceRoot.PrefabInstance.RevertComponentChanges( comp ); } /// /// /// public static void RevertGameObjectInstanceChanges( GameObject go ) { if ( !go.IsValid() ) return; if ( !go.IsPrefabInstance ) return; go.OutermostPrefabInstanceRoot.PrefabInstance.RevertGameObjectChanges( go ); } /// /// /// public static void ApplyComponentInstanceChangesToPrefab( Component comp ) { if ( !comp.IsValid() ) return; if ( !comp.GameObject.IsPrefabInstance ) return; // Store go id we use it to restore inspector // Need to use id to restore inspector because prefab refresh may destroy references var goId = comp.GameObject.Id; comp.GameObject.OutermostPrefabInstanceRoot.PrefabInstance.ApplyComponentChangesToPrefab( comp ); UpdatePrefabAfterModification( comp.GameObject.OutermostPrefabInstanceRoot.PrefabInstanceSource ); EditorUtility.InspectorObject = SceneEditorSession.Active.Scene.Directory.FindByGuid( goId ); } /// /// /// public static void AddInstanceAddedGameObjectToPrefab( GameObject go ) { if ( !go.IsValid() ) return; if ( !go.IsPrefabInstance ) return; // Store go id we use it to restore inspector // Need to use id to restore inspector because prefab refresh may destroy references var goId = go.Id; // Call AddGameObject on the correct prefab instance // If go is an Added Prefab Instance // we need to add it to the parent prefab instance if ( go.IsOutermostPrefabInstanceRoot && go.Parent.IsValid() && go.Parent.IsPrefabInstance ) { go.Parent.OutermostPrefabInstanceRoot.PrefabInstance.AddGameObjectToPrefab( go ); } else { go.OutermostPrefabInstanceRoot.PrefabInstance.AddGameObjectToPrefab( go ); } UpdatePrefabAfterModification( go.OutermostPrefabInstanceRoot.PrefabInstanceSource ); EditorUtility.InspectorObject = SceneEditorSession.Active.Scene.Directory.FindByGuid( goId ); } /// /// /// public static void ApplyGameObjectInstanceChangesToPrefab( GameObject go ) { if ( !go.IsValid() ) return; if ( !go.IsPrefabInstance ) return; // Store go id we use it to restore inspector // Need to use id to restore inspector because prefab refresh may destroy references var goId = go.Id; go.OutermostPrefabInstanceRoot.PrefabInstance.ApplyGameObjectChangesToPrefab( go ); UpdatePrefabAfterModification( go.OutermostPrefabInstanceRoot.PrefabInstanceSource ); EditorUtility.InspectorObject = SceneEditorSession.Active.Scene.Directory.FindByGuid( goId ); } /// /// Revert a prefab instance to the state of the prefab. /// public static void RevertInstanceToPrefab( GameObject go ) { if ( !go.IsPrefabInstance ) { return; } go.OutermostPrefabInstanceRoot.PrefabInstance.ClearPatch( true ); go.UpdateFromPrefab(); } /// /// Write a prefab instance back to the prefab file and save it to disk. /// public static void WriteInstanceToPrefab( GameObject go, bool skipDiskWrite = false ) { if ( !go.IsValid() ) return; if ( !go.IsPrefabInstance ) return; var prefabSource = go.OutermostPrefabInstanceRoot.PrefabInstanceSource; WriteGameObjectToPrefab( go, prefabSource, skipDiskWrite ); } private static (PrefabFile, Dictionary) WriteGameObjectToPrefab( GameObject go, string saveLocation, bool skipDiskWrite = false ) { if ( !go.IsValid() ) { Log.Warning( "Failed to write GameObject to prefab, GameObject is invalid." ); return (null, null); } // We cannot allow writing prefabs to disk that contain themselves as instance. if ( go.IsPrefabInstanceRoot ) { foreach ( var prefabInstanceRoot in go.GetAllObjects( false ).Where( x => x.IsPrefabInstanceRoot ) ) { if ( prefabInstanceRoot != go && prefabInstanceRoot.PrefabInstanceSource == go.PrefabInstanceSource ) { Log.Warning( $"Failed to write GameObject to prefab, {prefabInstanceRoot.PrefabInstanceSource} occurs more than once in hierarchy." ); EditorUtility.PlayRawSound( "sounds/editor/fail.wav" ); return (null, null); } } } // Save instance transform for later var instanceTransform = go.LocalTransform; var prefabFile = ResourceLibrary.Get( saveLocation ); if ( !prefabFile.IsValid() ) { prefabFile = new PrefabFile(); prefabFile.SetIdFromResourcePath( saveLocation ); prefabFile.Register( saveLocation ); } Dictionary instanceToPrefabGuid = null; bool isWritingBackToExistingInstance = go.IsOutermostPrefabInstanceRoot && prefabFile.ResourcePath == go.PrefabInstanceSource; // Some special care when writing back existing prefab instances // Need to keep ids up to date if ( isWritingBackToExistingInstance ) { go.OutermostPrefabInstanceRoot.PrefabInstance.OverridePrefabWithInstance(); } else { // Zero instance location for serialization to prefab file go.LocalPosition = Vector3.Zero; prefabFile.RootObject = go.SerializeStandard( new GameObject.SerializeOptions() ); instanceToPrefabGuid = SceneUtility.MakeIdGuidsUnique( prefabFile.RootObject ); // Reset transform go.LocalTransform = instanceTransform; } UpdatePrefabAfterModification( prefabFile.ResourcePath, skipDiskWrite ); if ( isWritingBackToExistingInstance ) { go.PrefabInstance.RefreshPatch(); } return (prefabFile, instanceToPrefabGuid); } private static void WritePrefabToDisk( PrefabFile prefabFile, string saveLocation ) { var asset = AssetSystem.FindByPath( saveLocation ); if ( asset is null ) { asset = AssetSystem.CreateResource( "prefab", saveLocation ); } asset.SaveToDisk( prefabFile ); } private static void UpdatePrefabAfterModification( string prefabSource, bool skipFileWrite = false ) { var prefabFile = ResourceLibrary.Get( prefabSource ); // Need to write back the changes to the editor session of this prefab if it exists, before updating the instances var openSession = SceneEditorSession.All.OfType().FirstOrDefault( session => session.Scene.Source.ResourcePath == prefabSource ); if ( openSession is not null ) { using var undo = openSession.UndoScope( "Apply Prefab Instance Changes" ) .WithGameObjectChanges( openSession.Scene, GameObjectUndoFlags.All ) .Push(); openSession.Scene.Clear(); openSession.Scene.Deserialize( prefabFile.RootObject ); } // TODO this only reason for skipFileWrite exists is because we cannot easily spinup an asset system in tests if ( !skipFileWrite ) { WritePrefabToDisk( prefabFile, prefabFile.ResourcePath ); } } /// /// Convert a GameObject to a prefab. This will write the newly created prefab to disk and set the prefab source on the GameObject. /// public static void ConvertGameObjectToPrefab( GameObject go, string saveLocation, bool skipDiskWrite = false ) { // We cannot convert the existing go in-place if it's part of a prefab instance. if ( go.IsPrefabInstance ) { var oldGo = go; var oldGoSibling = go.GetNextSibling( false ); go = go.Clone(); if ( oldGoSibling != null ) { oldGoSibling.AddSibling( go, true ); } else { go.Parent = oldGo.Parent; } oldGo.OutermostPrefabInstanceRoot.PrefabInstance.RemoveHierarchyFromLookup( oldGo ); oldGo.Destroy(); } var (prefabFile, instanceToPrefabGuid) = WriteGameObjectToPrefab( go, saveLocation, skipDiskWrite ); if ( prefabFile is null ) { Log.Warning( "Failed to convert GameObject to prefab, could not write file." ); return; } // set or change prefab source go.InitPrefabInstance( prefabFile.ResourcePath, false ); // Invert lookup var prefabToInstanceGuid = new Dictionary( instanceToPrefabGuid.Count ); foreach ( var kvp in instanceToPrefabGuid ) { prefabToInstanceGuid[kvp.Value] = kvp.Key; } go.PrefabInstance.InitLookups( prefabToInstanceGuid ); go.PrefabInstance.RefreshPatch(); } private static GameObject ResolveGameObject( object target ) { if ( target is GameTransform gt ) return gt.GameObject; if ( target is GameObject go ) return go; if ( target is Component c ) return c.GameObject; return default; } /// /// Get a SerializedProperty representing variable targets. Will return null if there are no targets /// [Obsolete] public static SerializedProperty GetTargets( GameObject root, PrefabVariable variable ) { return null; } [Obsolete] public static PrefabScene.VariableCollection GetVariables( SerializedObject obj ) { return new PrefabScene.VariableCollection(); } /// /// Create a prefab out of any GameObject /// [Obsolete] public static PrefabFile CreateAsset( GameObject clone ) { return null; } /// /// Fetches all prefab templates to show in Create GameObject menus /// public static IEnumerable GetTemplates() { return ResourceLibrary.GetAll() .Where( x => x.IsValid() && x.ShowInMenu ) .GroupBy( x => x.MenuPath ?? string.Empty ) .Select( g => g.First() ) .OrderByDescending( x => (x.MenuPath ?? string.Empty).Count( c => c == '/' ) ) .ThenBy( x => x.MenuPath ?? string.Empty ); } } }