├── Library ├── BuildPlayer.prefs ├── TagManager.asset ├── TimeManager.asset ├── AudioManager.asset ├── EditorSettings.asset ├── InputManager.asset ├── NetworkManager.asset ├── TextSceneBuildSettings.txt ├── DynamicsManager.asset ├── ProjectSettings.asset ├── QualitySettings.asset └── InspectorExpandedItems.asset ├── Assets ├── Prefabs.meta ├── Scenes.meta ├── Scripts.meta ├── Materials.meta ├── Scenes │ ├── spheres.txt.meta │ ├── scenedemo.txt.meta │ ├── sceneinscene.txt.meta │ ├── otherscenedemo.txt.meta │ ├── sceneinscene.txt │ ├── spheres.txt │ ├── otherscenedemo.txt │ └── scenedemo.txt ├── Scripts │ ├── Editor.meta │ ├── General.meta │ ├── TextScene.meta │ ├── Editor │ │ ├── TextScene.meta │ │ ├── EditorHelper.cs.meta │ │ ├── MaterialPreserver.cs.meta │ │ ├── PlayerPrefsTools.cs.meta │ │ ├── TextScene │ │ │ ├── TextScene.cs.meta │ │ │ ├── TextSceneMenu.cs.meta │ │ │ ├── TextSceneWindow.cs.meta │ │ │ ├── TextSceneHierarchy.cs.meta │ │ │ ├── TextSceneInspector.cs.meta │ │ │ ├── TextSceneMonitor.cs.meta │ │ │ ├── TextSceneSerializer.cs.meta │ │ │ ├── TextSceneDeserializer.cs.meta │ │ │ ├── TextSceneInspector.cs │ │ │ ├── TextSceneMenu.cs │ │ │ ├── TextSceneWindow.cs │ │ │ ├── TextScene.cs │ │ │ ├── TextSceneMonitor.cs │ │ │ ├── TextSceneSerializer.cs │ │ │ ├── TextSceneHierarchy.cs │ │ │ └── TextSceneDeserializer.cs │ │ ├── PlayerPrefsTools.cs │ │ ├── EditorHelper.cs │ │ └── MaterialPreserver.cs │ ├── General │ │ ├── Helper.cs.meta │ │ ├── MyColor.cs.meta │ │ ├── MyColor.cs │ │ └── Helper.cs │ └── TextScene │ │ ├── TextSceneLinkTest.cs.meta │ │ ├── TextSceneObject.cs.meta │ │ ├── TextSceneMonitorRelauncher.cs.meta │ │ ├── TextSceneObject.cs │ │ ├── TextSceneLinkTest.cs │ │ └── TextSceneMonitorRelauncher.cs ├── Prefabs │ ├── Capsules.prefab.meta │ └── Capsules.prefab └── Materials │ ├── CustomMaterial.mat.meta │ └── CustomMaterial.mat ├── .gitignore ├── LICENSE.txt └── README.txt /Library/BuildPlayer.prefs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Assets/Prefabs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: f9aceb4c7b17849b0b1ea6a9ee704464 3 | -------------------------------------------------------------------------------- /Assets/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: a85f79dc4975844e38bc1b03e5837a31 3 | -------------------------------------------------------------------------------- /Assets/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 06cd262a3aab2934e86944baea9b58c1 3 | -------------------------------------------------------------------------------- /Assets/Materials.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: f175a448cb5f5455eb2c0ea6593c86f8 3 | -------------------------------------------------------------------------------- /Assets/Scenes/spheres.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 66152891b922a419fbdab4bf019657d1 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: afd829c1ab38a4946b1fffa8b71fed30 3 | -------------------------------------------------------------------------------- /Assets/Scripts/General.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 7d2f5950c41a5754c8b4eb7f8145fb46 3 | -------------------------------------------------------------------------------- /Assets/Scripts/TextScene.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: c78afe397e8b040d9baa371f39350c4d 3 | -------------------------------------------------------------------------------- /Assets/Prefabs/Capsules.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: f0529f741bc2540eea3ec100e446c639 3 | -------------------------------------------------------------------------------- /Assets/Scenes/scenedemo.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 10bff0427622240a28d6a168b67a6a81 3 | -------------------------------------------------------------------------------- /Assets/Scenes/sceneinscene.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 8a1bf9ef9a41f4f42b7d260235032519 3 | -------------------------------------------------------------------------------- /Assets/Materials/CustomMaterial.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: e9b851d350fc14be09ce3ba99babfe07 3 | -------------------------------------------------------------------------------- /Assets/Scenes/otherscenedemo.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 7adf941b4440fba40afcdc066c2e50ce 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 3e07b0048c56b43faaf6056fb6b8db22 3 | -------------------------------------------------------------------------------- /Assets/Scripts/General/Helper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 63b50f93355a73344ad0be84b399071d 3 | -------------------------------------------------------------------------------- /Assets/Scripts/General/MyColor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: ae2ab01f0b9e6ce409b9a290498f0780 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/EditorHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 02200f17fae8c4694ae4a37813bc4f14 3 | -------------------------------------------------------------------------------- /Library/TagManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/TagManager.asset -------------------------------------------------------------------------------- /Library/TimeManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/TimeManager.asset -------------------------------------------------------------------------------- /Assets/Scripts/Editor/MaterialPreserver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 6f4d5df8c6294084aa522fe7249211ea 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/PlayerPrefsTools.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: d8d13231945febb4a972f449203951fe 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextScene.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 169a73f779578455ebbfa89344781a2e 3 | -------------------------------------------------------------------------------- /Assets/Scripts/TextScene/TextSceneLinkTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 1d92b3e6c83a2b5429f7d7e6b7d0a98d 3 | -------------------------------------------------------------------------------- /Assets/Scripts/TextScene/TextSceneObject.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: dc824b060b5be4dff8dd18f2173bef36 3 | -------------------------------------------------------------------------------- /Library/AudioManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/AudioManager.asset -------------------------------------------------------------------------------- /Library/EditorSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/EditorSettings.asset -------------------------------------------------------------------------------- /Library/InputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/InputManager.asset -------------------------------------------------------------------------------- /Library/NetworkManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/NetworkManager.asset -------------------------------------------------------------------------------- /Library/TextSceneBuildSettings.txt: -------------------------------------------------------------------------------- 1 | scene Assets/Scenes/scenedemo.txt 2 | scene Assets/Scenes/otherscenedemo.txt 3 | -------------------------------------------------------------------------------- /Assets/Prefabs/Capsules.prefab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Assets/Prefabs/Capsules.prefab -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneMenu.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 7b150f4ca1761492c9c99014f1eb7fee 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 3f8256cb1bcff4dac99b9a9a464cb1ee 3 | -------------------------------------------------------------------------------- /Library/DynamicsManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/DynamicsManager.asset -------------------------------------------------------------------------------- /Library/ProjectSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/ProjectSettings.asset -------------------------------------------------------------------------------- /Library/QualitySettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/QualitySettings.asset -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneHierarchy.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: a66cdc3d14ce0e14f904d737ab12c97b 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 62a67117b823d44dfafbc8fffe6c3be8 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneMonitor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 9057b6c053eda4099a7725025081c63e 3 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneSerializer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: fb8e4ac47f8154837ba188ea2c243689 3 | -------------------------------------------------------------------------------- /Assets/Scripts/TextScene/TextSceneMonitorRelauncher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: b32c77ef5241944bdb27cfefa659c883 3 | -------------------------------------------------------------------------------- /Assets/Materials/CustomMaterial.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Assets/Materials/CustomMaterial.mat -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneDeserializer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 1 2 | guid: 6907f6bbc4ac5401ab36d66f467ba935 3 | -------------------------------------------------------------------------------- /Library/InspectorExpandedItems.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terravision/UnityTextScene/HEAD/Library/InspectorExpandedItems.asset -------------------------------------------------------------------------------- /Assets/Scripts/General/MyColor.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEngine; 7 | using System.Collections; 8 | 9 | public class MyColor : MonoBehaviour { 10 | 11 | public Color color = Color.red; 12 | 13 | // Use this for initialization 14 | void Start () { 15 | renderer.material.color = color; 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Assets/Scripts/TextScene/TextSceneObject.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | /// TODO: It should not be necessary to hold a reference to the actual asset, path and/or GUID 6 | /// should be sufficient. This way it shouldn't be included in a final build as a 7 | /// dependency. 8 | 9 | using UnityEngine; 10 | 11 | public class TextSceneObject : MonoBehaviour 12 | { 13 | public TextAsset textScene; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/PlayerPrefsTools.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEngine; 7 | using UnityEditor; 8 | 9 | public class PlayerPrefsTools 10 | { 11 | [MenuItem("Tools/PlayerPrefsTools/Delete All")] 12 | public static void DeleteAll() 13 | { 14 | if (EditorUtility.DisplayDialog("Warning", "This will clear the playerprefs! Are you sure?", "Yes", "No")) 15 | PlayerPrefs.DeleteAll(); 16 | } 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Standard 2 | .DS_Store 3 | 4 | # Project specific 5 | obj 6 | Temp 7 | Build 8 | TempScenes 9 | *.csproj 10 | *.sln 11 | *.suo 12 | *.pidb 13 | *.userprefs 14 | Library/AssetServerCacheV3 15 | Library/AssetVersioning.db 16 | Library/BuildSettings.asset 17 | Library/EditorBuildSettings.asset 18 | Library/FailedAssetImports.txt 19 | Library/MonoManager.asset 20 | Library/ProjectUsesCompressTexturesOnImport 21 | Library/ScriptAssemblies 22 | Library/ScriptMapper 23 | Library/assetDatabase3 24 | Library/cache 25 | Library/expandedItems 26 | Library/guidmapper 27 | Library/metadata 28 | Library/unity default resources 29 | Library/unity editor resources 30 | -------------------------------------------------------------------------------- /Assets/Scripts/TextScene/TextSceneLinkTest.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEngine; 7 | 8 | class TextSceneLinkTest : MonoBehaviour 9 | { 10 | public Material[] materialList; 11 | public GameObject goLink; 12 | public Transform transformLink; 13 | public GameObject prefabLink; 14 | public BoxCollider colliderLink; 15 | 16 | public string nextScene; 17 | 18 | protected void OnGUI() 19 | { 20 | if (GUI.Button(new Rect(20.0f, 20.0f, 100.0f, 20.0f), "Change scene")) 21 | { 22 | if (nextScene != null && nextScene.Length > 0) 23 | Application.LoadLevel(nextScene); 24 | else 25 | Application.LoadLevel(Application.loadedLevelName); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 TerraVision AS 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneInspector.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | [CustomEditor(typeof(TextSceneObject))] 10 | public class TextSceneInspector : Editor 11 | { 12 | public override void OnInspectorGUI() 13 | { 14 | TextSceneObject tso = target as TextSceneObject; 15 | 16 | EditorGUILayout.BeginVertical(); 17 | 18 | GUILayout.Label("External scene"); 19 | 20 | EditorGUILayout.BeginHorizontal (); 21 | GUILayout.Label("Open scene: "); 22 | 23 | if (GUILayout.Button(tso.textScene.name)) 24 | { 25 | if (EditorUtility.DisplayDialog("Open scenefile?", "Do you want to close the current scene and open the scene pointed to by this TextSceneObject?", "Yes", "No")) 26 | { 27 | TextSceneDeserializer.LoadSafe(EditorHelper.GetProjectFolder() + AssetDatabase.GetAssetPath(tso.textScene)); 28 | GUIUtility.ExitGUI(); 29 | } 30 | } 31 | 32 | EditorGUILayout.EndHorizontal (); 33 | EditorGUILayout.EndVertical(); 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/EditorHelper.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEngine; 7 | using UnityEditor; 8 | using System.Reflection; 9 | using System; 10 | 11 | 12 | public class EditorHelper 13 | { 14 | public static void ClearLog() 15 | { 16 | Assembly assembly = Assembly.GetAssembly(typeof(SceneView)); 17 | 18 | Type type = assembly.GetType("UnityEditor.LogEntries"); 19 | MethodInfo method = type.GetMethod("Clear"); 20 | method.Invoke(new object(), null); 21 | } 22 | 23 | public static string GetProjectFolder() 24 | { 25 | string dataPath = Application.dataPath; 26 | 27 | return dataPath.Substring(0, dataPath.LastIndexOf('/')+1); 28 | } 29 | 30 | public static System.Object[] ParamList(params System.Object[] paramList) 31 | { 32 | return paramList; 33 | } 34 | 35 | private static MethodInfo textContentMethod; 36 | 37 | public static GUIContent TextContent(string text) 38 | { 39 | if (textContentMethod == null) 40 | textContentMethod = typeof(EditorGUIUtility).GetMethod("TextContent", BindingFlags.Static | BindingFlags.NonPublic); 41 | 42 | return textContentMethod.Invoke(null, EditorHelper.ParamList(text)) as GUIContent; 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /Assets/Scripts/TextScene/TextSceneMonitorRelauncher.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEngine; 7 | using System.Reflection; 8 | 9 | /// 10 | /// The sole purpose of this component is to re-launch the monitor we use to detect file changes 11 | /// etc in the editor. 12 | /// 13 | public class TextSceneMonitorRelauncher : MonoBehaviour 14 | { 15 | void OnDisable() 16 | { 17 | string projectPath = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf('/')); 18 | 19 | string assemblyPath = projectPath + "/Library/ScriptAssemblies/Assembly - CSharp - Editor.dll"; 20 | 21 | Debug.Log("Loading assembly to launch TextSceneMonitor: " + assemblyPath); 22 | 23 | Assembly editorAssembly = Assembly.LoadFile(assemblyPath); 24 | 25 | System.Type tsm = editorAssembly.GetType("TextSceneMonitor"); 26 | 27 | if (tsm != null) 28 | { 29 | 30 | 31 | MethodInfo mi = tsm.GetMethod("MonitorUpdate", BindingFlags.Static | BindingFlags.Public); 32 | 33 | if (mi != null) 34 | { 35 | mi.Invoke(null, null); 36 | } 37 | else 38 | Debug.LogError("Unable to find method: MonitorUpdate"); 39 | } 40 | else 41 | Debug.LogError("Unable to find type: TextSceneMonitor"); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneMenu.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | /// 10 | /// All TextScene menu items. 11 | /// 12 | public class TextSceneMenu 13 | { 14 | [MenuItem("TextScene/Hierarchy")] 15 | public static void Hierarchy() 16 | { 17 | TextSceneHierarchy.CreateHierarchy(); 18 | } 19 | 20 | [MenuItem("TextScene/Load")] 21 | public static void Load() 22 | { 23 | TextSceneDeserializer.Load(); 24 | } 25 | 26 | [MenuItem("TextScene/Save as")] 27 | public static void SaveAs() 28 | { 29 | TextSceneSerializer.SaveAs(); 30 | } 31 | 32 | [MenuItem("TextScene/Save")] 33 | public static void SaveCurrent() 34 | { 35 | TextSceneSerializer.SaveCurrent(); 36 | } 37 | 38 | [MenuItem ("TextScene/Build/Add current to build")] 39 | public static void AddCurrentToBuild() 40 | { 41 | TextScene.AddCurrentSceneToBuild(); 42 | } 43 | 44 | [MenuItem ("TextScene/Build/Open Window")] 45 | public static void OpenWindow() 46 | { 47 | TextSceneWindow.Create(); 48 | } 49 | 50 | [MenuItem ("Assets/TextScene Open")] 51 | public static void LoadContext() 52 | { 53 | TextSceneDeserializer.LoadContext(); 54 | } 55 | 56 | [MenuItem ("Assets/TextScene add to build")] 57 | public static void AddContext() 58 | { 59 | TextScene.AddSelectedSceneToBuild(); 60 | } 61 | 62 | [MenuItem ("TextScene/Warnings/Toggle Default Hierarchy")] 63 | public static void ToggleDefaultHierarchyWarning() 64 | { 65 | int currentValue = PlayerPrefs.GetInt("ShowDefaultHierarchyWarning", 1); 66 | 67 | if (currentValue == 0) 68 | { 69 | PlayerPrefs.SetInt("ShowDefaultHierarchyWarning", 1); 70 | EditorUtility.DisplayDialog("Enabled warning", "Default hierarchy warning has been enabled", "OK"); 71 | } 72 | else 73 | { 74 | if (EditorUtility.DisplayDialog("Disable warning", "Are you sure you want to disable the default hierarchy warning? It is recommended to leave this warning on.", "Disable it", "Keep it on")) 75 | PlayerPrefs.SetInt("ShowDefaultHierarchyWarning", 0); 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/MaterialPreserver.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEngine; 7 | using UnityEditor; 8 | using System.IO; 9 | 10 | public class MaterialPreserver : AssetPostprocessor 11 | { 12 | public Material OnAssignMaterialModel(Material material, Renderer renderer) 13 | { 14 | string subfolderName = assetPath.Substring(0, assetPath.LastIndexOf("/")) + "/Materials"; 15 | string materialFileName = "/" + material.name + ".mat"; 16 | 17 | if (material.name == null || material.name.Length == 0) 18 | { 19 | subfolderName = "Assets/Materials"; 20 | materialFileName = "/Unnamed.mat"; 21 | } 22 | 23 | string assetMaterialDirectory = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("/")) + "/" + subfolderName; 24 | 25 | //Debug.Log(assetMaterialDirectory); 26 | 27 | if (!Directory.Exists(assetMaterialDirectory)) 28 | { 29 | Debug.Log("Creating directory for material (" + material.name + "): " + assetMaterialDirectory); 30 | Directory.CreateDirectory(assetMaterialDirectory); 31 | } 32 | 33 | 34 | string materialAssetPath = subfolderName + materialFileName; 35 | 36 | //Use the existing material if there is one. If you want to regenerate the material, 37 | //delete the file itself. 38 | Material existing = AssetDatabase.LoadAssetAtPath(materialAssetPath, typeof(Material)) as Material; 39 | 40 | if (existing != null) 41 | { 42 | Debug.Log("Material (" + material.name + ") already exists, using that one"); 43 | return existing; 44 | } 45 | 46 | 47 | 48 | 49 | Debug.Log("Material (" + material.name + ") does not exist, creating new one at " + materialAssetPath); 50 | 51 | 52 | // Create a new material asset using the specular shader 53 | // but otherwise the default values from the model 54 | material.shader = Shader.Find("Specular"); 55 | AssetDatabase.CreateAsset(material, materialAssetPath); 56 | return material; 57 | } 58 | } -------------------------------------------------------------------------------- /Assets/Scenes/sceneinscene.txt: -------------------------------------------------------------------------------- 1 | prefab Capsules_instance 2 | assetpath Assets/Prefabs/Capsules.prefab, f0529f741bc2540eea3ec100e446c639 3 | -7.25459 -8.03027 -0.12661 4 | 0.46448 0.00000 0.00000 0.88558 5 | 1.76387 1.76387 1.76387 6 | 7 | gameobject ScaledSphere 8 | tag Untagged layer 0 9 | 8.81933 0.00000 6.95787 10 | 0.00000 0.00000 0.00000 1.00000 11 | 1.73578 5.00000 1.73578 12 | children 0 13 | components 3 14 | UnityEngine.MeshFilter 1 15 | property sharedMesh builtinmesh UnityEngine.Mesh = Sphere 16 | UnityEngine.SphereCollider 3 17 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 18 | property radius primitive System.Single = 0.5000001 19 | property isTrigger primitive System.Boolean = False 20 | UnityEngine.MeshRenderer 5 21 | property castShadows primitive System.Boolean = True 22 | property receiveShadows primitive System.Boolean = True 23 | property sharedMaterials array UnityEngine.Material[] = 1 24 | asset UnityEngine.Material = Assets/Materials/CustomMaterial.mat, CustomMaterial, e9b851d350fc14be09ce3ba99babfe07 25 | property lightmapIndex primitive System.Int32 = -1 26 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 27 | 28 | gameobject Sphere 29 | tag Untagged layer 0 30 | 0.00000 0.00000 4.27119 31 | 0.00000 0.00000 0.00000 1.00000 32 | 2.96577 2.96577 2.96577 33 | children 0 34 | components 3 35 | UnityEngine.MeshFilter 1 36 | property sharedMesh builtinmesh UnityEngine.Mesh = Sphere 37 | UnityEngine.SphereCollider 3 38 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 39 | property radius primitive System.Single = 0.5000001 40 | property isTrigger primitive System.Boolean = False 41 | UnityEngine.MeshRenderer 5 42 | property castShadows primitive System.Boolean = True 43 | property receiveShadows primitive System.Boolean = True 44 | property sharedMaterials array UnityEngine.Material[] = 1 45 | asset UnityEngine.Material = Assets/Materials/CustomMaterial.mat, CustomMaterial, e9b851d350fc14be09ce3ba99babfe07 46 | property lightmapIndex primitive System.Int32 = -1 47 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 48 | 49 | textscene spheres 50 | assetpath Assets/Scenes/spheres.txt, 66152891b922a419fbdab4bf019657d1 51 | 0.00000 0.00000 0.00000 52 | 0.00000 0.00000 0.00000 1.00000 53 | 1.00000 1.00000 1.00000 54 | 55 | -------------------------------------------------------------------------------- /Assets/Scenes/spheres.txt: -------------------------------------------------------------------------------- 1 | gameobject Sphere 2 | tag Untagged layer 0 3 | 0.00000 0.00000 0.00000 4 | 0.00000 0.00000 0.00000 1.00000 5 | 2.96577 2.96577 2.96577 6 | children 0 7 | components 3 8 | UnityEngine.MeshFilter 1 9 | property sharedMesh builtinmesh UnityEngine.Mesh = Sphere 10 | UnityEngine.SphereCollider 3 11 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 12 | property radius primitive System.Single = 0.5000001 13 | property isTrigger primitive System.Boolean = False 14 | UnityEngine.MeshRenderer 5 15 | property castShadows primitive System.Boolean = True 16 | property receiveShadows primitive System.Boolean = True 17 | property sharedMaterials array UnityEngine.Material[] = 1 18 | builtinmaterial UnityEngine.Material = Default-Diffuse 19 | property lightmapIndex primitive System.Int32 = -1 20 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 21 | 22 | gameobject Sphere 23 | tag Untagged layer 0 24 | -4.66703 -7.40282 6.95787 25 | 0.00000 0.00000 0.00000 1.00000 26 | 1.73578 1.73578 1.73578 27 | children 0 28 | components 3 29 | UnityEngine.MeshFilter 1 30 | property sharedMesh builtinmesh UnityEngine.Mesh = Sphere 31 | UnityEngine.SphereCollider 3 32 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 33 | property radius primitive System.Single = 0.5000001 34 | property isTrigger primitive System.Boolean = False 35 | UnityEngine.MeshRenderer 5 36 | property castShadows primitive System.Boolean = True 37 | property receiveShadows primitive System.Boolean = True 38 | property sharedMaterials array UnityEngine.Material[] = 1 39 | builtinmaterial UnityEngine.Material = Default-Diffuse 40 | property lightmapIndex primitive System.Int32 = -1 41 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 42 | 43 | gameobject UniqueName 44 | tag Untagged layer 0 45 | 4.67459 0.00000 6.95787 46 | 0.00000 0.00000 0.00000 1.00000 47 | 1.73578 1.73578 1.73578 48 | children 0 49 | components 3 50 | UnityEngine.MeshFilter 1 51 | property sharedMesh builtinmesh UnityEngine.Mesh = Sphere 52 | UnityEngine.SphereCollider 3 53 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 54 | property radius primitive System.Single = 0.5000001 55 | property isTrigger primitive System.Boolean = False 56 | UnityEngine.MeshRenderer 5 57 | property castShadows primitive System.Boolean = True 58 | property receiveShadows primitive System.Boolean = True 59 | property sharedMaterials array UnityEngine.Material[] = 1 60 | builtinmaterial UnityEngine.Material = Default-Diffuse 61 | property lightmapIndex primitive System.Int32 = -1 62 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 63 | 64 | -------------------------------------------------------------------------------- /Assets/Scenes/otherscenedemo.txt: -------------------------------------------------------------------------------- 1 | gameobject Cube 2 | tag Untagged layer 0 3 | 0.00000 -2.48280 0.00000 4 | 0.00000 0.00000 0.00000 1.00000 5 | 1.00000 1.00000 1.00000 6 | children 0 7 | components 3 8 | UnityEngine.MeshFilter 1 9 | property sharedMesh builtinmesh UnityEngine.Mesh = Cube 10 | UnityEngine.BoxCollider 4 11 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 12 | property size primitive UnityEngine.Vector3 = (1.0, 1.0, 1.0) 13 | property extents primitive UnityEngine.Vector3 = (0.5, 0.5, 0.5) 14 | property isTrigger primitive System.Boolean = False 15 | UnityEngine.MeshRenderer 5 16 | property castShadows primitive System.Boolean = True 17 | property receiveShadows primitive System.Boolean = True 18 | property sharedMaterials array UnityEngine.Material[] = 1 19 | builtinmaterial UnityEngine.Material = Default-Diffuse 20 | property lightmapIndex primitive System.Int32 = -1 21 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 22 | 23 | gameobject Cube 24 | tag Untagged layer 0 25 | 3.00000 -2.48280 2.00000 26 | 0.00000 0.00000 0.00000 1.00000 27 | 1.00000 1.00000 1.00000 28 | children 1 29 | gameobject Cube 30 | tag Untagged layer 0 31 | -10.00000 6.00000 0.00000 32 | 0.00000 0.00000 0.00000 1.00000 33 | 1.00000 1.00000 1.00000 34 | children 0 35 | components 3 36 | UnityEngine.MeshFilter 1 37 | property sharedMesh builtinmesh UnityEngine.Mesh = Cube 38 | UnityEngine.BoxCollider 4 39 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 40 | property size primitive UnityEngine.Vector3 = (1.0, 1.0, 1.0) 41 | property extents primitive UnityEngine.Vector3 = (0.5, 0.5, 0.5) 42 | property isTrigger primitive System.Boolean = False 43 | UnityEngine.MeshRenderer 5 44 | property castShadows primitive System.Boolean = True 45 | property receiveShadows primitive System.Boolean = True 46 | property sharedMaterials array UnityEngine.Material[] = 1 47 | asset UnityEngine.Material = Assets/Materials/CustomMaterial.mat, CustomMaterial, e9b851d350fc14be09ce3ba99babfe07 48 | property lightmapIndex primitive System.Int32 = -1 49 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 50 | components 3 51 | UnityEngine.MeshFilter 1 52 | property sharedMesh builtinmesh UnityEngine.Mesh = Cube 53 | UnityEngine.BoxCollider 4 54 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 55 | property size primitive UnityEngine.Vector3 = (1.0, 1.0, 1.0) 56 | property extents primitive UnityEngine.Vector3 = (0.5, 0.5, 0.5) 57 | property isTrigger primitive System.Boolean = False 58 | UnityEngine.MeshRenderer 5 59 | property castShadows primitive System.Boolean = True 60 | property receiveShadows primitive System.Boolean = True 61 | property sharedMaterials array UnityEngine.Material[] = 1 62 | builtinmaterial UnityEngine.Material = Default-Diffuse 63 | property lightmapIndex primitive System.Int32 = -1 64 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 65 | 66 | gameobject Directional light 67 | tag Untagged layer 0 68 | 740.81120 66.71914 1054.85100 69 | 0.37889 0.00000 0.00000 0.92544 70 | 1.00000 1.00000 1.00000 71 | children 0 72 | components 1 73 | UnityEngine.Light 12 74 | property type primitive UnityEngine.LightType = Directional 75 | property color primitive UnityEngine.Color = (1.000, 1.000, 1.000, 1.000) 76 | property intensity primitive System.Single = 0.5 77 | property shadows primitive UnityEngine.LightShadows = None 78 | property shadowStrength primitive System.Single = 0.8 79 | property shadowConstantBias primitive System.Single = 0.07 80 | property shadowObjectSizeBias primitive System.Single = 0.01 81 | property attenuate primitive System.Boolean = True 82 | property range primitive System.Single = 10 83 | property spotAngle primitive System.Single = 30 84 | property renderMode primitive UnityEngine.LightRenderMode = Auto 85 | property cullingMask primitive System.Int32 = -1 86 | 87 | gameobject Links 88 | tag Untagged layer 0 89 | 8.54124 9.88922 -6.45947 90 | 0.00000 0.00000 0.00000 1.00000 91 | 1.00000 1.00000 1.00000 92 | children 0 93 | components 1 94 | TextSceneLinkTest 4 95 | field materialList array UnityEngine.Material[] = 0 96 | field prefabLink asset UnityEngine.GameObject = Assets/Prefabs/Capsules.prefab, Capsules, f0529f741bc2540eea3ec100e446c639 97 | field colliderLink scenelink UnityEngine.BoxCollider = /Cube/Cube 98 | field nextScene primitive System.String = scenedemo 99 | 100 | gameobject Main Camera 101 | tag MainCamera layer 0 102 | 0.00000 1.00000 -10.00000 103 | 0.00000 0.00000 0.00000 1.00000 104 | 1.00000 1.00000 1.00000 105 | children 0 106 | components 3 107 | UnityEngine.Camera 15 108 | property fov primitive System.Single = 60 109 | property near primitive System.Single = 0.3 110 | property far primitive System.Single = 1000 111 | property fieldOfView primitive System.Single = 60 112 | property nearClipPlane primitive System.Single = 0.3 113 | property farClipPlane primitive System.Single = 1000 114 | property orthographicSize primitive System.Single = 100 115 | property orthographic primitive System.Boolean = False 116 | property isOrthoGraphic primitive System.Boolean = False 117 | property depth primitive System.Single = -1 118 | property cullingMask primitive System.Int32 = -1 119 | property backgroundColor primitive UnityEngine.Color = (0.192, 0.302, 0.475, 0.020) 120 | property rect primitive UnityEngine.Rect = (0.00, 0.00, 1.00, 1.00) 121 | property clearFlags primitive UnityEngine.CameraClearFlags = Skybox 122 | property depthTextureMode primitive UnityEngine.DepthTextureMode = None 123 | UnityEngine.GUILayer 0 124 | UnityEngine.AudioListener 1 125 | property velocityUpdateMode primitive UnityEngine.AudioVelocityUpdateMode = Dynamic 126 | 127 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneWindow.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | /// TODO: *Make this look less crap. 6 | /// *Rename to something better, probably including "BuildSettings". 7 | /// *Check for built-in build settings window and alert user if it is up 8 | /// (similar to TextSceneHierarchy whining if the built-in is up). 9 | 10 | using UnityEngine; 11 | using UnityEditor; 12 | 13 | using System.IO; 14 | using System.Collections.Generic; 15 | using System; 16 | using System.Text; 17 | 18 | 19 | /// 20 | /// Editor window currently only holding the build settings and build options. 21 | /// 22 | public class TextSceneWindow : EditorWindow 23 | { 24 | public static void Create () 25 | { 26 | // Get existing open window or if none, make a new one: 27 | TextSceneWindow window = (TextSceneWindow)EditorWindow.GetWindow (typeof (TextSceneWindow)); 28 | 29 | window.title = "TextScene Build Settings"; 30 | window.Show (); 31 | window.LoadSettings(); 32 | } 33 | 34 | double nextBuildSettingsCheck = 0.0; 35 | DateTime loadedBuildSettingsTime; 36 | 37 | List scenes = new List(); 38 | 39 | Vector2 scroll = Vector2.zero; 40 | 41 | void LoadSettings() 42 | { 43 | scenes = TextScene.ReadScenes(); 44 | 45 | loadedBuildSettingsTime = TextScene.BuildSettingsDate(); 46 | } 47 | 48 | void OnGUI () 49 | { 50 | GUILayout.BeginVertical(); 51 | 52 | scroll = GUILayout.BeginScrollView(scroll); 53 | 54 | GUILayout.Label("Scenes to build"); 55 | 56 | foreach(string scene in scenes) 57 | { 58 | EditorGUILayout.BeginHorizontal(); 59 | 60 | if (GUILayout.Button(scene)) 61 | { 62 | TextSceneDeserializer.LoadSafe(EditorHelper.GetProjectFolder() + scene); 63 | GUIUtility.ExitGUI(); 64 | return; 65 | } 66 | 67 | 68 | if (GUILayout.Button("Remove", GUILayout.MaxWidth(60))) 69 | { 70 | if (EditorUtility.DisplayDialog("Remove scene", "Are you sure you want to remove this scene from the build settings?", "Yes", "No")) 71 | { 72 | TextScene.RemoveSceneFromBuild(scene); 73 | LoadSettings(); 74 | } 75 | } 76 | 77 | if (GUILayout.Button("+", GUILayout.MaxWidth(20))) 78 | { 79 | TextScene.MoveScenePosition(scene, 1); 80 | LoadSettings(); 81 | } 82 | 83 | if (GUILayout.Button("-", GUILayout.MaxWidth(20))) 84 | { 85 | TextScene.MoveScenePosition(scene, -1); 86 | LoadSettings(); 87 | } 88 | 89 | GUILayout.EndHorizontal(); 90 | } 91 | 92 | if (scenes.Count > 0) 93 | { 94 | EditorGUILayout.Separator(); 95 | EditorGUILayout.BeginVertical(GUILayout.MaxWidth(150)); 96 | 97 | if (GUILayout.Button("Build Temp")) 98 | { 99 | TextScene.BuildTempScenes(); 100 | GUIUtility.ExitGUI(); 101 | return; 102 | } 103 | 104 | if (GUILayout.Button("Build Streamed Web")) 105 | { 106 | TextScene.Build(BuildTarget.WebPlayerStreamed); 107 | GUIUtility.ExitGUI(); 108 | return; 109 | } 110 | 111 | if (GUILayout.Button("Build Web")) 112 | { 113 | TextScene.Build(BuildTarget.WebPlayer); 114 | GUIUtility.ExitGUI(); 115 | return; 116 | } 117 | 118 | if (GUILayout.Button("Build OSX")) 119 | { 120 | TextScene.Build(BuildTarget.StandaloneOSXUniversal); 121 | GUIUtility.ExitGUI(); 122 | return; 123 | } 124 | 125 | if (GUILayout.Button("Build Windows")) 126 | { 127 | TextScene.Build(BuildTarget.StandaloneWindows); 128 | GUIUtility.ExitGUI(); 129 | return; 130 | } 131 | 132 | EditorGUILayout.EndVertical(); 133 | } 134 | else 135 | { 136 | GUILayout.Label("Add scenes via the TextScene menu item to enable build"); 137 | } 138 | 139 | EditorGUILayout.Separator(); 140 | 141 | if (GUILayout.Button("Add current", GUILayout.MaxWidth(100))) 142 | { 143 | TextScene.AddCurrentSceneToBuild(); 144 | LoadSettings(); 145 | } 146 | 147 | EditorGUILayout.Separator(); 148 | 149 | if (GUILayout.Button("Validate Settings", GUILayout.MaxWidth(100))) 150 | { 151 | List invalidScenes; 152 | 153 | if (TextScene.ValidateBuildSettings(out invalidScenes)) 154 | EditorUtility.DisplayDialog("Valid settings", "The build settings seem valid enough", "OK"); 155 | else 156 | { 157 | StringBuilder sb = new StringBuilder(); 158 | 159 | sb.Append("There were errors in validation: \n"); 160 | 161 | foreach(string scene in invalidScenes) 162 | { 163 | sb.Append(" "); 164 | sb.Append(scene); 165 | sb.Append('\n'); 166 | } 167 | 168 | sb.Append("Try running 'Build Temp' to fix them up, or inspect the console for further hints."); 169 | 170 | EditorUtility.DisplayDialog("Validation failed", sb.ToString(), "OK"); 171 | } 172 | } 173 | 174 | GUILayout.EndScrollView(); 175 | 176 | GUILayout.EndVertical(); 177 | } 178 | 179 | void OnHierarchyChange() 180 | { 181 | //TODO/FIXME: Detect this and set TextSceneMonitor to dirty so we can warn users if they try to 182 | // load a different scene. 183 | 184 | //UPDATE: Tried to go around this by using 'SaveIfUserWantsTo' where possible. 185 | } 186 | 187 | void OnInspectorUpdate() 188 | { 189 | //TODO FIXME HACK: I can't currently see how to make the instance survive a play/stop session, 190 | // so we just make sure it is constantly requested (and renewed if necessary). 191 | //UPDATE: Moved this to a possibly even worse hack, but at least it works without needing to have 192 | // this editor window active. 193 | //TextSceneMonitor.MonitorUpdate(); 194 | 195 | 196 | if (EditorApplication.timeSinceStartup > nextBuildSettingsCheck) 197 | { 198 | nextBuildSettingsCheck = EditorApplication.timeSinceStartup + 2.0f; 199 | 200 | DateTime buildSettingsTime = TextScene.BuildSettingsDate(); 201 | 202 | if (buildSettingsTime > loadedBuildSettingsTime) 203 | { 204 | LoadSettings(); 205 | loadedBuildSettingsTime = buildSettingsTime; 206 | } 207 | } 208 | } 209 | } -------------------------------------------------------------------------------- /Assets/Scenes/scenedemo.txt: -------------------------------------------------------------------------------- 1 | prefab Capsule 2 | assetpath Assets/Prefabs/Capsules.prefab, f0529f741bc2540eea3ec100e446c639 3 | 3.32136 0.00000 0.00000 4 | 0.00000 0.00000 -0.35426 0.93515 5 | 1.00000 1.00000 1.00000 6 | 7 | gameobject Cube 8 | tag Untagged layer 0 9 | 0.00000 -2.48280 0.00000 10 | 0.00000 0.00000 0.00000 1.00000 11 | 1.00000 1.00000 1.00000 12 | children 0 13 | components 3 14 | UnityEngine.MeshFilter 1 15 | property sharedMesh builtinmesh UnityEngine.Mesh = Cube 16 | UnityEngine.BoxCollider 4 17 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 18 | property size primitive UnityEngine.Vector3 = (1.0, 1.0, 1.0) 19 | property extents primitive UnityEngine.Vector3 = (0.5, 0.5, 0.5) 20 | property isTrigger primitive System.Boolean = False 21 | UnityEngine.MeshRenderer 5 22 | property castShadows primitive System.Boolean = True 23 | property receiveShadows primitive System.Boolean = True 24 | property sharedMaterials array UnityEngine.Material[] = 1 25 | builtinmaterial UnityEngine.Material = Default-Diffuse 26 | property lightmapIndex primitive System.Int32 = -1 27 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 28 | 29 | gameobject Cube 30 | tag Untagged layer 0 31 | 3.00000 -2.48280 2.00000 32 | 0.00000 0.00000 0.00000 1.00000 33 | 1.00000 1.00000 1.00000 34 | children 1 35 | gameobject Cube 36 | tag Untagged layer 0 37 | -10.00000 6.00000 0.00000 38 | 0.00000 0.00000 0.00000 1.00000 39 | 1.00000 1.00000 1.00000 40 | children 0 41 | components 3 42 | UnityEngine.MeshFilter 1 43 | property sharedMesh builtinmesh UnityEngine.Mesh = Cube 44 | UnityEngine.BoxCollider 4 45 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 46 | property size primitive UnityEngine.Vector3 = (1.0, 1.0, 1.0) 47 | property extents primitive UnityEngine.Vector3 = (0.5, 0.5, 0.5) 48 | property isTrigger primitive System.Boolean = False 49 | UnityEngine.MeshRenderer 5 50 | property castShadows primitive System.Boolean = True 51 | property receiveShadows primitive System.Boolean = True 52 | property sharedMaterials array UnityEngine.Material[] = 1 53 | asset UnityEngine.Material = Assets/Materials/CustomMaterial.mat, CustomMaterial, e9b851d350fc14be09ce3ba99babfe07 54 | property lightmapIndex primitive System.Int32 = -1 55 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 56 | components 3 57 | UnityEngine.MeshFilter 1 58 | property sharedMesh builtinmesh UnityEngine.Mesh = Cube 59 | UnityEngine.BoxCollider 4 60 | property center primitive UnityEngine.Vector3 = (0.0, 0.0, 0.0) 61 | property size primitive UnityEngine.Vector3 = (1.0, 1.0, 1.0) 62 | property extents primitive UnityEngine.Vector3 = (0.5, 0.5, 0.5) 63 | property isTrigger primitive System.Boolean = False 64 | UnityEngine.MeshRenderer 5 65 | property castShadows primitive System.Boolean = True 66 | property receiveShadows primitive System.Boolean = True 67 | property sharedMaterials array UnityEngine.Material[] = 1 68 | builtinmaterial UnityEngine.Material = Default-Diffuse 69 | property lightmapIndex primitive System.Int32 = -1 70 | property lightmapTilingOffset primitive UnityEngine.Vector4 = (1.0, 1.0, 0.0, 0.0) 71 | 72 | gameobject Directional light 73 | tag Untagged layer 0 74 | 740.81120 66.71914 1054.85100 75 | 0.37889 0.00000 0.00000 0.92544 76 | 1.00000 1.00000 1.00000 77 | children 0 78 | components 1 79 | UnityEngine.Light 12 80 | property type primitive UnityEngine.LightType = Directional 81 | property color primitive UnityEngine.Color = (1.000, 1.000, 1.000, 1.000) 82 | property intensity primitive System.Single = 0.5 83 | property shadows primitive UnityEngine.LightShadows = None 84 | property shadowStrength primitive System.Single = 0.8 85 | property shadowConstantBias primitive System.Single = 0.07 86 | property shadowObjectSizeBias primitive System.Single = 0.01 87 | property attenuate primitive System.Boolean = True 88 | property range primitive System.Single = 10 89 | property spotAngle primitive System.Single = 30 90 | property renderMode primitive UnityEngine.LightRenderMode = Auto 91 | property cullingMask primitive System.Int32 = -1 92 | 93 | gameobject Links 94 | tag Untagged layer 0 95 | 8.54124 9.88922 -6.45947 96 | 0.00000 0.00000 0.00000 1.00000 97 | 1.00000 1.00000 1.00000 98 | children 0 99 | components 1 100 | TextSceneLinkTest 6 101 | field materialList array UnityEngine.Material[] = 0 102 | field goLink scenelink UnityEngine.GameObject = /spheres/UniqueName 103 | field transformLink scenelink UnityEngine.Transform = /Capsule/Capsule 104 | field prefabLink asset UnityEngine.GameObject = Assets/Prefabs/Capsules.prefab, Capsules, f0529f741bc2540eea3ec100e446c639 105 | field colliderLink scenelink UnityEngine.BoxCollider = /Cube/Cube 106 | field nextScene primitive System.String = otherscenedemo 107 | 108 | gameobject Main Camera 109 | tag MainCamera layer 0 110 | 0.00000 1.00000 -10.00000 111 | 0.00000 0.00000 0.00000 1.00000 112 | 1.00000 1.00000 1.00000 113 | children 0 114 | components 3 115 | UnityEngine.Camera 15 116 | property fov primitive System.Single = 60 117 | property near primitive System.Single = 0.3 118 | property far primitive System.Single = 1000 119 | property fieldOfView primitive System.Single = 60 120 | property nearClipPlane primitive System.Single = 0.3 121 | property farClipPlane primitive System.Single = 1000 122 | property orthographicSize primitive System.Single = 100 123 | property orthographic primitive System.Boolean = False 124 | property isOrthoGraphic primitive System.Boolean = False 125 | property depth primitive System.Single = -1 126 | property cullingMask primitive System.Int32 = -1 127 | property backgroundColor primitive UnityEngine.Color = (0.192, 0.302, 0.475, 0.020) 128 | property rect primitive UnityEngine.Rect = (0.00, 0.00, 1.00, 1.00) 129 | property clearFlags primitive UnityEngine.CameraClearFlags = Skybox 130 | property depthTextureMode primitive UnityEngine.DepthTextureMode = None 131 | UnityEngine.GUILayer 0 132 | UnityEngine.AudioListener 1 133 | property velocityUpdateMode primitive UnityEngine.AudioVelocityUpdateMode = Dynamic 134 | 135 | textscene sceneinscene 136 | assetpath Assets/Scenes/sceneinscene.txt, 8a1bf9ef9a41f4f42b7d260235032519 137 | -7.54487 -4.46076 8.38350 138 | -0.60867 -0.12085 0.35437 0.69953 139 | 1.00000 1.00000 1.00000 140 | 141 | textscene spheres 142 | assetpath Assets/Scenes/spheres.txt, 66152891b922a419fbdab4bf019657d1 143 | 0.00000 0.00000 0.00000 144 | 0.00000 0.00000 0.00000 1.00000 145 | 1.00000 1.00000 1.00000 146 | 147 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | DISCLAIMER: Use this code at your own risk! It is WIP! 2 | 3 | ----What is this? 4 | 5 | The TextScene system is intended to be a complete replacement of the Unity built-in binary scene format. The reason to hack together and use such a format, is because it is very difficult, if not impossible, to merge scene conflicts in a team environment using a binary scene format. If you don't care about diffable scenes, you should avoid this (one-man teams will most likely not benefit at all from this, unless you desperately want scene history). 6 | 7 | If you decide to venture into the source code, be warned that it is poorly commented and work in process. 8 | 9 | ----How does this stuff work? 10 | 11 | When using TextScenes, you are highly advised against mixing them with binary Unity scenes. This is because the TextScene system does a few things in parallell with or re-implements (badly, mind you) certain functionality from the binary Unity scene handling. What immediately comes to mind is the Hierarchy View and the build process. 12 | 13 | While you will primarily work with text-based scene files, the binary unity scenes are still used as temporary files. The TextScene files are only used on save and load, and then saved to temporary unity scenes outside your assets folder. These files are also used when a player is built (this way scene dependencies should work as they do using the regular binary scene format). 14 | 15 | The TextScene format requires certain changes to the normal workflow in Unity. First of all, as far as I know, there are no useful hooks for the GUI save and load exposed to editor scripts, so you should no longer use Load or Save from the main menu (in fact, Save *should* work, as the TextSceneMonitor should pick that one up, but it is recommended to keep with the TextScene functionality). To open a TextScene, please use the menu TextScene or right-click a TextAsset and choose 'TextScene Open'. It is also possible to open TextScenes via the TextScene build settings window, or via the in-scene TextScenes' inspector (they have a component called TextSceneObject). 16 | 17 | When you are working on a TextScene, it is recommended to keep the TextSceneHierarchy view open at all times. This hierarchy has added functionality in order to prevent user mistakes, such as disabling children of TextSceneObjects. It also regularly pokes the TextSceneMonitor so it is able to keep monitoring our files. 18 | 19 | To get started, open the project. Go to the TextScene menu and choose "Hierarchy". You should now be told to close down the standard hierarchy, and it is advised to do so. You will also very likely be prompted with a file dialog. Click cancel and click the next dialog which tells you to save using the TextScene menu. When you intend to create new scenes, it is recommended to not click away the file dialog, but save to a TextScene to keep the TextSceneMonitor happy. However, now expand the "Scenes" folder in the Project view. Right-click 'scenedemo' and choose TextScene Open. You should now be looking at a scene containing a few primitives. 20 | 21 | As you can see, there are both blue and yellow colored objects in the hierarchy. The yellow-colored object is a TextSceneObject and it is in reality a link to another TextScene. If you open 'demoscene' in a text editor, you will see that the 'sceneinscene' object is only a transform and a pointer to a different TextScene. Select the yellow object in the hierarchy, and click "sceneinscene" on the button that should appear in the Inspector. Choose "yes", and you should now have navigated to the TextScene the link points at. Drag the MyColor-script from "Scripts/General" onto the ScaledSphere, set the color to something other than red or white, and save the scene. Go back to the main scene by right-clicking 'demoscene' in the Project view, and hit 'play'. If all goes well, it should now start up and the scaled sphere should turn into whatever color you set it to. 22 | 23 | If you expand the yellow objects, you will notice that the children are uneditable. This is to prevent any changes in the scene that will not persist when you save and load (there is no "apply" functionality for TextSceneObjects, unfortunately). Now, select the Capsule prefab. Notice that you can drag the root prefab and root TextSceneObject around in the hierarchy, but you cannot move children out, for the same save/load reason. 24 | 25 | At last, go to TextScene again and go to Build->Open Window. This is where you should create builds from. Hopefully, demoscene should be in the list. Feel free to build a player to verify that it actually works. 26 | 27 | 28 | ----What are the main elements? 29 | 30 | *TextSceneSerializer: 31 | 32 | This class writes the current scene to a human-readable, diffable textfile. If it fails, it will give the user feedback (hopefully) and not write anything to the actual file until the errors have been resolved. 33 | 34 | *TextSceneDeserializer: 35 | 36 | Reads a TextScene file and populates a scene (either a clean one, or loads a scene into an existing scene). 37 | 38 | *TextSceneMonitor: 39 | 40 | Is polled continously by the TextSceneHierarchy. This class monitors user actions and external file changes. When a scene is loaded, the TextSceneDeserializer notifies this class and tells it to create binary temporary representations of the scene (note: The implementation for this is extremely hacky, but works for now. It is related to what seems like a bug with the Unity API SaveScene/LoadScene, see code for details). These temporary scenes are standard Unity scenes, and they are also what we pass on to the player builder. These scenes are regularly monitored for changes, and this is currently how we try to detect if the user saved using built-in functionality. The binary temp scenes are stored outside the Assets folder, like this: /Project/TempScenes/somefilename.unity, in parallell with their TextScene counterpart, which is in /Project/Assets/somefilename.txt 41 | 42 | The TextSceneMonitor also monitors editor state changes. The only one we actually react upon, is whenever the user presses "play". The TextScene system will then validate any BuildSettings in order to make the in-editor play session as painless as possible. Keep in mind that we are using TextScenes which need to be converted to binary Unity scenes before they are usable, so part of the validation process is making sure these actually exist and are up to date. 43 | 44 | *TextSceneHierarchy 45 | 46 | A poor replacement of the built-in Scene Hierarchy. It has the most basic functionality, such as moving stuff around the tree, new color codes for TextSceneObjects, the ability to add TextScenes into scenes (can be thought of as a way of doing prefabs-in-prefabs - drag'n'drop a TextAsset containing a TextScene into the view pane, the result should be a yellow-colored object containing the other scene), instantiating objects by drag'n'drop from the Project View and script assignment. 47 | 48 | *TextSceneWindow (stupid name, this is in fact the build settings) 49 | 50 | Exposes TextScene build functionality to the user. Most of the functionality itself is implemented in TextScene (which basically is a utility class). 51 | 52 | *TextScene 53 | 54 | Utility class that most importantly has functionality for reading/writing TextSceneBuildSettings and creating builds. 55 | 56 | 57 | ----What are the main limitations and areas that desperately need work? 58 | 59 | In random order: 60 | 61 | *Does NOT work as-is with Unity3. Manual changes need to be applied manually for the time being. Inspect error console for hints. 62 | 63 | *Pro is required (at least for the build stuff, not sure about how/if the asset reference GUID resolves will break on Unity 'regular' or using the Asset Server - the current project has External VCS enabled). 64 | 65 | *Currently, it is not possible to save and load prefabs *with instance changes*. Such changes will simply be ignored when saving, so they are not preserved. 66 | 67 | *Certain Unity objects cannot be entirely reconstructed from scripts, for example the ellipsoid particle emitter. Such objects should be made into prefabs and dropped into the scene for TextScene compatibility. 68 | 69 | *The TextSceneHierarchy view has very limited functionality compared to the built in hiererchy. Multiple selection, keyboard shortcuts, object renaming is just a fraction of the missing features. 70 | 71 | *In-scene links (typically drag'n'drop links between components) require the linked object to have a unique path in the scene. The reason for this, is that the only reference stored in the TextScene is the full scene path of the object. Using InstanceID is a possible way to get around this, but is currently not implemented. 72 | 73 | *The textscene format currently lists the count of children, components, script members, array sizes etc in the format itself. This should be removed, as it makes hand-editing the scene files more difficult than it should be. 74 | 75 | *I have seen at a couple of occasions that Unity gives strange error messages regarding temp file overwrites. I have yet to figure out exactly why or when this happens, but I have not (yet) seen any loss of data - it happens to the temporary binary files, not the TextScenes themselves. 76 | 77 | *TextSceneObjects (scene-in-scene links) are not monitored for external changes. 78 | 79 | *Prefabs seem to not revert correctly. The reverts arent 'recursive', so if you instantiate a prefab, move it's instance child around, select the root instance again and click 'revert' - nothing happens. You have to select the child itself and 'revert' that one. Given that instance changes to prefabs isn't currently supported, changes like this will automatically revert themselves when you save and re-load a scene. You have been warned ;) 80 | 81 | *The context-menu item 'Disconnect' in the TextSceneHierarchy is badly implemented - it re-instantiates a new object and deletes the prefab instance, resulting in loss of in-scene links if any objects were pointing at the prefab instance. 82 | 83 | *Code cleanups. Also look for TODO/FIXME tags in the code. 84 | 85 | *Probably more. 86 | 87 | Good luck, and thanks for trying it out! 88 | 89 | ----About UnityTextScene 90 | UnityTextScene was an internal project at serious games studio TerraVision (http://www.terravision.no), which was decided to be shared with the Unity community, for further development. 91 | 92 | -------------------------------------------------------------------------------- /Assets/Scripts/General/Helper.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEngine; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | 11 | public class Helper 12 | { 13 | public static float SolveQuadricGetMax(float a, float b, float c) 14 | { 15 | float x1 = 0.0f; 16 | float x2 = 0.0f; 17 | 18 | SolveQuadric(a, b, c, ref x1, ref x2); 19 | 20 | return Mathf.Max(x1, x2); 21 | } 22 | 23 | public static int SolveQuadric(float a, float b, float c, ref float x1, ref float x2) 24 | { 25 | float discriminant = (b * b) - 4 * a * c; 26 | 27 | float denom = 2 * a; 28 | 29 | if (Mathf.Abs(discriminant) < 0.0001f) 30 | { 31 | x1 = -b / denom; 32 | x2 = x1; 33 | return 1; 34 | } 35 | else if (discriminant > 0.0f) 36 | { 37 | float sq = Mathf.Sqrt(discriminant); 38 | 39 | x1 = (-b + sq) / denom; 40 | x2 = (-b - sq) / denom; 41 | 42 | return 2; 43 | } 44 | else 45 | return 0; 46 | 47 | } 48 | 49 | public static T GetObjectOfType() where T : Component 50 | { 51 | UnityEngine.Object[] comps = GameObject.FindObjectsOfType(typeof(T)); 52 | 53 | if (comps == null || comps.Length == 0) 54 | return default(T); 55 | else 56 | return comps[0] as T; 57 | } 58 | 59 | public static T GetObjectOfTypeByName(string name) where T : Component 60 | { 61 | UnityEngine.Object[] comps = GameObject.FindObjectsOfType(typeof(T)); 62 | 63 | if (comps == null || comps.Length == 0) 64 | return default(T); 65 | else 66 | { 67 | for (int i = 0; i < comps.Length; i++) 68 | { 69 | if (comps[i].name == name) 70 | return comps[i] as T; 71 | } 72 | } 73 | 74 | return default(T); 75 | } 76 | 77 | public static T[] GetObjectsOfType() where T : Component 78 | { 79 | UnityEngine.Object[] comps = GameObject.FindObjectsOfType(typeof(T)); 80 | 81 | if (comps == null || comps.Length == 0) 82 | return new T[0]; 83 | else 84 | { 85 | T[] result = new T[comps.Length]; 86 | 87 | for (int i = 0; i < comps.Length; i++) 88 | result[i] = comps[i] as T; 89 | 90 | return result; 91 | } 92 | } 93 | 94 | public static GameObject[] GetGameObjectsByName(string name) 95 | { 96 | List transforms = new List(GameObject.FindObjectsOfType(typeof(Transform))); 97 | 98 | return transforms.FindAll(t => t.name == name).ConvertAll(tr => (tr as Transform).gameObject).ToArray(); 99 | } 100 | 101 | public static T[] GetGameObjectsByTypeAndName(string name) where T : Component 102 | { 103 | UnityEngine.Object[] comps = GameObject.FindObjectsOfType(typeof(T)); 104 | 105 | if (comps == null || comps.Length == 0) 106 | return new T[0]; 107 | else 108 | { 109 | List filtered = new List(); 110 | 111 | for (int i = 0; i < comps.Length; i++) 112 | { 113 | if (comps[i].name == name) 114 | filtered.Add(comps[i] as T); 115 | } 116 | 117 | return filtered.ToArray(); 118 | } 119 | } 120 | 121 | public static T AddComponent(GameObject go) where T : Component 122 | { 123 | if (go == null) 124 | return null; 125 | 126 | T comp = go.GetComponent(typeof(T)) as T; 127 | 128 | if (comp == null) 129 | comp = go.AddComponent(typeof(T)) as T; 130 | 131 | return comp; 132 | } 133 | 134 | public static T[] AddComponent(GameObject[] gameObjects) where T : Component 135 | { 136 | if (gameObjects == null) 137 | return new T[0]; 138 | 139 | List added = new List(); 140 | 141 | for (int i = 0; i < gameObjects.Length; i++) 142 | { 143 | T comp = gameObjects[i].AddComponent(typeof(T)) as T; 144 | 145 | added.Add(comp); 146 | } 147 | 148 | return added.ToArray(); 149 | } 150 | 151 | public static void AddComponent(GameObject[] gameObjects, Type component) 152 | { 153 | if (gameObjects == null) 154 | return; 155 | 156 | for (int i = 0; i < gameObjects.Length; i++) 157 | gameObjects[i].AddComponent(component); 158 | } 159 | 160 | public static void AddComponent(T[] gameObjects) where T : Component where R : Component 161 | { 162 | if (gameObjects == null) 163 | return; 164 | 165 | for (int i = 0; i < gameObjects.Length; i++) 166 | gameObjects[i].gameObject.AddComponent(typeof(R)); 167 | } 168 | 169 | public static T[] AddComponentToType(GameObject go) where T : Component where R : Component 170 | { 171 | if (go == null) 172 | return new T[0]; 173 | 174 | List added = new List(); 175 | 176 | R[] comp = Helper.GetComponentsInChildren(go); 177 | 178 | for(int i = 0; i < comp.Length; i++) 179 | { 180 | added.Add(comp[i].gameObject.AddComponent(typeof(T)) as T); 181 | } 182 | 183 | return added.ToArray(); 184 | } 185 | 186 | /// 187 | /// Returns all components in a hierarchy until it hits the specified type. 188 | /// 189 | public static Component[] GetComponentsInChildrenAboveType(GameObject go) where T : Component 190 | { 191 | if (go.GetComponent() != null) 192 | return new Component[0]; 193 | 194 | 195 | List components = new List(); 196 | 197 | foreach (Transform child in go.transform) 198 | { 199 | components.AddRange(GetComponentsInChildrenAboveType(child.gameObject)); 200 | } 201 | 202 | components.AddRange(go.GetComponents()); 203 | 204 | return components.ToArray(); 205 | } 206 | 207 | /// 208 | /// Returns components recursively "layers" deep. If you have a hierarchy 209 | /// of three parented colliders, and send in "2" for layers, you will get 210 | /// the two first components, but not the third. 211 | /// 212 | public static T[] GetComponentsInChildren(GameObject go, int layers) where T : Component 213 | { 214 | if (layers == 0) 215 | return new T[0]; 216 | 217 | List components = new List(); 218 | 219 | T[] layerComps = go.GetComponents(); 220 | 221 | if (layerComps.Length > 0) 222 | { 223 | components.AddRange(layerComps); 224 | layers--; 225 | } 226 | 227 | foreach (Transform child in go.transform) 228 | { 229 | components.AddRange(GetComponentsInChildren(child.gameObject, layers)); 230 | } 231 | 232 | return components.ToArray(); 233 | } 234 | 235 | public static T GetComponentInChildren(GameObject go) where T : Component 236 | { 237 | return go.GetComponentInChildren(typeof(T)) as T; 238 | } 239 | 240 | public static T[] GetComponentsInChildren(GameObject go) where T : Component 241 | { 242 | if (go == null) 243 | return new T[0]; 244 | 245 | Component[] comps = go.GetComponentsInChildren(typeof(T)); 246 | 247 | if (comps == null || comps.Length == 0) 248 | return new T[0]; 249 | 250 | 251 | T[] result = new T[comps.Length]; 252 | 253 | for (int i = 0; i < comps.Length; i++) 254 | result[i] = comps[i] as T; 255 | 256 | 257 | //WTF FIXME: What's wrong with this one? 258 | //result = System.Array.ConvertAll(comps, comp => (T)comp); 259 | 260 | return result; 261 | } 262 | 263 | public static void SetLayerRecursively(GameObject go, int layer) 264 | { 265 | go.layer = layer; 266 | 267 | foreach(Transform child in go.transform) 268 | { 269 | SetLayerRecursively(child.gameObject, layer); 270 | } 271 | } 272 | 273 | 274 | public static void DestroyComponentsInChildren(GameObject go, bool immediate) 275 | { 276 | Component[] comps = go.GetComponentsInChildren(typeof(T)); 277 | 278 | for (int i = 0; i < comps.Length; i++) 279 | { 280 | if (immediate) 281 | GameObject.DestroyImmediate(comps[i]); 282 | else 283 | GameObject.Destroy(comps[i]); 284 | } 285 | } 286 | 287 | public static void Destroy(GameObject[] gos) 288 | { 289 | if (gos == null) 290 | return; 291 | 292 | for (int i = 0; i < gos.Length; i++) 293 | { 294 | GameObject.Destroy(gos[i]); 295 | } 296 | } 297 | 298 | public static Bounds CalculateBounds(GameObject go) 299 | { 300 | Bounds b = new Bounds(); 301 | 302 | Renderer[] renderers = Helper.GetComponentsInChildren(go); 303 | 304 | for (int i = 0; i < renderers.Length; i++) 305 | { 306 | if (i == 0) 307 | b = renderers[i].bounds; 308 | else 309 | b.Encapsulate(renderers[i].bounds); 310 | } 311 | 312 | return b; 313 | } 314 | 315 | public static T AddComponentToNamedObject(string name) where T : Component 316 | { 317 | GameObject go = GameObject.Find(name); 318 | 319 | if (go == null) 320 | return default(T); 321 | 322 | return go.AddComponent(typeof(T)) as T; 323 | } 324 | 325 | public static T[] AddComponentToNamedObjects(string name) where T : Component 326 | { 327 | UnityEngine.Object[] objects = GameObject.FindObjectsOfType(typeof(Transform)); 328 | 329 | List filtered = new List(); 330 | 331 | for (int i = 0; i < objects.Length; i++) 332 | { 333 | if (objects[i].name.Equals(name)) 334 | filtered.Add((objects[i] as Transform).gameObject.AddComponent(typeof(T)) as T); 335 | } 336 | 337 | return filtered.ToArray(); 338 | } 339 | 340 | public static T[] AddComponentToPartiallyNamedObjects(string partialName) where T : Component 341 | { 342 | UnityEngine.Object[] objects = GameObject.FindObjectsOfType(typeof(Transform)); 343 | 344 | List filtered = new List(); 345 | 346 | for (int i = 0; i < objects.Length; i++) 347 | { 348 | if (objects[i].name.Contains(partialName)) 349 | filtered.Add((objects[i] as Transform).gameObject.AddComponent(typeof(T)) as T); 350 | } 351 | 352 | return filtered.ToArray(); 353 | } 354 | 355 | public static T CreateObject(Vector3 position, Quaternion rotation, Vector3 scale) where T : Component 356 | { 357 | T instance = CreateObject(); 358 | instance.transform.position = position; 359 | instance.transform.rotation = rotation; 360 | instance.transform.localScale = scale; 361 | 362 | return instance; 363 | } 364 | 365 | public static T CreateObject() where T : Component 366 | { 367 | GameObject go = new GameObject(); 368 | go.name = typeof(T).ToString(); 369 | return go.AddComponent(typeof(T)) as T; 370 | } 371 | 372 | public static GameObject[] FindObjectsWithPartialName(string partialName) 373 | { 374 | UnityEngine.Object[] objects = GameObject.FindObjectsOfType(typeof(Transform)); 375 | 376 | List filtered = new List(); 377 | 378 | for (int i = 0; i < objects.Length; i++) 379 | { 380 | if (objects[i].name.Contains(partialName)) 381 | filtered.Add((objects[i] as Transform).gameObject); 382 | } 383 | 384 | return filtered.ToArray(); 385 | } 386 | 387 | public static void DisableScriptsInScene() where T : MonoBehaviour 388 | { 389 | T[] comps = Helper.GetObjectsOfType(); 390 | 391 | for(int i = 0; i < comps.Length; i++) 392 | comps[i].enabled = false; 393 | } 394 | 395 | public static GameObject FindOrFail(string name) 396 | { 397 | GameObject go = GameObject.Find(name); 398 | 399 | if (go == null) 400 | { 401 | Debug.LogError("Unable to find object: " + name); 402 | Debug.Break(); 403 | } 404 | 405 | return go; 406 | } 407 | 408 | public static GameObject FindInChildren(GameObject go, string name) 409 | { 410 | foreach (Transform child in go.transform) 411 | { 412 | if (child.name == name) 413 | return child.gameObject; 414 | 415 | GameObject ret = FindInChildren(child.gameObject, name); 416 | 417 | if (ret != null) 418 | return ret; 419 | } 420 | 421 | return null; 422 | } 423 | 424 | public static string GetFullName(GameObject go) 425 | { 426 | 427 | 428 | Transform current = go.transform; 429 | 430 | List parentList = new List(); 431 | 432 | while(current != null) 433 | { 434 | parentList.Add(current); 435 | 436 | current = current.parent; 437 | } 438 | 439 | parentList.Reverse(); 440 | 441 | StringBuilder sb = new StringBuilder(); 442 | 443 | foreach(Transform t in parentList) 444 | { 445 | sb.Append('/'); 446 | sb.Append(t.name); 447 | } 448 | 449 | return sb.ToString(); 450 | } 451 | 452 | public static GameObject[] FindGameObjectsFromFullName(string fullName) 453 | { 454 | Transform[] transforms = Helper.GetObjectsOfType(); 455 | 456 | 457 | List matched = new List(); 458 | 459 | foreach(Transform t in transforms) 460 | { 461 | if (t.parent == null) 462 | { 463 | //Debug.Log("Finding full: " + fullName); 464 | FindMatching(t, matched, fullName); 465 | } 466 | } 467 | 468 | return matched.ToArray(); 469 | } 470 | 471 | private static void FindMatching(Transform t, List matched, string remaining) 472 | { 473 | string currentLevelName = remaining.Substring(1); 474 | 475 | int separatorIndex = currentLevelName.IndexOf('/'); 476 | 477 | if (separatorIndex > 0) 478 | remaining = currentLevelName.Substring(separatorIndex); 479 | else 480 | remaining = ""; 481 | 482 | if (separatorIndex > 0) 483 | currentLevelName = currentLevelName.Substring(0, separatorIndex); 484 | 485 | //Debug.Log("matching object: '" + currentLevelName + "' (" + remaining + ")"); 486 | 487 | if (t.name == currentLevelName) 488 | { 489 | if (remaining.Length == 0) 490 | { 491 | //Debug.Log("MATCH"); 492 | matched.Add(t.gameObject); 493 | } 494 | else 495 | { 496 | foreach(Transform child in t) 497 | { 498 | FindMatching(child, matched, remaining); 499 | } 500 | } 501 | } 502 | } 503 | } 504 | 505 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextScene.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | /// TODO: *Create function to validate all scenes in the build settings and opt the user to create 6 | /// temp scenes if they do not exist (typically fresh checkout where all text scenes are 7 | /// present, but no binary temp version - this will make the player fail in editor mode if 8 | /// the game needs to change level). 9 | 10 | using UnityEditor; 11 | using System.IO; 12 | using System.Collections.Generic; 13 | using UnityEngine; 14 | using System; 15 | 16 | 17 | /// 18 | /// Utility class currently handling custom build settings and a few 19 | /// functions for getting matching text/binary files based on path. 20 | /// 21 | public static class TextScene 22 | { 23 | private const string buildSettingsFile = "Library/TextSceneBuildSettings.txt"; 24 | 25 | /// 26 | /// Returns the time the build settings file was last written to. 27 | /// 28 | public static DateTime BuildSettingsDate() 29 | { 30 | string fullPath = EditorHelper.GetProjectFolder() + buildSettingsFile; 31 | 32 | if (!File.Exists(fullPath)) 33 | return new DateTime(1, 1, 1); 34 | 35 | return File.GetLastWriteTime(fullPath); 36 | } 37 | 38 | /// 39 | /// Takes a project-relatived temp-scene path and 40 | /// converts it to the matching TextScene path. 41 | /// 42 | public static string TempToTextSceneFile(string tempScene) 43 | { 44 | string textSceneFile = "Assets" + tempScene.Substring(tempScene.IndexOf('/')); 45 | 46 | textSceneFile = textSceneFile.Replace(".unity", ".txt"); 47 | 48 | return textSceneFile; 49 | } 50 | 51 | /// 52 | /// Takes a project-relative TextScene filename and 53 | /// converts it to the matching binary temp file. 54 | /// 55 | public static string TextSceneToTempBinaryFile(string textScene) 56 | { 57 | string tempSceneFile = "TempScenes" + textScene.Substring(textScene.IndexOf('/')); 58 | 59 | tempSceneFile = tempSceneFile.Replace(".txt", ".unity"); 60 | 61 | return tempSceneFile; 62 | } 63 | 64 | /// 65 | /// Reads in the current list of scenes registered in the custom 66 | /// TextScene buildsettings. 67 | /// 68 | public static List ReadScenes() 69 | { 70 | List sceneList = new List(); 71 | 72 | string fullPath = EditorHelper.GetProjectFolder() + buildSettingsFile; 73 | 74 | if (!File.Exists(fullPath)) 75 | return sceneList; 76 | 77 | StreamReader reader = File.OpenText(fullPath); 78 | 79 | while(!reader.EndOfStream) 80 | { 81 | string line = reader.ReadLine(); 82 | 83 | string[] elements = line.Trim().Split(); 84 | 85 | string key = ""; 86 | string val = ""; 87 | 88 | if (elements.Length >= 2) 89 | { 90 | key = elements[0]; 91 | val = elements[1]; 92 | } 93 | 94 | if (key == "scene") 95 | sceneList.Add(val); 96 | } 97 | 98 | reader.Close(); 99 | 100 | return sceneList; 101 | } 102 | 103 | /// 104 | /// Writes a list of project-relative TextScene files to buildsettings. The 105 | /// corresponding binary unity scenes are stored in the Unity standard 106 | /// build-settings. 107 | /// 108 | private static void WriteBuildSettings(List sceneList) 109 | { 110 | StreamWriter writer = File.CreateText(EditorHelper.GetProjectFolder() + buildSettingsFile); 111 | 112 | foreach(string scene in sceneList) 113 | { 114 | writer.Write("scene " + scene + '\n'); 115 | } 116 | 117 | writer.Close(); 118 | 119 | //Also update the editor build settings 120 | List binaryScenes = new List(); 121 | 122 | foreach(string scene in sceneList) 123 | { 124 | EditorBuildSettingsScene ebss = new EditorBuildSettingsScene(); 125 | 126 | ebss.path = TextScene.TextSceneToTempBinaryFile(scene); 127 | ebss.enabled = true; 128 | 129 | binaryScenes.Add(ebss); 130 | } 131 | 132 | EditorBuildSettings.scenes = binaryScenes.ToArray(); 133 | } 134 | 135 | /// 136 | /// Adds a project-relative TextScene to the TextScene buildsettings 137 | /// and the corresponding binary temp file is written to the standard 138 | /// Unity build settings. 139 | /// 140 | public static bool AddSceneToBuild(string scene) 141 | { 142 | string projectFolder = EditorHelper.GetProjectFolder(); 143 | 144 | string fullScenePath = projectFolder + scene; 145 | 146 | //Make sure the scene file actually exists 147 | if (!File.Exists(fullScenePath)) 148 | { 149 | EditorUtility.DisplayDialog("ERROR", "The text scene '" + scene + "' does not seem to exist!", "OK"); 150 | return false; 151 | } 152 | 153 | List sceneList = ReadScenes(); 154 | 155 | if (sceneList.Contains(scene)) 156 | { 157 | EditorUtility.DisplayDialog("Already added", "This scene is already in the build list", "OK"); 158 | return false; 159 | } 160 | 161 | Debug.Log("Added scene to build: " + scene); 162 | 163 | sceneList.Add(scene); 164 | 165 | WriteBuildSettings(sceneList); 166 | 167 | return true; 168 | } 169 | 170 | /// 171 | /// Removes a project-relative TextScene from the TextScene build-settings. 172 | /// Also updates the standard Unity build settings. 173 | /// 174 | public static void RemoveSceneFromBuild (string scene) 175 | { 176 | List sceneList = ReadScenes(); 177 | 178 | sceneList.Remove(scene); 179 | 180 | WriteBuildSettings(sceneList); 181 | } 182 | 183 | /// 184 | /// Moves a scene up or down in the build settings list. 185 | /// 186 | public static void MoveScenePosition (string scene, int direction) 187 | { 188 | if (direction == 0) 189 | return; 190 | 191 | List sceneList = ReadScenes(); 192 | 193 | int index = sceneList.IndexOf(scene); 194 | 195 | if (index < 0) 196 | return; 197 | 198 | if (direction > 0) 199 | { 200 | if (index+1 >= sceneList.Count) 201 | return; 202 | 203 | string nextEntry = sceneList[index+1]; 204 | 205 | sceneList[index] = nextEntry; 206 | sceneList[index+1] = scene; 207 | } 208 | else if (direction < 0) 209 | { 210 | if (index-1 < 0) 211 | return; 212 | 213 | string prevEntry = sceneList[index-1]; 214 | 215 | sceneList[index] = prevEntry; 216 | sceneList[index-1] = scene; 217 | } 218 | 219 | 220 | WriteBuildSettings(sceneList); 221 | } 222 | 223 | /// 224 | /// Adds the currently open scene to the TextScene buildsettings, and updates 225 | /// the standard Unity buildsettings with the corresponding temp binary scene file. 226 | /// User will be notified if the current scene is not saved or is not a temp binary 227 | /// file. 228 | /// 229 | public static void AddCurrentSceneToBuild() 230 | { 231 | string currentScene = EditorApplication.currentScene; 232 | 233 | if (currentScene.Length == 0) 234 | { 235 | EditorUtility.DisplayDialog("Unsaved", "The currently open scene is not saved. Please save it using the TextScene menu option and try again.", "OK"); 236 | return; 237 | } 238 | 239 | if (currentScene.StartsWith("Assets/")) 240 | { 241 | EditorUtility.DisplayDialog("Invalid scene", "The currently open scene is not a TextScene. Please re-save using the TextScene menu options and try again", "OK"); 242 | return; 243 | } 244 | 245 | //Debug.Log("Text scene file to save in build settings: " + textSceneFile); 246 | 247 | TextScene.AddSceneToBuild(TextScene.TempToTextSceneFile(EditorApplication.currentScene)); 248 | } 249 | 250 | /// 251 | /// Adds the selected TextScene file to buildsettings. Also updates the standard Unity 252 | /// buildsettings. 253 | /// 254 | public static void AddSelectedSceneToBuild() 255 | { 256 | if (Selection.activeObject == null) 257 | { 258 | EditorUtility.DisplayDialog ("Nothing selected", "You need to select a text scene asset to add to build", "OK"); 259 | return; 260 | } 261 | 262 | TextAsset asset = Selection.activeObject as TextAsset; 263 | 264 | string assetPath = ""; 265 | 266 | if (asset != null) 267 | assetPath = AssetDatabase.GetAssetPath(asset); 268 | 269 | if (!assetPath.EndsWith(".txt")) 270 | EditorUtility.DisplayDialog ("Not a text file", "Text scenes can be TextAssets (*.txt)", "OK"); 271 | else 272 | { 273 | TextScene.AddSceneToBuild(assetPath); 274 | } 275 | } 276 | 277 | /// 278 | /// Read and write scenes so unity build settings gets updated. 279 | /// 280 | private static void SyncBuildSettings() 281 | { 282 | WriteBuildSettings(ReadScenes()); 283 | } 284 | 285 | //TODO: Finish up and make public! 286 | public static bool ValidateBuildSettings(out List invalidScenes) 287 | { 288 | SyncBuildSettings(); 289 | 290 | List sceneList = ReadScenes(); 291 | 292 | invalidScenes = new List(); 293 | 294 | foreach (string scene in sceneList) 295 | { 296 | string absoluteTextScene = EditorHelper.GetProjectFolder() + scene; 297 | 298 | //Make sure the scene exists at all in a TextScene format 299 | if (!File.Exists(absoluteTextScene)) 300 | { 301 | Debug.LogWarning("Scene does not exist: " + scene); 302 | 303 | EditorApplication.isPlaying = false; 304 | 305 | if (EditorUtility.DisplayDialog("Invalid scene", "The scene '" + scene + "' is listed in your build settings but it does not exist. Do you want to remove it from build settings?", "Yes", "No")) 306 | { 307 | TextScene.RemoveSceneFromBuild(scene); 308 | } 309 | else 310 | { 311 | invalidScenes.Add(scene); 312 | continue; 313 | } 314 | } 315 | else 316 | { 317 | //While the textscene might be present, we also need the binary temp file. 318 | //Make sure there is one up-to-date, if not, the user should be prompted 319 | //to generate one. 320 | string absoluteBinaryTempScene = EditorHelper.GetProjectFolder() + TextSceneToTempBinaryFile(scene); 321 | 322 | if (!File.Exists(absoluteBinaryTempScene)) 323 | { 324 | Debug.LogWarning("Temp scene does not exist: " + absoluteBinaryTempScene); 325 | 326 | //EditorApplication.isPlaying = false; 327 | //if (EditorUtility.DisplayDialog("Missing temp file", "Missing temp file for '" + scene + "' - do you want to generate it now?", "Yes", "No")) 328 | // TextSceneDeserializer.LoadSafe(absoluteTextScene); 329 | 330 | invalidScenes.Add(scene); 331 | continue; 332 | } 333 | else 334 | { 335 | //Both files exist, but we also need to make sure the temp scene isn't outdated. 336 | DateTime textSceneTime = File.GetLastWriteTime(absoluteTextScene); 337 | DateTime binaryTempSceneTime = File.GetLastWriteTime(absoluteBinaryTempScene); 338 | 339 | if (textSceneTime > binaryTempSceneTime) 340 | { 341 | Debug.LogWarning("Temp scene for '" + scene + "' is outdated: " + binaryTempSceneTime + " is older than " + textSceneTime); 342 | 343 | //EditorApplication.isPlaying = false; 344 | //if (EditorUtility.DisplayDialog("Outdated temp file", "Outdated temp file for '" + scene + "' - do you want to update it now?", "Yes", "No")) 345 | // TextSceneDeserializer.LoadSafe(absoluteTextScene); 346 | 347 | invalidScenes.Add(scene); 348 | continue; 349 | } 350 | } 351 | } 352 | } 353 | 354 | return invalidScenes.Count == 0; 355 | } 356 | 357 | /// 358 | /// Builds a self-launching player based on the passed BuildTarget parameter. Will go through all 359 | /// scenes in the buildsettings list and make sure they have a temporary binary file to build. Will 360 | /// notify user if anything went wrong (such as empty build settings or unsupported build target). 361 | /// 362 | public static void Build(BuildTarget buildTarget) 363 | { 364 | string extension = ""; 365 | 366 | if (buildTarget == BuildTarget.WebPlayer 367 | || buildTarget == BuildTarget.WebPlayerStreamed) 368 | extension = "unity3d"; 369 | else if (buildTarget == BuildTarget.StandaloneWindows) 370 | extension = "exe"; 371 | else if (buildTarget == BuildTarget.StandaloneOSXUniversal) 372 | extension = "app"; 373 | 374 | if (extension.Length == 0) 375 | { 376 | EditorUtility.DisplayDialog("Build target not supported", "Build target not currently supported: " + buildTarget.ToString(), "OK"); 377 | return; 378 | } 379 | 380 | string startPath = Application.dataPath; 381 | 382 | startPath = startPath.Substring(0, startPath.LastIndexOf('/')) + "/Build"; 383 | 384 | if (!Directory.Exists(startPath)) 385 | Directory.CreateDirectory(startPath); 386 | 387 | string path = EditorUtility.SaveFilePanel("Build target", startPath, "build", extension); 388 | 389 | if (path.Length == 0) 390 | return; 391 | 392 | new TextSceneBuilder(path, buildTarget); 393 | } 394 | 395 | public static void BuildTempScenes(List scenes) 396 | { 397 | new TextSceneBuilder(null, BuildTarget.PlayerDataFolderForDevelopment, scenes); 398 | } 399 | 400 | public static void BuildTempScenes() 401 | { 402 | //TODO: Get rid of unused parameters in constructor. 403 | new TextSceneBuilder(null, BuildTarget.PlayerDataFolderForDevelopment); 404 | } 405 | } 406 | 407 | class TextSceneBuilder 408 | { 409 | private List binarySceneList = new List(); 410 | private Queue sceneQueue; 411 | private string sceneToLoad; 412 | private string buildPath; 413 | private BuildTarget buildTarget; 414 | 415 | public TextSceneBuilder(string buildPath, BuildTarget buildTarget) : this(buildPath, buildTarget, null) 416 | { 417 | 418 | } 419 | 420 | public TextSceneBuilder(string buildPath, BuildTarget buildTarget, List sceneList) 421 | { 422 | if (sceneList == null) 423 | sceneList = TextScene.ReadScenes(); 424 | 425 | if (sceneList.Count == 0) 426 | { 427 | EditorUtility.DisplayDialog("No scenes", "No scenes have been added to the build settings. Please add some, and try to build again.", "OK"); 428 | return; 429 | } 430 | 431 | this.sceneQueue = new Queue(sceneList); 432 | this.sceneToLoad = TextSceneMonitor.Instance.GetCurrentScene(); 433 | this.buildPath = buildPath; 434 | this.buildTarget = buildTarget; 435 | 436 | if (EditorApplication.SaveCurrentSceneIfUserWantsTo()) 437 | { 438 | TextSceneMonitor.Instance.SaveIfTempIsNewer(); 439 | } 440 | else 441 | { 442 | Debug.Log("Cancelled build"); 443 | return; 444 | } 445 | 446 | BuildNext(); 447 | } 448 | 449 | private void BuildNext() 450 | { 451 | //TODO: Must be fixed up to work with callback - scene saving does not work very well if done 452 | // immediately after load, which is why we need to make this into a async operation 453 | // of some sort. See unity case 336621. 454 | //Run through all scenes and build them based on the human readable representation. 455 | if (sceneQueue.Count > 0) 456 | { 457 | string textScene = sceneQueue.Dequeue(); 458 | 459 | Debug.Log("Building temp for: " + textScene); 460 | 461 | string result = TextSceneDeserializer.Load(EditorHelper.GetProjectFolder() + textScene, this.BuildNext); 462 | 463 | if (result.Length == 0) 464 | { 465 | EditorUtility.DisplayDialog("Scene does not exist", "Unable to find scene file: " + textScene + " Will be excluded from build", "OK"); 466 | BuildNext(); 467 | } 468 | else 469 | binarySceneList.Add(result); 470 | } 471 | else 472 | FinishBuild(); 473 | } 474 | 475 | private void FinishBuild() 476 | { 477 | Debug.Log("Building " + binarySceneList.Count + " scenes: "); 478 | 479 | foreach(string scene in binarySceneList) 480 | { 481 | Debug.Log(scene); 482 | } 483 | 484 | if (buildPath != null) 485 | { 486 | BuildPipeline.BuildPlayer(binarySceneList.ToArray(), buildPath, buildTarget, BuildOptions.AutoRunPlayer); 487 | } 488 | else 489 | { 490 | Debug.Log("Temp scenes generated, no player will be built."); 491 | } 492 | 493 | if (sceneToLoad.Length > 0) 494 | TextSceneDeserializer.Load(sceneToLoad); 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneMonitor.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | 6 | using UnityEngine; 7 | using UnityEditor; 8 | using System; 9 | using System.IO; 10 | using System.Timers; 11 | using System.Collections.Generic; 12 | using System.Text; 13 | 14 | /// 15 | /// Class that saves and loads a binary temp file for the TextScene system. It delays each 16 | /// operation by a set amount of frames because it seems to make the operation a lot 17 | /// more stable :S 18 | /// 19 | class TextSceneTempCreator 20 | { 21 | public enum Status 22 | { 23 | Working = 0, 24 | Complete, 25 | Failed 26 | } 27 | 28 | enum State 29 | { 30 | SaveTemp = 0, 31 | CreateNew, 32 | LoadTemp 33 | } 34 | 35 | 36 | private const float SAVE_AND_RELOAD_FRAMES = 20.0f; 37 | 38 | //HACK: To get around bug with save/load instantly after instantiating prefabs. 39 | private int saveAndReloadTimer = 0; 40 | private string saveAndReload = ""; 41 | private TextSceneDeserializer.TempSceneSaved saveAndReloadCallback = null; 42 | 43 | State state; 44 | 45 | public TextSceneTempCreator(string scene, TextSceneDeserializer.TempSceneSaved callback) 46 | { 47 | state = State.SaveTemp; 48 | 49 | saveAndReloadTimer = Mathf.RoundToInt(SAVE_AND_RELOAD_FRAMES); 50 | saveAndReload = scene; 51 | saveAndReloadCallback = callback; 52 | } 53 | 54 | public Status Update() 55 | { 56 | if (saveAndReload.Length == 0) 57 | { 58 | Debug.LogError("Invalid saveandreload name! Cancelling load/save process..."); 59 | return Status.Failed; 60 | } 61 | 62 | if (saveAndReloadTimer > 0) 63 | { 64 | EditorUtility.DisplayProgressBar("Creating temp...", "Creating binary temp file for TextScene: " + state.ToString(), 1.0f - saveAndReloadTimer / SAVE_AND_RELOAD_FRAMES); 65 | 66 | saveAndReloadTimer--; 67 | return Status.Working; 68 | } 69 | else 70 | { 71 | if (state == State.SaveTemp) 72 | { 73 | Debug.Log("SaveAndReload: " + saveAndReload); 74 | 75 | ///FIXME: Unity sometimes puts a lock on the scenes we try to save, this is a CRUEL way to 76 | ///get around it. 77 | /// 78 | ///Repro-steps: *Comment out the try/catch 79 | /// *Clean out tempscenes-folder. 80 | /// *Open up a scene (LEVEL1) from build settings, hit play 81 | /// *While playing, do something to make the game 82 | /// change to another level (LEVEL2). 83 | /// *Stop playing, you should now be back in the level where you 84 | /// hit play from. 85 | /// *Try to switch to the level you switched to in-game (LEVEL2). 86 | /// *You should, after the progress bar has completed, be prompted 87 | /// with an error saying Unity could not move file from Temp/Tempfile 88 | /// 89 | try 90 | { 91 | FileStream f = File.OpenWrite(saveAndReload); 92 | f.Close(); 93 | } 94 | catch 95 | { 96 | Debug.LogWarning("HACK: Getting around 'access denied' on temp files!"); 97 | 98 | //HACK: This seems to make Unity release the file so we can try to save it in a new go. 99 | if (!EditorApplication.OpenScene(saveAndReload)) 100 | { 101 | //Uh oh. 102 | Debug.LogError("HACK failed! What to do next?"); 103 | EditorUtility.ClearProgressBar(); 104 | return Status.Failed; 105 | } 106 | 107 | TextSceneDeserializer.Load(EditorHelper.GetProjectFolder() + TextScene.TempToTextSceneFile(EditorApplication.currentScene), saveAndReloadCallback); 108 | return Status.Working; 109 | } 110 | 111 | if (!EditorApplication.SaveScene(saveAndReload)) 112 | { 113 | Debug.LogError("Failed to save temp: " + saveAndReload); 114 | 115 | 116 | if (EditorUtility.DisplayDialog("ERROR", "Failed to save temp (" + saveAndReload + ") - try again?", "Yes", "No")) 117 | saveAndReloadTimer = Mathf.RoundToInt(SAVE_AND_RELOAD_FRAMES); 118 | else 119 | return Status.Failed; 120 | 121 | 122 | EditorUtility.ClearProgressBar(); 123 | return Status.Working; 124 | } 125 | 126 | state = State.CreateNew; 127 | saveAndReloadTimer = Mathf.RoundToInt(SAVE_AND_RELOAD_FRAMES); 128 | return Status.Working; 129 | } 130 | else if (state == State.CreateNew) 131 | { 132 | EditorApplication.NewScene(); 133 | 134 | state = State.LoadTemp; 135 | saveAndReloadTimer = Mathf.RoundToInt(SAVE_AND_RELOAD_FRAMES); 136 | return Status.Working; 137 | } 138 | else if (state == State.LoadTemp) 139 | { 140 | if (!EditorApplication.OpenScene(saveAndReload)) 141 | { 142 | Debug.LogError("Failed to load temp: " + saveAndReload); 143 | 144 | if (EditorUtility.DisplayDialog("ERROR", "Failed to load temp (" + saveAndReload + ") - try again?", "Yes", "No")) 145 | saveAndReloadTimer = Mathf.RoundToInt(SAVE_AND_RELOAD_FRAMES); 146 | else 147 | return Status.Failed; 148 | 149 | EditorUtility.ClearProgressBar(); 150 | return Status.Working; 151 | } 152 | 153 | string writtenFile = EditorHelper.GetProjectFolder() + EditorApplication.currentScene; 154 | 155 | DateTime writtenTime = File.GetLastWriteTime(writtenFile); 156 | 157 | Debug.Log("Wrote temp file at " + writtenTime); 158 | 159 | TextSceneMonitor.Instance.SetCurrentScene(EditorHelper.GetProjectFolder() + TextScene.TempToTextSceneFile(EditorApplication.currentScene)); 160 | 161 | saveAndReload = ""; 162 | 163 | EditorUtility.ClearProgressBar(); 164 | 165 | return Status.Complete; 166 | } 167 | } 168 | 169 | Debug.LogError("Failing...."); 170 | return Status.Failed; 171 | } 172 | 173 | public void InvokeCallback() 174 | { 175 | if (saveAndReloadCallback != null) 176 | saveAndReloadCallback(); 177 | } 178 | } 179 | 180 | /// 181 | /// Class monitoring the state of TextScenes and user action. Tries to identify situations where 182 | /// the user does something he/she didn't want to, such as saving using built-in save functionality, 183 | /// using regular binary unity scenes together with TextScenes etc. Also checks for externally changed 184 | /// files, which is handy when using VCS. 185 | /// 186 | /// FIXME: This class could have been a lot cleaner if there were hooks for the most common user actions 187 | /// and editor events. 188 | /// 189 | [Serializable] 190 | public class TextSceneMonitor 191 | { 192 | TextSceneTempCreator process; 193 | 194 | private static TextSceneMonitor instance; 195 | //private static System.Timers.Timer timer; 196 | 197 | public static TextSceneMonitor Instance 198 | { 199 | get 200 | { 201 | if (instance == null) 202 | { 203 | instance = new TextSceneMonitor(); 204 | /* 205 | timer = new System.Timers.Timer(1000); 206 | 207 | timer.Elapsed += MonitorUpdate; 208 | timer.AutoReset = true; 209 | timer.Enabled = true; 210 | */ 211 | //FIXME: These seem to get lost during play sessions? 212 | EditorApplication.update += MonitorUpdate; 213 | EditorApplication.playmodeStateChanged += MonitorStateChange; 214 | 215 | 216 | } 217 | 218 | return instance; 219 | } 220 | } 221 | 222 | private static void MonitorStateChange() 223 | { 224 | if (EditorApplication.isPlayingOrWillChangePlaymode) 225 | { 226 | List invalidScenes; 227 | 228 | if (!TextScene.ValidateBuildSettings(out invalidScenes)) 229 | { 230 | EditorApplication.isPlaying = false; 231 | 232 | 233 | StringBuilder sb = new StringBuilder(); 234 | 235 | sb.Append("Errors were found while validating Build Settings: \n"); 236 | 237 | foreach(string scene in invalidScenes) 238 | { 239 | sb.Append(" "); 240 | sb.Append(scene); 241 | sb.Append('\n'); 242 | } 243 | 244 | sb.Append("Your levels may not switch correctly in play-mode! Do you want to fix up any outdated/missing temp file issues now?"); 245 | 246 | if (EditorUtility.DisplayDialog("Build settings", sb.ToString(), "Yes", "No")) 247 | TextScene.BuildTempScenes(invalidScenes); 248 | } 249 | } 250 | } 251 | 252 | /* 253 | private static void MonitorStateChange() 254 | { 255 | if (EditorApplication.isPlayingOrWillChangePlaymode) 256 | { 257 | Debug.Log("Editor about to start playmode: " + EditorApplication.timeSinceStartup.ToString("F3")); 258 | 259 | //HACK: We're creating this so it can tell us whenever we leave playmode :S 260 | GameObject go = new GameObject("TextSceneMonitorRelauncher"); 261 | go.hideFlags = HideFlags.NotEditable; 262 | go.AddComponent(); 263 | } 264 | else 265 | { 266 | Debug.Log("Editor not in playmode: " + EditorApplication.timeSinceStartup.ToString("F3")); 267 | 268 | TextSceneMonitorRelauncher[] objects = Helper.GetObjectsOfType(); 269 | 270 | foreach(TextSceneMonitorRelauncher o in objects) 271 | GameObject.DestroyImmediate(o.gameObject); 272 | } 273 | } 274 | */ 275 | /* 276 | public static void MonitorCallbacks(object sender, System.Timers.ElapsedEventArgs e) 277 | { 278 | Debug.Log("Setting editor callbacks"); 279 | 280 | //EditorApplication.update += MonitorUpdate; 281 | EditorApplication.playmodeStateChanged += MonitorStateChange; 282 | } 283 | */ 284 | 285 | /// 286 | /// Wrapper for running an update on the monitor. 287 | /// 288 | public static void MonitorUpdate() 289 | { 290 | Instance.Update(); 291 | } 292 | 293 | private double nextCheckForChangedFile = 0.0f; 294 | 295 | //Absolute path of currently monitored scene, and when it was last loaded. 296 | private string currentScene = ""; 297 | private string currentTempBinaryScene = ""; 298 | private DateTime currentSceneLoaded; 299 | 300 | //Current scene open. If this changes unexpectedly, the user will be notified. 301 | private string alarmingEditorScene; 302 | 303 | 304 | 305 | /// 306 | /// Creates a new TextSceneMonitor and reads in relevant values from PlayerPrefs (such as 307 | /// last monitored scene). 308 | /// 309 | public TextSceneMonitor() 310 | { 311 | process = null; 312 | 313 | currentScene = PlayerPrefs.GetString("TextSceneMonitorCurrentScene", ""); 314 | 315 | alarmingEditorScene = PlayerPrefs.GetString("TextSceneAlarmingEditorScene", ""); 316 | 317 | currentTempBinaryScene = EditorHelper.GetProjectFolder() + alarmingEditorScene; 318 | 319 | //Use file timestamp instead of DateTime.Now for consistency reasons. 320 | if (File.Exists(currentTempBinaryScene)) 321 | { 322 | currentSceneLoaded = File.GetLastWriteTime(currentTempBinaryScene); 323 | } 324 | else 325 | { 326 | Debug.LogWarning("Unable to find temp file: " + currentTempBinaryScene); 327 | currentSceneLoaded = DateTime.Now; 328 | } 329 | 330 | Debug.Log("Creating new TextSceneMonitor instance: " + currentScene); 331 | } 332 | 333 | /// 334 | /// Sets the current TextScene we will monitor for changes. Also stores the current 335 | /// scene Unity has open (the binary version) so we can detect if the user suddenly 336 | /// changes to either a new scene or a different binary scene without going via 337 | /// the TextScene functionality. 338 | /// 339 | /// 340 | /// A holding the full absolute path to the TextScene file. 341 | /// 342 | public void SetCurrentScene(string filename) 343 | { 344 | alarmingEditorScene = EditorApplication.currentScene; 345 | currentTempBinaryScene = EditorHelper.GetProjectFolder() + alarmingEditorScene; 346 | 347 | currentScene = filename; 348 | 349 | //Use file timestamp instead of DateTime.Now for consistency reasons. 350 | if (File.Exists(currentTempBinaryScene)) 351 | { 352 | currentSceneLoaded = File.GetLastWriteTime(currentTempBinaryScene); 353 | } 354 | else 355 | { 356 | Debug.LogWarning("Unable to find temp file: " + currentTempBinaryScene); 357 | currentSceneLoaded = DateTime.Now; 358 | } 359 | 360 | Debug.Log("TextSceneMonitor setting current scene: " + filename + " (" + currentSceneLoaded + ")"); 361 | 362 | nextCheckForChangedFile = EditorApplication.timeSinceStartup + 1.0f; 363 | 364 | PlayerPrefs.SetString("TextSceneMonitorCurrentScene", currentScene); 365 | PlayerPrefs.SetString("TextSceneAlarmingEditorScene", alarmingEditorScene); 366 | } 367 | 368 | /// 369 | /// Returns the current scene being monitored. 370 | /// 371 | /// 372 | /// A holding the absolute path to the currently monitored scene. 373 | /// 374 | public string GetCurrentScene() 375 | { 376 | return currentScene; 377 | } 378 | 379 | public void DoSaveAndReload(string scene, TextSceneDeserializer.TempSceneSaved callback) 380 | { 381 | //Must wait a couple of frames before we do the save for prefabs to behave correctly. 382 | //TODO: Report bug. 383 | process = new TextSceneTempCreator(scene, callback); 384 | } 385 | 386 | /// 387 | /// Regularly checks if something worthy of notifying the user happens. This includes 388 | /// unexpected scene changes (double-clicking .unity files), creating new scenes and 389 | /// source TextScene files having been changed between now and the time it was loaded. 390 | /// 391 | private void Update() 392 | { 393 | //HACK: To get around bug (TOOD: Insert case #) where Save immediately 394 | // after instantiating prefabs results in weird behaviour. 395 | if (process != null) 396 | { 397 | TextSceneTempCreator.Status status = process.Update(); 398 | 399 | switch (status) 400 | { 401 | case TextSceneTempCreator.Status.Failed: 402 | Debug.LogError("Creating temp files failed!"); 403 | process = null; 404 | break; 405 | case TextSceneTempCreator.Status.Complete: 406 | Debug.Log("Creating temp files succeeded!"); 407 | 408 | //FIXME: Either do this, or get a reference to the delegate and call 409 | // if after clearing tempCreator. The callback might 410 | // end up setting the tempCreator again, typically in a build-cycle, 411 | // for example. 412 | TextSceneTempCreator tempCreatorRef = process; 413 | 414 | process = null; 415 | 416 | tempCreatorRef.InvokeCallback(); 417 | 418 | break; 419 | default: 420 | break; 421 | } 422 | 423 | return; 424 | } 425 | 426 | //Did the user create a new scene? 427 | if (currentScene.Length > 0 428 | && EditorApplication.currentScene.Length == 0) 429 | { 430 | if (CheckForChangedTemp()) 431 | EditorApplication.NewScene(); 432 | 433 | currentScene = ""; 434 | alarmingEditorScene = ""; 435 | 436 | TextSceneSerializer.SaveCurrent(); 437 | 438 | //Warn the user if the save scene dialog was cancelled. 439 | if (currentScene.Length == 0) 440 | EditorUtility.DisplayDialog("New Scene", "You have started a new scene after using the TextScene system. Please use the TextScene menu to save it if you want to continue using the TextScene system", "OK"); 441 | } 442 | 443 | //Try to detect when we go from a TextScene to a regular unit scene. 444 | if ((currentScene.Length > 0 445 | && EditorApplication.currentScene.Length > 0) 446 | || (currentScene.Length == 0 447 | && alarmingEditorScene.Length == 0)) 448 | { 449 | if (alarmingEditorScene != EditorApplication.currentScene) 450 | { 451 | string current = EditorApplication.currentScene; 452 | 453 | if (CheckForChangedTemp()) 454 | EditorApplication.OpenScene(current); 455 | 456 | if (EditorUtility.DisplayDialog("TextScene/built-in mixed usage", "It is not recommended to mix TextScene usage with built-in Unity scenes. This may cause the TextScene system to miss updates or simply behave totally weird! If you plan on using the TextScene system, you should save the current scene via the TextScene menu item and - if successfully saved (please inspect the log for errors and warnings) - remove the original from the Assets folder. Please note that not all components/Unity objects can be saved into the TextScene format, so don't delete the original until you are 100% sure you saved what you need!", "Save to TextScene now!", "I know what I'm doing")) 457 | { 458 | TextSceneSerializer.SaveCurrent(); 459 | } 460 | else 461 | { 462 | alarmingEditorScene = EditorApplication.currentScene; 463 | currentScene = ""; 464 | } 465 | } 466 | } 467 | 468 | //Regular checks to see if the scene file was edited since our last load. 469 | if (currentScene.Length > 0 470 | && EditorApplication.timeSinceStartup > nextCheckForChangedFile) 471 | { 472 | if (File.Exists(currentScene)) 473 | { 474 | DateTime lastWriteTime = File.GetLastWriteTime(currentScene); 475 | 476 | if (lastWriteTime > currentSceneLoaded) 477 | { 478 | int result = EditorUtility.DisplayDialogComplex("Scene changed", "The TextScene you currently have open changed (" + lastWriteTime + "). Do you want to reload it?", "Yes", "Backup mine first", "No"); 479 | 480 | if (result == 0)//Yes 481 | { 482 | TextSceneDeserializer.Load(currentScene); 483 | } 484 | else if (result == 1)//Backup first 485 | { 486 | string filename = EditorUtility.SaveFilePanel("Backup TextScene", currentScene.Substring(0, currentScene.LastIndexOf('/')), "backup", "txt"); 487 | 488 | if (filename.Length != 0) 489 | { 490 | //This is overwritten during the save. 491 | string toLoad = currentScene; 492 | 493 | TextSceneSerializer.Save(filename); 494 | TextSceneDeserializer.Load(toLoad); 495 | } 496 | else 497 | { 498 | EditorUtility.DisplayDialog("Unsaved", "You chose to cancel the backup of your own scene file. It is recommended that you manually save a copy, and merge with the updated file from disk (" + currentScene + ")", "OK"); 499 | } 500 | } 501 | else//No 502 | { 503 | //HACK: Shut this message up. We don't want to get asked this again until the 504 | // file changes again. 505 | currentSceneLoaded = DateTime.Now; 506 | } 507 | } 508 | } 509 | else 510 | { 511 | if (EditorUtility.DisplayDialog("TextScene file gone!", "It seems like the TextScene representation of your open file has been deleted. Do you want to re-save it (" + currentScene + ")?", "Yes", "No")) 512 | TextSceneSerializer.Save(currentScene); 513 | else 514 | currentScene = ""; 515 | } 516 | 517 | //Also check for changed temp files (.unity in TempScenes). This happens if 518 | //the user uses the built-in save functionality. 519 | CheckForChangedTemp(); 520 | 521 | nextCheckForChangedFile = EditorApplication.timeSinceStartup + 1.0f; 522 | } 523 | } 524 | 525 | public void SaveIfTempIsNewer() 526 | { 527 | if (File.Exists(currentTempBinaryScene)) 528 | { 529 | if (File.GetLastWriteTime(currentTempBinaryScene) > currentSceneLoaded) 530 | { 531 | TextSceneSerializer.SaveCurrent(); 532 | Debug.Log("Temp file updated: " + currentTempBinaryScene); 533 | } 534 | else 535 | Debug.Log("Temp file already up to date: " + currentTempBinaryScene); 536 | } 537 | else 538 | Debug.LogWarning("Temp file does not exist!"); 539 | } 540 | 541 | private bool CheckForChangedTemp() 542 | { 543 | //Also check for temp binary file changes, so we can give the user some options. 544 | if (File.Exists(currentTempBinaryScene)) 545 | { 546 | DateTime lastTempWrite = File.GetLastWriteTime(currentTempBinaryScene); 547 | 548 | if (lastTempWrite > currentSceneLoaded) 549 | { 550 | if (EditorUtility.DisplayDialog("Temp scene changed", "The temp scene changed (" + lastTempWrite + "), this probably means that either you or Unity saved using the standard menu items - you should re-save using the TextScene menu if you want these changes to be kept!", "Re-save now", "I'll do it later")) 551 | { 552 | if (EditorApplication.currentScene != alarmingEditorScene) 553 | EditorApplication.OpenScene(alarmingEditorScene); 554 | 555 | 556 | TextSceneSerializer.SaveCurrent(); 557 | return true; 558 | } 559 | else 560 | SetCurrentScene(currentScene);//Reset the timers so we don't get this popping up more than once per save. 561 | 562 | } 563 | } 564 | 565 | return false; 566 | } 567 | } -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneSerializer.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | /// TODO: *Don't write component/field count at the start of each listing - makes editing more complicated 6 | /// than it needs to be. This must of course be reflected in TextSceneDeserializer. 7 | /// *Prefabs with changed instance parameters are not supported (it will be 'reverted' on scene load). 8 | 9 | using UnityEngine; 10 | using UnityEditor; 11 | 12 | using System.Xml.Serialization; 13 | using System.IO; 14 | using System.Reflection; 15 | using System.Collections.Generic; 16 | using System.Text; 17 | 18 | /// 19 | /// Class responsible for writing Unity scene hierarchies to a textual, humanly readable format. 20 | /// 21 | public class TextSceneSerializer 22 | { 23 | private static int warnings = 0; 24 | 25 | public static void SaveAs() 26 | { 27 | string currentScene = EditorApplication.currentScene; 28 | 29 | string startFolder = ""; 30 | 31 | 32 | 33 | if (currentScene.Length == 0) 34 | { 35 | SaveCurrent(); 36 | return; 37 | } 38 | else if (currentScene.StartsWith("Assets/")) 39 | { 40 | string needResaveAs = currentScene.Substring(currentScene.IndexOf('/')); 41 | 42 | needResaveAs = needResaveAs.Replace(".unity", ".txt"); 43 | 44 | needResaveAs = Application.dataPath + needResaveAs; 45 | 46 | startFolder = needResaveAs.Substring(0, needResaveAs.LastIndexOf('/')); 47 | } 48 | else 49 | { 50 | //TODO: Verify that it starts with TempScenes? 51 | 52 | startFolder = EditorHelper.GetProjectFolder() + TextScene.TempToTextSceneFile(currentScene); 53 | 54 | startFolder = startFolder.Substring(0, startFolder.LastIndexOf('/')); 55 | } 56 | 57 | string fileName = EditorUtility.SaveFilePanel("Save TextScene as", startFolder, "textscene", "txt"); 58 | 59 | if (fileName.Length > 0) 60 | Save(fileName); 61 | } 62 | 63 | public static void SaveCurrent() 64 | { 65 | string currentScene = EditorApplication.currentScene; 66 | 67 | string needResaveAs = ""; 68 | 69 | //Don't save over scenes not in the TempScenes-folder. 70 | if (currentScene.StartsWith("Assets/")) 71 | { 72 | if (!EditorUtility.DisplayDialog("Need resave", "This scene is residing in your Assets folder. For the TextScenes we need to re-save the .unity file in a temporary folder - you should from now on not be working on this scene file, but rather use the new TextScene file that will be generated next to your currently open scene.", "OK", "Hell no.")) 73 | return; 74 | 75 | needResaveAs = currentScene.Substring(currentScene.IndexOf('/')); 76 | 77 | needResaveAs = needResaveAs.Replace(".unity", ".txt"); 78 | 79 | needResaveAs = Application.dataPath + needResaveAs; 80 | 81 | 82 | 83 | if (File.Exists(needResaveAs)) 84 | { 85 | string overwriteFile = currentScene.Replace(".unity", ".txt"); 86 | 87 | Object o = AssetDatabase.LoadAssetAtPath(overwriteFile, typeof(TextAsset)); 88 | 89 | Selection.activeObject = o; 90 | 91 | if (!EditorUtility.DisplayDialog("Overwrite?", "A file already exists at the default save position (" + overwriteFile +") - do you want to overwrite, or choose a new name?", "Overwrite", "Choose new name")) 92 | needResaveAs = ""; 93 | else 94 | Debug.Log("Converting and overwriting scene to text: " + needResaveAs); 95 | 96 | } 97 | else 98 | Debug.Log("Converting scene to text: " + needResaveAs); 99 | 100 | 101 | currentScene = ""; 102 | } 103 | 104 | 105 | if (currentScene.Length == 0 || needResaveAs.Length > 0) 106 | { 107 | string startPath = Application.dataPath; 108 | 109 | string path = needResaveAs.Length > 0 ? needResaveAs : EditorUtility.SaveFilePanel("Save scene file", startPath, "newtextscene", "txt"); 110 | 111 | if (path.Length == 0) 112 | return; 113 | 114 | currentScene = path.Replace(EditorHelper.GetProjectFolder(), ""); 115 | 116 | Debug.LogWarning("Saving new scene to text: " + currentScene); 117 | } 118 | else 119 | { 120 | Debug.LogWarning("Re-saving temp scene to text: " + EditorApplication.currentScene); 121 | } 122 | 123 | 124 | string textScene = currentScene.Substring(currentScene.IndexOf('/')); 125 | 126 | 127 | string saveFile = textScene.Replace(".unity", ".txt"); 128 | 129 | Save(Application.dataPath + saveFile); 130 | } 131 | 132 | private static int CompareTransform(Transform obj1, Transform obj2) 133 | { 134 | return string.Compare(obj1.name + obj1.position, obj2.name + obj2.position); 135 | } 136 | 137 | public static void Save(string filePath) 138 | { 139 | if (EditorApplication.isPlayingOrWillChangePlaymode) 140 | { 141 | EditorUtility.DisplayDialog("Game is running", "You cannot save in Play-mode", "OK"); 142 | return; 143 | } 144 | 145 | 146 | 147 | Transform[] sceneObjects = Helper.GetObjectsOfType(); 148 | 149 | List sortedTransforms = new List(); 150 | 151 | //Sort these to get a somewhat predictable output 152 | foreach (Transform o in sceneObjects) 153 | { 154 | //Only serialize root objects, children are handled by their parents 155 | if (o.parent == null) 156 | sortedTransforms.Add(o); 157 | } 158 | 159 | warnings = 0; 160 | 161 | sortedTransforms.Sort(CompareTransform); 162 | 163 | StringBuilder sceneText = new StringBuilder(); 164 | 165 | foreach(Transform o in sortedTransforms) 166 | Serialize(sceneText, o.gameObject, 0); 167 | 168 | 169 | //TODO: If the save didn't go without warnings, show a message/confirmation 170 | // dialog here. 171 | if (warnings > 0) 172 | { 173 | EditorUtility.DisplayDialog("ERROR: Scene not saved", "You had " + warnings + " errors or warnings during the save. Please fix them up and try again.", "OK"); 174 | return; 175 | } 176 | 177 | 178 | StreamWriter fileStream = File.CreateText(filePath); 179 | fileStream.Write(sceneText.ToString()); 180 | fileStream.Close(); 181 | 182 | Debug.Log("Wrote scene to file: " + filePath); 183 | 184 | string assetPath = filePath.Replace(EditorHelper.GetProjectFolder(), ""); 185 | 186 | Debug.Log("Reimporting: " + assetPath); 187 | 188 | //Import the asset. 189 | AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); 190 | 191 | Selection.activeObject = AssetDatabase.LoadAssetAtPath(assetPath, typeof(TextAsset)); 192 | 193 | TextSceneDeserializer.Load(filePath); 194 | } 195 | 196 | 197 | 198 | private static void Serialize(StringBuilder stream, GameObject go, int indent) 199 | { 200 | //FIXME: Uh-oh. Seems like I missed this the first time - I might have overcomplicated 201 | // matters slightly :S Need to investigate this further. 202 | /* 203 | SerializedObject serializeObject = new SerializedObject(go); 204 | 205 | SerializedProperty seralizedProperty = serializeObject.GetIterator(); 206 | 207 | do 208 | { 209 | stream.WriteLine(seralizedProperty.name + " = " + seralizedProperty.ToString()); 210 | }while(seralizedProperty.Next(true)); 211 | 212 | Component[] cList = Helper.GetComponentsInChildren(go); 213 | 214 | foreach(Component c in cList) 215 | { 216 | stream.WriteLine("Component: " + c.GetType().ToString()); 217 | 218 | serializeObject = new SerializedObject(go); 219 | 220 | seralizedProperty = serializeObject.GetIterator(); 221 | 222 | do 223 | { 224 | stream.WriteLine(seralizedProperty.name + " = " + seralizedProperty.stringValue); 225 | }while(seralizedProperty.Next(true)); 226 | } 227 | 228 | stream.WriteLine(); 229 | 230 | return; 231 | */ 232 | 233 | Object prefab = EditorUtility.GetPrefabParent(go); 234 | 235 | if (prefab != null) 236 | { 237 | PrefabType prefabType = EditorUtility.GetPrefabType(go); 238 | 239 | if (prefabType == PrefabType.DisconnectedModelPrefabInstance 240 | || prefabType == PrefabType.DisconnectedPrefabInstance) 241 | { 242 | } 243 | else 244 | { 245 | string path = AssetDatabase.GetAssetPath(prefab); 246 | 247 | string guid = AssetDatabase.AssetPathToGUID(path); 248 | 249 | WriteLine(stream, indent, "prefab " + go.name); 250 | WriteLine(stream, indent, "assetpath " + path + ", " + guid); 251 | WriteLine(stream, indent, Vector3ToString(go.transform.localPosition)); 252 | WriteLine(stream, indent, QuatToString(go.transform.localRotation)); 253 | WriteLine(stream, indent, Vector3ToString(go.transform.localScale)); 254 | 255 | if (indent == 0) 256 | WriteLine(stream, indent, ""); 257 | 258 | return; 259 | 260 | } 261 | } 262 | 263 | TextSceneObject tso = go.GetComponent(); 264 | 265 | if (tso) 266 | { 267 | string path = AssetDatabase.GetAssetPath(tso.textScene); 268 | 269 | if (path.Length == 0) 270 | { 271 | EditorUtility.DisplayDialog("ERROR", "TextSceneObjects must point at assets!", "OK"); 272 | warnings++; 273 | return; 274 | } 275 | 276 | string fullPath = Helper.GetFullName(go); 277 | 278 | GameObject[] matchingObjects = Helper.FindGameObjectsFromFullName(fullPath); 279 | 280 | if (matchingObjects.Length > 1) 281 | { 282 | EditorUtility.DisplayDialog("WARNING", "There are several objects that has the path '" + fullPath + "' in the scene. It is not a good thing to let TextSceneObjects have the same path as other objects, because links within the TextSceneObjects may horribly break when you least need it.", "OK, I'll fix it"); 283 | warnings++; 284 | } 285 | 286 | string guid = AssetDatabase.AssetPathToGUID(path); 287 | 288 | WriteLine(stream, indent, "textscene " + go.name); 289 | WriteLine(stream, indent, "assetpath " + path + ", " + guid); 290 | WriteLine(stream, indent, Vector3ToString(go.transform.localPosition)); 291 | WriteLine(stream, indent, QuatToString(go.transform.localRotation)); 292 | WriteLine(stream, indent, Vector3ToString(go.transform.localScale)); 293 | 294 | if (indent == 0) 295 | WriteLine(stream, indent, ""); 296 | 297 | return; 298 | } 299 | 300 | //Debug.Log("Writing object: " + go.name); 301 | 302 | 303 | 304 | WriteLine(stream, indent, "gameobject " + go.name); 305 | WriteLine(stream, indent, "tag " + go.tag + " layer " + go.layer); 306 | 307 | WriteLine(stream, indent, Vector3ToString(go.transform.localPosition)); 308 | WriteLine(stream, indent, QuatToString(go.transform.localRotation)); 309 | WriteLine(stream, indent, Vector3ToString(go.transform.localScale)); 310 | 311 | int childCount = go.transform.GetChildCount(); 312 | 313 | 314 | WriteLine(stream, indent, "children " + childCount); 315 | 316 | //Write out all children , sorted. 317 | List children = new List(); 318 | 319 | foreach (Transform t in go.transform) 320 | children.Add(t); 321 | 322 | children.Sort(CompareTransform); 323 | 324 | foreach(Transform t in children) 325 | Serialize(stream, t.gameObject, indent + 1); 326 | 327 | 328 | Component[] components = go.GetComponents(); 329 | 330 | List componentsToWrite = new List(); 331 | 332 | foreach(Component comp in components) 333 | { 334 | if (comp is Transform) 335 | continue; 336 | 337 | //HACK. Fix this gracefully. 338 | if (comp is ParticleEmitter) 339 | { 340 | EditorUtility.DisplayDialog("Warning", "The component " + comp.GetType().ToString() + " cannot be saved. You can get around this by making the object " + Helper.GetFullName(go) + " into a prefab and drop that into the scene instead", "OK"); 341 | 342 | Debug.LogWarning("Skipping abstract component: " + comp.GetType().ToString(), go); 343 | 344 | warnings++; 345 | continue; 346 | } 347 | 348 | componentsToWrite.Add(comp); 349 | } 350 | 351 | WriteLine(stream, indent, "components " + componentsToWrite.Count); 352 | 353 | 354 | foreach(Component comp in componentsToWrite) 355 | { 356 | Serialize(stream, comp, indent + 1); 357 | } 358 | 359 | 360 | if (indent == 0) 361 | WriteLine(stream, indent, ""); 362 | } 363 | 364 | private static string Vector3ToString(Vector3 source) 365 | { 366 | return source.x.ToString("F5") + " " + source.y.ToString("F5") + " " + source.z.ToString("F5"); 367 | } 368 | 369 | private static string QuatToString(Quaternion source) 370 | { 371 | return source.x.ToString("F5") + " " + source.y.ToString("F5") + " " + source.z.ToString("F5") + " " + source.w.ToString("F5"); 372 | } 373 | 374 | private static void WriteLine(StringBuilder stream, int indent, string line) 375 | { 376 | if (indent > 0) 377 | stream.Append(IndentString(indent)); 378 | 379 | stream.Append(line); 380 | stream.Append('\n'); 381 | } 382 | 383 | private static string IndentString(int indent) 384 | { 385 | StringBuilder sb = new StringBuilder(); 386 | 387 | for (int i = 0; i < indent; i++) 388 | sb.Append(" "); 389 | 390 | return sb.ToString(); 391 | } 392 | 393 | private static void Serialize(StringBuilder stream, Component comp, int indent) 394 | { 395 | System.Type type = comp.GetType(); 396 | 397 | //Save off the fields for later, so we know beforehand how many we need to 398 | //read when deserializing. 399 | List fieldList = new List(); 400 | 401 | 402 | MemberInfo[] mil = type.GetMembers(BindingFlags.Public | BindingFlags.Instance); 403 | 404 | foreach(MemberInfo mi in mil) 405 | { 406 | FieldInfo fi = type.GetField(mi.Name); 407 | 408 | object val = null; 409 | string memberType = ""; 410 | string memberName = mi.Name; 411 | 412 | 413 | if (fi != null) 414 | { 415 | memberType = "field"; 416 | val = fi.GetValue(comp); 417 | } 418 | 419 | PropertyInfo pi = type.GetProperty(mi.Name); 420 | 421 | if (pi != null) 422 | { 423 | memberType = "property"; 424 | 425 | MethodInfo getMethod = pi.GetGetMethod(); 426 | 427 | //HACKs. 428 | if (memberName == "inertiaTensorRotation" && comp is Rigidbody) 429 | val = null; 430 | else if (memberName == "mesh" && comp is MeshFilter) 431 | val = null;//Debug.Log("Skipping mesh property because it will leak in edit mode"); 432 | else if (memberName == "material" && comp is Renderer) 433 | val = null;//Debug.Log("Skipping renderer material property because it will leak in edit mode"); 434 | else if (memberName == "materials" && comp is Renderer) 435 | val = null;//Debug.Log("Skipping renderer materials property because it will leak in edit mode"); 436 | else if (memberName == "sharedMaterial" && comp is Renderer) 437 | val = null;//sharedMaterials will include this one, it's redundant. 438 | else if (memberName == "material" && comp is Collider) 439 | val = null;//Debug.Log("Skipping physics material property because it will leak in edit mode"); 440 | else if (memberName == "mesh" && comp is Collider) 441 | val = null;//Debug.Log("Skipping physics mesh property because... I have no good reason."); 442 | else if (memberName == "particles" && comp is ParticleEmitter) 443 | val = null;//Debug.Log("Skipping particles property because it will change all the time if selected"); 444 | else if (memberName == "name" || memberName == "tag" || memberName == "layer") 445 | val = null; 446 | else if (memberName == "active") 447 | val = null;//Deprecated, AFAIK. 448 | else if (memberName == "hideFlags" && comp.hideFlags == 0) 449 | val = null;//Skip this if it has the default value. 450 | else if (memberName == "enabled" 451 | && getMethod != null 452 | && (System.Boolean)getMethod.Invoke(comp, null) == true) 453 | val = null;//Skip this if it has the default value. 454 | //Camera properties not editable from the editor. 455 | else if (memberName == "pixelRect" && comp is Camera) 456 | val = null; 457 | else if (memberName == "aspect" && comp is Camera) 458 | val = null; 459 | else if (memberName == "layerCullDistances" && comp is Camera) 460 | val = null; 461 | //The reason we don't call this above, is that it will leak resources if called on "mesh" and maybe "material". 462 | else if (getMethod != null && pi.GetSetMethod() != null) 463 | val = getMethod.Invoke(comp, null); 464 | } 465 | 466 | 467 | if (val != null) 468 | { 469 | WriteComponentValue(comp, memberType, memberName, val, fieldList); 470 | } 471 | } 472 | 473 | 474 | WriteLine(stream, indent, type.ToString() + " " + fieldList.Count); 475 | 476 | //To correctly indent lines not directly fields (such as array entries) 477 | string indentString = "\n" + IndentString(indent+1); 478 | 479 | foreach(string field in fieldList) 480 | { 481 | string indentedField = field.Replace("\n", indentString); 482 | 483 | WriteLine(stream, indent+1, indentedField); 484 | } 485 | } 486 | 487 | private static void PopupInSceneLinkWarning(Component comp, string memberName, string linkedObject) 488 | { 489 | Selection.activeObject = comp.gameObject; 490 | 491 | EditorUtility.DisplayDialog("Warning", "There seems to be an unresolvable link from '" + comp.GetType().ToString() + "' on '" + comp.gameObject.name + "' (" + memberName + " = " + linkedObject + "). This link will be lost! Once the save has completed, you can resolve the problem manually.", "OK"); 492 | 493 | warnings++; 494 | } 495 | 496 | private static bool WriteComponentValue(Component comp, string memberType, string memberName, object val, List fieldList) 497 | { 498 | //Debug.Log("Writing component member: " + memberName); 499 | 500 | if (val is UnityEngine.Object) 501 | { 502 | Object uo = val as Object; 503 | 504 | string assetPath = val == null ? "" : AssetDatabase.GetAssetPath(uo); 505 | string assetGUID = ""; 506 | 507 | string uoName = uo == null ? "" : uo.name; 508 | 509 | if (assetPath.Length > 0) 510 | assetGUID = AssetDatabase.AssetPathToGUID(assetPath); 511 | 512 | if (assetPath.Length > 0) 513 | { 514 | fieldList.Add(memberType + " " + memberName + " asset " + val.GetType() + " = " + assetPath + ", " + uoName + ", " + assetGUID); 515 | } 516 | else if (uoName.Length > 0) 517 | { 518 | string fullGOName = ""; 519 | 520 | if (uo is Component) 521 | fullGOName = Helper.GetFullName((uo as Component).gameObject); 522 | else if (uo is GameObject) 523 | fullGOName = Helper.GetFullName((uo as GameObject)); 524 | 525 | if (fullGOName.Length > 0) 526 | { 527 | //GameObject tmpGo = GameObject.Find(fullGOName); 528 | 529 | //if (tmpGo == null) 530 | // EditorUtility.DisplayDialog("WARNING", "Unable to find: '" + fullGOName + "'", "OK"); 531 | 532 | GameObject[] gos = Helper.FindGameObjectsFromFullName(fullGOName); 533 | 534 | if (gos.Length > 1) 535 | { 536 | warnings++; 537 | 538 | if (!EditorUtility.DisplayDialog("WARNING", "There are several objects that will have the path '" + fullGOName + "' - please rename them so they become unique. The TextScene in-scene links rely on unique object paths", "Continue", "Stop writing this object")) 539 | return false; 540 | } 541 | fieldList.Add(memberType + " " + memberName + " scenelink " + val.GetType() + " = " + fullGOName); 542 | } 543 | else if (uo is Mesh) 544 | { 545 | fieldList.Add(memberType + " " + memberName + " builtinmesh " + val.GetType() + " = " + uoName); 546 | } 547 | 548 | else if (uo is Material) 549 | { 550 | fieldList.Add(memberType + " " + memberName + " builtinmaterial " + val.GetType() + " = " + uoName); 551 | } 552 | else 553 | { 554 | PopupInSceneLinkWarning(comp, memberName, uoName); 555 | 556 | Debug.LogWarning("In-scene links currently not supported for UnityEngine.Object: " + comp.gameObject.name + ":" + memberName + " (" + uoName + ")"); 557 | 558 | return false; 559 | } 560 | } 561 | else 562 | { 563 | return false; 564 | } 565 | } 566 | else 567 | { 568 | System.Type valueType = val.GetType(); 569 | 570 | if (valueType == typeof(System.Int32) 571 | || valueType == typeof(System.Single) 572 | || valueType == typeof(System.Boolean) 573 | || valueType == typeof(System.String) 574 | || valueType == typeof(UnityEngine.Vector2) 575 | || valueType == typeof(UnityEngine.Vector3) 576 | || valueType == typeof(UnityEngine.Vector4) 577 | || valueType == typeof(UnityEngine.Quaternion) 578 | || valueType.IsEnum) 579 | fieldList.Add(memberType + " " + memberName + " primitive " + valueType.ToString() + " = " + val.ToString()); 580 | else if (valueType == typeof(UnityEngine.Color)) 581 | { 582 | fieldList.Add(memberType + " " + memberName + " primitive " + valueType.ToString() + " = " + val.ToString().Replace("RGBA", "")); 583 | } 584 | else if (valueType == typeof(UnityEngine.Rect)) 585 | { 586 | string rectStrippedString = val.ToString(); 587 | 588 | rectStrippedString = rectStrippedString.Replace("left:", ""); 589 | rectStrippedString = rectStrippedString.Replace("width:", ""); 590 | rectStrippedString = rectStrippedString.Replace("top:", ""); 591 | rectStrippedString = rectStrippedString.Replace("height:", ""); 592 | 593 | fieldList.Add(memberType + " " + memberName + " primitive " + valueType.ToString() + " = " + rectStrippedString); 594 | } 595 | else if (valueType == typeof(Matrix4x4)) 596 | { 597 | //'Gracefully' skip matrices. 598 | } 599 | else if (val.GetType().IsArray) 600 | { 601 | System.Array array = (val as System.Array); 602 | 603 | List arrayEntryList = new List(); 604 | 605 | arrayEntryList.Add(memberType + " " + memberName + " array " + valueType.ToString() + " = " + array.Length); 606 | 607 | for (int i = 0; i < array.Length; i++) 608 | { 609 | if (array.GetValue(i) == null) 610 | arrayEntryList.Add("null"); 611 | else 612 | { 613 | if (!WriteComponentValue(comp, " ", " ", array.GetValue(i), arrayEntryList)) 614 | { 615 | arrayEntryList.Clear(); 616 | break; 617 | } 618 | } 619 | } 620 | 621 | 622 | StringBuilder sb = new StringBuilder(); 623 | 624 | for (int i = 0; i < arrayEntryList.Count; i++) 625 | { 626 | if (i == arrayEntryList.Count-1) 627 | sb.Append(arrayEntryList[i]); 628 | else 629 | sb.Append(arrayEntryList[i] + '\n'); 630 | } 631 | 632 | if (sb.Length > 0) 633 | fieldList.Add(sb.ToString()); 634 | } 635 | else 636 | { 637 | //Try to write "complex" stuff, typically structs/classes we haven't converted to seomthing 638 | //more handy/readable. 639 | try 640 | { 641 | List complexEntryList = new List(); 642 | 643 | FieldInfo[] fields = val.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); 644 | 645 | foreach(FieldInfo fi in fields) 646 | { 647 | WriteComponentValue(comp, " field", fi.Name, fi.GetValue(val), complexEntryList); 648 | } 649 | 650 | 651 | PropertyInfo[] properties = val.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); 652 | 653 | foreach(PropertyInfo pi in properties) 654 | { 655 | MethodInfo getMethod = pi.GetGetMethod(); 656 | 657 | MethodInfo setMethd = pi.GetSetMethod(); 658 | 659 | if (getMethod != null && setMethd != null) 660 | { 661 | //Debug.LogError(memberName + " Comp: " + comp.gameObject.name + " - " + comp.GetType().ToString() + " Prop: " + pi.Name); 662 | object propertyValue = getMethod.Invoke(val, null); 663 | 664 | WriteComponentValue(comp, " property", pi.Name, propertyValue, complexEntryList); 665 | } 666 | } 667 | 668 | 669 | StringBuilder sb = new StringBuilder(); 670 | 671 | sb.Append(memberType + " " + memberName + " complex " + valueType.ToString() + " = " + complexEntryList.Count + '\n'); 672 | 673 | for (int i = 0; i < complexEntryList.Count; i++) 674 | { 675 | if (i == complexEntryList.Count-1) 676 | sb.Append(complexEntryList[i]); 677 | else 678 | sb.Append(complexEntryList[i] + '\n'); 679 | } 680 | 681 | if (sb.Length > 0) 682 | fieldList.Add(sb.ToString()); 683 | 684 | return true; 685 | } 686 | catch (System.Exception e) 687 | { 688 | warnings++; 689 | Debug.LogError("Failed to write: " + memberName + " on " + comp.GetType().ToString() + " on object " + comp.name + " Exception: " + e); 690 | return false; 691 | } 692 | } 693 | } 694 | 695 | return true; 696 | } 697 | } -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneHierarchy.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | /// BUGS: Somewhat fixed: 6 | /// *Prefab instances gets renamed to their original prefab name if you load a TextScene and hit 7 | /// "apply" on the prefab instance (See unity case 336621) 8 | /// 9 | /// TODO: *Add support for multiple selected items 10 | 11 | 12 | using UnityEngine; 13 | using UnityEditor; 14 | using System.Reflection; 15 | using System.Collections.Generic; 16 | 17 | /// 18 | /// Custom hierarchy view compatible with TextScene functionality (scene-in-scene links, various 19 | /// constraints and drag'n'drop instantiation of TextScene-links). 20 | /// 21 | public class TextSceneHierarchy : EditorWindow 22 | { 23 | enum EntryType 24 | { 25 | Normal = 0, 26 | PrefabChild, 27 | PrefabRoot, 28 | TextSceneChild, 29 | TextSceneRoot 30 | } 31 | 32 | static class EntryTypeStyle 33 | { 34 | private static Dictionary styles = new Dictionary(); 35 | 36 | public static GUIStyle Get(EntryType type) 37 | { 38 | if (styles.ContainsKey(type)) 39 | return styles[type]; 40 | 41 | GUIStyle style = new GUIStyle("HI Label"); 42 | 43 | if (type == EntryType.PrefabRoot) 44 | style.normal.textColor = new Color(0.2f, 0.2f, 0.9f); 45 | else if (type == EntryType.TextSceneRoot) 46 | style.normal.textColor = new Color(0.9f, 0.9f, 0.2f); 47 | else if (type == EntryType.PrefabChild) 48 | style.normal.textColor = new Color(0.2f, 0.2f, 0.6f); 49 | else if (type == EntryType.TextSceneChild) 50 | style.normal.textColor = new Color(0.6f, 0.6f, 0.2f); 51 | else 52 | style.normal.textColor = Color.black; 53 | 54 | styles.Add(type, style); 55 | 56 | return styles[type]; 57 | } 58 | } 59 | 60 | static class Styles 61 | { 62 | public static GUIStyle foldout = new GUIStyle("IN Foldout"); 63 | public static GUIStyle insertion = new GUIStyle("PR Insertion"); 64 | } 65 | 66 | class Entry 67 | { 68 | private static Entry selected = null; 69 | 70 | private double expandedTimer = 0.0; 71 | public bool expanded = false; 72 | public GameObject go; 73 | 74 | private List entries = new List(); 75 | 76 | private EntryType type = EntryType.Normal; 77 | 78 | public Entry() 79 | { 80 | } 81 | 82 | public Entry(GameObject go) 83 | { 84 | this.go = go; 85 | } 86 | 87 | public void AddChild(GameObject gameObject) 88 | { 89 | AddChild(gameObject, EntryType.Normal); 90 | } 91 | 92 | public bool hasChildren 93 | { 94 | get 95 | { 96 | return entries.Count > 0; 97 | } 98 | } 99 | 100 | private void Duplicate() 101 | { 102 | if (go == null) 103 | return; 104 | 105 | Undo.RegisterSceneUndo("Duplicate " + go.name); 106 | 107 | GameObject copy = null; 108 | 109 | GameObject prefab = EditorUtility.GetPrefabParent(go) as GameObject; 110 | 111 | if (prefab != null) 112 | { 113 | PrefabType type = EditorUtility.GetPrefabType(prefab); 114 | 115 | if (type == PrefabType.ModelPrefab || type == PrefabType.Prefab) 116 | { 117 | copy = EditorUtility.InstantiatePrefab(prefab) as GameObject; 118 | copy.transform.position = go.transform.position; 119 | copy.transform.rotation = go.transform.rotation; 120 | copy.transform.localScale = go.transform.localScale; 121 | } 122 | } 123 | 124 | if (copy == null) 125 | copy = GameObject.Instantiate(go, go.transform.position, go.transform.rotation) as GameObject; 126 | 127 | copy.transform.parent = go.transform.parent; 128 | 129 | copy.name = go.name + "_copy"; 130 | 131 | Selection.activeGameObject = copy; 132 | } 133 | 134 | private void DisconnectPrefab() 135 | { 136 | if (go == null) 137 | return; 138 | 139 | GameObject copy = null; 140 | 141 | GameObject prefab = EditorUtility.GetPrefabParent(go) as GameObject; 142 | 143 | if (prefab != null) 144 | { 145 | PrefabType type = EditorUtility.GetPrefabType(prefab); 146 | 147 | if (type == PrefabType.ModelPrefab || type == PrefabType.Prefab) 148 | { 149 | Undo.RegisterSceneUndo("Disconnect prefab " + go.name); 150 | 151 | copy = GameObject.Instantiate(go, go.transform.position, go.transform.rotation) as GameObject; 152 | copy.transform.position = go.transform.position; 153 | copy.transform.rotation = go.transform.rotation; 154 | copy.transform.localScale = go.transform.localScale; 155 | 156 | copy.transform.parent = go.transform.parent; 157 | 158 | copy.name = go.name; 159 | 160 | DestroyImmediate(go); 161 | 162 | Selection.activeGameObject = copy; 163 | } 164 | } 165 | } 166 | 167 | private void DisconnectTextScene() 168 | { 169 | if (go == null) 170 | return; 171 | 172 | Undo.RegisterSceneUndo("Disconnect TextScene " + go.name); 173 | 174 | TextSceneObject textSceneComp = go.GetComponent(); 175 | 176 | if (textSceneComp != null) 177 | DestroyImmediate(textSceneComp); 178 | 179 | TextSceneObject[] firstLayer = Helper.GetComponentsInChildren(go, 1); 180 | 181 | //Enable next layer of TextSceneObjects. 182 | foreach (TextSceneObject tso in firstLayer) 183 | { 184 | tso.hideFlags = 0; 185 | tso.transform.hideFlags = 0; 186 | } 187 | 188 | 189 | //Enable all disconnected gameobjects until the next TextSceneObject. 190 | foreach (Transform child in go.transform) 191 | { 192 | Component[] toEnable = Helper.GetComponentsInChildrenAboveType(child.gameObject); 193 | 194 | foreach (Component comp in toEnable) 195 | comp.hideFlags = 0; 196 | } 197 | 198 | TextSceneHierarchy tsh = EditorWindow.GetWindow(typeof(TextSceneHierarchy)) as TextSceneHierarchy; 199 | tsh.OnHierarchyChange(); 200 | } 201 | 202 | private void Delete() 203 | { 204 | if (go != null) 205 | { 206 | Undo.RegisterSceneUndo("Delete " + go.name); 207 | DestroyImmediate(go); 208 | } 209 | } 210 | 211 | public void AddChild(GameObject gameObject, EntryType entryType) 212 | { 213 | Entry entry = entries.Find(delegate(Entry x) { return x.go == gameObject; }); 214 | 215 | if (entry == null) 216 | { 217 | entry = new Entry(gameObject); 218 | 219 | entries.Add(entry); 220 | } 221 | 222 | entry.type = entryType; 223 | 224 | if (entryType == EntryType.Normal) 225 | { 226 | if (entry.go.GetComponent() != null) 227 | { 228 | entry.type = EntryType.TextSceneRoot; 229 | } 230 | else if (EditorUtility.GetPrefabParent(entry.go) != null) 231 | { 232 | entry.type = EntryType.PrefabRoot; 233 | } 234 | } 235 | else if (entryType == EntryType.PrefabRoot) 236 | entry.type = EntryType.PrefabChild; 237 | else if (entryType == EntryType.TextSceneRoot) 238 | entry.type = EntryType.TextSceneChild; 239 | 240 | foreach (Transform t in entry.go.transform) 241 | entry.AddChild(t.gameObject, entry.type); 242 | } 243 | 244 | public bool HandleDrag() 245 | { 246 | if (Event.current.type == EventType.DragUpdated 247 | || Event.current.type == EventType.DragPerform) 248 | { 249 | Entry e = DragAndDrop.GetGenericData("entry") as Entry; 250 | 251 | //Do not allow dragging stuff onto prefabs or textscene links. 252 | if (this.type != EntryType.Normal) 253 | { 254 | selected = null; 255 | DragAndDrop.visualMode = DragAndDropVisualMode.None; 256 | return false; 257 | } 258 | //Do not allow dragging prefab or textscene children around. 259 | else if (e != null 260 | && (e.type == EntryType.PrefabChild 261 | || e.type == EntryType.TextSceneChild)) 262 | { 263 | selected = null; 264 | DragAndDrop.visualMode = DragAndDropVisualMode.None; 265 | return false; 266 | } 267 | else 268 | { 269 | selected = this; 270 | 271 | if (expanded == false 272 | && EditorApplication.timeSinceStartup > this.expandedTimer) 273 | this.expanded = true; 274 | 275 | DragAndDrop.visualMode = DragAndDropVisualMode.Link; 276 | 277 | if (Event.current.type == EventType.DragPerform) 278 | { 279 | DragAndDrop.AcceptDrag(); 280 | 281 | if (e != null && e.go != null) 282 | { 283 | GameObject dragged = e.go; 284 | 285 | if (dragged != null) 286 | { 287 | if (dragged != go) 288 | { 289 | Undo.RegisterSceneUndo("Move " + dragged.name); 290 | 291 | //If the target is null (the root), we might want to orphan the object. 292 | if (go == null) 293 | { 294 | if (dragged.transform.parent != null) 295 | { 296 | if (EditorUtility.DisplayDialog("Orphan", "Do you want to make " + dragged.name + " an orphan?", "Yes", "No")) 297 | { 298 | dragged.transform.parent = null; 299 | Selection.activeGameObject = dragged; 300 | TextSceneHierarchy.Refresh(); 301 | } 302 | } 303 | } 304 | else 305 | { 306 | //Change parent to whatever we're hovering above at the time of drag end. 307 | if (EditorUtility.DisplayDialog("Change parent", "Do you want to make " + go.name + " the parent of " + dragged.name + "?", "Yes", "No")) 308 | { 309 | dragged.transform.parent = go.transform; 310 | Selection.activeGameObject = dragged; 311 | TextSceneHierarchy.Refresh(); 312 | } 313 | } 314 | } 315 | } 316 | } 317 | else if (DragAndDrop.objectReferences.Length > 0) 318 | { 319 | UnityEngine.Object dragged = DragAndDrop.objectReferences[0]; 320 | 321 | MonoScript comp = dragged as MonoScript; 322 | GameObject draggedGO = dragged as GameObject; 323 | TextAsset draggedTextScene = dragged as TextAsset; 324 | 325 | if (comp != null) 326 | { 327 | if (this.go != null) 328 | { 329 | this.go.AddComponent(comp.GetClass()); 330 | Selection.activeGameObject = this.go; 331 | } 332 | } 333 | else if (draggedTextScene != null) 334 | { 335 | Undo.RegisterSceneUndo("Add scene " + draggedTextScene.name); 336 | 337 | TextSceneDeserializer.LoadAdditive(draggedTextScene, this.go); 338 | } 339 | else if (draggedGO != null) 340 | { 341 | PrefabType pt = EditorUtility.GetPrefabType(dragged); 342 | 343 | if (pt == PrefabType.ModelPrefab || pt == PrefabType.Prefab) 344 | { 345 | Undo.RegisterSceneUndo("Create " + draggedGO.name); 346 | 347 | GameObject instance = EditorUtility.InstantiatePrefab(dragged) as GameObject; 348 | 349 | if (this.go != null) 350 | instance.transform.parent = this.go.transform; 351 | 352 | instance.transform.localPosition = Vector3.zero; 353 | instance.transform.localRotation = Quaternion.identity; 354 | instance.name = instance.name + "_instance"; 355 | 356 | Selection.activeGameObject = instance; 357 | } 358 | } 359 | } 360 | } 361 | 362 | 363 | } 364 | 365 | Event.current.Use(); 366 | return true; 367 | } 368 | 369 | return false; 370 | } 371 | 372 | public void OnGUI(int level, ref float offset) 373 | { 374 | float startOffset = offset; 375 | 376 | GUIStyle style = EntryTypeStyle.Get(type); 377 | 378 | if (go != null) 379 | { 380 | Rect lineRect = new Rect(0, offset, 1000, style.lineHeight); 381 | Rect labelRect = new Rect(level * 20.0f, offset, 1000, style.lineHeight); 382 | Rect expandRect = new Rect(level * 20.0f, offset, 20, style.lineHeight); 383 | 384 | offset += style.lineHeight; 385 | 386 | 387 | 388 | if (selected == this) 389 | style.Draw(lineRect, false, true, true, false); 390 | 391 | 392 | if (hasChildren) 393 | expanded = GUI.Toggle(expandRect, expanded, GUIContent.none, Styles.foldout); 394 | 395 | GUI.Label(labelRect, go.name, style); 396 | 397 | //FIXME: This is to get the scrollbar to work :S 398 | GUILayout.BeginHorizontal(); 399 | GUILayout.Space(level * 20.0f); 400 | GUILayout.Label("", style); 401 | GUILayout.EndHorizontal(); 402 | 403 | } 404 | 405 | if (go == null || expanded) 406 | { 407 | foreach (Entry e in entries) 408 | e.OnGUI(level + 1, ref offset); 409 | } 410 | 411 | Rect selectRect = new Rect(0, startOffset + style.lineHeight * 0.1f, 200, offset-startOffset - style.lineHeight * 0.1f); 412 | 413 | //Base entry 414 | if (go == null) 415 | selectRect.height = float.MaxValue; 416 | 417 | if (selectRect.Contains(Event.current.mousePosition)) 418 | { 419 | if (go != null) 420 | { 421 | if (Event.current.type == EventType.MouseDown) 422 | { 423 | if (Event.current.button == 1) 424 | { 425 | EditorGUIUtility.PingObject(go); 426 | 427 | GenericMenu menu = new GenericMenu(); 428 | 429 | if (type == EntryType.Normal 430 | || type == EntryType.PrefabRoot 431 | || type == EntryType.TextSceneRoot) 432 | { 433 | menu.AddItem(EditorHelper.TextContent("Duplicate"), false, new GenericMenu.MenuFunction(this.Duplicate)); 434 | menu.AddItem(EditorHelper.TextContent("Delete"), false, new GenericMenu.MenuFunction(this.Delete)); 435 | 436 | if (type == EntryType.PrefabRoot) 437 | { 438 | menu.AddSeparator(""); 439 | menu.AddItem(EditorHelper.TextContent("Disconnect"), false, new GenericMenu.MenuFunction(this.DisconnectPrefab)); 440 | } 441 | else if (type == EntryType.TextSceneRoot) 442 | { 443 | menu.AddSeparator(""); 444 | menu.AddItem(EditorHelper.TextContent("Disconnect"), false, new GenericMenu.MenuFunction(this.DisconnectTextScene)); 445 | } 446 | else if (type == EntryType.Normal) 447 | { 448 | menu.AddSeparator(""); 449 | menu.AddItem(EditorHelper.TextContent("Create Empty"), false, 450 | new GenericMenu.MenuFunction(delegate 451 | { 452 | GameObject empty = new GameObject(); 453 | empty.transform.parent = go.transform; 454 | empty.transform.localPosition = Vector3.zero; 455 | empty.transform.localRotation = Quaternion.identity; 456 | })); 457 | } 458 | } 459 | else 460 | menu.AddItem(EditorHelper.TextContent("No options"), false, null); 461 | 462 | menu.ShowAsContext(); 463 | } 464 | else if (Event.current.clickCount == 2) 465 | expanded = !expanded; 466 | 467 | Event.current.Use(); 468 | } 469 | else if (Event.current.type == EventType.MouseUp) 470 | { 471 | if (Event.current.clickCount == 1) 472 | { 473 | Selection.activeGameObject = go; 474 | 475 | TextSceneObject tso = go.GetComponent(); 476 | 477 | if (tso != null) 478 | EditorGUIUtility.PingObject(tso.textScene); 479 | 480 | Object prefabParent = EditorUtility.GetPrefabParent(go); 481 | 482 | if (prefabParent != null) 483 | EditorGUIUtility.PingObject(prefabParent); 484 | } 485 | 486 | Event.current.Use(); 487 | } 488 | else if (Event.current.type == EventType.MouseDrag) 489 | { 490 | DragAndDrop.PrepareStartDrag(); 491 | 492 | UnityEngine.Object[] draggedGO = new Object[1]; 493 | 494 | draggedGO[0] = go; 495 | 496 | 497 | DragAndDrop.objectReferences = draggedGO; 498 | DragAndDrop.SetGenericData("entry", this); 499 | DragAndDrop.StartDrag("Dragging " + go.name); 500 | 501 | Event.current.Use(); 502 | } 503 | } 504 | 505 | HandleDrag(); 506 | } 507 | else 508 | { 509 | //Reset expand timer if the cursor moves out of the entry rect. 510 | expandedTimer = EditorApplication.timeSinceStartup + 0.3; 511 | } 512 | 513 | if (selected == this) 514 | { 515 | if (go != null) 516 | { 517 | if (Event.current.type == EventType.KeyDown) 518 | { 519 | if (Event.current.keyCode == KeyCode.Delete) 520 | { 521 | this.Delete(); 522 | } 523 | //FIXME: Use control/command, not shift. 524 | else if (Event.current.shift && Event.current.keyCode == KeyCode.D) 525 | { 526 | this.Duplicate(); 527 | } 528 | } 529 | } 530 | } 531 | } 532 | 533 | public bool FindAndTagSelected(GameObject gameObject) 534 | { 535 | if (go == gameObject) 536 | { 537 | selected = this; 538 | return true; 539 | } 540 | 541 | foreach (Entry e in entries) 542 | { 543 | if (e.FindAndTagSelected(gameObject)) 544 | { 545 | expanded = true; 546 | return true; 547 | } 548 | } 549 | 550 | return false; 551 | } 552 | 553 | internal void Sort() 554 | { 555 | entries.Sort(delegate(Entry x, Entry y) { return string.Compare(x.go.name, y.go.name); }); 556 | } 557 | 558 | internal void FillExpanded(List expandedObjects) 559 | { 560 | if (expanded) 561 | expandedObjects.Add(go); 562 | 563 | foreach (Entry e in entries) 564 | e.FillExpanded(expandedObjects); 565 | } 566 | 567 | internal void Expand(GameObject gameObject) 568 | { 569 | if (go == gameObject) 570 | { 571 | expanded = true; 572 | return; 573 | } 574 | 575 | foreach (Entry e in entries) 576 | e.Expand(gameObject); 577 | } 578 | } 579 | 580 | Entry root = new Entry(); 581 | 582 | private Vector2 scroll = new Vector2(); 583 | 584 | private double nextCheckForDefaultHierarchy = 0.0f; 585 | 586 | private static bool CheckForDefaultHierarchy() 587 | { 588 | Assembly editorAssembly = Assembly.GetAssembly(typeof(EditorWindow)); 589 | 590 | System.Type hierarchyWindowType = editorAssembly.GetType("UnityEditor.HierarchyWindow"); 591 | 592 | Object[] window = Object.FindObjectsOfTypeAll(hierarchyWindowType); 593 | 594 | if (window.Length > 0) 595 | { 596 | if (EditorUtility.DisplayDialog("Default Hierarchy", "The default hierarchy window seems to be up. It is recommended that this one is not used, as it lets you do a few things regarding TextSceneObjects that the system will not correctly pick up (such as moving TextScene subobjects out of their parent). The default hierarchy do have a lot of handy functionality, though, so you can feel free to ignore this message if you know what the limitations of the TextScene system is. You can turn off future warnings by disabling it in the TextScene menu item.", "Close default hierarchy", "Ignore, I know the limitations!")) 597 | { 598 | foreach (EditorWindow w in window) 599 | { 600 | if (w != null) 601 | w.Close(); 602 | } 603 | } 604 | else 605 | return false; 606 | } 607 | 608 | return true; 609 | } 610 | 611 | public static void CreateHierarchy() 612 | { 613 | CheckForDefaultHierarchy(); 614 | 615 | TextSceneHierarchy tsh = EditorWindow.GetWindow(typeof(TextSceneHierarchy)) as TextSceneHierarchy; 616 | 617 | tsh.Show(); 618 | tsh.OnHierarchyChange(); 619 | } 620 | 621 | protected void OnEnable() 622 | { 623 | nextCheckForDefaultHierarchy = EditorApplication.timeSinceStartup + 2.0f; 624 | OnHierarchyChange(); 625 | } 626 | 627 | protected void Update() 628 | { 629 | //Redundant update. FIXME: Necessary? 630 | TextSceneMonitor.MonitorUpdate(); 631 | 632 | if (!EditorApplication.isPlaying && EditorApplication.timeSinceStartup > nextCheckForDefaultHierarchy) 633 | { 634 | if (PlayerPrefs.GetInt("ShowDefaultHierarchyWarning", 1) > 0) 635 | { 636 | if (CheckForDefaultHierarchy()) 637 | nextCheckForDefaultHierarchy = EditorApplication.timeSinceStartup + 2.0f; 638 | else 639 | nextCheckForDefaultHierarchy = double.MaxValue; 640 | } 641 | 642 | 643 | } 644 | } 645 | 646 | public static void Refresh() 647 | { 648 | TextSceneHierarchy tsh = EditorWindow.GetWindow(typeof(TextSceneHierarchy)) as TextSceneHierarchy; 649 | tsh.OnHierarchyChange(); 650 | } 651 | 652 | protected void OnHierarchyChange() 653 | { 654 | Transform[] transforms = Helper.GetObjectsOfType(); 655 | 656 | List expanded = new List(); 657 | 658 | root.FillExpanded(expanded); 659 | 660 | root = new Entry(); 661 | 662 | foreach (Transform t in transforms) 663 | { 664 | if (t.parent == null) 665 | root.AddChild(t.gameObject); 666 | } 667 | 668 | foreach (GameObject go in expanded) 669 | root.Expand(go); 670 | 671 | root.FindAndTagSelected(Selection.activeGameObject); 672 | 673 | root.Sort(); 674 | 675 | Repaint(); 676 | } 677 | 678 | protected void OnSelectionChange() 679 | { 680 | root.FindAndTagSelected(Selection.activeGameObject); 681 | 682 | Repaint(); 683 | } 684 | 685 | protected void OnGUI() 686 | { 687 | float offset = 0.0f; 688 | 689 | scroll = GUILayout.BeginScrollView(scroll); 690 | GUILayout.BeginVertical(); 691 | root.OnGUI(-1, ref offset); 692 | GUILayout.EndVertical(); 693 | GUILayout.EndScrollView(); 694 | } 695 | } -------------------------------------------------------------------------------- /Assets/Scripts/Editor/TextScene/TextSceneDeserializer.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2010 TerraVision AS 3 | /// See LICENSE file for licensing details 4 | /// 5 | /// TODO: *Ensure that nested, complex objects don't blow up the entire scenefile. 6 | /// *Clean up and comment - subroutine each "type" (primitive, complex, scenelink) 7 | /// *Make the Load functions return a bool instead of a string, since the loading is 8 | /// now somewhat async - the callback passed to Load should provide the caller with 9 | /// the binary name (the scene isn't fully loaded when Load returns, the TextSceneMonitor 10 | /// handles the rest). 11 | 12 | using UnityEngine; 13 | using UnityEditor; 14 | 15 | using System.Xml.Serialization; 16 | using System.IO; 17 | using System.Reflection; 18 | using System.Collections.Generic; 19 | using System.Globalization; 20 | 21 | /// 22 | /// Class reading and populating a scene based on text-based scene files. 23 | /// 24 | public class TextSceneDeserializer 25 | { 26 | public delegate void TempSceneSaved(); 27 | 28 | private enum Pass 29 | { 30 | CreateObjects = 0, 31 | ValueAssignment 32 | } 33 | 34 | private Pass currentPass; 35 | 36 | private Queue gameObjects; 37 | private Queue gameComponents; 38 | 39 | private Dictionary builtinMesh; 40 | private Material builtinMaterial; 41 | 42 | private GameObject container; 43 | private string recursionGuard = ""; 44 | 45 | 46 | /// 47 | /// Loads a TextScene into a clean scene. Will ask user for path. 48 | /// 49 | public static void Load() 50 | { 51 | string path = EditorUtility.OpenFilePanel("Load scene file", Application.dataPath, "txt"); 52 | 53 | TextSceneDeserializer.LoadSafe(path); 54 | } 55 | 56 | /// 57 | /// Loads the currently selected TextScene. Will fail and notify user if the selected file is 58 | /// not a text file. 59 | /// 60 | public static void LoadContext() 61 | { 62 | if (Selection.activeObject == null) 63 | { 64 | EditorUtility.DisplayDialog ("Nothing selected", "You need to select a text scene asset to load one", "OK"); 65 | return; 66 | } 67 | 68 | TextAsset asset = Selection.activeObject as TextAsset; 69 | 70 | string assetPath = ""; 71 | 72 | if (asset != null) 73 | assetPath = AssetDatabase.GetAssetPath(asset); 74 | 75 | if (!assetPath.EndsWith(".txt")) 76 | EditorUtility.DisplayDialog ("Not a text file", "Text scenes can only be loaded from TextAssets (*.txt)", "OK"); 77 | else 78 | { 79 | string fullPath = Application.dataPath 80 | + assetPath.Substring(assetPath.IndexOf('/')); 81 | 82 | TextSceneDeserializer.LoadSafe(fullPath); 83 | } 84 | } 85 | 86 | /// 87 | /// Additively loads a TextScene. All objects from the TextScene will be children of 88 | /// the passed parent, if any. The TextScene will be added with a link to its original 89 | /// source asset, so it can easily be treated as a prefab, or scene-in-scene link. 90 | /// 91 | public static void LoadAdditive(TextAsset asset, GameObject parent) 92 | { 93 | string assetPath = ""; 94 | 95 | if (asset != null) 96 | assetPath = AssetDatabase.GetAssetPath(asset); 97 | 98 | if (!assetPath.EndsWith(".txt")) 99 | EditorUtility.DisplayDialog ("Not a text file", "Text scenes can only be loaded from TextAssets (*.txt)", "OK"); 100 | else 101 | { 102 | string fullPath = Application.dataPath 103 | + assetPath.Substring(assetPath.IndexOf('/')); 104 | 105 | if (fullPath.ToLower() == TextSceneMonitor.Instance.GetCurrentScene().ToLower()) 106 | { 107 | EditorUtility.DisplayDialog("ERROR", "Adding this as a TextScene object (" + assetPath + ") would cause an infinite circular dependency", "OK"); 108 | return; 109 | } 110 | 111 | //string name = assetPath.Substring(assetPath.LastIndexOf('/')); 112 | 113 | string spawnPath = '/' + asset.name; 114 | 115 | if (parent != null) 116 | { 117 | string uniqueParentPath = Helper.GetFullName(parent); 118 | 119 | //Make sure there will be no confusion regarding the parent path. 120 | GameObject[] gos = Helper.FindGameObjectsFromFullName(uniqueParentPath); 121 | 122 | if (gos.Length > 1) 123 | { 124 | EditorUtility.DisplayDialog("ERROR", "There are multiple objects that have the path " + uniqueParentPath + ". Please rename your parent object, or create a new, uniquely named one", "OK"); 125 | return; 126 | } 127 | 128 | spawnPath = uniqueParentPath + spawnPath; 129 | } 130 | 131 | //"Random" name until we get the links set up. 132 | //FIXME: Make sure this is unbreakable :S 133 | GameObject go = new GameObject("TextScene-" + EditorApplication.timeSinceStartup); 134 | 135 | go.AddComponent().textScene = asset; 136 | 137 | (new TextSceneDeserializer(go, TextSceneMonitor.Instance.GetCurrentScene())).LoadScene(fullPath); 138 | 139 | if (parent != null) 140 | { 141 | go.transform.parent = parent.transform; 142 | go.transform.localPosition = Vector3.zero; 143 | go.transform.localRotation = Quaternion.identity; 144 | } 145 | 146 | //If we collide with another path, create a new with a suffix 147 | int suffix = 1; 148 | 149 | string suffixedSpawnPath = spawnPath; 150 | string suffixedName = asset.name; 151 | 152 | while (Helper.FindGameObjectsFromFullName(suffixedSpawnPath).Length > 0) 153 | { 154 | suffixedName = asset.name + suffix; 155 | 156 | suffixedSpawnPath = spawnPath + suffix; 157 | 158 | suffix++; 159 | } 160 | 161 | go.name = suffixedName; 162 | 163 | Selection.activeObject = go; 164 | } 165 | } 166 | 167 | /// 168 | /// Loads a new scene from path. Path is absolute. 169 | /// 170 | public static string Load(string path) 171 | { 172 | return Load(path, null); 173 | } 174 | 175 | public static string Load(string path, TempSceneSaved callback) 176 | { 177 | return (new TextSceneDeserializer()).LoadNewScene(path, callback); 178 | } 179 | 180 | public static string LoadSafe(string path) 181 | { 182 | //Check if the user wants to save his current work 183 | if (EditorApplication.SaveCurrentSceneIfUserWantsTo()) 184 | { 185 | TextSceneMonitor.Instance.SaveIfTempIsNewer(); 186 | } 187 | else 188 | { 189 | Debug.Log("Cancelled TextScene load"); 190 | return ""; 191 | } 192 | 193 | return Load(path); 194 | } 195 | 196 | private TextSceneDeserializer() 197 | { 198 | 199 | } 200 | 201 | private TextSceneDeserializer(GameObject container) 202 | { 203 | this.container = container; 204 | } 205 | 206 | private TextSceneDeserializer(GameObject container, string recursionGuard) 207 | { 208 | this.container = container; 209 | this.recursionGuard = recursionGuard; 210 | } 211 | 212 | public string LoadNewScene(string path) 213 | { 214 | return LoadNewScene(path, null); 215 | } 216 | 217 | //TODO: Return bool instead of string. Pass the binary temp scene path with the callback. 218 | public string LoadNewScene(string path, TempSceneSaved callback) 219 | { 220 | if (EditorApplication.isPlayingOrWillChangePlaymode) 221 | { 222 | EditorUtility.DisplayDialog("Game is running", "You cannot load in Play-mode", "OK"); 223 | return ""; 224 | } 225 | 226 | if (path == null || path.Length == 0) 227 | return ""; 228 | 229 | string startPath = Application.dataPath; 230 | 231 | Debug.Log("Opening scene file: " + path + " current: " + EditorApplication.currentScene); 232 | 233 | //Make sure the file exists 234 | if (!File.Exists(path)) 235 | { 236 | Debug.LogError("File does not exist: " + path); 237 | return ""; 238 | } 239 | 240 | EditorApplication.NewScene(); 241 | 242 | Transform[] sceneObjects = Helper.GetObjectsOfType(); 243 | 244 | foreach (Transform o in sceneObjects) 245 | { 246 | if (o != null && o.parent == null) 247 | { 248 | Debug.Log("Destroying object: " + o.name); 249 | GameObject.DestroyImmediate(o.gameObject); 250 | } 251 | } 252 | 253 | recursionGuard = path; 254 | 255 | LoadScene(path); 256 | 257 | 258 | string tmpScenePath = startPath.Substring(0, startPath.LastIndexOf('/')); 259 | 260 | string assetPath = path.Replace(startPath, ""); 261 | 262 | string[] subFolders = assetPath.Split('/'); 263 | 264 | Debug.Log("tmpScenePath: " + tmpScenePath + " assetPath: " + assetPath); 265 | 266 | //First, make sure we have a temp scenes folder 267 | tmpScenePath += "/TempScenes"; 268 | 269 | if (!Directory.Exists(tmpScenePath)) 270 | Directory.CreateDirectory(tmpScenePath); 271 | 272 | //Laat entry is the filename itself. 273 | for (int i = 0; i < subFolders.Length-1; i++) 274 | { 275 | tmpScenePath += subFolders[i] + "/"; 276 | 277 | if (!Directory.Exists(tmpScenePath)) 278 | Directory.CreateDirectory(tmpScenePath); 279 | } 280 | 281 | string fileName = subFolders[subFolders.Length-1]; 282 | 283 | fileName = fileName.Replace(".txt", ".unity"); 284 | 285 | string scenePath = tmpScenePath + fileName; 286 | 287 | 288 | Debug.Log("Saving binary unity scene at path: " + scenePath + " (" + path + ")"); 289 | 290 | //EditorApplication.SaveScene(scenePath); 291 | //EditorApplication.OpenScene(scenePath); 292 | 293 | //TextSceneMonitor.Instance.SetCurrentScene(path); 294 | 295 | 296 | //FIXME: Bugz0rs: Need to delay the save & reload or else prefabs will behave weirdly, 297 | // such as getting name reset if doing changes on the prefab and position reset 298 | // to prefab pos if hitting revert. 299 | TextSceneMonitor.Instance.DoSaveAndReload(scenePath, callback); 300 | 301 | return scenePath; 302 | } 303 | 304 | /// 305 | /// Loads objects from TextScene file into the currently open scene. 306 | /// 307 | public bool LoadScene(string path) 308 | { 309 | try 310 | { 311 | StreamReader fileStream = File.OpenText(path); 312 | 313 | 314 | gameObjects = new Queue(); 315 | gameComponents = new Queue(); 316 | 317 | SetupBuiltinMeshes(); 318 | 319 | //We're doing this in two passes - first pass simply creates all objects and 320 | //components, the second pass resolves links and assigns them (the second pass 321 | //is necessary for in-scene links, so we're just handling everything in there, 322 | //even prefab and other asset links). 323 | currentPass = Pass.CreateObjects; 324 | Deserialize(fileStream, container); 325 | 326 | fileStream.Close(); 327 | 328 | GameObject[] gameObjectArray = gameObjects.ToArray(); 329 | Component[] gameComponentArray = gameComponents.ToArray(); 330 | 331 | fileStream = File.OpenText(path); 332 | 333 | currentPass = Pass.ValueAssignment; 334 | Deserialize(fileStream, container); 335 | 336 | fileStream.Close(); 337 | 338 | //Don't let the user edit scene-in-scenes (aka prefabs), since 339 | //we currently have no way of applying changes. 340 | if (container != null) 341 | { 342 | foreach (GameObject go in gameObjectArray) 343 | go.hideFlags = HideFlags.NotEditable; 344 | 345 | foreach (Component comp in gameComponentArray) 346 | comp.hideFlags = HideFlags.NotEditable; 347 | } 348 | } 349 | catch (System.Exception e) 350 | { 351 | Debug.LogError("Exception raised when loading level: " + path + " " + e.ToString()); 352 | return false; 353 | } 354 | 355 | return true; 356 | } 357 | 358 | /// 359 | /// Instantiates and destroys the built-in primitives so we can get a reference to their meshes. 360 | /// FIXME: It would be a lot better if we could get these mesh references in a less 361 | /// hack-ish way! 362 | /// 363 | private void SetupBuiltinMeshes() 364 | { 365 | builtinMesh = new Dictionary(); 366 | 367 | GameObject tmp = GameObject.CreatePrimitive(PrimitiveType.Cube); 368 | builtinMesh.Add("Cube", tmp.GetComponent().sharedMesh); 369 | 370 | GameObject.DestroyImmediate(tmp); 371 | 372 | tmp = GameObject.CreatePrimitive(PrimitiveType.Sphere); 373 | builtinMesh.Add("Sphere", tmp.GetComponent().sharedMesh); 374 | 375 | GameObject.DestroyImmediate(tmp); 376 | 377 | tmp = GameObject.CreatePrimitive(PrimitiveType.Plane); 378 | builtinMesh.Add("Plane", tmp.GetComponent().sharedMesh); 379 | 380 | GameObject.DestroyImmediate(tmp); 381 | 382 | tmp = GameObject.CreatePrimitive(PrimitiveType.Capsule); 383 | builtinMesh.Add("Capsule", tmp.GetComponent().sharedMesh); 384 | 385 | GameObject.DestroyImmediate(tmp); 386 | 387 | tmp = GameObject.CreatePrimitive(PrimitiveType.Cylinder); 388 | builtinMesh.Add("Cylinder", tmp.GetComponent().sharedMesh); 389 | 390 | builtinMaterial = tmp.GetComponent().sharedMaterial; 391 | 392 | GameObject.DestroyImmediate(tmp); 393 | } 394 | 395 | /// 396 | /// Main function for deserializing the scene from file. 397 | /// 398 | private void Deserialize(StreamReader stream, GameObject parent) 399 | { 400 | while (!stream.EndOfStream) 401 | { 402 | string line = stream.ReadLine().Trim(); 403 | 404 | ProcessLine(stream, line, parent); 405 | } 406 | } 407 | 408 | private void ProcessLine(StreamReader stream, string line, GameObject parent) 409 | { 410 | if (line.StartsWith("gameobject")) 411 | { 412 | DeserializeGameObject(stream, line.Substring(line.IndexOf(' ')).Trim(), parent); 413 | } 414 | else if (line.StartsWith("prefab")) 415 | { 416 | DeserializePrefab(stream, line.Substring(line.IndexOf(' ')).Trim(), parent); 417 | } 418 | else if (line.StartsWith("textscene")) 419 | { 420 | DeserializeTextScene(stream, line.Substring(line.IndexOf(' ')).Trim(), parent); 421 | } 422 | } 423 | 424 | private void DeserializeTransform(StreamReader stream, Transform transform) 425 | { 426 | string position = stream.ReadLine().Trim(); 427 | string rotation = stream.ReadLine().Trim(); 428 | string localScale = stream.ReadLine().Trim(); 429 | 430 | string[] floats = position.Split(); 431 | 432 | transform.localPosition = new Vector3( 433 | float.Parse(floats[0], CultureInfo.InvariantCulture), 434 | float.Parse(floats[1], CultureInfo.InvariantCulture), 435 | float.Parse(floats[2], CultureInfo.InvariantCulture) 436 | ); 437 | 438 | floats = rotation.Split(); 439 | 440 | transform.localRotation = new Quaternion( 441 | float.Parse(floats[0], CultureInfo.InvariantCulture), 442 | float.Parse(floats[1], CultureInfo.InvariantCulture), 443 | float.Parse(floats[2], CultureInfo.InvariantCulture), 444 | float.Parse(floats[3], CultureInfo.InvariantCulture) 445 | ); 446 | 447 | floats = localScale.Split(); 448 | 449 | transform.localScale = new Vector3( 450 | float.Parse(floats[0], CultureInfo.InvariantCulture), 451 | float.Parse(floats[1], CultureInfo.InvariantCulture), 452 | float.Parse(floats[2], CultureInfo.InvariantCulture) 453 | ); 454 | } 455 | 456 | private void DeserializeTextScene(StreamReader stream, string name, GameObject parent) 457 | { 458 | string line = stream.ReadLine().Trim(); 459 | 460 | char [] separators = new char [] {' ', ','}; 461 | 462 | string[] parts = line.Split(separators, System.StringSplitOptions.RemoveEmptyEntries); 463 | 464 | string assetPath = null; 465 | 466 | //Expected format: 467 | // assetpath Asset/Path/asset, GUID 468 | if (parts.Length > 1) 469 | { 470 | assetPath = parts[1].Trim(); 471 | 472 | //Prefer GUID over path if present. 473 | if (parts.Length > 2) 474 | { 475 | string guid = parts[2].Trim(); 476 | 477 | assetPath = AssetDatabase.GUIDToAssetPath(guid); 478 | } 479 | } 480 | 481 | if (assetPath == null) 482 | { 483 | Debug.LogError("Failed to get path for textscene object '" + name + "': " + line); 484 | return; 485 | } 486 | 487 | string fullPath = EditorHelper.GetProjectFolder() + assetPath; 488 | 489 | GameObject go = null; 490 | 491 | if (currentPass == Pass.CreateObjects) 492 | { 493 | //Random initial name until the local links have been set up. 494 | //FIXME: I can imagine this random stuff can blow up at any point when we least need it. 495 | //go = new GameObject(Random.Range(10000, 100000).ToString()); 496 | go = new GameObject(name); 497 | go.AddComponent().textScene = AssetDatabase.LoadAssetAtPath(assetPath, typeof(TextAsset)) as TextAsset; 498 | 499 | //Debug.Log("Full: " + fullPath + " recguard: " + recursionGuard); 500 | 501 | string fullName = "/" + name; 502 | 503 | if (parent != null) 504 | { 505 | fullName = Helper.GetFullName(parent) + fullName; 506 | } 507 | 508 | if (fullPath.ToLower() == recursionGuard.ToLower()) 509 | { 510 | EditorUtility.DisplayDialog("Recursion guard", "Loading this TextScene (" + assetPath + ") into the current TextScene will throw you into an infinite recursion. Please remove the TextScene object (" + fullName + ") or change the TextScene it points to so it no longer results in a loop", "OK"); 511 | } 512 | else 513 | { 514 | bool result = (new TextSceneDeserializer(go, recursionGuard)).LoadScene(fullPath); 515 | 516 | if (!result) 517 | Debug.LogError("Failed to load TextSceneObject: " + line); 518 | } 519 | 520 | gameObjects.Enqueue(go); 521 | } 522 | else 523 | { 524 | go = gameObjects.Dequeue(); 525 | } 526 | 527 | go.name = name; 528 | 529 | if (parent != null) 530 | go.transform.parent = parent.transform; 531 | 532 | DeserializeTransform(stream, go.transform); 533 | } 534 | 535 | private void DeserializePrefab(StreamReader stream, string name, GameObject parent) 536 | { 537 | GameObject prefab = null; 538 | 539 | string line = stream.ReadLine().Trim(); 540 | 541 | char [] separators = new char [] {' ', ','}; 542 | 543 | string[] parts = line.Split(separators, System.StringSplitOptions.RemoveEmptyEntries); 544 | 545 | //Expected format: 546 | // assetpath Asset/Path/asset, GUID 547 | if (parts.Length > 1) 548 | { 549 | string assetPath = parts[1].Trim(); 550 | 551 | //Prefer GUID over path if present. 552 | if (parts.Length > 2) 553 | { 554 | string guid = parts[2].Trim(); 555 | 556 | assetPath = AssetDatabase.GUIDToAssetPath(guid); 557 | } 558 | 559 | 560 | prefab = AssetDatabase.LoadAssetAtPath(assetPath, typeof(GameObject)) as GameObject; 561 | } 562 | 563 | if (prefab == null) 564 | { 565 | Debug.LogError("Failed to load asset for object '" + name + "': " + line); 566 | return; 567 | } 568 | 569 | 570 | GameObject go = null; 571 | 572 | 573 | if (currentPass == Pass.CreateObjects) 574 | { 575 | go = EditorUtility.InstantiatePrefab(prefab) as GameObject; 576 | gameObjects.Enqueue(go); 577 | } 578 | else 579 | go = gameObjects.Dequeue(); 580 | 581 | go.name = name; 582 | 583 | if (parent != null) 584 | go.transform.parent = parent.transform; 585 | 586 | DeserializeTransform(stream, go.transform); 587 | 588 | 589 | 590 | /* 591 | //Had to try to see if this was related to save/load prefab bugs (336621) 592 | 593 | //EditorUtility.ResetGameObjectToPrefabState(go); 594 | 595 | 596 | 597 | SerializedObject serObj = new SerializedObject(go); 598 | 599 | SerializedProperty nameProp = serObj.FindProperty("m_Name"); 600 | //SerializedProperty localPosX = serObj.FindProperty("m_LocalPosition.x"); 601 | //SerializedProperty localPosY = serObj.FindProperty("m_LocalPosition.y"); 602 | //SerializedProperty localPosZ = serObj.FindProperty("m_LocalPosition.z"); 603 | 604 | nameProp.stringValue = name; 605 | //localPosX.floatValue = go.transform.localPosition.x; 606 | //localPosY.floatValue = go.transform.localPosition.y; 607 | //localPosZ.floatValue = go.transform.localPosition.z; 608 | nameProp.serializedObject.ApplyModifiedProperties(); 609 | 610 | serObj.ApplyModifiedProperties();*/ 611 | } 612 | 613 | private void DeserializeGameObject(StreamReader stream, string name, GameObject parent) 614 | { 615 | //Debug.Log("Loading GameObject: " + name); 616 | 617 | GameObject go = null; 618 | 619 | 620 | if (currentPass == Pass.CreateObjects) 621 | { 622 | go = new GameObject(); 623 | gameObjects.Enqueue(go); 624 | } 625 | else 626 | go = gameObjects.Dequeue(); 627 | 628 | go.name = name; 629 | 630 | if (parent != null) 631 | { 632 | //Debug.Log("Parent: " + parent.name); 633 | go.transform.parent = parent.transform; 634 | } 635 | else 636 | { 637 | //Debug.Log("Parent: null"); 638 | } 639 | 640 | string tagLayerLine = stream.ReadLine(); 641 | 642 | string[] tagLayerLineElements = tagLayerLine.Trim().Split(); 643 | 644 | go.tag = tagLayerLineElements[1]; 645 | go.layer = int.Parse(tagLayerLineElements[3], CultureInfo.InvariantCulture); 646 | 647 | DeserializeTransform(stream, go.transform); 648 | 649 | string children = stream.ReadLine().Trim(); 650 | 651 | if (children.Contains("children")) 652 | { 653 | int childrenCount = int.Parse(children.Split()[1], CultureInfo.InvariantCulture); 654 | 655 | for (int i = 0; i < childrenCount; i++) 656 | { 657 | string child = stream.ReadLine().Trim(); 658 | 659 | ProcessLine(stream, child, go); 660 | } 661 | } 662 | 663 | string components = stream.ReadLine().Trim(); 664 | 665 | if (components.Contains("components")) 666 | { 667 | int componentCount = int.Parse(components.Split()[1], CultureInfo.InvariantCulture); 668 | 669 | //TODO: Subroutine each component read. 670 | for (int c = 0; c < componentCount; c++) 671 | { 672 | string line = stream.ReadLine().Trim(); 673 | 674 | //Debug.Log("Reading component: " + line); 675 | 676 | string[] componentSplit = line.Trim().Split(); 677 | 678 | string componentName = componentSplit[1]; 679 | 680 | int fieldCount = int.Parse(componentName, CultureInfo.InvariantCulture); 681 | 682 | Component comp = null; 683 | 684 | if (currentPass == Pass.CreateObjects) 685 | { 686 | comp = go.AddComponent(componentSplit[0]); 687 | gameComponents.Enqueue(comp); 688 | } 689 | else 690 | comp = gameComponents.Dequeue(); 691 | 692 | for (int i = 0; i < fieldCount; i++) 693 | { 694 | ProcessValueLine(stream, componentName, comp); 695 | } 696 | } 697 | } 698 | } 699 | 700 | private bool ProcessValueLine(StreamReader stream, string name, object obj) 701 | { 702 | //TODO: Rename to 'member' 703 | string field = stream.ReadLine().Trim(); 704 | 705 | //Debug.Log("Reading member: " + field); 706 | 707 | string[] fieldElements = field.Split(); 708 | 709 | string fieldType = fieldElements[0]; 710 | string fieldName = fieldElements[1]; 711 | string fieldEditorType = fieldElements[2]; 712 | string fieldObjectType = fieldElements[3]; 713 | string fieldValue = field.Substring(field.LastIndexOf('=')+1).Trim(); 714 | 715 | //Debug.Log("Reading component member: " + field); 716 | 717 | object parameter = null; 718 | 719 | if (ReadComponentValue(stream, fieldEditorType, fieldObjectType, fieldValue, ref parameter) == false) 720 | return false; 721 | 722 | 723 | if (obj == null) 724 | { 725 | Debug.LogWarning("Skipping member for null object: " + name); 726 | return false; 727 | } 728 | 729 | 730 | if (currentPass == Pass.ValueAssignment) 731 | AssignValue(obj, fieldType, fieldName, parameter); 732 | 733 | return true; 734 | } 735 | 736 | private void AssignValue(object comp, string fieldType, string fieldName, object parameter) 737 | { 738 | try 739 | { 740 | if (fieldType == "field") 741 | { 742 | FieldInfo fi = comp.GetType().GetField(fieldName); 743 | 744 | if (fi != null) 745 | { 746 | fi.SetValue(comp, parameter); 747 | } 748 | else 749 | Debug.LogWarning("Failed to find field (" + fieldName + ") on object: " + comp.GetType().ToString()); 750 | } 751 | else if (fieldType == "property") 752 | { 753 | PropertyInfo pi = comp.GetType().GetProperty(fieldName); 754 | 755 | if (pi != null) 756 | { 757 | MethodInfo mi = pi.GetSetMethod(); 758 | 759 | if (mi != null) 760 | { 761 | object[] paramList = {parameter}; 762 | 763 | //Debug.Log("Set property: " + fieldName); 764 | 765 | mi.Invoke(comp, paramList); 766 | } 767 | else 768 | { 769 | Debug.LogWarning("No setter for property: " + fieldName); 770 | } 771 | } 772 | else 773 | Debug.LogWarning("Failed to find property (" + fieldName + ") on object: " + comp.GetType().ToString()); 774 | 775 | } 776 | else 777 | Debug.LogWarning("Assignment of '" + fieldName + "' failed - invalid field type: " + fieldType); 778 | } 779 | catch (System.Exception e) 780 | { 781 | Debug.LogWarning("Assignment of '" + fieldName + "' failed. Did it change type since the TextScene was saved? " + e.ToString()); 782 | } 783 | } 784 | 785 | private static float ConvertFloat(string s) 786 | { 787 | try 788 | { 789 | return float.Parse(s, CultureInfo.InvariantCulture); 790 | } 791 | catch 792 | { 793 | if (s.ToLower() == "infinity") 794 | return float.PositiveInfinity; 795 | } 796 | 797 | return float.NaN; 798 | } 799 | 800 | private bool ReadComponentValue(StreamReader stream, string fieldEditorType, string fieldObjectType, string fieldValue, ref object parameter) 801 | { 802 | if (fieldEditorType == "asset") 803 | { 804 | System.Type type = Assembly.GetAssembly(typeof(UnityEngine.Object)).GetType(fieldObjectType); 805 | 806 | if (type == null) 807 | { 808 | //FIXME: Hacky way to get assembly? 809 | type = Assembly.GetAssembly(typeof(TextSceneObject)).GetType(fieldObjectType); 810 | 811 | if (type == null) 812 | { 813 | Debug.LogError("Failed to get asset type: " + fieldObjectType); 814 | return false; 815 | } 816 | } 817 | 818 | 819 | if (fieldValue.Length > 0) 820 | { 821 | string[] assetParts = fieldValue.Split(','); 822 | 823 | //Expected format: 824 | // Asset/Path/Comes/First, AssetName, GUID 825 | if (assetParts.Length >= 1) 826 | { 827 | string assetPath = assetParts[0].Trim(); 828 | 829 | //We'll use the GUID instead of the filename to get the asset. 830 | if (assetParts.Length >= 3) 831 | { 832 | string guid = assetParts[2].Trim(); 833 | 834 | assetPath = AssetDatabase.GUIDToAssetPath(guid); 835 | } 836 | 837 | //If there is an assetname defined, we'll search all assets at path instead 838 | //of using the root object. 839 | if (assetParts.Length >= 2) 840 | { 841 | Object[] assets = AssetDatabase.LoadAllAssetsAtPath(assetPath); 842 | 843 | string assetName = assetParts[1].Trim(); 844 | 845 | foreach(Object asset in assets) 846 | { 847 | if (asset.GetType() == type && asset.name == assetName) 848 | { 849 | parameter = asset; 850 | break; 851 | } 852 | } 853 | } 854 | else 855 | { 856 | //Just use whatever the assetpath resolves to if no assetname is defined. 857 | parameter = AssetDatabase.LoadAssetAtPath(assetPath, type); 858 | } 859 | } 860 | } 861 | } 862 | else if (fieldEditorType == "scenelink") 863 | { 864 | System.Type type = Assembly.GetAssembly(typeof(UnityEngine.Object)).GetType(fieldObjectType); 865 | 866 | if (type == null) 867 | { 868 | //FIXME: Hacky way to get assembly? 869 | type = Assembly.GetAssembly(typeof(TextSceneObject)).GetType(fieldObjectType); 870 | 871 | if (type == null) 872 | { 873 | Debug.LogError("Failed to get object type: " + fieldObjectType); 874 | return false; 875 | } 876 | } 877 | 878 | 879 | if (currentPass == Pass.ValueAssignment && fieldValue.Length > 0) 880 | { 881 | string prefix = container != null ? Helper.GetFullName(container) : ""; 882 | 883 | string fullName = prefix + fieldValue; 884 | 885 | GameObject[] matches = Helper.FindGameObjectsFromFullName(fullName); 886 | GameObject go = matches.Length > 0 ? matches[0] : null; 887 | 888 | //FIXME: Why on Earth doesn't this work always? 889 | // Try it having a reference to /Geometry/box/box_instance2 and 890 | // having a second object in the scene at the path 891 | // /Geometry/box/box_instance 892 | // Reported unity case #336886 on this. 893 | //GameObject go = GameObject.Find(fullName); 894 | 895 | if (go == null) 896 | { 897 | EditorUtility.DisplayDialog("Error", "Unable to find object in scene: '" + fullName + "'", "OK"); 898 | return false; 899 | } 900 | else 901 | { 902 | if (type.Equals(typeof(GameObject))) 903 | parameter = go; 904 | else 905 | parameter = go.GetComponent(type); 906 | } 907 | } 908 | } 909 | else if (fieldEditorType == "builtinmesh") 910 | { 911 | if (currentPass == Pass.ValueAssignment) 912 | { 913 | if (builtinMesh.ContainsKey(fieldValue)) 914 | parameter = builtinMesh[fieldValue]; 915 | else 916 | Debug.LogWarning("Unknown builtin mesh: " + fieldValue); 917 | } 918 | } 919 | else if (fieldEditorType == "builtinmaterial") 920 | { 921 | if (currentPass == Pass.ValueAssignment) 922 | { 923 | parameter = builtinMaterial; 924 | } 925 | } 926 | else if (fieldEditorType == "primitive") 927 | { 928 | System.Type primitiveType = System.Type.GetType(fieldObjectType); 929 | 930 | if (primitiveType == null) 931 | { 932 | primitiveType = Assembly.GetAssembly(typeof(UnityEngine.Object)).GetType(fieldObjectType); 933 | } 934 | if (primitiveType == null) 935 | { 936 | primitiveType = Assembly.GetAssembly(typeof(TextSceneObject)).GetType(fieldObjectType); 937 | } 938 | if (primitiveType == null) 939 | { 940 | Debug.LogError("No primitive type found for '" + fieldObjectType + "'"); 941 | return false; 942 | } 943 | 944 | 945 | //Debug.Log("Handling type: " + fieldObjectType); 946 | 947 | char [] separators = new char [] {' ', ',', '(', ')' }; 948 | 949 | if (typeof(System.Single) == primitiveType) 950 | { 951 | parameter = ConvertFloat(fieldValue); 952 | } 953 | else if (typeof(System.Int32) == primitiveType) 954 | parameter = int.Parse(fieldValue, CultureInfo.InvariantCulture); 955 | else if (typeof(System.Boolean) == primitiveType) 956 | parameter = bool.Parse(fieldValue); 957 | else if (typeof(System.String) == primitiveType) 958 | parameter = fieldValue; 959 | else if (typeof(UnityEngine.Vector4) == primitiveType) 960 | { 961 | string[] v = fieldValue.Split(separators, System.StringSplitOptions.RemoveEmptyEntries); 962 | 963 | parameter = new Vector4(float.Parse(v[0], CultureInfo.InvariantCulture), 964 | float.Parse(v[1], CultureInfo.InvariantCulture), 965 | float.Parse(v[2], CultureInfo.InvariantCulture), 966 | float.Parse(v[3], CultureInfo.InvariantCulture)); 967 | } 968 | else if (typeof(UnityEngine.Quaternion) == primitiveType) 969 | { 970 | string[] v = fieldValue.Split(separators, System.StringSplitOptions.RemoveEmptyEntries); 971 | 972 | parameter = new Quaternion(float.Parse(v[0], CultureInfo.InvariantCulture), 973 | float.Parse(v[1], CultureInfo.InvariantCulture), 974 | float.Parse(v[2], CultureInfo.InvariantCulture), 975 | float.Parse(v[3], CultureInfo.InvariantCulture)); 976 | } 977 | else if (typeof(UnityEngine.Vector3) == primitiveType) 978 | { 979 | string[] v = fieldValue.Split(separators, System.StringSplitOptions.RemoveEmptyEntries); 980 | 981 | parameter = new Vector3(float.Parse(v[0], CultureInfo.InvariantCulture), 982 | float.Parse(v[1], CultureInfo.InvariantCulture), 983 | float.Parse(v[2], CultureInfo.InvariantCulture)); 984 | } 985 | else if (typeof(UnityEngine.Vector2) == primitiveType) 986 | { 987 | string[] v = fieldValue.Split(separators, System.StringSplitOptions.RemoveEmptyEntries); 988 | 989 | parameter = new Vector2(float.Parse(v[0], CultureInfo.InvariantCulture), 990 | float.Parse(v[1], CultureInfo.InvariantCulture)); 991 | } 992 | else if (typeof(UnityEngine.Color) == primitiveType) 993 | { 994 | string[] v = fieldValue.Split(separators, System.StringSplitOptions.RemoveEmptyEntries); 995 | 996 | parameter = new Color(float.Parse(v[0], CultureInfo.InvariantCulture), 997 | float.Parse(v[1], CultureInfo.InvariantCulture), 998 | float.Parse(v[2], CultureInfo.InvariantCulture), 999 | float.Parse(v[3], CultureInfo.InvariantCulture)); 1000 | } 1001 | else if (typeof(UnityEngine.Rect) == primitiveType) 1002 | { 1003 | string[] v = fieldValue.Split(separators, System.StringSplitOptions.RemoveEmptyEntries); 1004 | 1005 | parameter = new Rect(float.Parse(v[0], CultureInfo.InvariantCulture), 1006 | float.Parse(v[1], CultureInfo.InvariantCulture), 1007 | float.Parse(v[2], CultureInfo.InvariantCulture), 1008 | float.Parse(v[3], CultureInfo.InvariantCulture)); 1009 | } 1010 | else if (primitiveType.IsEnum) 1011 | { 1012 | string[] names = System.Enum.GetNames(primitiveType); 1013 | System.Array values = System.Enum.GetValues(primitiveType); 1014 | 1015 | for (int ei = 0; ei < names.Length; ei++) 1016 | { 1017 | if (names[ei] == fieldValue) 1018 | { 1019 | //Debug.Log(primitiveType.ToString() + ": '" + names[ei] + "' set value: '" + fieldValue + "'"); 1020 | parameter = values.GetValue(ei); 1021 | break; 1022 | } 1023 | } 1024 | } 1025 | else 1026 | Debug.LogWarning("Unhandled field type: " + fieldObjectType); 1027 | } 1028 | else if (fieldEditorType == "array") 1029 | { 1030 | System.Type arrayType = System.Type.GetType(fieldObjectType); 1031 | 1032 | if (arrayType == null) 1033 | arrayType = Assembly.GetAssembly(typeof(UnityEngine.Object)).GetType(fieldObjectType); 1034 | 1035 | //FIXME: Hacky way to get assembly? 1036 | if (arrayType == null) 1037 | arrayType = Assembly.GetAssembly(typeof(TextSceneObject)).GetType(fieldObjectType); 1038 | 1039 | if (arrayType == null) 1040 | { 1041 | Debug.LogWarning("Found array of unresolvable type: " + fieldObjectType); 1042 | return false; 1043 | } 1044 | 1045 | System.Type arrayElementType = arrayType.GetElementType(); 1046 | 1047 | parameter = System.Array.CreateInstance(arrayElementType, int.Parse(fieldValue, CultureInfo.InvariantCulture)); 1048 | 1049 | 1050 | System.Array arrayEntries = parameter as System.Array; 1051 | 1052 | 1053 | //Debug.Log("Found array: " + arrayElementType.ToString() + " length: " + arrayEntries.Length); 1054 | 1055 | for (int e = 0; e < arrayEntries.Length; e++) 1056 | { 1057 | string arrayEntryLine = stream.ReadLine().Trim(); 1058 | 1059 | //Debug.Log("Reading array entry: " + arrayEntryLine); 1060 | 1061 | string[] elements = arrayEntryLine.Split(); 1062 | 1063 | string editorType = elements[0]; 1064 | string objectType = elements[1]; 1065 | string val = arrayEntryLine.Substring(arrayEntryLine.LastIndexOf('=')+1).Trim(); 1066 | 1067 | //Debug.Log("Array entry: " + editorType + " " + objectType + " = " + val); 1068 | 1069 | object arrayEntry = null; 1070 | 1071 | ReadComponentValue(stream, editorType, objectType, val, ref arrayEntry); 1072 | 1073 | arrayEntries.SetValue(arrayEntry, e); 1074 | } 1075 | } 1076 | else if (fieldEditorType == "complex") 1077 | { 1078 | System.Type complexType = System.Type.GetType(fieldObjectType); 1079 | 1080 | if (complexType == null) 1081 | complexType = Assembly.GetAssembly(typeof(UnityEngine.Object)).GetType(fieldObjectType); 1082 | 1083 | //FIXME: Hacky way to get assembly? 1084 | if (complexType == null) 1085 | complexType = Assembly.GetAssembly(typeof(TextSceneObject)).GetType(fieldObjectType); 1086 | 1087 | if (complexType == null) 1088 | { 1089 | Debug.LogWarning("Failed to find type: " + fieldObjectType); 1090 | return false; 1091 | } 1092 | 1093 | parameter = System.Activator.CreateInstance(complexType); 1094 | 1095 | int members = int.Parse(fieldValue, CultureInfo.InvariantCulture); 1096 | 1097 | //Debug.Log("Reading complex type: " + complexType.ToString() + " members: " + members); 1098 | 1099 | for(int i = 0; i < members; i++) 1100 | ProcessValueLine(stream, fieldObjectType, parameter); 1101 | 1102 | return true; 1103 | } 1104 | else 1105 | return false; 1106 | 1107 | return true; 1108 | } 1109 | } --------------------------------------------------------------------------------