I finally made it in a way that I wanted
But I had to use a static field, I’m still a learner though, and I couldn’t find other way
Main changes:
Add these variables in ProjectileFire class
private static ProjectileFire serverProjectileFire = null;
private List<Projectile> activeProjectiles = new List<Projectile>();
On network spawn store itself in a new static variable
public override void OnNetworkSpawn()
{
if (!IsOwner) { return; }
inputReader.PrimaryFireEvent.AddListener(HandleFilre);
if (IsServer)
{
serverProjectileFire = this;
}
else
{
CallForServerAlliveProjectilesServerRpc();
}
}
On server fire rpc add aditional code to store all fired projectiles
private void ClearProjectilesList(Projectile projectile)
{
serverProjectileFire.activeProjectiles.Remove(projectile);
}
[ServerRpc]
private void FireProjectileServerRpc(Vector3 position, Vector3 direction, ServerRpcParams serverRpc = default)
{
spawnedPrefab = Instantiate(serverProjectilePrefab, position, Quaternion.identity);
spawnedPrefab.transform.up = direction;
Physics2D.IgnoreCollision(playerCollider, spawnedPrefab.GetComponent<Collider2D>());
if (spawnedPrefab.TryGetComponent<DealDamageOnContact>(out DealDamageOnContact dealDamageOnContact))
{
dealDamageOnContact.SetOwner(OwnerClientId);
}
if (spawnedPrefab.TryGetComponent<Rigidbody2D>(out Rigidbody2D rb))
{
rb.velocity = rb.transform.up * projectileSpeed;
}
FireProjectileClientRpc(position, direction);
if(!IsServer) { return; }
if (serverProjectileFire != null && spawnedPrefab.TryGetComponent<Projectile>(out spawnedProjectile))
{
spawnedProjectile.OnProjectileDestroy.AddListener(ClearProjectilesList);
serverProjectileFire.activeProjectiles.Add(spawnedProjectile);
}
}
Note: I use a Projectile class for all projectiles, inside I also made an UnityEvent, which is called whenever the projectile is destroyed so it can clear the
private List<Projectile> activeProjectiles = new List<Projectile>();
and keep it up-to-date
So, whenever a new client tries to join it will call this ServerRpc function in OnNetworkSpawn code:
[ServerRpc]
private void CallForServerAlliveProjectilesServerRpc(ServerRpcParams serverRpc = default)
{
foreach (Projectile item in serverProjectileFire.activeProjectiles)
{
SpawnMissingProjectileClientRpc(serverRpc.Receive.SenderClientId, item.transform.position, item.transform.rotation, item.Rigidbody2D.velocity, item.LifeTimer);
}
}
Which ultimately will call this client rpc:
[ClientRpc]
private void SpawnMissingProjectileClientRpc(ulong ownerClientId, Vector3 position, Quaternion rotation, Vector3 velocity, float lifeTimer)
{
if (ownerClientId == OwnerClientId)
{
spawnedPrefab = Instantiate(clientProjectilePrefab, position, Quaternion.identity);
spawnedPrefab.transform.rotation = rotation;
if (spawnedPrefab.TryGetComponent<Rigidbody2D>(out Rigidbody2D rb))
{
rb.velocity = velocity;
}
if (spawnedPrefab.TryGetComponent<Projectile>(out spawnedProjectile))
{
spawnedProjectile.LifeTimer = lifeTimer;
}
}
}
In order to above code to work you need to make a Projectile class which will include Lifetimer, destory on collision and other necessary stuff:
[RequireComponent(typeof(Rigidbody2D))]
public class Projectile : NetworkBehaviour
{
[SerializeField] private LayerMask targetMask;
[SerializeField] private float lifeTime = 1.5f;
public float LifeTimer { get => lifeTimer; set => lifeTimer = value; }
public Rigidbody2D Rigidbody2D { get => rb; }
public UnityEvent<Projectile> OnProjectileDestroy = new UnityEvent<Projectile>();
private Rigidbody2D rb;
private float lifeTimer;
private void Awake()
{
lifeTimer = lifeTime;
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
lifeTimer -= Time.deltaTime;
if (lifeTimer < 0)
{
DestroySelf(this);
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (Utils.CheckLayer(targetMask, other.gameObject.layer))
{
DestroySelf(this);
}
}
private void DestroySelf(Projectile projectile)
{
OnProjectileDestroy?.Invoke(projectile);
Destroy(gameObject);
}
}
The Utils.CheckLayer looks like this:
public static class Utils
{
public static bool CheckLayer(LayerMask layerMask, int layer)
{
if ((layerMask.value & (1 << layer)) > 0)
{
return true;
}
else
{
return false;
}
}
}