These might have just been “it’s what we choose” questions but I wanted to ask about your approach to limiting fire rate and showing muzzle flash my code for this is as follows
using System.Collections;
using Unity.Netcode;
using UnityEngine;
public class PlayerShoot : NetworkBehaviour
{
[Header("References")]
[SerializeField] private InputReader inputReader;
[SerializeField] private GameObject serverProjectilePrefab;
[SerializeField] private GameObject clientProjectilePrefab;
[SerializeField] private Transform projectileSpawnPoint;
[SerializeField] private GameObject muzzleFlash;
[SerializeField] private Collider2D playerCollider;
[Header("Shooting Settings")]
[SerializeField] private float fireRate = 0.25f;
[SerializeField] private float projectileSpeed = 5f;
[SerializeField] private float muzzleFlashDuration = 0.1f;
private float _shootTimer;
private bool shouldFire;
public override void OnNetworkSpawn()
{
if (!IsOwner) return;
inputReader.PrimaryFireEvent += HandlePrimaryFire;
}
private void HandlePrimaryFire(bool fire)
{
shouldFire = fire;
}
private void Update()
{
if (!IsOwner)
return;
_shootTimer += Time.deltaTime;
bool canFire = _shootTimer >= fireRate;
if (!canFire || !shouldFire)
return;
_shootTimer = 0f;
var spawnPos = projectileSpawnPoint.position;
var spawnDir = projectileSpawnPoint.up;
SpawnDummyProjectile(spawnPos, spawnDir);
PrimaryFireServerRpc(spawnPos, spawnDir);
}
//spawn on client right away to reduce felt lag
private void SpawnDummyProjectile(Vector3 spawnPos, Vector3 direction)
{
StartCoroutine(ShowMuzzleFlash());
var clientProjectileInstance = Instantiate(clientProjectilePrefab, spawnPos, Quaternion.identity);
clientProjectileInstance.transform.up = direction;
Physics2D.IgnoreCollision(playerCollider, clientProjectileInstance.GetComponent<Collider2D>());
SetProjectileVelocity(clientProjectileInstance);
}
private void SetProjectileVelocity(GameObject projectileInstance)
{
if (projectileInstance.TryGetComponent<Rigidbody2D>(out var rb))
rb.velocity = rb.transform.up * projectileSpeed;
}
[ServerRpc]
private void PrimaryFireServerRpc(Vector3 spawnPos, Vector3 direction)
{
var serverProjectileInstance = Instantiate(serverProjectilePrefab, spawnPos, Quaternion.identity);
serverProjectileInstance.transform.up = direction;
Physics2D.IgnoreCollision(playerCollider, serverProjectileInstance.GetComponent<Collider2D>());
SetProjectileVelocity(serverProjectileInstance);
SpawnDummyProjectileClientRpc(spawnPos, direction);
}
[ClientRpc]
private void SpawnDummyProjectileClientRpc(Vector3 spawnPos, Vector3 direction)
{
if (IsOwner)
return;
SpawnDummyProjectile(spawnPos, direction);
}
private IEnumerator ShowMuzzleFlash()
{
muzzleFlash.SetActive(true);
yield return new WaitForSeconds(muzzleFlashDuration);
muzzleFlash.SetActive(false);
}
public override void OnNetworkDespawn()
{
if (!IsOwner) return;
inputReader.PrimaryFireEvent -= HandlePrimaryFire;
}
}
This all works fine - I am wondering why you didn’t just use _shootTimer += Time.deltaTime and resetting it on fire - the approach shown for limiting rate seems a little convoluted, especially given we are trusting the client on this anyways. For the muzzle flash, is there any reason not to use a co-routine like I did to handle this?