Hello,
Got an issue with my Networking it seems, Im new and still trying to learn so go easy on me.
Problem:
- When I create host, my player character moves at a perfect and intended speed, say 2f. No issues.
- However, when I create a Server and connects a client. My player character is moving at really really slow, dont know the exact speed but probably around 0.1f. If I temporarily change my speed in the inspector to 20 while testing, the player character moves at normal again. But its very choppy and laggy.
Appreciate any kind of help or insight on this!
using System;
using Unity.Netcode;
using UnityEngine;
using JetBrains.Annotations;
using UnityEngine.UI;
using System.Collections;
using TMPro;
using System.Collections.Generic;
public class PlayerController : NetworkBehaviour
{
public float normalSpeed = 2f;
public float fastSpeed = 4f; // Adjust the faster speed as desired
public float turnSpeed = 250f; // Adjust the turn speed as desired
public GameObject staminaSliderPrefab; // Reference to the stamina slider prefab
public float minTurnSpeed = 90f; // The minimum allowed value for turnSpeed
private Camera _mainCamera;
private bool isUpKeyPressed = false;
private PlayerLength _playerLength;
private readonly ulong[] _targetClientArray = new ulong[1];
private bool _canCollide = true;
private bool isUsingFastSpeed = false;
private float currentStamina;
private float staminaUsageRate = 100f; // Stamina drained per second when using fastSpeed
private float staminaRechargeRate = 15f; // Stamina recharged per second when not using fastSpeed
private float maxStamina = 100f;
private float fastSpeedDuration = 1f; // Duration of fastSpeed in seconds
private float fastSpeedTimer = 0f;
private float rechargeDelay = 0f; // Delay before stamina recharge starts after hitting 0%
private bool isRechargeDelayed = false;
private float rechargeDelayTimer = 0f;
private Slider staminaSlider; // Reference to the stamina slider in the UI
private bool isEliminated = false;
private bool hasCollidedWithCircle = false;
private BoxCollider2D bgBoxCollider; // Reference to the box collider of the background
private string playerName;
[CanBeNull] public static event System.Action GameOverEvent;
[SerializeField] private NetworkObject foodPrefab;
private void Initialize()
{
_mainCamera = Camera.main;
_playerLength = GetComponent<PlayerLength>();
currentStamina = maxStamina; // Initialize stamina to full on spawn
// Find the background box collider using its tag
GameObject bgGameObject = GameObject.FindGameObjectWithTag("Background");
bgBoxCollider = bgGameObject?.GetComponent<BoxCollider2D>();
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
Initialize();
// Find the PlayerCanvas GameObject and spawn the Slider prefab into it
GameObject playerCanvasGO = GameObject.Find("PlayerCanvas");
if (playerCanvasGO != null)
{
// Instantiate the Slider prefab and set it as a child of playerCanvasGO
GameObject sliderGO = Instantiate(staminaSliderPrefab, playerCanvasGO.transform);
staminaSlider = sliderGO.GetComponent<Slider>();
staminaSlider.minValue = 0f;
staminaSlider.maxValue = maxStamina;
staminaSlider.value = currentStamina;
// Find the PlayerNameFollow text object and update its text with the player's name
TMP_Text playerNameFollowText = GameObject.Find("PlayerNameFollow")?.GetComponent<TMP_Text>();
TMP_Text playerNameRefText = GameObject.Find("PlayerNameRef")?.GetComponent<TMP_Text>();
if (playerNameFollowText != null && playerNameRefText != null)
{
playerNameFollowText.text = playerNameRefText.text; // Copy text from PlayerName to PlayerNameFollow
UpdatePlayerNameServerRpc(playerNameFollowText.text); // Synchronize the initial name to other clients
}
else
{
Debug.LogError("PlayerNameFollow or PlayerName TextMeshPro text object not found! Make sure they exist in the scene.");
}
}
else
{
Debug.LogError("PlayerCanvas GameObject not found! Make sure it exists in the scene.");
}
}
void LateUpdate()
{
if (!IsOwner) return;
// Calculate the camera offset based on the object's rotation
Vector3 cameraOffset = new Vector3(0f, 0f, -10f);
// Update the camera position to follow the object
_mainCamera.transform.position = transform.position + cameraOffset;
_mainCamera.transform.LookAt(transform.position);
}
void Update()
{
if (!IsOwner || isEliminated) return;
UpdateStaminaUI();
HandleStamina();
MovePlayerServer();
UpdatePlayerNameServerRpc(playerName);
}
[ServerRpc]
private void UpdatePlayerNameServerRpc(string name)
{
playerName = name;
UpdatePlayerNameClientRpc(name);
}
[ClientRpc]
private void UpdatePlayerNameClientRpc(string name)
{
// Update the PlayerNameFollow text with the name received from the server
TMP_Text playerNameFollowText = GameObject.Find("PlayerNameFollow")?.GetComponent<TMP_Text>();
if (playerNameFollowText != null)
{
playerNameFollowText.text = name;
}
else
{
Debug.LogError("PlayerNameFollow TextMeshPro text object not found! Make sure it exists in the scene.");
}
}
private IEnumerator CollisionCheckCoroutine()
{
_canCollide = false;
yield return new WaitForSeconds(0.5f);
_canCollide = true;
}
private void UpdateStaminaUI()
{
if (staminaSlider != null)
{
// Update the slider value based on current stamina percentage
staminaSlider.value = currentStamina;
}
}
private void HandleStamina()
{
// Check if the up arrow key is pressed
isUpKeyPressed = Input.GetKey(KeyCode.UpArrow);
// Check if using fastSpeed
if (isUpKeyPressed && currentStamina > 0f && !isRechargeDelayed)
{
if (!isUsingFastSpeed)
{
// Start using fastSpeed
isUsingFastSpeed = true;
fastSpeedTimer = fastSpeedDuration;
}
// Drain stamina while using fastSpeed
currentStamina -= staminaUsageRate * Time.deltaTime;
fastSpeedTimer -= Time.deltaTime;
if (fastSpeedTimer <= 0f)
{
// Stop using fastSpeed when the duration is over
isUsingFastSpeed = false;
isRechargeDelayed = true;
rechargeDelayTimer = rechargeDelay;
}
}
else
{
// Recharge delay after reaching 0% stamina
if (isRechargeDelayed)
{
rechargeDelayTimer -= Time.deltaTime;
if (rechargeDelayTimer <= 0f)
{
isRechargeDelayed = false;
}
}
else
{
// Recharge stamina when not using fastSpeed and not in recharge delay
if (!isUpKeyPressed && currentStamina < maxStamina)
{
currentStamina += staminaRechargeRate * Time.deltaTime;
}
}
}
// Ensure the stamina stays within bounds
currentStamina = Mathf.Clamp(currentStamina, 0f, maxStamina);
// Disable forward key (KeyCode.UpArrow) if stamina is empty
if (currentStamina <= 0f)
{
isUpKeyPressed = false;
}
// Update the stamina bar UI here with 'currentStamina' value
}
public void IncreaseTurnSpeed()
{
turnSpeed = Mathf.Max(turnSpeed - 0.8f, minTurnSpeed);
}
private void MovePlayerServer()
{
if (isEliminated || hasCollidedWithCircle) return;
float rotationInput = Input.GetAxisRaw("Horizontal");
float rotationAmount = rotationInput * turnSpeed * Time.deltaTime;
// If the rotation input is positive (arrow key to the right),
// rotate the player in the negative direction (opposite way).
if (rotationInput > 0)
{
transform.Rotate(Vector3.forward, -rotationAmount);
}
else if (rotationInput < 0)
{
// If the rotation input is negative (arrow key to the left),
// rotate the player in the positive direction (opposite way).
transform.Rotate(Vector3.forward, Mathf.Abs(rotationAmount));
}
float currentSpeed = isUpKeyPressed && currentStamina > 0f ? fastSpeed : normalSpeed;
transform.position += transform.up * currentSpeed * Time.deltaTime;
// Inform the server of the intended movement
MovePlayerServerRpc(transform.position, transform.rotation);
if (bgBoxCollider == null)
{
Debug.LogError("Background box collider object not found. Make sure it has the 'Background' tag and a BoxCollider2D component.");
return;
}
// Check if the player is beyond the box's bounds
Vector2 boxCenter = bgBoxCollider.bounds.center;
Vector2 boxExtents = bgBoxCollider.bounds.extents;
Vector2 playerPosition = transform.position;
if (playerPosition.x - boxCenter.x > boxExtents.x ||
playerPosition.x - boxCenter.x < -boxExtents.x ||
playerPosition.y - boxCenter.y > boxExtents.y ||
playerPosition.y - boxCenter.y < -boxExtents.y)
{
Debug.Log("Player Beyond the Box's Bounds");
StartCoroutine(CollisionCheckCoroutine());
GameOverClientRpc(); // Activate Game Over on the Client
EliminatePlayerServerRpc(); // Eliminate the player on the Server
// If the player is beyond the box's bounds, prevent further movement
hasCollidedWithCircle = true;
return;
}
}
[ServerRpc]
private void MovePlayerServerRpc(Vector3 position, Quaternion rotation)
{
// Server handles movement and collision resolution
transform.position = position;
transform.rotation = rotation;
}
private void OnCollisionEnter2D(Collision2D col)
{
if (col.gameObject == this.gameObject) return; // Skip collision with self
if (!IsOwner || !_canCollide) return;
StartCoroutine(CollisionCheckCoroutine());
// Handle collision with the background box collider
if (bgBoxCollider != null && col.collider == bgBoxCollider)
{
// Check if the player is beyond the box's bounds
Vector2 boxCenter = bgBoxCollider.bounds.center;
Vector2 boxExtents = bgBoxCollider.bounds.extents;
Vector2 playerPosition = transform.position;
if (playerPosition.x - boxCenter.x > boxExtents.x ||
playerPosition.x - boxCenter.x < -boxExtents.x ||
playerPosition.y - boxCenter.y > boxExtents.y ||
playerPosition.y - boxCenter.y < -boxExtents.y)
{
Debug.Log("Player Beyond the Box's Bounds");
StartCoroutine(CollisionCheckCoroutine());
GameOverClientRpc(); // Activate Game Over on the Client
EliminatePlayerServerRpc(); // Eliminate the player on the Server
}
}
else // Head-on Collision or Tail Collision
{
if (col.gameObject.TryGetComponent(out PlayerLength playerLength))
{
Debug.Log("Tail Collision");
var player1 = new PlayerData()
{
id = OwnerClientId,
length = _playerLength.length.Value
};
var player2 = new PlayerData()
{
id = playerLength.OwnerClientId,
length = playerLength.length.Value
};
DetermineCollisionWinnerServer(player1, player2);
}
else if (col.gameObject.TryGetComponent(out Tail tail))
{
Debug.Log("Tail Collision");
WinInformationServerRpc(winner: tail.networkedOwner.GetComponent<PlayerController>().OwnerClientId, loser: OwnerClientId);
}
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (!IsOwner || isEliminated) return;
// Handle trigger collision with the background box collider
if (bgBoxCollider != null && other == bgBoxCollider)
{
// Check if the player is beyond the box's bounds
Vector2 boxCenter = bgBoxCollider.bounds.center;
Vector2 boxExtents = bgBoxCollider.bounds.extents;
Vector2 playerPosition = transform.position;
if (playerPosition.x - boxCenter.x > boxExtents.x ||
playerPosition.x - boxCenter.x < -boxExtents.x ||
playerPosition.y - boxCenter.y > boxExtents.y ||
playerPosition.y - boxCenter.y < -boxExtents.y)
{
Debug.Log("Player Beyond the Box's Bounds");
isEliminated = true;
GameOverClientRpc(); // Activate Game Over on the Client
EliminatePlayerServerRpc(); // Eliminate the player on the Server
}
}
}
private void DetermineCollisionWinnerServer(PlayerData player1, PlayerData player2)
{
if (player1.length > player2.length)
{
WinInformationServerRpc(winner: player1.id, loser: player2.id);
}
else
{
WinInformationServerRpc(winner: player2.id, loser: player1.id);
}
}
[ServerRpc]
private void WinInformationServerRpc(ulong winner, ulong loser)
{
_targetClientArray[0] = winner;
ClientRpcParams clientRpcParams = new ClientRpcParams
{
Send = new ClientRpcSendParams
{
TargetClientIds = _targetClientArray
}
};
AtePlayerClientRpc(clientRpcParams);
_targetClientArray[0] = loser;
clientRpcParams.Send.TargetClientIds = _targetClientArray;
GameOverClientRpc(clientRpcParams);
}
[ClientRpc]
private void AtePlayerClientRpc(ClientRpcParams ClientRpcParams = default)
{
if (!IsOwner) return;
Debug.Log(message: "You Ate a Player");
}
[ClientRpc]
private void GameOverClientRpc(ClientRpcParams clientRpcParams = default)
{
if (!IsOwner) return;
Debug.Log(message: "You Lose");
// Call the method to spawn food at the location of each tail
if (isEliminated && _playerLength != null)
{
SpawnFoodAtTails(_playerLength.Tails);
}
GameOverEvent?.Invoke();
NetworkManager.Singleton.Shutdown();
}
[ServerRpc]
private void EliminatePlayerServerRpc()
{
if (IsOwner && !isEliminated)
{
isEliminated = true;
Debug.Log("You are eliminated.");
// Additional elimination logic can be added here
}
}
private void SpawnFoodAtTails(List<GameObject> tails)
{
// Loop through all the tails and spawn food at their positions
foreach (var tailObj in tails)
{
Vector3 tailPosition = tailObj.transform.position;
// Spawn the food prefab using NetworkManager
NetworkObject foodObj = NetworkObject.Instantiate(foodPrefab, tailPosition, Quaternion.identity);
if (!foodObj.IsSpawned) foodObj.Spawn(destroyWithScene: true);
}
}
struct PlayerData : INetworkSerializable
{
public ulong id;
public ushort length;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref id);
serializer.SerializeValue(ref length);
}
}
}