Hello community!
I have a problem transforming a simple game into a working multiplayer game using #Mirror.
Here is my controller that works in a single player context:
using UnityEngine;
namespace QuickStart
{
public class VelocityMovement : MonoBehaviour
{
public float mouseSensitivity = 150.0f;
public float hitForceMultiplier = 2.0f; // Multiplier for force applied to the ball
private bool cursorLocked = true;
private Rigidbody rbPlayer;
private Vector3 accumulatedMovement = Vector3.zero;
public void Awake()
{
LockCursor();
rbPlayer = GetComponent<Rigidbody>();
rbPlayer.constraints = RigidbodyConstraints.FreezeRotation | RigidbodyConstraints.FreezePositionY;
rbPlayer.interpolation = RigidbodyInterpolation.Interpolate;
}
public void Update()
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
Vector3 movement = new Vector3(mouseX, 0, mouseY) * mouseSensitivity * Time.deltaTime;
if (movement.magnitude > 0.001f)
{
accumulatedMovement += movement;
}
}
void FixedUpdate()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
UnlockCursor();
}
else if (!cursorLocked && Input.GetKeyDown(KeyCode.S))
{
LockCursor();
}
if (cursorLocked)
{
HandleMouseMovement();
}
}
private Vector3 GetMouseMovement()
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
return new Vector3(mouseX, 0, mouseY) * mouseSensitivity;
}
private void HandleMouseMovement()
{
if (accumulatedMovement.magnitude > 0.01f)
{
Vector3 targetVelocity = accumulatedMovement / Time.fixedDeltaTime;
rbPlayer.linearVelocity = new Vector3(targetVelocity.x, rbPlayer.linearVelocity.y, targetVelocity.z);
}
else
{
rbPlayer.linearVelocity = new Vector3(0, rbPlayer.linearVelocity.y, 0);
}
accumulatedMovement = Vector3.zero;
}
private void LockCursor()
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
cursorLocked = true;
}
private void UnlockCursor()
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
cursorLocked = false;
}
void OnDisable()
{
UnlockCursor();
}
}
}
This script moves my player in sync with mouse movements.
Once the player collides with a ball the ball gets hit based on rigidbody physics.
Now In the multiplayer context I extended the script as follows:
using UnityEngine;
using Mirror;
namespace QuickStart
{
public class VelocityMovementNetwork : NetworkBehaviour
{
public float mouseSensitivity = 150.0f;
public float hitForceMultiplier = 2.0f;
private bool cursorLocked = true;
private Rigidbody rbPlayer;
private Vector3 accumulatedMovement = Vector3.zero;
private GameObject ball;
private Rigidbody ballRb;
public void Awake()
{
LockCursor();
ball = GameObject.FindWithTag("Ball");
ballRb = ball.GetComponent<Rigidbody>();
rbPlayer = GetComponent<Rigidbody>();
rbPlayer.constraints = RigidbodyConstraints.FreezeRotation | RigidbodyConstraints.FreezePositionY;
rbPlayer.interpolation = RigidbodyInterpolation.Interpolate;
}
public void Update()
{
if (!isLocalPlayer) return;
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
Vector3 movement = new Vector3(mouseX, 0, mouseY) * mouseSensitivity * Time.deltaTime;
if (movement.magnitude > 0.001f)
{
accumulatedMovement += movement;
}
}
void FixedUpdate()
{
if (!isLocalPlayer) return;
if (Input.GetKeyDown(KeyCode.Escape))
{
UnlockCursor();
}
else if (!cursorLocked && Input.GetKeyDown(KeyCode.S))
{
LockCursor();
}
if (cursorLocked)
{
HandleMouseMovement();
}
}
private void HandleMouseMovement()
{
if (accumulatedMovement.magnitude > 0.01f)
{
Vector3 targetVelocity = accumulatedMovement / Time.fixedDeltaTime;
//rbPlayer.AddForce(targetVelocity, ForceMode.VelocityChange);
rbPlayer.linearVelocity = new Vector3(targetVelocity.x, rbPlayer.linearVelocity.y, targetVelocity.z);
}
else
{
Vector3 targetVelocity = new Vector3(0, rbPlayer.linearVelocity.y, 0);
//rbPlayer.AddForce(targetVelocity, ForceMode.VelocityChange);
rbPlayer.linearVelocity = targetVelocity;
}
accumulatedMovement = Vector3.zero;
}
private void LockCursor()
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
cursorLocked = true;
}
private void UnlockCursor()
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
cursorLocked = false;
}
void OnDisable()
{
UnlockCursor();
}
[Command]
private void CmdHitBall(Vector3 hitDirection, float hitForce)
{
// call the Apply function on the server
Apply(hitDirection, hitForce);
}
private void Apply(Vector3 hitDirection, float hitForce)
{
hitDirection.y = 0; // Prevent the ball from flying up
if (ball)
{
Debug.Log("Hit ball with force: " + hitForce);
ballRb.linearVelocity = hitDirection * hitForce;
}
}
private void TriggerForce(Vector3 hitDirection, float hitForce)
{
Debug.Log("Hit ball with force: " + hitForce);
Apply(hitDirection, hitForce); // Client Prediction
if (isClient)
{
CmdHitBall(hitDirection, hitForce); // Server Authority
}
}
// Detect collision with the ball and apply speed-based force
private void OnCollisionEnter(Collision collision)
{
if (!isLocalPlayer) return;
if (collision.gameObject.CompareTag("Ball"))
{
float playerSpeed = rbPlayer.linearVelocity.magnitude;
Debug.Log("Player speed: " + playerSpeed);
if (playerSpeed > 0.01f)
{
Vector3 hitDirection = (collision.transform.position - transform.position).normalized;
float hitForce = playerSpeed * hitForceMultiplier;
TriggerForce(hitDirection, hitForce);
}
}
}
}
}
As you can see in the video:
movements don’t feel natural sometimes and also the ball is tunneling the player in some frames.
The player has a Network Transform
applied and I added a network latency (Latency Simulation) of 10ms.
The ball has a Rigidbody
as well as a Predicted Rigidbody
:
Does anybody have an idea how to fix this issue? Is there something I am conceptionally doing wrong? E.g. is it a problem to use Rigidbody
and to apply a force OnCollisionEnter
? If yes, how would I solve that?
I would be very grateful for any help.
Best regards
Josch