Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

ServerRPC Raycast Issues

Discussion in 'Netcode for GameObjects' started by mclane_, Apr 5, 2023.

  1. mclane_

    mclane_

    Joined:
    Feb 28, 2021
    Posts:
    2
    I am trying to hobble together a little multiplayer FPS and am running into issues with having the client being able to shoot. How I set it up, is have the client fire a ray, and if it hits, call a ServerRpc to fire the same ray on the server end, and if that hits, run the on hitlogic. I know that the Rpc is calling, but I can't seem to call the onhit() function when hitting, from the ServerRpc. From the host, it all works as expected. Code is below if that helps.

    Would love if someone had an idea of what I'm doing wrong!

    Code (CSharp):
    1. using Cinemachine;
    2. using Unity.Netcode;
    3. using UnityEngine;
    4. using UnityEngine.InputSystem;
    5.  
    6. [RequireComponent(typeof(CharacterController))]
    7.  
    8. public class PlayerController : NetworkBehaviour
    9. {
    10.     [SerializeField] private float speed;
    11.     [SerializeField] private float turnSpeed;
    12.     [SerializeField] private Vector2 minMaxRotation;
    13.     [SerializeField] private Transform cam;
    14.  
    15.     private float nextTimeToFire = 0f;
    16.     [SerializeField] private float fireRate;
    17.     [SerializeField] private NetworkVariable<float> damage;
    18.     [SerializeField] private float shotForce;
    19.     [SerializeField] private float range = 100f;
    20.     [SerializeField] private GameObject hitParticles;
    21.  
    22.     private CharacterController characterController;
    23.     private PlayerControl playerControl;
    24.     private float cameraAngle;
    25.  
    26.     public override void OnNetworkSpawn()
    27.     {
    28.         CinemachineVirtualCamera cinemachineVirtual = cam.GetComponentInChildren<CinemachineVirtualCamera>();
    29.  
    30.         if (IsOwner)
    31.         {
    32.             cinemachineVirtual.Priority = 1;
    33.         }
    34.         else
    35.         {
    36.             cinemachineVirtual.Priority = 0;
    37.         }
    38.     }
    39.     void Start()
    40.     {
    41.         characterController = GetComponent<CharacterController>();
    42.         playerControl = new PlayerControl();
    43.         playerControl.Enable();
    44.  
    45.         // Cursor.lockState = CursorLockMode.Locked;
    46.     }
    47.  
    48.     void Update()
    49.     {
    50.         if (!IsOwner) return;
    51.  
    52.         Vector2 movementInput = playerControl.Player.Move.ReadValue<Vector2>();
    53.         Vector2 lookInput = playerControl.Player.Look.ReadValue<Vector2>();
    54.  
    55.         if (IsServer && IsLocalPlayer)
    56.         {
    57.             MovePlayer(movementInput);
    58.             RotatePlayer(lookInput);
    59.             RotateCamera(lookInput);
    60.         }
    61.         else if (IsLocalPlayer)
    62.         {
    63.             RotatePlayer(lookInput);
    64.             PlayerMovementServerRpc(movementInput, lookInput);
    65.             RotateCamera(lookInput);
    66.         }
    67.         if(playerControl.Player.Fire.inProgress)
    68.         {
    69.             if (IsServer && IsLocalPlayer)
    70.             {
    71.                 PlayerFire();
    72.             }
    73.             else if (IsLocalPlayer)
    74.             {
    75.                 LocalPlayerRaycast();
    76.             }
    77.         }
    78.     }
    79.     private void LocalPlayerRaycast()
    80.     {
    81.    
    82.         /* if (playerControl.Player.Fire.inProgress)
    83.          {// FIX THE TIME.TIME STUFF, POSSIBLY THE RPC TO CLIENT FIRE. TBD
    84.              /* if (Time.time >= nextTimeToFire)
    85.               {*/
    86.         // nextTimeToFire = Time.time + 1f / fireRate;
    87.         Debug.DrawRay(cam.position, cam.forward, Color.red, 100);
    88.         Debug.Log(cam.transform.position);
    89.         RaycastHit hit;
    90.         if (Physics.Raycast(cam.position, cam.forward, out hit, range))
    91.         {
    92.             if (hit.collider != null)
    93.             {
    94.                 PlayerFireServerRpc();
    95.                 Debug.Log(hit.collider);
    96.             }
    97.         }
    98.     }
    99.     private void PlayerFire()
    100.     {
    101.         RaycastHit hit;
    102.         if (Physics.Raycast(cam.position, cam.forward, out hit, range))
    103.         {
    104.             GameObject hitParticlesGO = Instantiate(hitParticles, hit.transform);
    105.             //Destroy(hitParticlesGO, 3);
    106.             //hitParticlesGO.GetComponent<NetworkObject>().Spawn(true);
    107.             Damageable damageable = hit.transform.GetComponent<Damageable>();
    108.             if (damageable != null)
    109.             {
    110.                 damageable.OnHit(damage, shotForce, -hit.normal);
    111.                 Debug.Log(damageable.gameObject);
    112.             }
    113.         }
    114.     }
    115.     [ServerRpc]
    116.     private void PlayerFireServerRpc()
    117.     {
    118.         RaycastHit hit;
    119.         Debug.Log(cam.position - transform.position);
    120.         if (Physics.Raycast(cam.position, cam.forward, out hit, range))
    121.         {
    122.             Debug.Log(hit);
    123.             GameObject hitParticlesGO = Instantiate(hitParticles, hit.transform);
    124.             hitParticlesGO.transform.parent = null;
    125.             Damageable damageable = hit.transform.GetComponent<Damageable>();
    126.             if (damageable != null)
    127.             {
    128.                 Debug.Log("PlayerServerRPC hit damageable");
    129.                 damageable.OnHit(damage, shotForce, -hit.normal);
    130.                 Debug.Log(damageable.gameObject);
    131.             }
    132.         }
    133.     }
    134.     private void MovePlayer(Vector2 movementInput)
    135.     {
    136.         Vector3 movement = movementInput.x * cam.right + movementInput.y * cam.forward;
    137.         movement.y = -9.8f * Time.deltaTime;
    138.         characterController.Move(movement * speed * Time.deltaTime);
    139.     }
    140.     private void RotatePlayer(Vector2 lookInput)
    141.     {
    142.         transform.RotateAround(transform.position, transform.up, lookInput.x * turnSpeed * Time.deltaTime);
    143.     }
    144.     private void RotateCamera(Vector2 lookInput)
    145.     {
    146.         cameraAngle = Vector3.SignedAngle(transform.forward, cam.forward, cam.right);
    147.         float cameraRotationAmount = lookInput.y * turnSpeed * Time.deltaTime;
    148.         float newCameraAngle = cameraAngle - cameraRotationAmount;
    149.         if (newCameraAngle <= minMaxRotation.x && newCameraAngle > minMaxRotation.y)
    150.         {
    151.             cam.RotateAround(cam.position, cam.right, -lookInput.y * turnSpeed * Time.deltaTime);
    152.         }
    153.     }
    154.  
    155.     [ServerRpc]
    156.     private void PlayerMovementServerRpc(Vector2 movementInput, Vector2 lookInput)
    157.     {
    158.         RotatePlayer(lookInput);
    159.         MovePlayer(movementInput);
    160.     }
    161. }
    162.  
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    4,191
    Not sure if that is your problem, but it is definitely flawed to only send the ServerRPC if the client determines a hit. The reason is simply that by the time the server receives that RPC message, the gameplay has advanced. On top, game simulation on the server is ahead a couple ticks to begin with.

    Thus you can get into odd situations where the client determines a hit, but the server doesn't. Or the client determines a miss, but if it had been run by the server it would have been a hit. You have to send the Raycast message to the server either way, the client must not make that decision or else it will be a buggy mix of client and server authoritative behaviour.

    The client can still determine the hit and perform a sound or effect or animation, but where possible the client needs to undo those visualizations in case the server tells the client that it was actually a miss.
     
    RikuTheFuffs likes this.
  3. mclane_

    mclane_

    Joined:
    Feb 28, 2021
    Posts:
    2
    Thanks for responding! The targets are stationary so I'm not sure if it's the problem, but just so I'm understanding you, you're saying it's a better practice to have the fire key just run ServerRPC for the shot right away? And also run the local raycast for shot particles/fx etc.? I'll certainly give that a shot!
     
  4. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    426
    Yes, clients only send input ("I' want to shoot") and the server does all the math.

    Yes, it's called client-side prediction

    Pun intended? :D