Shooting Raycast Incorrect Direction (Mirror)

Hello,

I am using Mirror in my unity project, and I want to incorporate shooting via equipping guns. The variable raycastTransform and sealTestController refers to the camera, and assigned upon equip.

The issue is that once I debug its forward orientation on the client, it is stuck on a constant y value of -0.53 with x and z values being approximately 0.

On the host, it’s fine. Shooting bullets works normally with the camera looking forward.

Why?

Here is my code:

void Start()
    {
        startRotation = transform.localEulerAngles;
        currentAmmo = maxAmmo;

        if (gunType == GunType.Grapple) {
            originalHookPos = hook.transform.localPosition;
            originalHookRot = hook.transform.localRotation;
        }

        startPosition = transform.localPosition;
    }

    void Update()
    {
        if (!isOwned) { return; }
        targetRotation = Vector3.Lerp(targetRotation, Vector3.zero, returnSpeed * Time.deltaTime);
        currentRotation = Vector3.Slerp(currentRotation, targetRotation, snappiness * Time.fixedDeltaTime);
        transform.localRotation = Quaternion.Euler(currentRotation);
                
        if (sealTestController != null && gunType != GunType.Grapple && sealTestController.isFireLongPressed)
            transform.parent.forward = raycastTransform.forward;

        if (gunType != GunType.Grapple) {
            if (sealTestController != null) {
                
            }

            else {
                targetRotation = Vector3.Lerp(targetRotation, startRotation, returnSpeed * Time.deltaTime);
                currentRotation = Vector3.Slerp(currentRotation, targetRotation, snappiness * Time.fixedDeltaTime);
                transform.localRotation = Quaternion.Euler(currentRotation);
                
                if (raycastTransform != null)
                transform.parent.forward = raycastTransform.forward;
            }
        }

        if (sealTestController != null) {
        if (gunType == GunType.Automatic) {
            if (sealTestController.isFireLongPressed && Time.time >= nextTimeToFire) {
                nextTimeToFire = Time.time + 1f / fireRate;
                CmdShoot();
            }
        }

        if (gunType == GunType.Minigun) {
            if (sealTestController.isFireLongPressed) {
                if (Time.time >= nextTimeToFire) {
                    nextTimeToFire = Time.time + 1f / fireRate;
                    CmdShoot();
                }

                if (currentAmmo == 0)
                    currentMinigunRotationSpeed = Mathf.Lerp(currentMinigunRotationSpeed, 0f, Time.deltaTime * minigunAccelerationRate);
                else
                    currentMinigunRotationSpeed = Mathf.Lerp(currentMinigunRotationSpeed, rotationSpeed, Time.deltaTime * minigunAccelerationRate);
            }

            else {
                currentMinigunRotationSpeed = Mathf.Lerp(currentMinigunRotationSpeed, 0f, Time.deltaTime * minigunAccelerationRate);
            }

            rotationPartMinigun.transform.Rotate(Vector3.right, currentMinigunRotationSpeed * Time.deltaTime);
        }

        if (gunType == GunType.Pistol) {
            if (sealTestController.isFirePressed) {
                CmdShoot();
            }
        }

        if (gunType == GunType.Sniper) {
            if (sealTestController.isFirePressed) {
                CmdShoot();
            }
        }

        if (gunType == GunType.Shotgun) {
            if (sealTestController.isFirePressed) {
                CmdShoot();
            }
        }

        if (gunType == GunType.RPG) {
            if (sealTestController.isFirePressed) { 
                CmdShootRPG();
            }
        }
        
        if (gunType == GunType.Grapple) {
            if (sealTestController.isFirePressed && !IsGrappling()) {
                CmdStartGrapple();
            }

            else if (!sealTestController.isFirePressed && IsGrappling()){
                CmdStopGrapple();
            }

            if (IsGrappling()) {
                grapplePoint = hit.collider.ClosestPoint(hit.point);
                player.GetComponent<SealTestController>().EnableRagdoll();
                transform.parent.LookAt(GetGrapplePoint());
            }
        }

        if (sealTestController.isReloadPressed) {
            CmdReload();
        }

        if (isReloading) {
            timePassed += Time.deltaTime;

            float normalizedTime = Mathf.Clamp01(timePassed / duration);

            float scale = scaleCurve.Evaluate(normalizedTime);
                        
            if (gunType == GunType.RPG)
                placeHolderRocket.transform.localScale = new Vector3(scale, scale, scale);

            if (timePassed >= duration)
            {
                timePassed = 0f;

                if (gunType == GunType.RPG)
                    placeHolderRocket.transform.localScale = Vector3.one;
                
                isReloading = false;
            }
        }
        }
    }
    
    [Command]
    void CmdShoot() {
        if (currentAmmo <= 0) {
        Debug.LogWarning("No ammo left!");
        return;
    }

    if (raycastTransform == null) {
        Debug.LogError("RaycastTransform is not assigned!");
        return;
    }

        RaycastHit hit;

        Vector3 screenCenter = new Vector3(Screen.width / 2f, Screen.height / 2f, 0f);

        Ray ray;
        
        if (sealTestController != null) {
            ray = raycastTransform.GetComponent<Camera>().ScreenPointToRay(screenCenter);
            sealTestController.ScreenShake(ray.direction, sealTestController.recoilShakeAmount);
        }

        else {
            ray = new Ray(raycastTransform.position, raycastTransform.forward);
        }
        
        if (ray.Equals(null)) {
            Debug.LogError("Failed to create ray for shooting!");
            return;
        }

        Vector3 direction = GetDirection();

        if (Physics.Raycast(raycastTransform.position, direction, out hit, range, whatIsShooting)) {            
            if (hit.transform.GetComponent<Barrel>()) {
                hit.transform.GetComponent<Barrel>().Damage(damageToObject);
                
                if (sealTestController != null) {
                if (Vector3.Distance(player.transform.position, hit.transform.position) > hit.transform.GetComponent<Barrel>().bomb.explosionRadius) {
                    sealTestController.ScreenShake(ray.direction, sealTestController.explosionShakeAmount);
                }
                }
            }

            if (hit.collider.tag.Contains("Guard")) {
                hit.transform.GetComponent<Guard>().Damage(damageToObject);
                                
                if (sealTestController != null && hit.transform.GetComponent<Guard>().guardState != Guard.GuardState.Fleeing) {
                    hit.transform.GetComponent<Guard>().sealToChase = sealTestController.gameObject;
                    hit.transform.GetComponent<Guard>().guardState = Guard.GuardState.Running;
                }

                Debug.Log("Damage");
            }

            if (hit.collider.GetComponent<Breakable>()) {
                hit.collider.GetComponent<Breakable>().RpcBreak();
            }

            if (hit.collider.GetComponent<SealTestController>()) {
                hit.collider.GetComponent<SealTestController>().ScreenShake(ray.direction, hit.collider.GetComponent<SealTestController>().recoilShakeAmount);
            }
            
            if (hit.rigidbody != null) {
                hit.rigidbody.AddForce(-hit.normal * impactForce);
            }
        }
        
        RpcShoot(hit.point, hit.normal);

        currentAmmo--;
    }

    [ClientRpc]
    void RpcShoot(Vector3 hitPoint, Vector3 hitNormal) {
        muzzleFlash.Play();
        if (gunType == GunType.Minigun)
            bulletParticles.Play();

        
        TrailRenderer trail = Instantiate(BulletTrail, gunTip.position, Quaternion.identity);
        if (hitPoint != Vector3.zero)
        StartCoroutine(SpawnTrail(trail, hitPoint, hitNormal, true));
        else
        StartCoroutine(SpawnTrail(trail, gunTip.position + GetDirection() * 100, Vector3.zero, false));
    }

    [Command]
    void CmdShootRPG() {
        Debug.Log("ShootRPG");

        if (currentAmmo <= 0) { return; }

        Ray ray = new Ray(raycastTransform.position, raycastTransform.forward);

        if (sealTestController != null)
            sealTestController.ScreenShake(ray.direction, sealTestController.recoilShakeAmount);

        GameObject spawnedRocket = Instantiate(rocket, rocketSpawn.position, raycastTransform.rotation);
        NetworkServer.Spawn(spawnedRocket);

        spawnedRocket.GetComponent<Rigidbody>().velocity = raycastTransform.forward * rocketLaunchForce;

        if (sealTestController != null)
            spawnedRocket.GetComponent<Rocket>().sealTestController = sealTestController;

        placeHolderRocket.SetActive(false);
        RpcShootRPG();
        currentAmmo--;
    }
    
    [ClientRpc]
    public void RpcShootRPG() {
        muzzleFlash.Play();
    }
    
    [Command]
    void CmdReload() {
        RpcReload();
    }

    [ClientRpc]
    public void RpcReload() {
        Debug.Log("Reloading");
        currentAmmo = maxAmmo;
        
        if (gunType == GunType.RPG) {
            placeHolderRocket.SetActive(true);
        }

        isReloading = true;
    }

    private Vector3 GetDirection()
    {
        Vector3 direction = raycastTransform.forward;
        Debug.Log($"Initial Direction: {direction}");

        if (sealTestController != null) {
            if (gunType != GunType.Shotgun) {
                if (!sealTestController.isAiming) {
                    direction += new Vector3(
                        Random.Range(-BulletSpreadVariance.x, BulletSpreadVariance.x),
                        Random.Range(-BulletSpreadVariance.y, BulletSpreadVariance.y),
                        Random.Range(-BulletSpreadVariance.z, BulletSpreadVariance.z)
                    );
                }
            }

            else {
                direction += new Vector3(
                    Random.Range(-BulletSpreadVariance.x, BulletSpreadVariance.x),
                    Random.Range(-BulletSpreadVariance.y, BulletSpreadVariance.y),
                    Random.Range(-BulletSpreadVariance.z, BulletSpreadVariance.z)
                );
            }
        }

        else {
            if (guard != null) {
                direction += new Vector3(
                    Random.Range(-InsaneBulletSpreadVariance.x, InsaneBulletSpreadVariance.x),
                    Random.Range(-InsaneBulletSpreadVariance.y, InsaneBulletSpreadVariance.y),
                    Random.Range(-InsaneBulletSpreadVariance.z, InsaneBulletSpreadVariance.z)
                );
            }

            else {
                direction += new Vector3(
                    Random.Range(-BulletSpreadVariance.x, BulletSpreadVariance.x),
                    Random.Range(-BulletSpreadVariance.y, BulletSpreadVariance.y),
                    Random.Range(-BulletSpreadVariance.z, BulletSpreadVariance.z)
                );
            }
        }
        
        Debug.Log($"Direction with Spread: {direction}");
        direction.Normalize();
        Debug.Log($"Calculated shooting direction: {direction}");
        return direction;
    }

    private IEnumerator SpawnTrail(TrailRenderer Trail, Vector3 HitPoint, Vector3 HitNormal, bool MadeImpact)
    {
        // This has been updated from the video implementation to fix a commonly raised issue about the bullet trails
        // moving slowly when hitting something close, and not
        Vector3 startPosition = Trail.transform.position;
        float distance = Vector3.Distance(Trail.transform.position, HitPoint);
        float remainingDistance = distance;

        while (remainingDistance > 0)
        {
            Trail.transform.position = Vector3.Lerp(startPosition, HitPoint, 1 - (remainingDistance / distance));

            remainingDistance -= BulletSpeed * Time.deltaTime;

            yield return null;
        }

        Trail.transform.position = HitPoint;

        if (NetworkServer.active)
        {
            GameObject impact = Instantiate(impactParticleSystem, HitPoint, Quaternion.LookRotation(HitNormal));
            NetworkServer.Spawn(impact);
        }
        else
        {
            Debug.LogWarning("Tried to spawn impact effect without an active server.");
        }

        Destroy(Trail.gameObject, Trail.time);
    }

Thank you

I am not sure exactly what is wrong, just throwing out a quick guess since it looks like that might be the smartest answer you will get.

It looks like your rotation might not be synchronized between the host and the client. Does your camera have a network transform component attached?