I tried to use a ServerRpc
and a ClientRpc
to control a particle system, but it only plays on the client itself, and does not show on other clients.
The particle system is in the player network object
Here is my code:
[SerializeField] private ParticleSystem shootParticles;
public override void Shoot(Vector3 targetPosition)
{
ShowEffectServerRpc();
}
[ServerRpc(RequireOwnership = false)]
private void ShowEffectServerRpc()
{
ShowEffectClientRpc();
}
[ClientRpc]
private void ShowEffectClientRpc()
{
shootParticles.Play();
}
I would suggest splitting things up into multiple classes. Suppose we have a AvatarState
NetworkBehaviour, a ServerAvatar
NetworkBehaviour (handling serverside stuff for the player avatar), and a AvatarEffects
MonoBehaviour.
I would also refactor your code to put the "Player" Network Object in a completely different object, similar to the "PersistentPlayer" within the Netcode "Boss Room" Example Project (see the official documentation, paying close attention to the point about Core gameplay structure) - with the "Avatar" being a distinct network object. It can still be owned by the player whom owns it, but this way, you can decouple the logical representation of the player from the avatar they're playing with (giving you more freedom to manipulate their avatar without completely breaking things)
I'm also assuming (from the way you have presented your code) that the 'shoot' particle system is some sort of 'emits a few particles and then stops emitting' system.
using System;
using UnityEngine;
using Unity.Netcode;
public class ServerAvatar: NetworkBehaviour
{
[SerializeField] private AvatarState _avatarState;
private bool _shootInputRecieved = false;
private Vector3 _shootTargetPos = Vector3.zero;
// ...
public override void OnNetworkSpawn()
{
if (_avatarState== null)
{
_avatarState = GetComponent<AvatarState>();
}
_avatarState.OnShootInput += AcceptShootInput;
}
public override void OnNetworkDespawn()
{
if (_avatarState!= null)
{
// unsubscribing to prevent awkwardness
_avatarState.OnShootInput -= AcceptShootInput;
}
}
private void AcceptShootInput(Vector3 target)
{
_shootInputRecieved = true;
_shootTargetPos = target;
}
void Update()
{
if (!IsServer){return;}
if (_shootInputRecieved)
{
_shootInputRecieved = false;
// blah blah logic to check that the player was allowed to shoot
// and also logic to handle the shooting and such
// and then, assuming that we were allowed to shoot
_avatarState.ShootEventHappenedClientRpc();
}
}
}
using Unity.Netcode;
using UnityEngine;
using System;
public class AvatarState: NetworkBehaviour
{
///<summary>Invoked by client (via serverRPC) upon sending shoot input, recieved by server. sends target position.</summary>
public event Action<Vector3> OnShootInput;
///<summary>Invoked by server (via clientRPC) upon succesful shoot, recieved by all clients</summary>
public event Action OnShootEvent;
// ...
///<summary>We tell the server that we want to shoot + where</summary>
///<param name="targetPosition">where we want to shoot</param>
[ServerRpc(RequireOwnership = false)]
public void SendShootInputServerRpc(Vector3 targetPosition)
{
OnShootInput?.Invoke(targetPosition);
}
///<summary>Invokes OnShootEvent for all clients</summary>
[ClientRpc]
public void ShootEventHappenedClientRpc()
{
OnShootEvent?.Invoke();
}
}
using UnityEngine;
using System;
public class AvatarEffects: MonoBehaviour
{
[SerializeField] private ParticleSystem _shootParticles;
[SerializeField] private int _shootParticlesToEmit = 10;
[SerializeField] private AvatarState _avatarState;
void Start()
{
// ...
if (_avatarState == null)
{
_avatarState = GetComponent<AvatarState>();
}
// call the Shoot() method whenever OnShootEvent happens
_avatarState.OnShootEvent += Shoot;
}
void OnDisable()
{
if (_avatarState != null)
{
// unsubscribing to prevent awkwardness
_avatarState.OnShootEvent -= Shoot;
}
}
///<summary>emits a few particles whenever it shoots</summary>
void Shoot()
{
_shootParticles.Emit(_shootParticlesToEmit);
}
}
Now, all you need to do is call the SendShootInputServerRpc
method of your AvatarState
with whatever position you want to target, and then all of the clients should hopefully see the same few particles emitted at roughly the same time as each other (lag permitting).