Skip to content

feat: Prefab QoL [MTT-5116] #2322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- `NetworkHide()` of an object that was just `NetworkShow()`n produces a warning, as remote clients will _not_ get a spawn/despawn pair.
- The default listen address of `UnityTransport` is now 0.0.0.0. (#2307)
- Renamed the NetworkTransform.SetState parameter `shouldGhostsInterpolate` to `teleportDisabled` for better clarity of what that parameter does. (#2228)
- 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)

### Fixed
- Fixed issue where `NetcodeSettingsProvider` would throw an exception in Unity 2020.3.x versions. (#2345)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ private void BuildDestinationToTransitionInfoTable()
private void BuildTransitionStateInfoList()
{
#if UNITY_EDITOR
if (UnityEditor.EditorApplication.isUpdating)
if (UnityEditor.EditorApplication.isUpdating || UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}
Expand Down
9 changes: 2 additions & 7 deletions com.unity.netcode.gameobjects/Editor/Configuration.meta

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

Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using UnityEditor;
using UnityEngine;


namespace Unity.Netcode.Editor.Configuration
{
internal class NetcodeForGameObjectsSettings
internal class NetcodeForGameObjectsEditorSettings
{
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
Expand All @@ -14,6 +15,7 @@ internal static int GetNetcodeInstallMultiplayerToolTips()
{
return EditorPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey);
}

return 0;
}

Expand All @@ -28,6 +30,7 @@ internal static bool GetAutoAddNetworkObjectSetting()
{
return EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists);
}

return false;
}

Expand All @@ -36,4 +39,15 @@ internal static void SetAutoAddNetworkObjectSetting(bool autoAddSetting)
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, autoAddSetting);
}
}

[FilePath("ProjectSettings/NetcodeForGameObjects.settings", FilePathAttribute.Location.ProjectFolder)]
internal class NetcodeForGameObjectsProjectSettings : ScriptableSingleton<NetcodeForGameObjectsProjectSettings>
{
[SerializeField] public bool GenerateDefaultNetworkPrefabs = true;

internal void SaveSettings()
{
Save(true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ namespace Unity.Netcode.Editor.Configuration
{
internal static class NetcodeSettingsProvider
{
private const float k_MaxLabelWidth = 450f;
private static float s_MaxLabelWidth;
private static bool s_ShowEditorSettingFields = true;
private static bool s_ShowProjectSettingFields = true;

[SettingsProvider]
public static SettingsProvider CreateNetcodeSettingsProvider()
{
Expand All @@ -20,6 +25,7 @@ public static SettingsProvider CreateNetcodeSettingsProvider()
return provider;
}


internal static NetcodeSettingsLabel NetworkObjectsSectionLabel;
internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle;
internal static NetcodeSettingsLabel MultiplayerToolsLabel;
Expand All @@ -41,7 +47,7 @@ private static void CheckForInitialize()

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

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

var autoAddNetworkObjectSetting = NetcodeForGameObjectsSettings.GetAutoAddNetworkObjectSetting();
var multiplayerToolsTipStatus = NetcodeForGameObjectsSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
var autoAddNetworkObjectSetting = NetcodeForGameObjectsEditorSettings.GetAutoAddNetworkObjectSetting();
var multiplayerToolsTipStatus = NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
var settings = NetcodeForGameObjectsProjectSettings.instance;
var generateDefaultPrefabs = settings.GenerateDefaultNetworkPrefabs;

EditorGUI.BeginChangeCheck();
NetworkObjectsSectionLabel.DrawLabel();
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting);
MultiplayerToolsLabel.DrawLabel();
multiplayerToolsTipStatus = MultiplayerToolTipStatusToggle.DrawToggle(multiplayerToolsTipStatus);

GUILayout.BeginVertical("Box");
s_ShowEditorSettingFields = EditorGUILayout.BeginFoldoutHeaderGroup(s_ShowEditorSettingFields, "Editor Settings");

if (s_ShowEditorSettingFields)
{
GUILayout.BeginVertical("Box");
NetworkObjectsSectionLabel.DrawLabel();
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting);
GUILayout.EndVertical();

GUILayout.BeginVertical("Box");
MultiplayerToolsLabel.DrawLabel();
multiplayerToolsTipStatus = MultiplayerToolTipStatusToggle.DrawToggle(multiplayerToolsTipStatus);
GUILayout.EndVertical();
}
EditorGUILayout.EndFoldoutHeaderGroup();
GUILayout.EndVertical();

GUILayout.BeginVertical("Box");
s_ShowProjectSettingFields = EditorGUILayout.BeginFoldoutHeaderGroup(s_ShowProjectSettingFields, "Project Settings");
if (s_ShowProjectSettingFields)
{
GUILayout.BeginVertical("Box");
const string generateNetworkPrefabsString = "Generate Default Network Prefabs List";

if (s_MaxLabelWidth == 0)
{
s_MaxLabelWidth = EditorStyles.label.CalcSize(new GUIContent(generateNetworkPrefabsString)).x;
s_MaxLabelWidth = Mathf.Min(k_MaxLabelWidth, s_MaxLabelWidth);
}

EditorGUIUtility.labelWidth = s_MaxLabelWidth;

GUILayout.Label("Network Prefabs", EditorStyles.boldLabel);
generateDefaultPrefabs = EditorGUILayout.Toggle(
new GUIContent(
generateNetworkPrefabsString,
"When enabled, a default NetworkPrefabsList object will be added to your project and kept up " +
"to date with all NetworkObject prefabs."),
generateDefaultPrefabs,
GUILayout.Width(s_MaxLabelWidth + 20));
GUILayout.EndVertical();
}
EditorGUILayout.EndFoldoutHeaderGroup();
GUILayout.EndVertical();
if (EditorGUI.EndChangeCheck())
{
NetcodeForGameObjectsSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
NetcodeForGameObjectsSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
NetcodeForGameObjectsEditorSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
settings.GenerateDefaultNetworkPrefabs = generateDefaultPrefabs;
settings.SaveSettings();
}
}
}
Expand Down Expand Up @@ -122,4 +175,5 @@ protected void AdjustLabelSize(string labelText, float offset = 0.0f)
m_LayoutWidth = GUILayout.Width(m_LabelSize + offset);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace Unity.Netcode.Editor.Configuration
{
/// <summary>
/// Updates the default <see cref="NetworkPrefabsList"/> instance when prefabs are updated (created, moved, deleted) in the project.
/// </summary>
public class NetworkPrefabProcessor : AssetPostprocessor
{
private static string s_DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset";
public static string DefaultNetworkPrefabsPath
{
get
{
return s_DefaultNetworkPrefabsPath;
}
internal set
{
s_DefaultNetworkPrefabsPath = value;
// Force a recache of the prefab list
s_PrefabsList = null;
}
}
private static NetworkPrefabsList s_PrefabsList;

private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
var settings = NetcodeForGameObjectsProjectSettings.instance;
if (!settings.GenerateDefaultNetworkPrefabs)
{
return;
}

bool ProcessImportedAssets(string[] importedAssets1)
{
var dirty = false;
foreach (var assetPath in importedAssets1)
{
// We only care about GameObjects, skip everything else. Can't use the more targeted
// OnPostProcessPrefabs since that's not called for moves or deletes
if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) != typeof(GameObject))
{
continue;
}

var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (go.TryGetComponent<NetworkObject>(out _))
{
s_PrefabsList.List.Add(new NetworkPrefab { Prefab = go });
dirty = true;
}
}

return dirty;
}

bool ProcessDeletedAssets(string[] strings)
{
var dirty = false;
var deleted = new List<string>(strings);
for (int i = s_PrefabsList.List.Count - 1; i >= 0 && deleted.Count > 0; --i)
{
GameObject prefab;
try
{
prefab = s_PrefabsList.List[i].Prefab;
}
catch (MissingReferenceException)
{
s_PrefabsList.List.RemoveAt(i);
continue;
}
if (prefab == null)
{
s_PrefabsList.List.RemoveAt(i);
}
else
{
string noPath = AssetDatabase.GetAssetPath(prefab);
for (int j = strings.Length - 1; j >= 0; --j)
{
if (noPath == strings[j])
{
s_PrefabsList.List.RemoveAt(i);
deleted.RemoveAt(j);
dirty = true;
}
}
}
}

return dirty;
}

if (s_PrefabsList == null)
{
s_PrefabsList = GetOrCreateNetworkPrefabs(DefaultNetworkPrefabsPath, out var newList, true);
// A new list already processed all existing assets, no need to double-process imports & deletes
if (newList)
{
return;
}
}

var markDirty = ProcessImportedAssets(importedAssets);
markDirty &= ProcessDeletedAssets(deletedAssets);

if (markDirty)
{
EditorUtility.SetDirty(s_PrefabsList);
}
}

internal static NetworkPrefabsList GetOrCreateNetworkPrefabs(string path, out bool isNew, bool addAll)
{
var defaultPrefabs = AssetDatabase.LoadAssetAtPath<NetworkPrefabsList>(path);
if (defaultPrefabs == null)
{
isNew = true;
defaultPrefabs = ScriptableObject.CreateInstance<NetworkPrefabsList>();
defaultPrefabs.IsDefault = true;
AssetDatabase.CreateAsset(defaultPrefabs, path);

if (addAll)
{
// This could be very expensive in large projects... maybe make it manually triggered via a menu?
defaultPrefabs.List = FindAll();
}
EditorUtility.SetDirty(defaultPrefabs);
AssetDatabase.SaveAssetIfDirty(defaultPrefabs);
return defaultPrefabs;
}

isNew = false;
return defaultPrefabs;
}

private static List<NetworkPrefab> FindAll()
{
var list = new List<NetworkPrefab>();

string[] guids = AssetDatabase.FindAssets("t:GameObject");
foreach (var guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);

if (go.TryGetComponent(out NetworkObject _))
{
list.Add(new NetworkPrefab { Prefab = go });
}
}

return list;
}
}
}

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

Loading