Hi, I'm making a simple spawning system but encountered an issue with transporting the player. I fixed it by switching the Authority mode to server, however now my movement code doesn't work since I wrote it as client side (owner).
So I'm wondering what I should do, should I find another fix or should I rewrite the movement code to work server side?
What benefit or loss is there?
#Advandtage / Disadvantage for Network Transform Authority Mode
1 messages · Page 1 of 1 (latest)
Server Authority's downside is increased input latency, which you will have to deal with somehow.
With owner Auth, the server will have to tell the client where to spawn in an RPC. The downside there is less security, as the client could cheat.
How severe is the latency and is it just plain advised to use owner?
The latency depends entirely on the client network connection. Could be 25ms or 250ms. What is acceptable depends on your game type. A card game is not going to matter as much as a fighting game.
Which one you use depends on how sensitive you are to potential cheating. A Co Op game with friends can be all client auth. But a competitive shooter would need to be server auth for fairness
Right, alright thanks for the explanation.
So as mentioned I was making a simple spawn points system thing. With owner auth, what would be a good way to do it?
The server needs to spawn the player first. then the client needs to reposition itself to the spawnpoint. The client can assign its own spawnpoint or the server can send an RPC with the spawn location
When you say "server needs to spawn" is that refering to the network manager
That is for the initial spawn on connection, yes. And if you are having the player despawn/respawn then the server has to call .Spawn() on the player object.
You can also do something like only disabling the player renderer and teleporting the player to a spawnpoint then reenabling the renderer
The only method I have for spawning is "OnNetworkSpawn"
public class SpawnManager : MonoBehaviour
{
private readonly List<SpawnPoint> spawnPoints = new();
public void Awake()
{
spawnPoints.Clear();
foreach (Transform child in transform)
{
spawnPoints.Add(new SpawnPoint
{
spTransform = child,
IsUsed = false,
OwnerClientId = null
});
}
}
public Transform ReserveSpawn(ulong clientId)
{
if (spawnPoints.Count == 0) return null;
var sp = spawnPoints.Find(p => !p.IsUsed);
if (sp == null)
sp = spawnPoints[clientId.GetHashCode() % spawnPoints.Count];
sp.IsUsed = true;
sp.OwnerClientId = clientId;
return sp.spTransform;
}
public void ReleaseSpawn(ulong clientId)
{
var sp = spawnPoints.Find(p => p.OwnerClientId == clientId);
if (sp == null) return;
sp.IsUsed = false;
sp.OwnerClientId = null;
}
}
public class SpawnPoint
{
public Transform spTransform;
public bool IsUsed;
public ulong? OwnerClientId;
//public MapType mapType { get; set; } // ADD ENUM
//public float Radius { get; private set; } // ADD RADIUS
// add rotation
//
}
And in the playermovement script
public override void OnNetworkSpawn()
{
state = GetComponent<PlayerState>();
IARefs.Clear();
if (moveAction != null) IARefs.Add(moveAction);
if (jumpAction != null) IARefs.Add(jumpAction);
if (lookAction != null) IARefs.Add(lookAction);
if (!IsOwner)
{
if (playerCamera) playerCamera.gameObject.SetActive(false);
SetInputsEnabled(false);
return;
}
if (IsServer)
{
var sp = GameManager.Instance.spawnManager.ReserveSpawn(OwnerClientId);
if (sp != null) transform.SetPositionAndRotation(sp.position, sp.rotation);
}
Cursor.lockState = CursorLockMode.Locked;
yaw = transform.eulerAngles.y;
if (playerCamera) camLocalPos = playerCamera.localPosition;
if (state != null)
{
state.IsAlive.OnValueChanged += OnAliveChanged;
OnAliveChanged(true, state.IsAlive.Value);
}
else
{
SetInputsEnabled(true);
}
Debug.Log($"Spawned {gameObject.name} | IsOwner={IsOwner} IsClient={IsClient} IsServer={IsServer}");
}
public class GameManager : NetworkBehaviour
{
public static GameManager Instance { get; private set; }
public SpawnManager spawnManager;
private readonly Dictionary<ulong, PlayerState> players = new();
public IReadOnlyDictionary<ulong, PlayerState> Players => players;
void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
void Start()
{
NetworkManager.Singleton.OnServerStarted += OnServerStarted;
}
private void OnDestroy()
{
if (NetworkManager.Singleton == null) return;
NetworkManager.Singleton.OnServerStarted -= OnServerStarted;
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
}
private void OnServerStarted()
{
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnected;
foreach (var clientId in NetworkManager.Singleton.ConnectedClientsIds)
OnClientConnected(clientId);
}
private void OnClientConnected(ulong clientId)
{
if (!IsServer) return;
var playerObj = NetworkManager.Singleton.ConnectedClients[clientId].PlayerObject;
if (playerObj == null) return;
var sp = spawnManager.ReserveSpawn(clientId);
if (sp == null) return;
playerObj.transform.SetPositionAndRotation(sp.position, sp.rotation);
}
private void OnClientDisconnected(ulong clientId)
{
if (!IsServer) return;
spawnManager.ReleaseSpawn(clientId);
}
public void RegisterPlayer(ulong clientId, PlayerState player)
{
if (!IsServer) return;
players[clientId] = player;
}
public void UnregisterPlayer(ulong clientId)
{
if (!IsServer) return;
players.Remove(clientId);
}
}
The server will not be able to set the player transform when you are using Owner Auth
You'll need to use an RPC for the server to tell the client which spawnpoint to use
Could you shortly explain how the chain would go?
My idea is:
OnClientConnected
Get clientID
ReserveSpawn
Set player Pos and Rot
But I'm also unsure where RPC would go in that line
ReserveSpawn is happening on the server. When it get a spawnpoint, it can send an RPC to the client with the spawnpoint position. That client can then set its player position
Ahh right. I'll try that.
Would you say I'm doing server stuff at the moment on the player movement manager that I shouldn't be doing?
Should I be splitting some of this up?
Should probably split it up. It should only be handling player movement. Since its Owner Auth there is nothing the server should be doing in there
So take out the OnNetworkSpawn and stuff
just the spawn manager stuff.
Made a quick UML diagram to show where I'm at instead of posting all the code 😂
Does this seem correct?
Hmm, doesn't seem to work.
When the host connects it can move and look.
But when a client connects it freezes the host and the client can only look around
Make sure there is only one active camera in the scene. Only the local player should have the camera
You should probably also disable the player input on the non local players if you are not already
Should there not be a camera per player?
It is disabling the other camera actually
But I'm unsure what you mean with disabling input on non local players?
A tool for sharing your source code with the world!
Found an easy way of sharing it
Yea. whatever script is handling inputs should only be enabled for the local player.
Which happens in OnNetworkSpawn like this:
[SerializeField] private PlayerSelfControlManager control;
state.IsAlive.OnValueChanged += OnAliveChanged;
OnAliveChanged(true, state.IsAlive.Value);
private void OnAliveChanged(bool oldVal, bool newVal)
{
if (!IsOwner) return;
if (control) control.enabled = newVal;
Cursor.lockState = newVal ? CursorLockMode.Locked : CursorLockMode.None;
}
That will still reenable control for the non local players. Do the check for IsLocalPlayer instead.
public override void OnNetworkSpawn()
{
state = GetComponent<PlayerState>();
if (!IsOwner)
{
SetLocalOnly(false);
return;
}
SetLocalOnly(true);
Cursor.lockState = CursorLockMode.Locked;
if (state != null)
{
state.IsAlive.OnValueChanged += OnAliveChanged;
OnAliveChanged(true, state.IsAlive.Value);
}
}
But I do the SetLocalOwner if owner no?
private void SetLocalOnly(bool enabled)
{
if (playerCamera) playerCamera.gameObject.SetActive(enabled);
if (audioListener) audioListener.enabled = enabled;
if (control) control.enabled = enabled;
}
for the remote players !IsOwner will be true
that's why you should check for IsLocalPlayer instead
From Host view on host player
From client view of client player
But I'll try IsLocalPlayer
Same result
control is still enabled for non local players?
This is the hosts console after host joins
This is the clients console after client joins
But both CharacterControllers are still enabled yes
Should they not
Character controller need to be disabled too. The network transform will keep things in sync
@obtuse girder Hi, thanks for all the help, I got it working fully now.
I was wondering, since I'm making a prototype game to 1. learn unity more than I did 2. to get an idea if the game is fun, if I wanted the ability to setup a small playtest with another person or two, how would I do that?
I'm guessing first make a build of the game.
And I need to port forward my router, but the steps in between or after I'm a bit confused on