Skip to content

Commit

Permalink
feat: use scriptable object for spawnable prefabs (#1127)
Browse files Browse the repository at this point in the history
squash of 3 commits:

- added network prefabs scriptable object
- add tests
- renamed Drawers to Inspector
  • Loading branch information
Hertzole authored and James-Frowen committed Feb 2, 2023
1 parent d4bc727 commit 1973e76
Show file tree
Hide file tree
Showing 14 changed files with 358 additions and 59 deletions.
68 changes: 37 additions & 31 deletions Assets/Mirage/Editor/ClientObjectManagerInspector.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

Expand All @@ -8,54 +7,61 @@ namespace Mirage
[CanEditMultipleObjects]
public class ClientObjectManagerInspector : Editor
{
private SerializedProperty networkPrefabs;

private void OnEnable()
{
networkPrefabs = serializedObject.FindProperty(nameof(ClientObjectManager.NetworkPrefabs));
}

public override void OnInspectorGUI()
{
base.OnInspectorGUI();

if (GUILayout.Button("Register All Prefabs"))
if (networkPrefabs.objectReferenceValue == null)
{
Undo.RecordObject(target, "Register prefabs for spawn");
PrefabUtility.RecordPrefabInstancePropertyModifications(target);
RegisterPrefabs((ClientObjectManager)target);
if (GUILayout.Button("Create NetworkPrefabs"))
{
var path = EditorUtility.SaveFilePanelInProject("Create NetworkPrefabs", "NetworkPrefabs", "asset", "Create NetworkPrefabs");
CreateNetworkPrefabs(path);
}
}
}

public void RegisterPrefabs(ClientObjectManager gameObject)
public void CreateNetworkPrefabs(string path)
{
var prefabs = LoadPrefabsContaining<NetworkIdentity>("Assets");

foreach (var existing in gameObject.spawnPrefabs)
if (string.IsNullOrWhiteSpace(path))
{
prefabs.Add(existing);
return;
}
gameObject.spawnPrefabs.Clear();
gameObject.spawnPrefabs.AddRange(prefabs);
}

private static ISet<T> LoadPrefabsContaining<T>(string path) where T : Component
{
var result = new HashSet<T>();
var prefabs = CreateInstance<NetworkPrefabs>();
AssetDatabase.CreateAsset(prefabs, path);
AssetDatabase.SaveAssets();
networkPrefabs.objectReferenceValue = prefabs;
serializedObject.ApplyModifiedProperties();

var guids = AssetDatabase.FindAssets("t:GameObject", new[] { path });
RegisterOldPrefabs(prefabs);
}

for (var i = 0; i < guids.Length; i++)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
#pragma warning disable CS0618 // Type or member is obsolete
private void RegisterOldPrefabs(NetworkPrefabs prefabs)
{
var so = new SerializedObject(prefabs);
so.Update();

var obj = AssetDatabase.LoadAssetAtPath<T>(assetPath);
var spawnPrefabs = so.FindProperty(nameof(NetworkPrefabs.Prefabs));

if (obj != null)
{
result.Add(obj);
}
// Disable warning about obsolete field because we are using it for backwards compatibility.
spawnPrefabs.arraySize = ((ClientObjectManager)target).spawnPrefabs.Count;

if (i % 100 == 99)
{
EditorUtility.UnloadUnusedAssetsImmediate();
}
for (var i = 0; i < spawnPrefabs.arraySize; i++)
{
spawnPrefabs.GetArrayElementAtIndex(i).objectReferenceValue = ((ClientObjectManager)target).spawnPrefabs[i];
}
EditorUtility.UnloadUnusedAssetsImmediate();
return result;

so.ApplyModifiedProperties();
}
#pragma warning restore CS0618
}
}
88 changes: 88 additions & 0 deletions Assets/Mirage/Editor/NetworkPrefabsInspector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace Mirage
{
[CustomEditor(typeof(NetworkPrefabs))]
public partial class NetworkPrefabsInspector : Editor
{
private SerializedProperty prefabs;

private void OnEnable()
{
prefabs = serializedObject.FindProperty(nameof(NetworkPrefabs.Prefabs));
}

public override void OnInspectorGUI()
{
serializedObject.Update();

if (GUILayout.Button("Register All Prefabs"))
{
RegisterPrefabs();
}

EditorGUILayout.PropertyField(prefabs);

serializedObject.ApplyModifiedProperties();
}

public void RegisterPrefabs()
{
var loadedPrefabs = LoadPrefabsContaining<NetworkIdentity>("Assets");

for (var i = 0; i < prefabs.arraySize; i++)
{
var item = prefabs.GetArrayElementAtIndex(i).objectReferenceValue;

if (item != null && item is NetworkIdentity identity)
{
loadedPrefabs.Add(identity);
}
}

prefabs.ClearArray();
prefabs.arraySize = loadedPrefabs.Count;

var index = 0;
foreach (var prefab in loadedPrefabs)
{
prefabs.GetArrayElementAtIndex(index).objectReferenceValue = prefab;
index++;
}

serializedObject.ApplyModifiedProperties();
}

private static ISet<T> LoadPrefabsContaining<T>(string path) where T : Component
{
var result = new HashSet<T>();

var guids = AssetDatabase.FindAssets("t:GameObject", new[]
{
path
});

for (var i = 0; i < guids.Length; i++)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);

var obj = AssetDatabase.LoadAssetAtPath<T>(assetPath);

if (obj != null)
{
result.Add(obj);
}

if (i % 100 == 99)
{
EditorUtility.UnloadUnusedAssetsImmediate();
}
}

EditorUtility.UnloadUnusedAssetsImmediate();
return result;
}
}
}
3 changes: 3 additions & 0 deletions Assets/Mirage/Editor/NetworkPrefabsInspector.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions Assets/Mirage/Editor/NetworkPrefabsInspectorUIToolkit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#if UNITY_2022_2_OR_NEWER
using UnityEditor.UIElements;
using UnityEngine.UIElements;

namespace Mirage
{
public partial class NetworkPrefabsInspector
{
private ListView listView;

public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();

var button = new Button(() =>
{
serializedObject.Update();
RegisterPrefabs();
}) { text = "Register All Prefabs" };

root.Add(button);

var prefabsField = new PropertyField(prefabs);

prefabsField.RegisterCallback<GeometryChangedEvent, VisualElement>((evt, element) =>
{
if (listView == null)
{
listView = element.Q<ListView>();
if (listView != null)
{
listView.style.maxHeight = new StyleLength(new Length(100, LengthUnit.Percent));
}
}
}, prefabsField);

root.Add(prefabsField);

return root;
}
}
}
#endif
3 changes: 3 additions & 0 deletions Assets/Mirage/Editor/NetworkPrefabsInspectorUIToolkit.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 25 additions & 11 deletions Assets/Mirage/Runtime/ClientObjectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,24 @@ public class ClientObjectManager : MonoBehaviour
/// List of prefabs that will be registered with the spawning system.
/// <para>For each of these prefabs, ClientManager.RegisterPrefab() will be automatically invoke.</para>
/// </summary>
[Header("Prefabs")]
[Obsolete("Use NetworkPrefabs instead")]
[HideInInspector]
public List<NetworkIdentity> spawnPrefabs = new List<NetworkIdentity>();

/// <summary>
/// A scriptable object that holds all the prefabs that will be registered with the spawning system.
/// <para>For each of these prefabs, ClientManager.RegisterPrefab() will be automatically invoked.</para>
/// </summary>
public NetworkPrefabs NetworkPrefabs;

/// <summary>
/// This is a dictionary of the prefabs and deligates that are registered on the client with ClientScene.RegisterPrefab().
/// <para>The key to the dictionary is the prefab asset Id.</para>
/// </summary>
internal readonly Dictionary<int, SpawnHandler> _handlers = new Dictionary<int, SpawnHandler>();

/// <summary>
/// List of handler that will be used
/// List of handler that will be used
/// </summary>
internal readonly List<DynamicSpawnHandlerDelegate> _dynamicHandlers = new List<DynamicSpawnHandlerDelegate>();

Expand Down Expand Up @@ -72,7 +79,14 @@ public void Start()
private void OnValidate()
{
_validateCache.Clear();
foreach (var prefab in spawnPrefabs)

// If the NetworkPrefabs is null, then we can't validate anything.
if (NetworkPrefabs == null || NetworkPrefabs.Prefabs == null)
{
return;
}

foreach (var prefab in NetworkPrefabs.Prefabs)
{
if (prefab == null)
continue;
Expand Down Expand Up @@ -189,9 +203,9 @@ public void PrepareToSpawnSceneObjects()
#region Spawn Prefabs and handlers
private void RegisterSpawnPrefabs()
{
for (var i = 0; i < spawnPrefabs.Count; i++)
for (var i = 0; i < NetworkPrefabs.Prefabs.Count; i++)
{
var prefab = spawnPrefabs[i];
var prefab = NetworkPrefabs.Prefabs[i];
if (prefab != null)
{
RegisterPrefab(prefab);
Expand Down Expand Up @@ -252,8 +266,8 @@ public SpawnHandler GetSpawnHandler(int prefabHash)
/// <summary>
/// Registers a prefab with the spawning system.
/// <para>
/// When a NetworkIdentity object is spawned on the server with ServerObjectManager.Spawn(),
/// the server will send a spawn message to the client with the PrefabHash.
/// When a NetworkIdentity object is spawned on the server with ServerObjectManager.Spawn(),
/// the server will send a spawn message to the client with the PrefabHash.
/// the client then finds the prefab registered with RegisterPrefab() to instantiate the client object.
/// </para>
/// <para>The ClientObjectManager has a list of spawnable prefabs, it uses this function to register those prefabs with the ClientScene.</para>
Expand All @@ -270,8 +284,8 @@ public void RegisterPrefab(NetworkIdentity identity, int newPrefabHash)
/// <summary>
/// Registers a prefab with the spawning system.
/// <para>
/// When a NetworkIdentity object is spawned on the server with ServerObjectManager.Spawn(),
/// the server will send a spawn message to the client with the PrefabHash.
/// When a NetworkIdentity object is spawned on the server with ServerObjectManager.Spawn(),
/// the server will send a spawn message to the client with the PrefabHash.
/// the client then finds the prefab registered with RegisterPrefab() to instantiate the client object.
/// </para>
/// <para>The ClientObjectManager has a list of spawnable prefabs, it uses this function to register those prefabs with the ClientScene.</para>
Expand Down Expand Up @@ -332,8 +346,8 @@ public void UnregisterPrefab(NetworkIdentity identity)
/// <summary>
/// Registers custom handlers for a prefab with the spawning system.
/// <para>
/// When a NetworkIdentity object is spawned on the server with ServerObjectManager.Spawn(),
/// the server will send a spawn message to the client with the PrefabHash.
/// When a NetworkIdentity object is spawned on the server with ServerObjectManager.Spawn(),
/// the server will send a spawn message to the client with the PrefabHash.
/// the client then finds the prefab registered with RegisterPrefab() to instantiate the client object.
/// </para>
/// <para>The ClientObjectManager has a list of spawnable prefabs, it uses this function to register those prefabs with the ClientScene.</para>
Expand Down
13 changes: 13 additions & 0 deletions Assets/Mirage/Runtime/NetworkPrefabs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Collections.Generic;
using UnityEngine;

namespace Mirage
{
/// <summary>
/// A scriptable object that contains a list of prefabs that can be spawned on the network.
/// </summary>
public sealed class NetworkPrefabs : ScriptableObject
{
public List<NetworkIdentity> Prefabs = new List<NetworkIdentity>();
}
}
3 changes: 3 additions & 0 deletions Assets/Mirage/Runtime/NetworkPrefabs.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1973e76

Please sign in to comment.