#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using FishNet.Editing.PrefabCollectionGenerator;
using FishNet.Object;
using FishNet.Utility.Extension;
using GameKit.Dependencies.Utilities;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityScene = UnityEngine.SceneManagement.Scene;
using UnitySceneManagement = UnityEngine.SceneManagement;
namespace FishNet.Editing
{
///
/// Contributed by YarnCat! Thank you!
///
public class ReserializeNetworkObjectsEditor : EditorWindow
{
///
/// True if currently iterating.
///
[System.NonSerialized]
internal static bool IsRunning;
private enum ReserializeSceneType : int
{
AllScenes = 0,
OpenScenes = 1,
SelectedScenes = 2,
BuildScenes = 3,
}
private struct OpenScene
{
public UnityScene Scene;
public string Path;
public OpenScene(UnityScene scene)
{
Scene = scene;
Path = scene.path;
}
}
private Texture2D _fishnetLogo;
private Texture2D _buttonBg;
private Texture2D _buttonBgHover;
private GUIStyle _upgradeRequiredStyle;
private GUIStyle _instructionsStyle;
private GUIStyle _buttonStyle;
private bool _loaded;
private bool _iteratePrefabs;
private bool _iterateScenes;
private ReserializeSceneType _sceneReserializeType = ReserializeSceneType.OpenScenes;
private bool _enabledOnlyBuildScenes = true;
private const string UPGRADE_PART_COLOR = "cd61ff";
private const string UPGRADE_COMPLETE_COLOR = "32e66e";
private const string PREFS_PREFIX = "FishNetReserialize";
private static ReserializeNetworkObjectsEditor _window;
[MenuItem("Tools/Fish-Networking/Utility/Reserialize NetworkObjects", false, 400)]
internal static void ReserializeNetworkObjects()
{
if (ApplicationState.IsPlaying())
{
Debug.LogError($"NetworkObjects cannot be reserialized while in play mode.");
return;
}
InitializeWindow();
}
private static void InitializeWindow()
{
if (_window != null)
return;
_window = (ReserializeNetworkObjectsEditor)EditorWindow.GetWindow(typeof(ReserializeNetworkObjectsEditor));
_window.position = new(0f, 0f, 550f, 300f);
Rect mainPos;
mainPos = EditorGUIUtility.GetMainWindowPosition();
Rect pos = _window.position;
float w = (mainPos.width - pos.width) * 0.5f;
float h = (mainPos.height - pos.height) * 0.5f;
pos.x = mainPos.x + w;
pos.y = mainPos.y + h;
_window.position = pos;
}
private static void StyleWindow()
{
if (_window == null)
return;
_window._fishnetLogo = (Texture2D)AssetDatabase.LoadAssetAtPath("Assets/FishNet/Runtime/Editor/Textures/UI/Logo_With_Text.png", typeof(Texture));
_window._upgradeRequiredStyle = new("label");
_window._upgradeRequiredStyle.fontSize = 20;
_window._upgradeRequiredStyle.wordWrap = true;
_window._upgradeRequiredStyle.alignment = TextAnchor.MiddleCenter;
_window._upgradeRequiredStyle.normal.textColor = new Color32(255, 102, 102, 255);
_window._instructionsStyle = new("label");
_window._instructionsStyle.fontSize = 14;
_window._instructionsStyle.wordWrap = true;
_window._instructionsStyle.alignment = TextAnchor.MiddleCenter;
_window._instructionsStyle.normal.textColor = new Color32(255, 255, 255, 255);
_window._instructionsStyle.hover.textColor = new Color32(255, 255, 255, 255);
_window._buttonBg = MakeBackgroundTexture(1, 1, new Color32(52, 111, 255, 255));
_window._buttonBgHover = MakeBackgroundTexture(1, 1, new Color32(99, 153, 255, 255));
_window._buttonStyle = new("button");
_window._buttonStyle.fontSize = 18;
_window._buttonStyle.fontStyle = FontStyle.Bold;
_window._buttonStyle.normal.background = _window._buttonBg;
_window._buttonStyle.active.background = _window._buttonBgHover;
_window._buttonStyle.focused.background = _window._buttonBgHover;
_window._buttonStyle.onFocused.background = _window._buttonBgHover;
_window._buttonStyle.hover.background = _window._buttonBgHover;
_window._buttonStyle.onHover.background = _window._buttonBgHover;
_window._buttonStyle.alignment = TextAnchor.MiddleCenter;
_window._buttonStyle.normal.textColor = new(1, 1, 1, 1);
}
private void OnGUI()
{
//If not yet loaded then set last used values.
if (!_loaded)
{
LoadLastValues();
_loaded = true;
}
float thisWidth = this.position.width;
StyleWindow();
//Starting values.
Vector2 requiredSize = new Vector2(this.position.width, 160f);
GUILayout.Box(_fishnetLogo, GUILayout.Width(requiredSize.x), GUILayout.Height(requiredSize.y));
GUILayout.Space(8f);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(5f);
CreateInformationLabel("Use this window to refresh serialized values on all NetworkObject prefabs and scene NetworkObjects.");
EditorGUILayout.EndHorizontal();
GUILayout.Space(8f);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(30f);
_iteratePrefabs = EditorGUILayout.Toggle("Reserialize Prefabs", _iteratePrefabs);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
//Some dumb reason Unity moves the checkbox further when using nested settings.
float rebuildScenesSpacing = (_iterateScenes) ? 27f : 30f;
GUILayout.Space(rebuildScenesSpacing);
EditorGUILayout.BeginVertical();
_iterateScenes = EditorGUILayout.Toggle("Reserialize Scenes", _iterateScenes);
if (_iterateScenes)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(15f);
_sceneReserializeType = (ReserializeSceneType)EditorGUILayout.EnumPopup("Targeted Scenes", _sceneReserializeType);
EditorGUILayout.EndHorizontal();
requiredSize.y += 20f;
if (_sceneReserializeType == ReserializeSceneType.BuildScenes)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(30f);
_enabledOnlyBuildScenes = EditorGUILayout.Toggle("Enabled Only", _enabledOnlyBuildScenes);
EditorGUILayout.EndHorizontal();
requiredSize.y += 18f;
}
if (_sceneReserializeType != ReserializeSceneType.OpenScenes)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(30f);
EditorGUILayout.HelpBox("This operation will open and close targeted scene one at a time. Your current open scenes will be closed and re-opened without saving.", MessageType.Warning);
EditorGUILayout.EndHorizontal();
requiredSize.y += 40f;
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
requiredSize.y += 80f;
GUILayout.Space(8f);
EditorGUILayout.BeginHorizontal();
if (!_iteratePrefabs && !_iterateScenes)
GUI.enabled = false;
if (GUILayout.Button("Run Task"))
{
IsRunning = true;
SaveLastValues();
ReserializeProjectPrefabs();
ReserializeScenes();
LogColoredText($"Task complete.", UPGRADE_COMPLETE_COLOR);
_iteratePrefabs = false;
_iterateScenes = false;
IsRunning = false;
}
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
this.minSize = requiredSize;
this.maxSize = this.minSize;
void CreateInformationLabel(string text, FontStyle? style = null)
{
EditorGUILayout.BeginHorizontal();
FontStyle firstStyle = _instructionsStyle.fontStyle;
if (style != null)
_instructionsStyle.fontStyle = style.Value;
GUILayout.Label(text, _instructionsStyle, GUILayout.Width(thisWidth * 0.95f));
_instructionsStyle.fontStyle = firstStyle;
EditorGUILayout.EndHorizontal();
requiredSize.y += 55f;
}
}
private void LoadLastValues()
{
_iteratePrefabs = EditorPrefs.GetBool($"{PREFS_PREFIX}{nameof(_iteratePrefabs)}", defaultValue: false);
_iterateScenes = EditorPrefs.GetBool($"{PREFS_PREFIX}{nameof(_iterateScenes)}", defaultValue: false);
_sceneReserializeType = (ReserializeSceneType)EditorPrefs.GetInt($"{PREFS_PREFIX}{nameof(_sceneReserializeType)}", defaultValue: (int)ReserializeSceneType.OpenScenes);
_enabledOnlyBuildScenes = EditorPrefs.GetBool($"{PREFS_PREFIX}{nameof(_enabledOnlyBuildScenes)}", defaultValue: true);
}
private void SaveLastValues()
{
EditorPrefs.SetBool($"{PREFS_PREFIX}{nameof(_iteratePrefabs)}", _iteratePrefabs);
EditorPrefs.SetBool($"{PREFS_PREFIX}{nameof(_iterateScenes)}", _iterateScenes);
EditorPrefs.SetInt($"{PREFS_PREFIX}{nameof(_sceneReserializeType)}", (int)_sceneReserializeType);
EditorPrefs.SetBool($"{PREFS_PREFIX}{nameof(_enabledOnlyBuildScenes)}", _enabledOnlyBuildScenes);
}
private void ReserializeProjectPrefabs()
{
if (!_iteratePrefabs)
return;
int checkedObjects = 0;
int duplicateNetworkObjectsRemoved = 0;
bool modified = false;
List networkObjects = Generator.GetNetworkObjects(settings: null);
foreach (NetworkObject nob in networkObjects)
{
checkedObjects++;
duplicateNetworkObjectsRemoved += nob.RemoveDuplicateNetworkObjects();
nob.ReserializeEditorSetValues(setWasActiveDuringEdit: true, setSceneId: false);
EditorUtility.SetDirty(nob);
modified = true;
}
if (modified)
AssetDatabase.SaveAssets();
Debug.Log($"Reserialized {checkedObjects} NetworkObject prefabs. Removed {duplicateNetworkObjectsRemoved} duplicate NetworkObject components.");
}
private void ReserializeScenes()
{
if (!_iterateScenes)
return;
int duplicateNetworkObjectsRemoved = 0;
int checkedObjects = 0;
int checkedScenes = 0;
int changedObjects = 0;
List openScenes = GetOpenScenes();
//If running for open scenes only.
if (_sceneReserializeType == ReserializeSceneType.OpenScenes)
{
ReserializeScenes(openScenes, ref checkedScenes, ref checkedObjects, ref changedObjects, ref duplicateNetworkObjectsRemoved);
}
//Running on multiple scenes.
else
{
//When working on multiple scenes make sure open scenes are not dirty to prevent data loss.
foreach (OpenScene os in openScenes)
{
if (os.Scene.isDirty)
{
Debug.LogError($"One or more open scenes are dirty. To prevent data loss scene reserialization will not complete. Ensure all open scenes are saved before continuing.");
return;
}
}
List targetedScenes;
if (_sceneReserializeType == ReserializeSceneType.SelectedScenes)
{
targetedScenes = Selection.GetFiltered(SelectionMode.Assets).ToList();
}
else if (_sceneReserializeType == ReserializeSceneType.AllScenes)
{
targetedScenes = new();
string[] scenePaths = Generator.GetProjectFiles("Assets", "unity", new(), recursive: true);
foreach (string path in scenePaths)
{
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath(path);
if (sceneAsset != null)
targetedScenes.Add(sceneAsset);
}
}
else if (_sceneReserializeType == ReserializeSceneType.BuildScenes)
{
targetedScenes = new();
EditorBuildSettingsScene[] buildScenes = EditorBuildSettings.scenes;
foreach (EditorBuildSettingsScene bs in buildScenes)
{
if (_enabledOnlyBuildScenes && !bs.enabled)
continue;
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath(bs.path);
if (sceneAsset != null)
targetedScenes.Add(sceneAsset);
}
}
else
{
Debug.LogError($"Unsupported {nameof(ReserializeSceneType)} type {_sceneReserializeType}.");
return;
}
ReserializeScenes(targetedScenes, ref checkedScenes, ref checkedObjects, ref changedObjects, ref duplicateNetworkObjectsRemoved);
//Reopen original scenes.
for (int i = 0; i < openScenes.Count; i++)
{
string path = openScenes[i].Path;
/* Make sure asset exists before trying to reopen scene.
* Its possible the dev had a scene open that wasn't saved, which
* would otherwise result in an error here. */
SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath(path);
if (sceneAsset != null)
{
OpenSceneMode mode = (i == 0) ? OpenSceneMode.Single : OpenSceneMode.Additive;
EditorSceneManager.OpenScene(path, mode);
}
}
}
if (changedObjects > 0)
AssetDatabase.SaveAssets();
string saveText = ((_sceneReserializeType == ReserializeSceneType.OpenScenes) && (changedObjects > 0)) ? " Please save your open scenes." : string.Empty;
Debug.Log($"Checked {checkedObjects} NetworkObjects over {checkedScenes} scenes. {changedObjects} sceneIds were generated. {duplicateNetworkObjectsRemoved} duplicate NetworkObject components were removed. {saveText}");
LogColoredText($"Scene NetworkObjects refreshed.", UPGRADE_PART_COLOR);
List GetOpenScenes()
{
List result = new();
int sceneCount = UnitySceneManagement.SceneManager.sceneCount;
for (int i = 0; i < sceneCount; i++)
{
UnityScene scene = UnitySceneManagement.SceneManager.GetSceneAt(i);
if (scene.isLoaded)
result.Add(new(scene));
}
return result;
}
}
///
/// Refreshes NetworkObjects for specified scenes.
///
private static void ReserializeScenes(List sceneAssets, ref int checkedScenes, ref int checkedObjects, ref int changedObjects, ref int duplicateNetworkObjectsRemoved)
{
foreach (SceneAsset sa in sceneAssets)
{
string path = AssetDatabase.GetAssetPath(sa);
UnityScene scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
List foundNobs = NetworkObject.CreateSceneId(scene, force: true, out int changed);
foreach (NetworkObject n in foundNobs)
{
duplicateNetworkObjectsRemoved += n.RemoveDuplicateNetworkObjects();
n.ReserializeEditorSetValues(setWasActiveDuringEdit: true, setSceneId: false);
}
EditorSceneManager.SaveScene(scene);
checkedScenes++;
checkedObjects += foundNobs.Count;
changedObjects += changed;
}
}
///
/// Refreshes NetworkObjects in OpenScenes.
///
private static void ReserializeScenes(List openScenes, ref int checkedScenes, ref int checkedObjects, ref int changedObjects, ref int duplicateNetworkObjectsRemoved)
{
foreach (OpenScene os in openScenes)
{
List foundNobs = NetworkObject.CreateSceneId(os.Scene, force: true, out int changed);
foreach (NetworkObject n in foundNobs)
{
duplicateNetworkObjectsRemoved += n.RemoveDuplicateNetworkObjects();
n.ReserializeEditorSetValues(setWasActiveDuringEdit: true, setSceneId: false);
}
checkedScenes++;
checkedObjects += foundNobs.Count;
changedObjects += changed;
}
}
private static void LogColoredText(string txt, string hexColor)
{
Debug.Log($"{txt}");
}
private static Texture2D MakeBackgroundTexture(int width, int height, Color color)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
pixels[i] = color;
Texture2D backgroundTexture = new(width, height);
backgroundTexture.SetPixels(pixels);
backgroundTexture.Apply();
return backgroundTexture;
}
}
}
#endif