Update: I’ve been working on this on my own on and off for a bit and I’m still confounded by a few weird things that I’ve been trying to solve but there’s two parts that just don’t work and I have no idea why.
- clients can’t set their own velocity even though it works in every other script (now I write this I realise it might just be because the velocity is being overwritten by fixedupdate so I will have to check later)
- using ClientRpcParams prevents all clients from recieving the rpc; I take the target client’s OwnerClientId which is passed to the server rpc which converts that into ClientRpcParams however it doesn’t find any client which is very odd.
(I’m gonna put the entire script here, the whole velocity stuff starts on line 141)
using System;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using TMPro;
/*============================ ======================Script==================================================*/
[RequireComponent(typeof(Rigidbody))]
public class PlayerMovement_FPS : NetworkBehaviour
{
[Header("Rigidbody Movement \n")]
[Tooltip("The variables that should be set manually.")] public PMFPSParameters Parameters;
[Tooltip("All variables that doesn't require manual intervention.")] public PMFPSFields Fields;
//setup
public override void OnNetworkSpawn()
{
if (!IsOwner) { return; } else { GetComponent<Rigidbody>().collisionDetectionMode = CollisionDetectionMode.Continuous; }
Camera.main.transform.SetParent(transform);
Camera.main.transform.localPosition = Parameters.CameraPosition;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
Fields.CursorLock = true;
Destroy(transform.Find("Eye").GetComponent<MeshRenderer>());
transform.Find("Nametag").GetComponent<TextMeshPro>().fontSize = 0;
Destroy(transform.Find("nametag background").GetComponent<MeshRenderer>());
Fields.NetworkObject = GetComponent<NetworkObject>();
Fields.Rigidbody = GetComponent<Rigidbody>();
Fields.GroundPoint = new GameObject("GroundPoint").transform;
Fields.GroundPoint.parent = transform;
Fields.GroundPoint.localPosition = new Vector3(0, Parameters.BottomDistance, 0);
SetGameObjectNameServerRpc(gameObject, GameObject.Find("UserName").GetComponent<TextMeshProUGUI>().text.Replace("Username: ", ""));
}
//inputs and camera
private void Update()
{
if (!IsOwner) return;
if (Input.GetKeyDown(KeyCode.Escape)) { Fields.CursorLock = false; }
else if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1)) { Fields.CursorLock = true; }
if (Fields.CursorLock)
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
Fields.MouseXY = new Vector2(Input.GetAxis("Mouse X") * Parameters.MouseSensitivity, Fields.MouseXY.y - Input.GetAxis("Mouse Y") * Parameters.MouseSensitivity);
Fields.MouseXY.y = Mathf.Clamp(Fields.MouseXY.y, -90f, 90f);
Camera.main.transform.localRotation = Quaternion.Euler(Camera.main.transform.localRotation.x + Fields.MouseXY.y, 0, 0);
transform.rotation *= Quaternion.Euler(0, Fields.MouseXY.x, 0);
}
else
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
Fields.XYInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
if (Fields.XYInput.magnitude > 1) Fields.XYInput.Normalize();
Fields.XYOutput = (Fields.XYInput * Parameters.MoveSpeed);
Fields.JumpInput = Input.GetButton("Jump");
Fields.JumpOutput = (Convert.ToInt32(Fields.JumpInput) * Parameters.JumpHeight);
}
//physics
void FixedUpdate()
{
if (!IsOwner) return;
Fields.OnGround_Walk = Physics.CheckSphere(Fields.GroundPoint.position, Parameters.MoveDistance, Parameters.GroundLayer);
Fields.OnGround_Jump = Physics.CheckSphere(Fields.GroundPoint.position, Parameters.JumpDistance, Parameters.GroundLayer);
Vector3 move = transform.forward * Fields.XYOutput.y + transform.right * Fields.XYOutput.x;
if (Fields.OnGround_Walk && Parameters.MovementType == PMFPSMovementType.realistic) Fields.Rigidbody.velocity = new Vector3(move.x, Fields.Rigidbody.velocity.y, move.z);
else if (Parameters.MovementType == PMFPSMovementType.forgiving)
{
if (Fields.OnGround_Walk)
{
if (Mathf.Abs(Fields.Rigidbody.velocity.x) < Parameters.MoveSpeed && move.x != 0) Fields.Rigidbody.velocity = new Vector3(move.x, Fields.Rigidbody.velocity.y, Fields.Rigidbody.velocity.z);
if (Mathf.Abs(Fields.Rigidbody.velocity.z) < Parameters.MoveSpeed && move.z != 0) Fields.Rigidbody.velocity = new Vector3(Fields.Rigidbody.velocity.x, Fields.Rigidbody.velocity.y, move.z);
}
else Fields.Rigidbody.AddForce(move.x, 0f, move.z);
}
else if (Parameters.MovementType == PMFPSMovementType.normal) Fields.Rigidbody.velocity = new Vector3(move.x, Fields.Rigidbody.velocity.y, move.z);
if (Fields.JumpInput && Fields.OnGround_Jump) Fields.Rigidbody.velocity = new Vector3(Fields.Rigidbody.velocity.x, Fields.Rigidbody.velocity.y + Fields.JumpOutput, Fields.Rigidbody.velocity.z);
}
//set a client's username
[ServerRpc]
public void SetGameObjectNameServerRpc(NetworkObjectReference Player, string Name)
{
((GameObject)Player).name = Name;
DebugLog("Setting player name in server hierarchy");
GameObject[] ServerNameObjects = GameObject.FindGameObjectsWithTag("Player");
string[] ServerNames = new string[ServerNameObjects.Length];
for(int i = 0; i < ServerNames.Length; i++)
{
ServerNames[i] = ServerNameObjects[i].name;
}
SyncNamesClientRpc(new NetworkStringArrayStruct { Array = ServerNames });
}
//sync all current usernames with all clients
[ClientRpc]
public void SyncNamesClientRpc(NetworkStringArrayStruct ServerNames)
{
DebugLog("Syncing Client Names");
GameObject[] NameTags = GameObject.FindGameObjectsWithTag("Nametag");
int i = 0;
foreach (GameObject Tag in NameTags)
{
Tag.GetComponent<PlayerNameTag>().Name = ServerNames.Array[i];
DebugLog("Sync Client Name: " + ServerNames.Array[i]);
i++;
}
}
//send collision velocity to player that was collided with
void OnCollisionEnter(Collision collision)
{
if (!IsOwner) return;
Vector3 V = Fields.Rigidbody.velocity;
if (collision.gameObject.CompareTag("Player"))
{
DebugLog("Collided with player with velocity: " + V);
ulong ID = collision.gameObject.GetComponent<NetworkObject>().OwnerClientId;
SetVelocityServerRpc(V, ID);
}
}
//send velocity to server to then send to client
[ServerRpc]
void SetVelocityServerRpc(Vector3 Velocity, ulong ID)
{
DebugLog("send collision message to server");
ClientRpcParams clientRpcParams = new ClientRpcParams
{
Send = new ClientRpcSendParams
{
TargetClientIds = new ulong[] { ID }
}
};
SetVelocityClientRpc(Velocity, clientRpcParams);
}
//Get velocity to set from server
[ClientRpc]
void SetVelocityClientRpc(Vector3 Velocity, ClientRpcParams clientRpcParams = default)
{
DebugLog("send message to client with velocity: " + Velocity);
SetVelocity(Velocity);
}
//set velocity based on input
void SetVelocity(Vector3 Velocity)
{
if (!IsOwner) { DebugLog("Not owner and cannot do SetVelocity()"); return; }
DebugLog("Velocity to send" + Velocity);
DebugLog(GetComponent<PlayerNameTag>().Name + " " + Fields.Rigidbody);
Fields.Rigidbody.velocity += Velocity;
}
//Debug
void DebugLog(object message)
{
if (!IsOwner) return;
DebugLogServerRpc(message.ToString());
}
[ServerRpc]
void DebugLogServerRpc(string message)
{
DebugLogClientRpc(message);
}
[ClientRpc]
void DebugLogClientRpc(string message)
{
Debug.Log(message);
}
}
//serialize player names array to use in RPC
public struct NetworkStringArrayStruct : INetworkSerializable
{
public string[] Array;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
var length = 0;
if (!serializer.IsReader)
length = Array.Length;
serializer.SerializeValue(ref length);
if (serializer.IsReader)
Array = new string[length];
for (var n = 0; n < length; ++n)
serializer.SerializeValue(ref Array[n]);
}
}
/*==================================================CustomClasses===========================================*/
[System.Serializable]
public class PMFPSParameters
{
[Header("Movement")]
[Tooltip("The speed of walking without any multipliers or adders.")] public float MoveSpeed;
[Tooltip("The distance the player has to be from the ground to walk.")] public float MoveDistance;
[Header("Mouse")]
[Tooltip("The sensitivity of the FPS mouse movement.")] public float MouseSensitivity;
[Tooltip("Offset of the camera from the player.")] public Vector3 CameraPosition;
[Header("Jumping")]
[Tooltip("The height of jumping without any multipliers or adders.")] public float JumpHeight;
[Tooltip("The distance the player has to be from the ground to jump.")] public float JumpDistance;
[Header("Types")]
[Tooltip("The type of movement that will be used.")] public PMFPSMovementType MovementType;
[Header("Miscellaneous")]
[Tooltip("The lowest point of the player from the centre.")] public float BottomDistance;
[Tooltip("The layer in which the player can jump on.")] public LayerMask GroundLayer;
[Header("Debug")]
[ReadOnly] [Tooltip("Broadcast all debug.log messages")] public bool DebugMode;
}
public enum PMFPSMovementType
{
realistic, //player has ZERO movement control in midair.
forgiving, //player has WEAK movement control in midair.
normal //player has FULL movement control in midair.
}
[System.Serializable]
public class PMFPSFields
{
[Header("Networking")]
[ReadOnly] [Tooltip("The network object of the player")] public NetworkObject NetworkObject;
[Header("Objects")]
[ReadOnly] [Tooltip("The Rigidbody used for the player to interact with physics.")] public Rigidbody Rigidbody;
[ReadOnly] [Tooltip("Should be touching the ground when the player is on the ground.")] public Transform GroundPoint;
[Header("Inputs")]
[ReadOnly] [Tooltip("The horizontal and vertical input.")] public Vector2 XYInput;
[ReadOnly] [Tooltip("The jump input.")] public bool JumpInput;
[Header("Camera")]
[ReadOnly] [Tooltip("The X and Y movement of the mouse.")] public Vector2 MouseXY;
[ReadOnly] [Tooltip("If mouse is being used in game.")] public bool CursorLock;
[Header("Movement")]
[ReadOnly] [Tooltip("The horizontal and vertical movement final values.")] public Vector2 XYOutput;
[ReadOnly] [Tooltip("The jump final output.")] public float JumpOutput;
[ReadOnly] [Tooltip("If the player is in jump mode.")] public bool JumpCooldown;
[Header("Conditions")]
[ReadOnly] [Tooltip("Is the player on the ground or not? (to be able to walk)")] public bool OnGround_Walk;
[ReadOnly] [Tooltip("Is the player on the ground or not? (to be able to jump)")] public bool OnGround_Jump;
}
Thanks for reading, and hopefully helping