Skip to content

feat!: Network Prefab Overrides, Inspector View, and the default Player Prefab #749

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 25 commits into from
Apr 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c729148
NetworkPrefab registration and default PlayerPrefab
NoelStephensUnity Apr 16, 2021
677b49e
Merge branch 'develop' into feat/mtt640
NoelStephensUnity Apr 16, 2021
d873ecc
refactor: Naming, Migration, and PlayerPrefab
NoelStephensUnity Apr 16, 2021
59511f1
refactor: PlayerPrefab assignment
NoelStephensUnity Apr 16, 2021
a217e7a
refactor: player prefab selection and blank new entries
NoelStephensUnity Apr 17, 2021
2559481
style: comments
NoelStephensUnity Apr 17, 2021
84f0942
style: property names and comments
NoelStephensUnity Apr 17, 2021
b78403e
refactor: Client Sync and Comments
NoelStephensUnity Apr 17, 2021
c600724
style: minor change
NoelStephensUnity Apr 17, 2021
97a41bc
Merge branch 'develop' into feat/mtt640
NoelStephensUnity Apr 20, 2021
9558732
Merge branch 'develop' into feat/mtt640
NoelStephensUnity Apr 20, 2021
e799b47
refactor: remove CreatePlayerPrefab
NoelStephensUnity Apr 20, 2021
5926c3c
refactor: cleaning up using
NoelStephensUnity Apr 20, 2021
481e862
style: PlayerPrefab tooltip
NoelStephensUnity Apr 20, 2021
7f2781c
style: spaces between slashes and comment text body
NoelStephensUnity Apr 20, 2021
3de2cac
fix: clearing network prefab links
NoelStephensUnity Apr 20, 2021
bb91f17
refactor: NetworkConfig compatibility, NetworkPrefab, and Style
NoelStephensUnity Apr 21, 2021
0e11960
fix: wrong hash reference
NoelStephensUnity Apr 22, 2021
5c22c63
refactor and style
NoelStephensUnity Apr 22, 2021
5046892
Merge branch 'develop' into feat/mtt640
NoelStephensUnity Apr 22, 2021
67f8e66
refactor: removing NetworkObject check
NoelStephensUnity Apr 22, 2021
02a726d
refactor: removing NetworkPrefab.Hash
NoelStephensUnity Apr 22, 2021
4cbde7c
Merge branch 'develop' into feat/mtt640
0xFA11 Apr 22, 2021
a2fe29d
Merge branch 'develop' into feat/mtt640
NoelStephensUnity Apr 22, 2021
df7b111
Merge branch 'develop' into feat/mtt640
0xFA11 Apr 23, 2021
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
659 changes: 336 additions & 323 deletions com.unity.multiplayer.mlapi/Editor/NetworkManagerEditor.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,23 @@ public class NetworkConfig
public bool AllowRuntimeSceneChanges = false;

/// <summary>
/// A list of spawnable prefabs
/// The default player prefab
/// </summary>
[Tooltip("The prefabs that can be spawned across the network")]
public List<NetworkPrefab> NetworkPrefabs = new List<NetworkPrefab>();
[Tooltip("When set, NetworkManager will automatically create and spawn the assigned player prefab. This can be overridden by adding it to the NetworkPrefabs list and selecting override.")]
public GameObject PlayerPrefab;

/// <summary>
/// The default player prefab
/// A list of spawnable prefabs
/// </summary>
[SerializeReference]
internal uint PlayerPrefabHash;
[SerializeField]
[Tooltip("The prefabs that can be spawned across the network")]
internal List<NetworkPrefab> NetworkPrefabs = new List<NetworkPrefab>();

/// <summary>
/// Whether or not a player object should be created by default. This value can be overridden on a case by case basis with ConnectionApproval.
/// This dictionary provides a quick way to check and see if a NetworkPrefab has a NetworkPrefab override.
/// Generated at runtime and OnValidate
/// </summary>
[Tooltip("Whether or not a player object should be created by default. This value can be overridden on a case by case basis with ConnectionApproval.")]
public bool CreatePlayerPrefab = true;
internal Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();

/// <summary>
/// Amount of times per second the receive queue is emptied and all messages inside are processed.
Expand Down Expand Up @@ -300,10 +301,10 @@ public ulong GetConfig(bool cache = true)

if (ForceSamePrefabs)
{
var sortedPrefabList = NetworkPrefabs.OrderBy(x => x.Hash).ToList();
for (int i = 0; i < sortedPrefabList.Count; i++)
var sortedDictionary = NetworkPrefabOverrideLinks.OrderBy(x => x.Key);
foreach (var sortedEntry in sortedDictionary)
{
writer.WriteUInt32Packed(sortedPrefabList[i].Hash);
writer.WriteUInt32Packed(sortedEntry.Key);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,43 @@

namespace MLAPI.Configuration
{
internal enum NetworkPrefabOverride
{
None,
Prefab,
Hash
}

/// <summary>
/// Class that represents a NetworkPrefab
/// </summary>
[Serializable]
public class NetworkPrefab
internal class NetworkPrefab
{
/// <summary>
/// The override setttings for this NetworkPrefab
/// </summary>
public NetworkPrefabOverride Override;

/// <summary>
/// Asset reference of the network prefab
/// </summary>
public GameObject Prefab;

/// <summary>
/// Whether or not this is a player prefab
/// Used when prefab is selected for the source prefab to override value (i.e. direct reference, the prefab is within the same project)
/// We keep a separate value as the user might want to have something different than the default Prefab for the SourcePrefabToOverride
/// </summary>
public bool IsPlayer;

internal uint Hash
{
get
{
if (Prefab == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} does not have a prefab assigned");
}
public GameObject SourcePrefabToOverride;

return 0;
}

var networkObject = Prefab.GetComponent<NetworkObject>();
if (networkObject == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {Prefab.name} does not have a {nameof(NetworkObject)} component");
}

return 0;
}
/// <summary>
/// Used when hash is selected for the source prefab to override value (i.e. a direct reference is not possible such as in a multi-project pattern)
/// </summary>
public uint SourceHashToOverride;

return networkObject.GlobalObjectIdHash;
}
}
/// <summary>
/// The prefab to replace (override) the source prefab with
/// </summary>
public GameObject OverridingTargetPrefab;
}
}
155 changes: 124 additions & 31 deletions com.unity.multiplayer.mlapi/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.ComponentModel;
using System.Diagnostics;
using UnityEngine;
using System.Linq;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

using MLAPI.Logging;
using MLAPI.Configuration;
using MLAPI.Internal;
Expand Down Expand Up @@ -216,7 +215,7 @@ private void OnValidate()
{
if (NetworkConfig == null)
{
return; //May occur when the component is added
return; // May occur when the component is added
}

if (GetComponentInChildren<NetworkObject>() != null)
Expand Down Expand Up @@ -247,39 +246,58 @@ private void OnValidate()
};
}

// During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it
NetworkConfig.NetworkPrefabOverrideLinks.Clear();

// Check network prefabs and assign to dictionary for quick look up
for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++)
{
if (NetworkConfig.NetworkPrefabs[i] != null && NetworkConfig.NetworkPrefabs[i].Prefab != null)
{
if (NetworkConfig.NetworkPrefabs[i].Prefab.GetComponent<NetworkObject>() == null)
var networkObject = NetworkConfig.NetworkPrefabs[i].Prefab.GetComponent<NetworkObject>();
if (networkObject == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} [{i}] does not have a {nameof(NetworkObject)} component");
}
}
}
}
else
{
// Default to the standard NetworkPrefab.Prefab's NetworkObject first
var globalObjectIdHash = networkObject.GlobalObjectIdHash;

int playerPrefabCount = NetworkConfig.NetworkPrefabs.Count(x => x.IsPlayer);
// Now check to see if it has an override
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Now check to see if it has an override

over-commenting, isn't it obvious?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, comments help guide people as to what is happening.
While it is obvious to us, this level of commenting to someone who is completely new to the SDK is actually useful.
I would rather leave comments in than remove them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree :P Let's discuss internally :)

switch (NetworkConfig.NetworkPrefabs[i].Override)
{
case NetworkPrefabOverride.Prefab:
{
if (NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride == null && NetworkConfig.NetworkPrefabs[i].Prefab != null)
{
NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride = NetworkConfig.NetworkPrefabs[i].Prefab;
}
globalObjectIdHash = NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.GetComponent<NetworkObject>().GlobalObjectIdHash;
}
break;
case NetworkPrefabOverride.Hash:
globalObjectIdHash = NetworkConfig.NetworkPrefabs[i].SourceHashToOverride;
break;
}

if (playerPrefabCount == 0 && !NetworkConfig.ConnectionApproval && NetworkConfig.CreatePlayerPrefab)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"There is no {nameof(NetworkPrefab)} marked as a {nameof(NetworkPrefab.IsPlayer)}");
}
}
else if (playerPrefabCount > 1)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Only one {nameof(NetworkPrefab)} can be marked as a {nameof(NetworkPrefab.IsPlayer)}");
// Add to the NetworkPrefabOverrideLinks or handle a new (blank) entries
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash))
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(globalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
}
else
{
// Duplicate entries can happen when adding a new entry into a list of existing entries
// Either this is user error or a new entry, either case we replace it with a new, blank, NetworkPrefab under this condition
NetworkConfig.NetworkPrefabs[i] = new NetworkPrefab();
}
}
}
}

var networkPrefab = NetworkConfig.NetworkPrefabs.FirstOrDefault(x => x.IsPlayer);
NetworkConfig.PlayerPrefabHash = networkPrefab?.Hash ?? (uint)0;
}
#endif

Expand Down Expand Up @@ -326,7 +344,7 @@ private void Init(bool server)
return;
}

//This 'if' should never enter
// This 'if' should never enter
if (NetworkTickSystem != null)
{
NetworkTickSystem.Dispose();
Expand All @@ -335,16 +353,16 @@ private void Init(bool server)

NetworkTickSystem = new NetworkTickSystem(NetworkConfig.NetworkTickIntervalSec);

//This should never happen, but in the event that it does there should be (at a minimum) a unity error logged.
// This should never happen, but in the event that it does there should be (at a minimum) a unity error logged.
if (RpcQueueContainer != null)
{
UnityEngine.Debug.LogError("Init was invoked, but rpcQueueContainer was already initialized! (destroying previous instance)");
RpcQueueContainer.Dispose();
RpcQueueContainer = null;
}

//The RpcQueueContainer must be initialized within the Init method ONLY
//It should ONLY be shutdown and destroyed in the Shutdown method (other than just above)
// The RpcQueueContainer must be initialized within the Init method ONLY
// It should ONLY be shutdown and destroyed in the Shutdown method (other than just above)
RpcQueueContainer = new RpcQueueContainer(this);

// Register INetworkUpdateSystem (always register this after rpcQueueContainer has been instantiated)
Expand All @@ -365,23 +383,98 @@ private void Init(bool server)
SceneManager.SetCurrentSceneIndex();
}

// This is used to remove entries not needed or invalid
var removeEmptyPrefabs = new List<int>();

// Always clear our prefab override links before building
NetworkConfig.NetworkPrefabOverrideLinks.Clear();

// Build the NetworkPrefabOverrideLinks dictionary
for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++)
{
if (NetworkConfig.NetworkPrefabs[i] == null || NetworkConfig.NetworkPrefabs[i].Prefab == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {i})");
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {i})");
}

// Provide the name of the prefab with issues so the user can more easily find the prefab and fix it
UnityEngine.Debug.LogWarning($"{nameof(NetworkPrefab)} (\"{NetworkConfig.NetworkPrefabs[i].Prefab.name}\") will be removed and ignored.");
removeEmptyPrefabs.Add(i);

continue;
}
else if (NetworkConfig.NetworkPrefabs[i].Prefab.GetComponent<NetworkObject>() == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"{nameof(NetworkPrefab)} (\"{NetworkConfig.NetworkPrefabs[i].Prefab.name}\") is missing a {nameof(NetworkObject)} component");
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} (\"{NetworkConfig.NetworkPrefabs[i].Prefab.name}\") is missing a {nameof(NetworkObject)} component");
}

// Provide the name of the prefab with issues so the user can more easily find the prefab and fix it
UnityEngine.Debug.LogWarning($"{nameof(NetworkPrefab)} (\"{NetworkConfig.NetworkPrefabs[i].Prefab.name}\") will be removed and ignored.");
removeEmptyPrefabs.Add(i);

continue;
}

var networkObject = NetworkConfig.NetworkPrefabs[i].Prefab.GetComponent<NetworkObject>();

// Assign the appropriate GlobalObjectIdHash to the appropriate NetworkPrefab
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
{
switch (NetworkConfig.NetworkPrefabs[i].Override)
{
default:
case NetworkPrefabOverride.None:
NetworkConfig.NetworkPrefabOverrideLinks.Add(networkObject.GlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
break;
case NetworkPrefabOverride.Prefab:
NetworkConfig.NetworkPrefabOverrideLinks.Add(NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.GetComponent<NetworkObject>().GlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
break;
case NetworkPrefabOverride.Hash:
NetworkConfig.NetworkPrefabOverrideLinks.Add(NetworkConfig.NetworkPrefabs[i].SourceHashToOverride, NetworkConfig.NetworkPrefabs[i]);
break;
}
}
else
{
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
UnityEngine.Debug.LogError($"{nameof(NetworkPrefab)} (\"{NetworkConfig.NetworkPrefabs[i].Prefab.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} {networkObject.GlobalObjectIdHash} entry! Removing entry from list!");
removeEmptyPrefabs.Add(i);
}
}

// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
if (NetworkConfig.PlayerPrefab != null)
{
var playerPrefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>();
if (playerPrefabNetworkObject != null)
{
//In the event there is no NetworkPrefab entry (i.e. no override for default player prefab)
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject.GlobalObjectIdHash))
{
//Then add a new entry for the player prefab
var playerNetworkPrefab = new NetworkPrefab();
playerNetworkPrefab.Prefab = NetworkConfig.PlayerPrefab;
NetworkConfig.NetworkPrefabs.Insert(0, playerNetworkPrefab);
NetworkConfig.NetworkPrefabOverrideLinks.Add(playerPrefabNetworkObject.GlobalObjectIdHash, playerNetworkPrefab);
}
}
else
{
// Provide the name of the prefab with issues so the user can more easily find the prefab and fix it
UnityEngine.Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{NetworkConfig.PlayerPrefab.name}\") has no NetworkObject assigned to it!.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
UnityEngine.Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{NetworkConfig.PlayerPrefab.name}\") has no NetworkObject assigned to it!.");
UnityEngine.Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{NetworkConfig.PlayerPrefab.name}\") has no {nameof(NetworkObject)} assigned to it!.");

}
}

// Clear out anything that is invalid or not used (for invalid entries we already logged warnings to the user earlier)
foreach (var networkPrefabIndexToRemove in removeEmptyPrefabs)
{
NetworkConfig.NetworkPrefabs.RemoveAt(networkPrefabIndexToRemove);
}
removeEmptyPrefabs.Clear();

NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;

Expand Down Expand Up @@ -604,7 +697,7 @@ public SocketTasks StartHost()
}
else
{
HandleApproval(ServerClientId, NetworkConfig.CreatePlayerPrefab, null, true, null, null);
HandleApproval(ServerClientId, NetworkConfig.PlayerPrefab != null, null, true, null, null);
}

SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
Expand Down Expand Up @@ -1477,7 +1570,7 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint?

if (createPlayerObject)
{
var networkObject = SpawnManager.CreateLocalNetworkObject(false, playerPrefabHash ?? NetworkConfig.PlayerPrefabHash, ownerClientId, null, position, rotation);
var networkObject = SpawnManager.CreateLocalNetworkObject(false, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, null, position, rotation);
SpawnManager.SpawnNetworkObjectLocally(networkObject, SpawnManager.GetNetworkObjectId(), false, true, ownerClientId, null, false, 0, false, false);

ConnectedClients[ownerClientId].PlayerObject = networkObject;
Expand Down Expand Up @@ -1566,7 +1659,7 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint?

OnClientConnectedCallback?.Invoke(ownerClientId);

if (!createPlayerObject || (playerPrefabHash == null && NetworkConfig.PlayerPrefabHash == 0))
if (!createPlayerObject || (playerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
{
return;
}
Expand Down Expand Up @@ -1594,7 +1687,7 @@ internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint?
// This is not a scene object
writer.WriteBool(false);

writer.WriteUInt32Packed(playerPrefabHash ?? NetworkConfig.PlayerPrefabHash);
writer.WriteUInt32Packed(playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);

if (ConnectedClients[ownerClientId].PlayerObject.IncludeTransformWhenSpawning == null || ConnectedClients[ownerClientId].PlayerObject.IncludeTransformWhenSpawning(ownerClientId))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public void HandleConnectionRequest(ulong clientId, Stream stream)
}
else
{
NetworkManager.HandleApproval(clientId, NetworkManager.NetworkConfig.CreatePlayerPrefab, null, true, null, null);
NetworkManager.HandleApproval(clientId, NetworkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

}
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Expand Down
Loading