#if (UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2) #define UNITY_PRE_5_3 #endif #if (UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7) #define UNITY_PRE_5_0 #endif #if UNITY_5_3_OR_NEWER || UNITY_5 #define UNITY_5_OR_NEWER #endif // Unity 5.1 introduced a new networking library. // Unless we define PLAYMAKER_LEGACY_NETWORK old network actions are disabled #if !(UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || PLAYMAKER_LEGACY_NETWORK) #define UNITY_NEW_NETWORK #endif // Some platforms do not support networking (at least the old network library) #if (UNITY_FLASH || UNITY_NACL || UNITY_METRO || UNITY_WP8 || UNITY_WIIU || UNITY_PSM || UNITY_WEBGL || UNITY_PS3 || UNITY_PS4 || UNITY_XBOXONE) #define PLATFORM_NOT_SUPPORTED #endif using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using HutongGames.PlayMaker; using UnityEditor; #if !UNITY_PRE_5_3 using UnityEditor.SceneManagement; #endif using UnityEngine; using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; namespace HutongGames.PlayMakerEditor { public class ProjectTools { // Change MenuRoot to move the Playmaker Menu // E.g., MenuRoot = "Plugins/PlayMaker/" private const string MenuRoot = "PlayMaker/"; [MenuItem(MenuRoot + "Tools/Update All Loaded FSMs", false, 25)] public static void UpdateAllLoadedFSMs() { ReSaveAllLoadedFSMs(); } [MenuItem(MenuRoot + "Tools/Update All FSMs in Build", false, 26)] public static void UpdateAllFSMsInBuild() { UpdateScenesInBuild(); } [MenuItem(MenuRoot + "Tools/Preprocess Prefab FSMs in Build", false, 27)] public static void PreprocessPrefabFSMs() { if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return; var timer = Stopwatch.StartNew(); var logOutput = DoPreprocessPrefabFSMsInBuild(); logOutput += "\nElapsed Time: " + timer.Elapsed.Seconds + "s"; Debug.Log(logOutput); } [MenuItem(MenuRoot + "Tools/Preprocess All Prefab FSMs", false, 27)] public static void PreprocessAllPrefabFSMs() { var timer = Stopwatch.StartNew(); var logOutput = DoPreprocessAllPrefabFSMs(); logOutput += "\nElapsed Time: " + timer.Elapsed.Seconds + "s"; Debug.Log(logOutput); } /*WIP [MenuItem(MenuRoot + "Tools/Scan Scenes", false, 33)] public static void ScanScenesInProject() { FindAllScenes(); } */ /// /// Collects all prefabs with FSMs referenced by scenes in the build. /// Then preprocess all FSMs on those prefabs. /// TODO: check if this handles: /// - PlayMakerFSMs referenced in actions /// - PlayMakerFSMs in Resources folders /// private static string DoPreprocessPrefabFSMsInBuild() { var loadedScenes = GetLoadedScenes(); var report = "Preprocess Prefab FSMs in Build:"; // We open enabled scenes in the build to find prefabs with PlayMakerFSMs // We do this first to avoid an AssetDatabase reload for each scene // Processing fsms in the scene load loop is much slower var prefabs = GetAllPrefabFSMsInBuild(); report += "\nPrefab FSMs found: " + prefabs.Count; if (prefabs.Count == 0) return report; // Note: StartAssetEditing/StopAssetEditing doesn't seem to work across scene loading. // another reason to collect all the prefabs first. AssetDatabase.StartAssetEditing(); report += DoPreprocessPrefabFSMs(prefabs); AssetDatabase.StopAssetEditing(); LoadScenes(loadedScenes); return report; } /// /// Get the main GameObject for a prefab file. /// In 2018.3+ loads the prefab so it can be edited. /// public static GameObject GetPrefabGameObject(string prefabPath) { GameObject go = null; // We need to put this in a try block otherwise it can hang Unity editor try { #if UNITY_2018_3_OR_NEWER go = PrefabUtility.LoadPrefabContents(prefabPath); #else go = AssetDatabase.LoadAssetAtPath(prefabPath); #endif } catch (Exception e) { Debug.LogException(e); } return go; } /// /// Get all scenes currently open in the editor /// public static List GetLoadedScenes() { var openScenes = new List(); for (var i = 0; i < EditorSceneManager.loadedSceneCount; i++) { openScenes.Add(SceneManager.GetSceneAt(i).path); } return openScenes; } /// /// Get all enabled scenes in Build Settings. /// public static List GetScenesInBuild() { return (from scene in EditorBuildSettings.scenes where scene.enabled select scene.path).ToList(); } /// /// Load multiple scenes in the editor. /// /// public static void LoadScenes(List scenes) { if (scenes.Count == 0 || string.IsNullOrEmpty(scenes[0])) return; var loadedScenes = GetLoadedScenes(); if (loadedScenes.SequenceEqual(scenes)) return; EditorSceneManager.OpenScene(scenes[0], OpenSceneMode.Single); for (var i = 1; i < scenes.Count; i++) { EditorSceneManager.OpenScene(scenes[i], OpenSceneMode.Additive); } } /// /// Get all prefab files in build that have PlayMakerFSMs /// public static List GetAllPrefabFSMsInBuild() { var scenes = GetScenesInBuild(); var loadedScenes = GetLoadedScenes(); var prefabs = new List(); // first get prefab FSMs that are already loaded GetLoadedPrefabFSMs(prefabs); foreach (var scene in loadedScenes) { scenes.Remove(scene); } // now iterate through other scenes in build float sceneCount = scenes.Count; if (sceneCount > 0) { for (var i = 0; i < scenes.Count; i++) { var scene = scenes[i]; EditorSceneManager.OpenScene(scene, OpenSceneMode.Single); var cancel = EditorUtility.DisplayCancelableProgressBar("PlayMaker", "Finding prefab FSMs in scene: " + scene, i / sceneCount); if (cancel) return new List(); GetLoadedPrefabFSMs(prefabs); } EditorUtility.ClearProgressBar(); } return prefabs; } /// /// Get loaded prefab files that have PlayMakerFSMs /// public static void GetLoadedPrefabFSMs(List prefabs) { var fsmList = Resources.FindObjectsOfTypeAll(); for (var i = 0; i < fsmList.Length; i++) { var playMakerFSM = fsmList[i]; if (playMakerFSM == null || !FsmPrefabs.IsPrefab(playMakerFSM.Fsm)) continue; var assetPath = AssetDatabase.GetAssetPath(playMakerFSM); if (prefabs.Contains(assetPath)) continue; prefabs.Add(assetPath); } } private static string DoPreprocessPrefabFSMs(List prefabs) { var report = ""; if (prefabs.Count == 0) return report; float prefabCount = prefabs.Count; for (int i = 0; i < prefabCount; i++) { var prefab = prefabs[i]; if (prefab == null) continue; var cancel = EditorUtility.DisplayCancelableProgressBar("PlayMaker", prefab.Substring(7), i/prefabCount); if (cancel) return report + "\nCancelled"; var go = GetPrefabGameObject(prefab); if (go == null) continue; var prefabFSMs = go.GetComponentsInChildren(); if (prefabFSMs.Length > 0) { foreach (var prefabFSM in prefabFSMs) { #if UNITY_2018_3_OR_NEWER // nested prefab will be processes as a prefab, so skip here if (PrefabUtility.IsPartOfPrefabInstance(prefabFSM)) { //report += "\nSkipping Nested Prefab: " + FsmUtility.GetFullFsmLabel(prefabFSM); continue; } #endif //report += "\nPreprocess: " + FsmUtility.GetFullFsmLabel(prefabFSM); prefabFSM.Preprocess(); } EditorUtility.SetDirty(go); #if UNITY_2018_3_OR_NEWER PrefabUtility.SaveAsPrefabAsset(go, prefab); } PrefabUtility.UnloadPrefabContents(go); #else } #endif } EditorUtility.ClearProgressBar(); return report; } /// /// /// private static string DoPreprocessAllPrefabFSMs() { var report = "Preprocess All Prefab FSMs:"; AssetDatabase.StartAssetEditing(); var prefabs = Files.GetPrefabsWithFsmComponent(); report += "\nPrefabs found: " + prefabs.Count; if (prefabs.Count == 0) return report; float prefabCount = prefabs.Count; for (int i = 0; i < prefabCount; i++) { var prefab = prefabs[i]; if (prefab == null) continue; var cancel = EditorUtility.DisplayCancelableProgressBar("PlayMaker", prefab, i/prefabCount); if (cancel) return report + "\nCancelled"; var go = GetPrefabGameObject(prefab); if (go == null) continue; var prefabFSMs = go.GetComponentsInChildren(); if (prefabFSMs.Length > 0) { foreach (var prefabFSM in prefabFSMs) { #if UNITY_2018_3_OR_NEWER // nested prefab will be processes as a prefab, so skip here if (PrefabUtility.IsPartOfPrefabInstance(prefabFSM)) { //report += "\nSkipping Nested Prefab: " + FsmUtility.GetFullFsmLabel(prefabFSM); continue; } #endif //report += "\nPreprocess: " + FsmUtility.GetFullFsmLabel(prefabFSM); prefabFSM.Preprocess(); } EditorUtility.SetDirty(go); #if UNITY_2018_3_OR_NEWER PrefabUtility.SaveAsPrefabAsset(go, prefab); } PrefabUtility.UnloadPrefabContents(go); #else } #endif } EditorUtility.ClearProgressBar(); AssetDatabase.StopAssetEditing(); return report; } private static List GetAllPrefabFiles() { var searchDirectory = new DirectoryInfo(Application.dataPath); var prefabFiles = searchDirectory.GetFiles("*.prefab", SearchOption.AllDirectories); var paths = new List(); float totalFiles = prefabFiles.Length; for (int i = 0; i < prefabFiles.Length; i++) { var file = prefabFiles[i]; EditorUtility.DisplayProgressBar("PlayMaker", "Finding prefabs...", i / totalFiles); var filePath = file.FullName.Replace('\\', '/').Replace(Application.dataPath, "Assets"); //Debug.Log(filePath + "\n" + Application.dataPath); paths.Add(filePath); } EditorUtility.ClearProgressBar(); return paths; } private static void ReSaveAllLoadedFSMs() { Debug.Log("Checking loaded FSMs..."); FsmEditor.RebuildFsmList(); foreach (var fsm in FsmEditor.FsmList) { // Re-initialize loads data and forces a dirty check // so we can just call this and let it handle dirty etc. fsm.Reinitialize(); } } private static void UpdateScenesInBuild() { // Allow the user to save his work! #if UNITY_PRE_5_3 if (!EditorApplication.SaveCurrentSceneIfUserWantsTo()) #else if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) #endif { return; } LoadPrefabsWithPlayMakerFSMComponents(); foreach (var scene in EditorBuildSettings.scenes) { Debug.Log("Open Scene: " + scene.path); #if UNITY_PRE_5_3 EditorApplication.OpenScene(scene.path); #else EditorSceneManager.OpenScene(scene.path); #endif #if UNITY_5_OR_NEWER UpdateGetSetPropertyActions(); #endif ReSaveAllLoadedFSMs(); #if UNITY_PRE_5_3 if (!EditorApplication.SaveScene()) #else if (!EditorSceneManager.SaveOpenScenes()) #endif { Debug.LogError("Could not save scene!"); } } } private static void LoadPrefabsWithPlayMakerFSMComponents() { Debug.Log("Finding Prefabs with PlayMakerFSMs"); var searchDirectory = new DirectoryInfo(Application.dataPath); var prefabFiles = searchDirectory.GetFiles("*.prefab", SearchOption.AllDirectories); foreach (var file in prefabFiles) { var filePath = file.FullName.Replace(@"\", "/").Replace(Application.dataPath, "Assets"); //Debug.Log(filePath + "\n" + Application.dataPath); var dependencies = AssetDatabase.GetDependencies(new[] { filePath }); foreach (var dependency in dependencies) { if (dependency.Contains("/PlayMaker.dll")) { Debug.Log("Found Prefab with FSM: " + filePath); AssetDatabase.LoadAssetAtPath(filePath, typeof(GameObject)); } } } FsmEditor.RebuildFsmList(); } #if UNITY_5_OR_NEWER private static void UpdateGetSetPropertyActions() { var getPropertyActionType = ActionData.GetActionType("HutongGames.PlayMaker.Actions.GetProperty"); var setPropertyActionType = ActionData.GetActionType("HutongGames.PlayMaker.Actions.SetProperty"); FsmEditor.RebuildFsmList(); foreach (var fsm in FsmEditor.FsmList) { if (fsm.GameObject == null) continue; // can't update property paths without GameObject var upgraded = false; foreach (var state in fsm.States) { foreach (var action in state.Actions) { var actionType = action.GetType(); if (actionType == getPropertyActionType) { var targetPropertyField = getPropertyActionType.GetField("targetProperty", BindingFlags.Public | BindingFlags.Instance); if (targetPropertyField != null) { upgraded |= TryUpgradeFsmProperty(fsm.GameObject, targetPropertyField.GetValue(action) as FsmProperty); } } else if (actionType == setPropertyActionType) { var targetPropertyField = setPropertyActionType.GetField("targetProperty", BindingFlags.Public | BindingFlags.Instance); if (targetPropertyField != null) { upgraded |= TryUpgradeFsmProperty(fsm.GameObject, targetPropertyField.GetValue(action) as FsmProperty); } } } } if (upgraded) { //Undo called in batch operation seems to crash Unity //FsmEditor.SaveActions(fsm); foreach (var state in fsm.States) { state.SaveActions(); } FsmEditor.SetFsmDirty(fsm, true); #if !UNITY_PRE_5_3 UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(fsm.Owner.gameObject.scene); #elif !UNITY_PRE_5_0 // Not sure if we need to do this...? UnityEditor.EditorApplication.MarkSceneDirty(); #endif } } } private static bool TryUpgradeFsmProperty(GameObject gameObject, FsmProperty fsmProperty) { if (gameObject == null || fsmProperty == null) return false; var propertyPath = fsmProperty.PropertyName; if (string.IsNullOrEmpty(propertyPath)) return false; var parts = propertyPath.Split('.'); if (TryFindComponent(gameObject, fsmProperty, parts[0])) { var oldPath = fsmProperty.PropertyName; fsmProperty.PropertyName = string.Join(".", parts, 1, parts.Length-1); Debug.Log("Fixed: " + oldPath + "->" + fsmProperty.PropertyName); return true; } return false; } private static bool TryFindComponent(GameObject gameObject, FsmProperty fsmProperty, string component) { if (component == "rigidbody") return FixFsmProperty(gameObject, fsmProperty, typeof(Rigidbody)); if (component == "rigidbody2D") return FixFsmProperty(gameObject, fsmProperty, typeof(Rigidbody2D)); if (component == "camera") return FixFsmProperty(gameObject, fsmProperty, typeof(Camera)); if (component == "light") return FixFsmProperty(gameObject, fsmProperty, typeof(Light)); if (component == "animation") return FixFsmProperty(gameObject, fsmProperty, typeof(Animation)); if (component == "constantForce") return FixFsmProperty(gameObject, fsmProperty, typeof(ConstantForce)); if (component == "renderer") return FixFsmProperty(gameObject, fsmProperty, typeof(Renderer)); if (component == "audio") return FixFsmProperty(gameObject, fsmProperty, typeof(AudioSource)); #if !UNITY_2017_2_OR_NEWER if (component == "guiText") return FixFsmProperty(gameObject, fsmProperty, typeof(GUIText)); if (component == "guiTexture") return FixFsmProperty(gameObject, fsmProperty, typeof(GUITexture)); #endif if (component == "collider") return FixFsmProperty(gameObject, fsmProperty, typeof(Collider)); if (component == "collider2D") return FixFsmProperty(gameObject, fsmProperty, typeof(Collider2D)); if (component == "hingeJoint") return FixFsmProperty(gameObject, fsmProperty, typeof(HingeJoint)); if (component == "particleSystem") return FixFsmProperty(gameObject, fsmProperty, typeof(ParticleSystem)); #if !UNITY_5_4_OR_NEWER if (component == "particleEmitter") return FixFsmProperty(gameObject, fsmProperty, typeof(ParticleEmitter)); #endif #if !(PLATFORM_NOT_SUPPORTED || UNITY_NEW_NETWORK || PLAYMAKER_NO_NETWORK) if (component == "networkView") return FixFsmProperty(gameObject, fsmProperty, typeof(NetworkView)); #endif return false; } private static bool FixFsmProperty(GameObject gameObject, FsmProperty fsmProperty, Type componentType) { fsmProperty.TargetObject.Value = gameObject.GetComponent(componentType); fsmProperty.TargetObject.ObjectType = componentType; return true; } #endif /* WIP [Localizable(false)] private static void FindAllScenes() { Debug.Log("Finding all scenes..."); var searchDirectory = new DirectoryInfo(Application.dataPath); var assetFiles = searchDirectory.GetFiles("*.unity", SearchOption.AllDirectories); foreach (var file in assetFiles) { var filePath = file.FullName.Replace(@"\", "/").Replace(Application.dataPath, "Assets"); var obj = AssetDatabase.LoadAssetAtPath(filePath, typeof(Object)); if (obj == null) { //Debug.Log(filePath + ": null!"); } else if (obj.GetType() == typeof(Object)) { Debug.Log(filePath);// + ": " + obj.GetType().FullName); } //var obj = AssetDatabase. } } */ } }