Przeglądaj źródła

Properly handle root object for generic (non-scene) prefabs

BearishSun 9 lat temu
rodzic
commit
6a325c9f67

+ 15 - 2
Source/BansheeCore/Include/BsPrefab.h

@@ -25,8 +25,12 @@ namespace BansheeEngine
 		/**
 		 * Creates a new prefab from the provided scene object. If the scene object has an existing prefab link it will 
 		 * be broken. After the prefab is created the scene object will be automatically linked to it.
+		 *
+		 * @param[in]	sceneObject		Scene object to create the prefab from.
+		 * @param[in]	isScene			Determines if the prefab represents a scene or just a generic group of objects.
+		 *								@see isScene().
 		 */
-		static HPrefab create(const HSceneObject& sceneObject);
+		static HPrefab create(const HSceneObject& sceneObject, bool isScene = true);
 
 		/**
 		 * Instantiates a prefab by creating an instance of the prefab's scene object hierarchy. The returned hierarchy 
@@ -48,6 +52,14 @@ namespace BansheeEngine
 		 */
 		UINT32 getHash() const { return mHash; }
 
+		/** 
+		 * Determines if the prefab represents a scene or just a generic group of objects. The only difference between the
+		 * two is the way root object is handled: scenes are assumed to be saved with the scene root object (which is 
+		 * hidden), while object group root is a normal scene object (not hidden). This is relevant when when prefabs are
+		 * loaded, so the systems knows to append the root object to non-scene prefabs.
+		 */
+		bool isScene() const { return mIsScene; }
+
 	public: // ***** INTERNAL ******
 		/** @name Internal
 		 *  @{
@@ -84,6 +96,7 @@ namespace BansheeEngine
 		UINT32 mHash;
 		String mUUID;
 		UINT32 mNextLinkId;
+		bool mIsScene;
 
 		/************************************************************************/
 		/* 								RTTI		                     		*/
@@ -92,7 +105,7 @@ namespace BansheeEngine
 	public:
 		friend class PrefabRTTI;
 		static RTTITypeBase* getRTTIStatic();
-		virtual RTTITypeBase* getRTTI() const override;
+		RTTITypeBase* getRTTI() const override;
 	};
 
 	/** @} */

+ 8 - 11
Source/BansheeCore/Include/BsPrefabRTTI.h

@@ -17,24 +17,21 @@ namespace BansheeEngine
 	class BS_CORE_EXPORT PrefabRTTI : public RTTIType < Prefab, Resource, PrefabRTTI >
 	{
 	private:
+		BS_BEGIN_RTTI_MEMBERS
+			BS_RTTI_MEMBER_PLAIN(mHash, 1)
+			BS_RTTI_MEMBER_PLAIN(mNextLinkId, 2)
+			BS_RTTI_MEMBER_PLAIN(mUUID, 3)
+			BS_RTTI_MEMBER_PLAIN(mIsScene, 4)
+		BS_END_RTTI_MEMBERS
+
 		SPtr<SceneObject> getSceneObject(Prefab* obj) { return obj->mRoot.getInternalPtr(); }
 		void setSceneObject(Prefab* obj, SPtr<SceneObject> value) { obj->mRoot = value->getHandle(); }
 
-		UINT32& getHash(Prefab* obj) { return obj->mHash; }
-		void setHash(Prefab* obj, UINT32& val) { obj->mHash = val; }
-
-		UINT32& getNextLinkId(Prefab* obj) { return obj->mNextLinkId; }
-		void setNextLinkId(Prefab* obj, UINT32& val) { obj->mNextLinkId = val; }
-
-		String& getUUID(Prefab* obj) { return obj->mUUID; }
-		void setUUID(Prefab* obj, String& val) { obj->mUUID = val; }
 	public:
 		PrefabRTTI()
+			:mInitMembers(this)
 		{
 			addReflectablePtrField("mRoot", 0, &PrefabRTTI::getSceneObject, &PrefabRTTI::setSceneObject);
-			addPlainField("mHash", 1, &PrefabRTTI::getHash, &PrefabRTTI::setHash);
-			addPlainField("mNextLinkId", 2, &PrefabRTTI::getNextLinkId, &PrefabRTTI::setNextLinkId);
-			addPlainField("mUUID", 3, &PrefabRTTI::getUUID, &PrefabRTTI::setUUID);
 		}
 
 		void onDeserializationStarted(IReflectable* ptr, const UnorderedMap<String, UINT64>& params) override

+ 3 - 2
Source/BansheeCore/Source/BsPrefab.cpp

@@ -10,7 +10,7 @@
 namespace BansheeEngine
 {
 	Prefab::Prefab()
-		:Resource(false), mHash(0), mNextLinkId(0)
+		:Resource(false), mHash(0), mNextLinkId(0), mIsScene(false)
 	{
 		
 	}
@@ -21,9 +21,10 @@ namespace BansheeEngine
 			mRoot->destroy(true);
 	}
 
-	HPrefab Prefab::create(const HSceneObject& sceneObject)
+	HPrefab Prefab::create(const HSceneObject& sceneObject, bool isScene)
 	{
 		SPtr<Prefab> newPrefab = createEmpty();
+		newPrefab->mIsScene = isScene;
 		newPrefab->initialize(sceneObject);
 
 		HPrefab handle = static_resource_cast<Prefab>(gResources()._createResourceHandle(newPrefab));

+ 62 - 4
Source/MBansheeEditor/General/EditorApplication.cs

@@ -412,10 +412,17 @@ namespace BansheeEditor
                 string scenePath = ProjectLibrary.GetPath(Scene.ActiveSceneUUID);
                 if (!string.IsNullOrEmpty(scenePath))
                 {
-                    SaveScene(scenePath);
+                    if (Scene.IsGenericPrefab)
+                    {
+                        SaveGenericPrefab(onSuccess, onFailure);
+                    }
+                    else
+                    {
+                        SaveScene(scenePath);
 
-                    if (onSuccess != null)
-                        onSuccess();
+                        if (onSuccess != null)
+                            onSuccess();
+                    }
                 }
                 else
                     SaveSceneAs(onSuccess, onFailure);
@@ -507,7 +514,7 @@ namespace BansheeEditor
         /// </summary>
         /// <param name="path">Path relative to the resource folder. This can be the path to the existing scene
         ///                    prefab if it just needs updating. </param>
-        public static void SaveScene(string path)
+        private static void SaveScene(string path)
         {
             Prefab scene = Internal_SaveScene(path);
             Scene.SetActive(scene);
@@ -516,6 +523,57 @@ namespace BansheeEditor
             SetSceneDirty(false);
         }
 
+        /// <summary>
+        /// Attempts to save the current scene by applying the changes to a prefab, instead of saving it as a brand new
+        /// scene. This is necessary for generic prefabs that have don't have a scene root included in the prefab. If the
+        /// object added any other objects to the root, or has moved or deleted the original generic prefab the user
+        /// will be asked to save the scene normally, creating a brand new prefab.
+        /// </summary>
+        private static void SaveGenericPrefab(Action onSuccess = null, Action onFailure = null)
+        {
+            // Find prefab root
+            SceneObject root = null;
+
+            int numChildren = Scene.Root.GetNumChildren();
+            int numNormalChildren = 0;
+
+            for (int i = 0; i < numChildren; i++)
+            {
+                SceneObject child = Scene.Root.GetChild(i);
+
+                string prefabUUID = PrefabUtility.GetPrefabUUID(child);
+                if (prefabUUID == Scene.ActiveSceneUUID)
+                    root = child;
+
+                if (EditorUtility.IsInternal(child))
+                    continue;
+
+                // If user added any other prefabs other than the initial one, the scene no longer represents a generic
+                // prefab (as we can now longer save it by applying changes only to that prefab)
+                numNormalChildren++;
+                if (numNormalChildren > 1)
+                {
+                    root = null;
+                    break;
+                }
+            }
+            
+            if (root != null)
+            {
+                PrefabUtility.ApplyPrefab(root);
+
+                ProjectLibrary.Refresh(true);
+                SetSceneDirty(false);
+
+                if (onSuccess != null)
+                    onSuccess();
+            }
+            else
+            {
+                SaveSceneAs(onSuccess, onFailure);
+            }
+        }
+
         /// <summary>
         /// Checks does the folder at the provieded path contain a valid project.
         /// </summary>

+ 19 - 0
Source/MBansheeEditor/Utility/EditorUtility.cs

@@ -1,5 +1,7 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+
+using System;
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
 using BansheeEngine;
@@ -78,6 +80,20 @@ namespace BansheeEditor
             return Internal_FindDependencies(resource, recursive);
         }
 
+        /// <summary>
+        /// Checks is the provided scene object internal (hidden from normal user, used in internal engine systems).
+        /// </summary>
+        /// <param name="so">Scene object to check.</param>
+        /// <returns>True if internal, false otherwise. </returns>
+        public static bool IsInternal(SceneObject so)
+        {
+            if (so == null)
+                return false;
+
+            IntPtr objPtr = so.GetCachedPtr();
+            return Internal_IsInternal(objPtr);
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_CalculateBounds(SceneObject so, out AABox bounds);
 
@@ -86,6 +102,9 @@ namespace BansheeEditor
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern Resource[] Internal_FindDependencies(Resource resource, bool recursive);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern bool Internal_IsInternal(IntPtr soPtr);
     }
 
     /** @} */

+ 17 - 0
Source/MBansheeEditor/Utility/PrefabUtility.cs

@@ -86,6 +86,20 @@ namespace BansheeEditor
             return Internal_GetPrefabParent(objPtr);
         }
 
+        /// <summary>
+        /// Returns the UUID of the prefab attached to the provided scene object. Only works on root prefab objects.
+        /// </summary>
+        /// <param name="obj">Scene object to retrieve the prefab UUID for.</param>
+        /// <returns>Prefab UUID if the object is part of a prefab, null otherwise. </returns>
+        public static string GetPrefabUUID(SceneObject obj)
+        {
+            if (obj == null)
+                return null;
+
+            IntPtr objPtr = obj.GetCachedPtr();
+            return Internal_GetPrefabUUID(objPtr);
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_BreakPrefab(IntPtr nativeInstance);
 
@@ -100,6 +114,9 @@ namespace BansheeEditor
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern SceneObject Internal_GetPrefabParent(IntPtr nativeInstance);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern string Internal_GetPrefabUUID(IntPtr nativeInstance);
     }
 
     /** @} */

+ 1 - 1
Source/MBansheeEditor/Windows/Library/LibraryWindow.cs

@@ -1477,7 +1477,7 @@ namespace BansheeEditor
                     if (so == null)
                         continue;
 
-                    Prefab newPrefab = new Prefab(so);
+                    Prefab newPrefab = new Prefab(so, false);
 
                     string destination = LibraryUtility.GetUniquePath(Path.Combine(destinationFolder, so.Name + ".prefab"));
                     addedResources.Add(destination);

+ 19 - 3
Source/MBansheeEngine/Scene/Prefab.cs

@@ -26,10 +26,12 @@ namespace BansheeEngine
         /// be broken. After the prefab is created the scene object will be automatically linked to it.
         /// </summary>
         /// <param name="so">Scene object to generate the prefab for.</param>
-        public Prefab(SceneObject so)
+        /// <param name="isScene">Determines if the prefab represents a scene or just a generic group of objects.
+        ///                       <see cref="IsScene"/></param>
+        public Prefab(SceneObject so, bool isScene = true)
         {
             IntPtr soPtr = so.GetCachedPtr();
-            Internal_CreateInstance(this, soPtr);
+            Internal_CreateInstance(this, soPtr, isScene);
         }
 
         /// <summary>
@@ -42,11 +44,25 @@ namespace BansheeEngine
             return Internal_Instantiate(mCachedPtr);
         }
 
+        /// <summary>
+        /// Determines if the prefab represents a scene or just a generic group of objects. The only difference between the
+        /// two is the way root object is handled: scenes are assumed to be saved with the scene root object (which is 
+        /// hidden), while object group root is a normal scene object (not hidden). This is relevant when when prefabs are
+        /// loaded, so the systems knows to append the root object to non-scene prefabs.
+        /// </summary>
+        public bool IsScene
+        {
+            get { return Internal_IsScene(mCachedPtr); }
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern void Internal_CreateInstance(Prefab instance, IntPtr so);
+        private static extern void Internal_CreateInstance(Prefab instance, IntPtr so, bool isScene);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern SceneObject Internal_Instantiate(IntPtr thisPtr);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern bool Internal_IsScene(IntPtr thisPtr);
     }
 
     /** @} */

+ 27 - 0
Source/MBansheeEngine/Scene/Scene.cs

@@ -23,8 +23,15 @@ namespace BansheeEngine
         /// </summary>
         internal static string ActiveSceneUUID { get { return activeSceneUUID; } }
 
+        /// <summary>
+        /// Checks is the loaded scene a generic scene object group, instead of an actual scene. 
+        /// <see cref="Prefab.IsScene"/>.
+        /// </summary>
+        internal static bool IsGenericPrefab { get { return isGenericPrefab; } }
+
         private static string activeSceneName = "Unnamed";
         private static string activeSceneUUID = "";
+        private static bool isGenericPrefab = false;
 
         /// <summary>
         /// Returns the root scene object for the current scene.
@@ -83,6 +90,7 @@ namespace BansheeEngine
             {
                 activeSceneUUID = scene.UUID;
                 activeSceneName = scene.Name;
+                isGenericPrefab = !scene.IsScene;
             }
         }
 
@@ -122,6 +130,25 @@ namespace BansheeEngine
             activeSceneUUID = uuid;
         }
 
+        /// <summary>
+        /// Wrapper around the isGenericPrefab static field because Mono has problems accessing static fields directly.
+        /// </summary>
+        /// <returns>True if the loaded scene a generic scene object group, instead of an actual scene.</returns>
+        private static bool GetIsGenericPrefab()
+        {
+            return isGenericPrefab;
+        }
+
+        /// <summary>
+        /// Wrapper around scene UUID static field because Mono has problems accessing static fields directly.
+        /// </summary>
+        /// <param name="isGenericPrefab">Determines is the loaded scene a generic scene object group, instead of an actual scene.
+        ///                    </param>
+        private static void SetIsGenericPrefab(bool isGenericPrefab)
+        {
+            Scene.isGenericPrefab = isGenericPrefab;
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern Prefab Internal_LoadScene(string path);
 

+ 1 - 0
Source/SBansheeEditor/Include/BsScriptEditorUtility.h

@@ -26,6 +26,7 @@ namespace BansheeEngine
 		static void internal_CalculateBounds(MonoObject* so, AABox* bounds);
 		static void internal_CalculateBoundsArray(MonoArray* objects, AABox* bounds);
 		static MonoArray* internal_FindDependencies(MonoObject* resource, bool recursive);
+		static bool internal_IsInternal(ScriptSceneObject* soPtr);
 	};
 
 	/** @} */

+ 1 - 0
Source/SBansheeEditor/Include/BsScriptPrefabUtility.h

@@ -26,6 +26,7 @@ namespace BansheeEngine
 		static void internal_revertPrefab(ScriptSceneObject* nativeInstance);
 		static bool internal_hasPrefabLink(ScriptSceneObject* nativeInstance);
 		static MonoObject* internal_getPrefabParent(ScriptSceneObject* nativeInstance);
+		static MonoString* internal_GetPrefabUUID(ScriptSceneObject* nativeInstance);
 
 		ScriptPrefabUtility(MonoObject* instance);
 	};

+ 11 - 0
Source/SBansheeEditor/Source/BsScriptEditorUtility.cpp

@@ -11,6 +11,7 @@
 #include "BsScriptResource.h"
 #include "BsScriptResourceManager.h"
 #include "BsUtility.h"
+#include "BsSceneObject.h"
 
 namespace BansheeEngine
 {
@@ -23,6 +24,7 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_CalculateBounds", &ScriptEditorUtility::internal_CalculateBounds);
 		metaData.scriptClass->addInternalCall("Internal_CalculateBoundsArray", &ScriptEditorUtility::internal_CalculateBoundsArray);
 		metaData.scriptClass->addInternalCall("Internal_FindDependencies", &ScriptEditorUtility::internal_FindDependencies);
+		metaData.scriptClass->addInternalCall("Internal_IsInternal", &ScriptEditorUtility::internal_IsInternal);
 	}
 
 	void ScriptEditorUtility::internal_CalculateBounds(MonoObject* so, AABox* bounds)
@@ -78,4 +80,13 @@ namespace BansheeEngine
 
 		return output.getInternal();
 	}
+
+	bool ScriptEditorUtility::internal_IsInternal(ScriptSceneObject* soPtr)
+	{
+		if (ScriptSceneObject::checkIfDestroyed(soPtr))
+			return false;
+
+		HSceneObject so = soPtr->getNativeSceneObject();
+		return so->hasFlag(SOF_Internal);
+	}
 }

+ 16 - 0
Source/SBansheeEditor/Source/BsScriptPrefabUtility.cpp

@@ -8,6 +8,7 @@
 #include "BsSceneObject.h"
 #include "BsPrefab.h"
 #include "BsResources.h"
+#include "BsMonoUtil.h"
 #include "BsScriptGameObjectManager.h"
 
 namespace BansheeEngine
@@ -23,6 +24,7 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_RevertPrefab", &ScriptPrefabUtility::internal_revertPrefab);
 		metaData.scriptClass->addInternalCall("Internal_HasPrefabLink", &ScriptPrefabUtility::internal_hasPrefabLink);
 		metaData.scriptClass->addInternalCall("Internal_GetPrefabParent", &ScriptPrefabUtility::internal_getPrefabParent);
+		metaData.scriptClass->addInternalCall("Internal_GetPrefabUUID", &ScriptPrefabUtility::internal_GetPrefabUUID);
 	}
 
 	void ScriptPrefabUtility::internal_breakPrefab(ScriptSceneObject* nativeInstance)
@@ -81,4 +83,18 @@ namespace BansheeEngine
 
 		return nullptr;
 	}
+
+	MonoString* ScriptPrefabUtility::internal_GetPrefabUUID(ScriptSceneObject* nativeInstance)
+	{
+		if (ScriptSceneObject::checkIfDestroyed(nativeInstance))
+			return nullptr;
+
+		HSceneObject so = nativeInstance->getNativeSceneObject();
+
+		String prefabUUID = nativeInstance->getNativeSceneObject()->getPrefabLink();
+		if (prefabUUID.empty())
+			return nullptr;
+
+		return MonoUtil::stringToMono(prefabUUID);
+	}
 }

+ 2 - 1
Source/SBansheeEngine/Include/BsScriptPrefab.h

@@ -29,8 +29,9 @@ namespace BansheeEngine
 		/************************************************************************/
 		/* 								CLR HOOKS						   		*/
 		/************************************************************************/
-		static void internal_CreateInstance(MonoObject* instance, ScriptSceneObject* so);
+		static void internal_CreateInstance(MonoObject* instance, ScriptSceneObject* so, bool isScene);
 		static MonoObject* internal_Instantiate(ScriptPrefab* instance);
+		static bool internal_IsScene(ScriptPrefab* instance);
 	};
 
 	/** @} */

+ 1 - 0
Source/SBansheeEngine/Include/BsScriptScene.h

@@ -37,6 +37,7 @@ namespace BansheeEngine
 
 		static String ActiveSceneUUID;
 		static WString ActiveSceneName;
+		static bool IsGenericPrefab;
 
 		/************************************************************************/
 		/* 								CLR HOOKS						   		*/

+ 9 - 5
Source/SBansheeEngine/Source/BsScriptPrefab.cpp

@@ -4,10 +4,7 @@
 #include "BsScriptResourceManager.h"
 #include "BsScriptGameObjectManager.h"
 #include "BsScriptMeta.h"
-#include "BsMonoField.h"
 #include "BsMonoClass.h"
-#include "BsMonoArray.h"
-#include "BsMonoManager.h"
 #include "BsScriptSceneObject.h"
 
 namespace BansheeEngine
@@ -22,11 +19,12 @@ namespace BansheeEngine
 	{
 		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptPrefab::internal_CreateInstance);
 		metaData.scriptClass->addInternalCall("Internal_Instantiate", &ScriptPrefab::internal_Instantiate);
+		metaData.scriptClass->addInternalCall("Internal_IsScene", &ScriptPrefab::internal_IsScene);
 	}
 
-	void ScriptPrefab::internal_CreateInstance(MonoObject* instance, ScriptSceneObject* so)
+	void ScriptPrefab::internal_CreateInstance(MonoObject* instance, ScriptSceneObject* so, bool isScene)
 	{
-		HPrefab prefab = Prefab::create(so->getNativeSceneObject());
+		HPrefab prefab = Prefab::create(so->getNativeSceneObject(), isScene);
 
 		ScriptPrefab* scriptInstance;
 		ScriptResourceManager::instance().createScriptResource(instance, prefab, &scriptInstance);
@@ -42,6 +40,12 @@ namespace BansheeEngine
 		return scriptInstance->getManagedInstance();
 	}
 
+	bool ScriptPrefab::internal_IsScene(ScriptPrefab* thisPtr)
+	{
+		HPrefab prefab = thisPtr->getHandle();
+		return prefab->isScene();
+	}
+
 	MonoObject* ScriptPrefab::createInstance()
 	{
 		return metaData.scriptClass->createInstance();

+ 18 - 1
Source/SBansheeEngine/Source/BsScriptScene.cpp

@@ -24,6 +24,7 @@ namespace BansheeEngine
 
 	String ScriptScene::ActiveSceneUUID;
 	WString ScriptScene::ActiveSceneName;
+	bool ScriptScene::IsGenericPrefab;
 
 	ScriptScene::ScriptScene(MonoObject* instance)
 		:ScriptObject(instance)
@@ -57,7 +58,12 @@ namespace BansheeEngine
 		if (prefab.isLoaded(false))
 		{
 			HSceneObject root = prefab->instantiate();
-			gSceneManager()._setRootNode(root);
+
+			// If scene replace current root node, otherwise just append to the current root node
+			if (prefab->isScene())
+				gSceneManager()._setRootNode(root);
+			else
+				gSceneManager().clearScene();
 
 			ScriptPrefab* scriptPrefab;
 			ScriptResourceManager::instance().getScriptResource(prefab, &scriptPrefab, true);
@@ -80,6 +86,10 @@ namespace BansheeEngine
 		MonoMethod* nameMethod = metaData.scriptClass->getMethod("GetSceneName");
 		if (nameMethod != nullptr)
 			ActiveSceneName = MonoUtil::monoToWString((MonoString*)nameMethod->invoke(nullptr, nullptr));
+
+		MonoMethod* genericPrefabMethod = metaData.scriptClass->getMethod("GetIsGenericPrefab");
+		if (genericPrefabMethod != nullptr)
+			IsGenericPrefab = *(bool*)MonoUtil::unbox(genericPrefabMethod->invoke(nullptr, nullptr));
 	}
 
 	void ScriptScene::onRefreshDomainLoaded()
@@ -101,6 +111,13 @@ namespace BansheeEngine
 
 			nameMethod->invoke(nullptr, params);
 		}
+
+		MonoMethod* genericPrefabMethod = metaData.scriptClass->getMethod("SetIsGenericPrefab", 1);
+		if (genericPrefabMethod != nullptr)
+		{
+			void* params[1] = { &IsGenericPrefab };
+			genericPrefabMethod->invoke(nullptr, params);
+		}
 	}
 
 	MonoObject* ScriptScene::internal_GetRoot()