I am trying to make a smash-bros like game. I am trying to make it so when a player joins, they are added to a list of currently connected players for the camera to follow. When I add a new NetworkObjectReference to a NetworkList of NetworkObjectReferences, I get a NullReferenceException. I have verified that I have initialized the list properly, and that the NetworkObjectReference is not null in any way. What is going on? Am I using the NetworkList improperly or am I just being dumb?
“Player” script:
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]
[RequireComponent(typeof(Movement))]
[RequireComponent(typeof(DeathCheck))]
public class Player : NetworkBehaviour
{
[Header("Scripts")]
public PlayerControls playerInput;
public Movement movementScript;
public DeathCheck deathCheckScript;
public void Awake()
{
if (!IsOwner) return;
playerInput = new PlayerControls();
playerInput.Enable();
}
public override void OnNetworkSpawn()
{
if (!IsOwner) return;
CamAddPlayerServerRpc(NetworkObject);
}
private void OnEnable()
{
if (!IsOwner) return;
playerInput.Player.Enable();
}
private void OnDisable()
{
if (!IsOwner) return;
playerInput.Player.Disable();
}
[ServerRpc]
private void CamAddPlayerServerRpc(NetworkObjectReference reference)
{
CameraController.players.Add(reference);
}
}
“CameraController” script:
using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
public class CameraController : NetworkBehaviour
{
public static NetworkList<NetworkObjectReference> players;
[SerializeField] private Vector3 cameraOffset;
[SerializeField] private float cameraBoundsSpeed;
Bounds cameraBounds;
Bounds newCameraBounds;
float dist;
private void Awake()
{
players = new NetworkList<NetworkObjectReference>();
}
private void LateUpdate()
{
if (!IsHost) return;
if (players.Count > 0)
{
newCameraBounds = new Bounds();
foreach (GameObject player in players)
{
Collider[] colliders = player.GetComponents<Collider>();
newCameraBounds.Encapsulate(colliders[0].bounds);
}
cameraBounds.extents = Vector3.Lerp(cameraBounds.extents, newCameraBounds.extents, cameraBoundsSpeed * Time.deltaTime);
cameraBounds.center = Vector3.Lerp(cameraBounds.center, newCameraBounds.center, cameraBoundsSpeed * Time.deltaTime);
dist = cameraBounds.extents.x + cameraBounds.extents.y + cameraOffset.z;
transform.position = cameraBounds.center + new Vector3(cameraOffset.x, cameraOffset.y, -dist);
}
else
transform.position = new Vector3(0, 0, -10);
}
}
There is going to be only one CameraController in the game so that is why it is set to static.
I tested this and on the host the Player is spawned before CameraController (assuming it’s an in-scene object) so the network list isn’t usable at that point. You can add the stacktrace of the error to confirm this.
It looks to me like it would be simpler to add the Players to the list on the host directly when the client connects rather than rpc them from the client, is there a particular reason you’re not doing this?
You don’t have to do this manually. The player prefabs, as well as any other networked object, are readily available via NetworkManager’s SpawnManager.
You can simplify initialization to this:
public static NetworkList<NetworkObjectReference> players = new();
But you may not want to do so if you have “Instant Play Mode” enabled (it’s actually called “Disabled Domain Reload”).
Don’t program Netcode like this. Every update this needlessly bumps out of Update, but it still runs nevertheless. Instead, you should implement OnNetworkSpawn and if it’s not the host, just disable the component (enabled = false). This also guarantees that no other host-only code runs in that components.
And where you need both host and client code, make two components. PlayerHost and PlayerClient for instance. Any RPCs will still be received on the other end. Both components would have a reference to each other to relay the RPC to the “correct” side. So if PlayerClient sends an RPC to the server, then its still received by PlayerClient but the RPC method should then make a call to playerHost in order for PlayerHost to run the host-side code.
This makes thinking about the various roles a whole lot easier in general by physically separating the logical roles.
I see you change the Camera’s transform.position. That’s the classic, old school, if not legacy way of dealing with the Camera.
You should look into Cinemachine because from the looks of it it seems like you want to have the camera adjust to the player’s bounds, keeping them all on screen. Guess what? Cinemachine has this feature built-in, and in high quality including smoothly blending in and out with safe zones, thresholds, yada yada. The package contains a Sample showcasing exactly this behaviour!
I will try your suggestions as soon as i can, as for Cinemachine, i just prefer doing it manually as it helps me learn a bit.
They are, but if he wants a list of Players to share with clients wouldn’t it be easier to grab each player as they become available?
I gave this a try and got a Leak Detected message, the documentation does mention memory leaks and the need to instantiate the list in Awake.
Sure, and come to think of it, you needn’t use an RPC for this use case.
Each player instance (local and remote) runs OnNetworkSpawn so the code only needs to add that spawned player to the local list of players, done. This needn’t be network synched at all because it inherently already is synchronized, provided that the code also removes the players in OnNetworkDespawn.
Oh, okay this seems different between NetworkVariable and NetworkList.
Learn what exactly? You can learn to drive a car without assembling the motor first, and you still have plenty to learn.
There are some benefits to DIY in areas where customization is important or just fun, but scripting the camera provides neither of that. Camera behaviour is among the hardest things to script and a good camera makes a huge difference in perceived quality. If you learn Cinemachine, that skill is way more valuable than how to deal with, say, gimbal lock.
1 Like