Skip to content

Commit b007fbd

Browse files
authored
feat: Prefab QoL [MTT-5116] (#2322)
feat: Prefab QoL Moves prefab list to a ScriptableObject and makes it publicly accessible to be programmable. Credit for most of the work goes to @JesseOlmer, I provided some refinements and testing (including unit tests)
1 parent 5b52d9b commit b007fbd

File tree

57 files changed

+1934
-802
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1934
-802
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1717
- `NetworkHide()` of an object that was just `NetworkShow()`n produces a warning, as remote clients will _not_ get a spawn/despawn pair.
1818
- The default listen address of `UnityTransport` is now 0.0.0.0. (#2307)
1919
- Renamed the NetworkTransform.SetState parameter `shouldGhostsInterpolate` to `teleportDisabled` for better clarity of what that parameter does. (#2228)
20+
- Network prefabs are now stored in a ScriptableObject that can be shared between NetworkManagers, and have been exposed for public access. By default, a Default Prefabs List is created that contains all NetworkObject prefabs in the project, and new NetworkManagers will default to using that unless that option is turned off in the Netcode for GameObjects settings. Existing NetworkManagers will maintain their existing lists, which can be migrated to the new format via a button in their inspector. (#2322)
2021

2122
### Fixed
2223
- Fixed issue where `NetcodeSettingsProvider` would throw an exception in Unity 2020.3.x versions. (#2345)

com.unity.netcode.gameobjects/Components/NetworkAnimator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ private void BuildDestinationToTransitionInfoTable()
232232
private void BuildTransitionStateInfoList()
233233
{
234234
#if UNITY_EDITOR
235-
if (UnityEditor.EditorApplication.isUpdating)
235+
if (UnityEditor.EditorApplication.isUpdating || UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
236236
{
237237
return;
238238
}

com.unity.netcode.gameobjects/Editor/Configuration.meta

Lines changed: 2 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsSettings.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
using UnityEditor;
2+
using UnityEngine;
23

34

45
namespace Unity.Netcode.Editor.Configuration
56
{
6-
internal class NetcodeForGameObjectsSettings
7+
internal class NetcodeForGameObjectsEditorSettings
78
{
89
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
910
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
@@ -14,6 +15,7 @@ internal static int GetNetcodeInstallMultiplayerToolTips()
1415
{
1516
return EditorPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey);
1617
}
18+
1719
return 0;
1820
}
1921

@@ -28,6 +30,7 @@ internal static bool GetAutoAddNetworkObjectSetting()
2830
{
2931
return EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists);
3032
}
33+
3134
return false;
3235
}
3336

@@ -36,4 +39,15 @@ internal static void SetAutoAddNetworkObjectSetting(bool autoAddSetting)
3639
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, autoAddSetting);
3740
}
3841
}
42+
43+
[FilePath("ProjectSettings/NetcodeForGameObjects.settings", FilePathAttribute.Location.ProjectFolder)]
44+
internal class NetcodeForGameObjectsProjectSettings : ScriptableSingleton<NetcodeForGameObjectsProjectSettings>
45+
{
46+
[SerializeField] public bool GenerateDefaultNetworkPrefabs = true;
47+
48+
internal void SaveSettings()
49+
{
50+
Save(true);
51+
}
52+
}
3953
}

com.unity.netcode.gameobjects/Editor/Configuration/NetcodeSettingsProvider.cs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ namespace Unity.Netcode.Editor.Configuration
55
{
66
internal static class NetcodeSettingsProvider
77
{
8+
private const float k_MaxLabelWidth = 450f;
9+
private static float s_MaxLabelWidth;
10+
private static bool s_ShowEditorSettingFields = true;
11+
private static bool s_ShowProjectSettingFields = true;
12+
813
[SettingsProvider]
914
public static SettingsProvider CreateNetcodeSettingsProvider()
1015
{
@@ -20,6 +25,7 @@ public static SettingsProvider CreateNetcodeSettingsProvider()
2025
return provider;
2126
}
2227

28+
2329
internal static NetcodeSettingsLabel NetworkObjectsSectionLabel;
2430
internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle;
2531
internal static NetcodeSettingsLabel MultiplayerToolsLabel;
@@ -41,7 +47,7 @@ private static void CheckForInitialize()
4147

4248
if (AutoAddNetworkObjectToggle == null)
4349
{
44-
AutoAddNetworkObjectToggle = new NetcodeSettingsToggle("Auto-Add NetworkObjects", "When enabled, NetworkObjects are automatically added to GameObjects when NetworkBehaviours are added first.", 20);
50+
AutoAddNetworkObjectToggle = new NetcodeSettingsToggle("Auto-Add NetworkObject Component", "When enabled, NetworkObject components are automatically added to GameObjects when NetworkBehaviour components are added first.", 20);
4551
}
4652

4753
if (MultiplayerToolsLabel == null)
@@ -60,17 +66,64 @@ private static void OnGuiHandler(string obj)
6066
// Make sure all NetcodeGUISettings derived classes are instantiated first
6167
CheckForInitialize();
6268

63-
var autoAddNetworkObjectSetting = NetcodeForGameObjectsSettings.GetAutoAddNetworkObjectSetting();
64-
var multiplayerToolsTipStatus = NetcodeForGameObjectsSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
69+
var autoAddNetworkObjectSetting = NetcodeForGameObjectsEditorSettings.GetAutoAddNetworkObjectSetting();
70+
var multiplayerToolsTipStatus = NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
71+
var settings = NetcodeForGameObjectsProjectSettings.instance;
72+
var generateDefaultPrefabs = settings.GenerateDefaultNetworkPrefabs;
73+
6574
EditorGUI.BeginChangeCheck();
66-
NetworkObjectsSectionLabel.DrawLabel();
67-
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting);
68-
MultiplayerToolsLabel.DrawLabel();
69-
multiplayerToolsTipStatus = MultiplayerToolTipStatusToggle.DrawToggle(multiplayerToolsTipStatus);
75+
76+
GUILayout.BeginVertical("Box");
77+
s_ShowEditorSettingFields = EditorGUILayout.BeginFoldoutHeaderGroup(s_ShowEditorSettingFields, "Editor Settings");
78+
79+
if (s_ShowEditorSettingFields)
80+
{
81+
GUILayout.BeginVertical("Box");
82+
NetworkObjectsSectionLabel.DrawLabel();
83+
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting);
84+
GUILayout.EndVertical();
85+
86+
GUILayout.BeginVertical("Box");
87+
MultiplayerToolsLabel.DrawLabel();
88+
multiplayerToolsTipStatus = MultiplayerToolTipStatusToggle.DrawToggle(multiplayerToolsTipStatus);
89+
GUILayout.EndVertical();
90+
}
91+
EditorGUILayout.EndFoldoutHeaderGroup();
92+
GUILayout.EndVertical();
93+
94+
GUILayout.BeginVertical("Box");
95+
s_ShowProjectSettingFields = EditorGUILayout.BeginFoldoutHeaderGroup(s_ShowProjectSettingFields, "Project Settings");
96+
if (s_ShowProjectSettingFields)
97+
{
98+
GUILayout.BeginVertical("Box");
99+
const string generateNetworkPrefabsString = "Generate Default Network Prefabs List";
100+
101+
if (s_MaxLabelWidth == 0)
102+
{
103+
s_MaxLabelWidth = EditorStyles.label.CalcSize(new GUIContent(generateNetworkPrefabsString)).x;
104+
s_MaxLabelWidth = Mathf.Min(k_MaxLabelWidth, s_MaxLabelWidth);
105+
}
106+
107+
EditorGUIUtility.labelWidth = s_MaxLabelWidth;
108+
109+
GUILayout.Label("Network Prefabs", EditorStyles.boldLabel);
110+
generateDefaultPrefabs = EditorGUILayout.Toggle(
111+
new GUIContent(
112+
generateNetworkPrefabsString,
113+
"When enabled, a default NetworkPrefabsList object will be added to your project and kept up " +
114+
"to date with all NetworkObject prefabs."),
115+
generateDefaultPrefabs,
116+
GUILayout.Width(s_MaxLabelWidth + 20));
117+
GUILayout.EndVertical();
118+
}
119+
EditorGUILayout.EndFoldoutHeaderGroup();
120+
GUILayout.EndVertical();
70121
if (EditorGUI.EndChangeCheck())
71122
{
72-
NetcodeForGameObjectsSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
73-
NetcodeForGameObjectsSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
123+
NetcodeForGameObjectsEditorSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
124+
NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
125+
settings.GenerateDefaultNetworkPrefabs = generateDefaultPrefabs;
126+
settings.SaveSettings();
74127
}
75128
}
76129
}
@@ -122,4 +175,5 @@ protected void AdjustLabelSize(string labelText, float offset = 0.0f)
122175
m_LayoutWidth = GUILayout.Width(m_LabelSize + offset);
123176
}
124177
}
178+
125179
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using System.Collections.Generic;
2+
using UnityEditor;
3+
using UnityEngine;
4+
5+
namespace Unity.Netcode.Editor.Configuration
6+
{
7+
/// <summary>
8+
/// Updates the default <see cref="NetworkPrefabsList"/> instance when prefabs are updated (created, moved, deleted) in the project.
9+
/// </summary>
10+
public class NetworkPrefabProcessor : AssetPostprocessor
11+
{
12+
private static string s_DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset";
13+
public static string DefaultNetworkPrefabsPath
14+
{
15+
get
16+
{
17+
return s_DefaultNetworkPrefabsPath;
18+
}
19+
internal set
20+
{
21+
s_DefaultNetworkPrefabsPath = value;
22+
// Force a recache of the prefab list
23+
s_PrefabsList = null;
24+
}
25+
}
26+
private static NetworkPrefabsList s_PrefabsList;
27+
28+
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
29+
{
30+
var settings = NetcodeForGameObjectsProjectSettings.instance;
31+
if (!settings.GenerateDefaultNetworkPrefabs)
32+
{
33+
return;
34+
}
35+
36+
bool ProcessImportedAssets(string[] importedAssets1)
37+
{
38+
var dirty = false;
39+
foreach (var assetPath in importedAssets1)
40+
{
41+
// We only care about GameObjects, skip everything else. Can't use the more targeted
42+
// OnPostProcessPrefabs since that's not called for moves or deletes
43+
if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) != typeof(GameObject))
44+
{
45+
continue;
46+
}
47+
48+
var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
49+
if (go.TryGetComponent<NetworkObject>(out _))
50+
{
51+
s_PrefabsList.List.Add(new NetworkPrefab { Prefab = go });
52+
dirty = true;
53+
}
54+
}
55+
56+
return dirty;
57+
}
58+
59+
bool ProcessDeletedAssets(string[] strings)
60+
{
61+
var dirty = false;
62+
var deleted = new List<string>(strings);
63+
for (int i = s_PrefabsList.List.Count - 1; i >= 0 && deleted.Count > 0; --i)
64+
{
65+
GameObject prefab;
66+
try
67+
{
68+
prefab = s_PrefabsList.List[i].Prefab;
69+
}
70+
catch (MissingReferenceException)
71+
{
72+
s_PrefabsList.List.RemoveAt(i);
73+
continue;
74+
}
75+
if (prefab == null)
76+
{
77+
s_PrefabsList.List.RemoveAt(i);
78+
}
79+
else
80+
{
81+
string noPath = AssetDatabase.GetAssetPath(prefab);
82+
for (int j = strings.Length - 1; j >= 0; --j)
83+
{
84+
if (noPath == strings[j])
85+
{
86+
s_PrefabsList.List.RemoveAt(i);
87+
deleted.RemoveAt(j);
88+
dirty = true;
89+
}
90+
}
91+
}
92+
}
93+
94+
return dirty;
95+
}
96+
97+
if (s_PrefabsList == null)
98+
{
99+
s_PrefabsList = GetOrCreateNetworkPrefabs(DefaultNetworkPrefabsPath, out var newList, true);
100+
// A new list already processed all existing assets, no need to double-process imports & deletes
101+
if (newList)
102+
{
103+
return;
104+
}
105+
}
106+
107+
var markDirty = ProcessImportedAssets(importedAssets);
108+
markDirty &= ProcessDeletedAssets(deletedAssets);
109+
110+
if (markDirty)
111+
{
112+
EditorUtility.SetDirty(s_PrefabsList);
113+
}
114+
}
115+
116+
internal static NetworkPrefabsList GetOrCreateNetworkPrefabs(string path, out bool isNew, bool addAll)
117+
{
118+
var defaultPrefabs = AssetDatabase.LoadAssetAtPath<NetworkPrefabsList>(path);
119+
if (defaultPrefabs == null)
120+
{
121+
isNew = true;
122+
defaultPrefabs = ScriptableObject.CreateInstance<NetworkPrefabsList>();
123+
defaultPrefabs.IsDefault = true;
124+
AssetDatabase.CreateAsset(defaultPrefabs, path);
125+
126+
if (addAll)
127+
{
128+
// This could be very expensive in large projects... maybe make it manually triggered via a menu?
129+
defaultPrefabs.List = FindAll();
130+
}
131+
EditorUtility.SetDirty(defaultPrefabs);
132+
AssetDatabase.SaveAssetIfDirty(defaultPrefabs);
133+
return defaultPrefabs;
134+
}
135+
136+
isNew = false;
137+
return defaultPrefabs;
138+
}
139+
140+
private static List<NetworkPrefab> FindAll()
141+
{
142+
var list = new List<NetworkPrefab>();
143+
144+
string[] guids = AssetDatabase.FindAssets("t:GameObject");
145+
foreach (var guid in guids)
146+
{
147+
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
148+
var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
149+
150+
if (go.TryGetComponent(out NetworkObject _))
151+
{
152+
list.Add(new NetworkPrefab { Prefab = go });
153+
}
154+
}
155+
156+
return list;
157+
}
158+
}
159+
}

com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)