Hello!
I’ve upgraded to Netcode 1.0 and am now having this crazy error that I really do not understand at all, hoping somebody here can offer some ideas.
When I spawn an object with a NetworkBehavior with a single network variable, I get this error:
NullReferenceException: Object reference not set to an instance of an object
Unity.Netcode.NetworkBehaviour.InitializeVariables () (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Core/NetworkBehaviour.cs:457)
Unity.Netcode.NetworkBehaviour.InternalOnNetworkSpawn () (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Core/NetworkBehaviour.cs:369)
Unity.Netcode.NetworkObject.InvokeBehaviourNetworkSpawn () (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Core/NetworkObject.cs:798)
Unity.Netcode.NetworkSpawnManager.SpawnNetworkObjectLocallyCommon (Unity.Netcode.NetworkObject networkObject, System.UInt64 networkId, System.Boolean sceneObject, System.Boolean playerObject, System.Nullable1[T] ownerClientId, System.Boolean destroyWithScene) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Spawning/NetworkSpawnManager.cs:525) Unity.Netcode.NetworkSpawnManager.SpawnNetworkObjectLocally (Unity.Netcode.NetworkObject networkObject, System.UInt64 networkId, System.Boolean sceneObject, System.Boolean playerObject, System.Nullable1[T] ownerClientId, System.Boolean destroyWithScene) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Spawning/NetworkSpawnManager.cs:438)
Unity.Netcode.NetworkObject.SpawnInternal (System.Boolean destroyWithScene, System.Nullable`1[T] ownerClientId, System.Boolean playerObject) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Core/NetworkObject.cs:476)
Unity.Netcode.NetworkObject.Spawn (System.Boolean destroyWithScene) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Core/NetworkObject.cs:499)
Kayyos.Shared.ObjectDefinition.CreateWorldInstance (UnityEngine.Vector3 worldPosition, UnityEngine.Quaternion rotation, System.Boolean destroyWithScene) (at Assets/Kayyos/Scripts/Shared/ObjectDatabase/ObjectDefinition.cs:55)
Kayyos.Shared.MultiPlayerNetworkController.RegisterPlayer (Kayyos.Shared.NetworkPlayerController controller) (at Assets/Kayyos/Scripts/Shared/Net/Player/MultiPlayerNetworkController.cs:72)
Kayyos.Shared.MultiPlayerNetworkController.RequestPlayerControllerServerRPC (System.Byte playerIndex) (at Assets/Kayyos/Scripts/Shared/Net/Player/MultiPlayerNetworkController.cs:116)
Kayyos.Shared.MultiPlayerNetworkController.__rpc_handler_1200881751 (Unity.Netcode.NetworkBehaviour target, Unity.Netcode.FastBufferReader reader, Unity.Netcode.__RpcParams rpcParams) (at <73ea9c94abc5452291b2ea4b834d97ed>:0)
Unity.Netcode.RpcMessageHelpers.Handle (Unity.Netcode.NetworkContext& context, Unity.Netcode.RpcMetadata& metadata, Unity.Netcode.FastBufferReader& payload, Unity.Netcode.__RpcParams& rpcParams) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Messaging/Messages/RpcMessages.cs:77)
Rethrow as Exception: Unhandled RPC exception!
UnityEngine.Debug:LogException(Exception)
Unity.Netcode.RpcMessageHelpers:Handle(NetworkContext&, RpcMetadata&, FastBufferReader&, __RpcParams&) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Messaging/Messages/RpcMessages.cs:81)
Unity.Netcode.ServerRpcMessage:Handle(NetworkContext&) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Messaging/Messages/RpcMessages.cs:122)
Unity.Netcode.NetworkBehaviour:__endSendServerRpc(FastBufferWriter&, UInt32, ServerRpcParams, RpcDelivery) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.0-pre.6/Runtime/Core/NetworkBehaviour.cs:93)
Kayyos.Shared.MultiPlayerNetworkController:RequestPlayerControllerServerRPC(Byte) (at Assets/Kayyos/Scripts/Shared/Net/Player/MultiPlayerNetworkController.cs:99)
Kayyos.Shared.MultiPlayerNetworkController:RequestPlayerController(Int32) (at Assets/Kayyos/Scripts/Shared/Net/Player/MultiPlayerNetworkController.cs:153)
Kayyos.Shared.MultiPlayerNetworkController:HandleOnPlayerJoined(PlayerInput) (at Assets/Kayyos/Scripts/Shared/Net/Player/MultiPlayerNetworkController.cs:93)
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr)
What’s weird is that, stepping through the code, the NetworkVariableFields list is in fact null somehow when InitializeVariables() is called:
internal void InitializeVariables()
{
if (m_VarInit)
{
return;
}
m_VarInit = true;
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
for (int i = 0; i < sortedFields.Length; i++)
{
Type fieldType = sortedFields[i].FieldType;
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
{
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
if (instance == null)
{
throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
}
instance.Initialize(this);
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
var sanitizedVariableName = sortedFields[i].Name.Replace("<", string.Empty).Replace(">k__BackingField", string.Empty);
instanceNameProperty?.SetValue(instance, sanitizedVariableName);
NetworkVariableFields.Add(instance); // NetworkVariableFields is NULL here...
}
}
...
The behavior is very simple, with a single NetworkVariable:
public class WorldObjectConceptState : NetworkBehaviour, IConceptStateProvider, IPlayerPossessable
{
public NetworkVariable<NetworkDataEntityRef> definition = new NetworkVariable<NetworkDataEntityRef>();
...
And here’s the ONLY weird part, NetworkDataEntityRef is a struct I’m using to simplify lookup into an object database I’ve defined:
public struct NetworkDataEntityRef : INetworkSerializable
{
private uint id;
public DataEntity Entity
{
get
{
var db = Vault.Data as TagSearchableDatabase;
return db.GetEntityByID<DataEntity>(id);
}
}
public T EntityAs<T>() where T : DataEntity
{
return Entity as T;
}
public NetworkDataEntityRef(DataEntity entity)
{
id = entity.ID;
}
public static implicit operator NetworkDataEntityRef(DataEntity entity)
{
return new NetworkDataEntityRef(entity);
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref id);
}
}
Removing that network variable, however, still yields the same error.
The flow is essentially this:
- The host creates the MultiplayerNetworkController as the PlayerObject.
- When input is detected, a RequestPlayerControllerServerRPC is sent where the server creates a new NetworkPlayerController object and calls RegisterPlayer().
- As a hack for testing, inside RegisterPlayer() I spawn an object from my database (via CreateWorldInstance()) and THAT object is causing this error.
This whole setup and flow worked for months with MLAPI, and WEIRDLY it worked fine yesterday. I’ve restarted Unity, my computer, nuked the Library folder, etc.
If I remove the WorldObjectConceptState (the NetworkBehavior I started working on this morning as part of a refactor of existing functionality) from the object that gets spawned, everything is peachy and no error occurs. Here’s the offender:
public class WorldObjectConceptState : NetworkBehaviour, IConceptStateProvider, IPlayerPossessable
{
public NetworkVariable<NetworkDataEntityRef> definition = new NetworkVariable<NetworkDataEntityRef>();
private List<IFixedTickableConcept> fixedTickableConcepts = new List<IFixedTickableConcept>();
private List<ITickableConcept> tickableConcepts = new List<ITickableConcept>();
public WorldObjectConceptState(CharacterController characterController)
{
CharacterController = characterController;
}
public NetworkPlayerController PossessingPlayer { get; private set; }
/// <summary>
/// Cached Components
/// </summary>
public CharacterController CharacterController { get; private set; }
public Rigidbody Rigidbody { get; private set; }
public Animator Animator { get; private set; }
public ObjectDefinition Definition
{
get => definition.Value.EntityAs<ObjectDefinition>();
set => definition.Value = value;
}
public bool CanPossess(NetworkPlayerController controller)
{
return true;
}
public void Possess(NetworkPlayerController controller)
{
PossessingPlayer = controller;
Debug.Log($"Object possessed: {name}->{Definition}");
}
public bool CanUnpossess()
{
return true;
}
public void Unpossess()
{
PossessingPlayer = null;
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
Debug.Log($"Object Spawned: {Definition}");
if (!Definition)
{
Debug.LogError("Definition isn't set on spawn, something went horribly wrong...");
return;
}
foreach (var concept in Definition.Concepts)
{
if (concept is IFixedTickableConcept fixedTickableConcept)
fixedTickableConcepts.Add(fixedTickableConcept);
if (concept is ITickableConcept tickableConcept)
tickableConcepts.Add(tickableConcept);
}
}
public override void OnNetworkDespawn()
{
base.OnNetworkDespawn();
}
protected void Awake()
{
definition.OnValueChanged += (value, newValue) =>
{
if (IsClient)
Debug.Log($"Client Definition changed: {Definition}");
if (IsServer)
Debug.Log($"Server Definition changed: {Definition}");
};
// Cache components
CharacterController ??= GetComponent<CharacterController>();
Rigidbody ??= GetComponent<Rigidbody>();
Animator ??= GetComponent<Animator>();
}
}
Somewhere in here, I’ve touched something in a bad way. Halp me UnityWanKenobi, you’re my only hope!