I'm fairly new to Unity, and I've begun learning how to use Fishnet networking. I've created a basic player movement script that syncs player position far faster than a Network Transform would. But I'm running into a bizarre problem I don't know how to solve.
In my scene, I have a Network Manager which, upon connection, spawns my Player prefab--a simple sprite with a player script and a network object. I haven't added a network transform, since I'll be syncing each player's position manually to reduce delay between clients. Here's the player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using FishNet.Object;
public class Player : NetworkBehaviour
{
private void Update()
{
if (IsOwner) //only the client that owns this object will run this code
{
//get input, send it to server
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
}
//since this is an observers rpc, only the server will call it
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
[ServerRpc]
public void RpcMoveCharacter(float x, float y)
{
//change the position of the server's instance of the player
transform.Translate(x * 10f * Time.deltaTime * Vector3.right);
transform.Translate(y * 10f * Time.deltaTime * Vector3.up);
}
[ObserversRpc]
public void RpcSendCharacterPosition(float x, float y)
{
if (IsClientOnly)
{
//ensure clients' instance of the player match the server's' position
transform.position = new Vector2(x, y);
}
}
}
The script works perfectly...except for one problem: the movement speed of the player isn't consistent for both players. The issues only occur when I build and run my game, then have the two versions of the game connect.
When either player is a host (server client) their player object moves at medium speed on both screens. This is the intended speed.
When the version of my game running from my unity editor window is only a client, the player moves at fast speed on both screen--many times faster than intended.
When the version of my game I created using 'build and run' is only a client, the player moves at slow speed on both screens--many times slower than intended.
I've tested everything I can think of. One test I did was to prevent the network manager from spawning the player prefab, place the player object in scene ahead of time, and convert this:
private void Update()
{
if (IsOwner)
{
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
}
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
[ServerRpc]
to this:
private void Update()
{
//now anyone can control the player object
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
//same effect as note above
[ServerRpc (RequireOwnership = false)]
in order to see if there was something about the player spawning feature was bugged. My changes had zero effect whatsoever--nothing changed at all. If my editor was a client only it still moved the player too quickly, and if my build was a client only it still moved the player too slowly.
Another thing I tried was to make a brand new project in case I had toggled a setting weirdly or something in the last one. Once I had created a new project, all I did was import fishnet, add fishnet's default NetworkManager object to my scene, create a simple prefab called player, add a network object and the original player script to the player prefab, set the network manager to spawn the player prefab, and tried again. No luck--everything was exactly the same.
Any ideas? I'm super stuck here--I don't know what else to try, since everything in the code/scene seems to be working perfectly. I can't figure out why my build would be behaving differently than my editor's play mode, regardless of which is the server (or host) and which is the client only.
Thanks!
CodePudding user response:
So I don't really know how exactly this Fishnet
works.
But as said in general in any networking you can never rely on
- All your devices running at the same FPS (frames per second)
- Your networked messages arriving at the server/other clients immediately
- Your networked messages arriving at the server/other clients in the exact same intervals as you ended them
So what I would rather do as mentioned is
First of all do not send network messages every frame but rather some fixed time intervals (like e.g. often used every 0.2 seconds)
Rather handle all local movement locally and immediately
It would be very bad for the UX if you send your user inputs to the server and have to wait until it is applied by receiving back the resulting position. This causes a 2-times network delay which would be extremely uncanny for the local user.
Instead of the delta rather synchronize the resulting position value.
This way you can be sure all players are in sync with the actual resulting positions and it works immediately also for players who joined the session later or potentially missed a few input messages due to network lag.
So I would do something like
public class Player : NetworkBehaviour
{
// Interval in seconds how often to send your position to the server/clients
[SerializeField] private float sendInterval = 0.2f;
// How fast you can move in units per second
[SerializeField] private float moveSpeed = 10f;
// Use this to adjust your input sensitivities
[SerializeField] [Min(0)] private float inputSensitivityX = 1f;
[SerializeField] [Min(0)] private float inputSensitivityY = 1f;
// Might have to play a bit with this value to make smooth interpolation faster or slower
// 5 is an arbitrary value but works quite good from experience
// depends on your sendInterval and movespeed as well
[SerializeField] privte float interpolation = 5f;
// keeps track of passed time
private float sendTimer;
private Vector2 receivedTargetPosition;
private void Start()
{
if(!IsOwner)
{
receivedTargetPosition = transform.position;
}
}
private void Update()
{
//only the client that owns this object will run this code
if (IsOwner)
{
//get input
var horizontalInput = Input.GetAxisRaw("Horizontal");
var verticalInput = Input.GetAxisRaw("Vertical");
var input = new Vector2(horizontalInput * inputSensitivityX, verticalInput * inputSensitivityY);
// Makes sure that you always have maximum 1 magnitude for the input
input = Vector2.ClampMagnitude(input, 1f);
// use the rotation to already rotate this vector from local into world space
input = trasform.rotation * input;
// Here you want the deltaTime of THIS DEVICE
var movement = moveSpeed * Time.deltaTime * input;
// Move your player LOCALLY
transform.position = (Vector3)movement;
}
// If you are not the owner you rather apply the received position
else
{
// I would e.g. smoothly interpolate somewhat like
transform.position = Vector3.Lerp(transform.position, receivedTargetPosition, interpolation * Time.deltaTime);
}
// Check if next send time interval has passed
sendTimer = Time.deltaTime;
if(sendTimer >= sendInterval)
{
sendTimer = 0;
if(IsServer)
{
RpcSendPositionToClients(transform.position.x, transform.position.y);
}
else
{
RpcSendPositionToServer(transform.position.x, transform.position.y);
}
}
}
[ServerRpc]
public void RpcSendPositionToServer(float x, float y)
{
// just in case
// the owner already gets its position in Update so nothing to do
if(IsOwner) return;
//change the position of the server's instance of the player
receivedTargetPosition = new Vector2(x, y);
}
[ClientRpc]
public void RpcSendPositionToClients(float x, float y)
{
// Owner and server already know the positions
if(IsOwner || IsServer) return;
receivedTargetPosition = new Vector2(x, y);
}
}