Unity Mirror, client doesn't execute server cmd - unity3d

When player press key if he touched other player he try markPlayer
When client touched host-player, log only "CmdServer" and stop, but on host-player, its full complete all methods and change color on another character;
Client code
//somemovementClass
private void TriggerEntered(PlayerContext playerContext)
{
Debug.Log($"Collided with {playerContext.name}");
CmdServer(playerContext);
}
[Command]
public void CmdServer(PlayerContext playerContext)
{
Debug.Log($"CmdServer");
playerContext.MarkPlayer();
}
// PlayerContext.cs
[SyncVar(hook = nameof(OnPlayerGetDamage))]
private bool wasDamaged;
[Server]
public void MarkPlayer()
{
Debug.Log("On Server");
wasDamaged = true;
}
private void OnPlayerGetDamage(bool oldState, bool newState)
{
Debug.Log("On Get Damage");
cashedMaterial.color = Color.red;
}

I think I solved the problem, although I'm not sure that the problem was specifically in this, but still, I didn't use the Monobehaviour class and hoped that the command would compile correctly, because usually mirror throws errors if something is wrong. I just moved the cmd method to a separate class from networkbehaviour

Related

Mirror/UNET c# - No authority on object even when using Command & ClientRpc - What am I missing?

Long story: I have made a multiplayer chat using Mirror/Unet that works. I have made it so after x number of seconds, the gameobject that displays the chat (“textContainer”) goes inactive. I would like it so that when client 1 presses “Submit”, all the client’s gameobjects for textContainer goes active again. But it only works on the client that presses Submit, and not on ALL clients. I have set up the functions in [Client] [Command] and [ClientRpc] but am still getting an error about no authority on object.
I think it is because client 1 does not have authority to request client 2 to activate their UI panel. I thought the Command and ClientRpc would have fixed this issue?
Short story: So, for simplicity, say when client 1 presses the Input for “Submit”, I would like all client’s textContainer GameObjects to go active.
I am hoping someone might be able to point me in the right direction. Cheers.
This script would be attached to the player prefab.
public GameObject textContainer;
[Client]
void Update()
{
if (Input.GetAxisRaw("Submit") == 1)
{
CmdActivateChatClientRPC();
textContainer.SetActive(true);
}
}
[Command]
private void CmdActivateChatClientRPC()
{
ActivateChatClientRPC();
}
[ClientRpc]
private void ActivateChatClientRPC()
{
textContainer.SetActive(true);
}
You are not checking whether you have the authority over this object.
void Update()
{
// Does your device have the authority over this object?
// only then you can invoke a Cmd
if(!hasAuthority) return;
if (Input.GetAxisRaw("Submit") == 1)
{
CmdActivateChatClientRPC();
textContainer.SetActive(true);
}
}
[Command]
private void CmdActivateChatClientRPC()
{
ActivateChatClientRPC();
}
// This will be executed on ALL clients!
// BUT only for this one instance of this class
[ClientRpc]
private void ActivateChatClientRPC()
{
textContainer.SetActive(true);
}
If it is rather about only you trying to invoke a command on an object that belongs to the server it gets tricky ;)
You will have to relay the command through your local player object and then on the server forward the call to the according object e.g. referencing the actual target via it's Network identity.
To me it sounds a bit like you are having each player prefab has this class and you want to invoke the RPC on all of them.
This is something that can only be done by the server like e.g.
void Update()
{
// Does your device have the authority over this object?
// only then you can invoke a Cmd
if(!hasAuthority) return;
if (Input.GetAxisRaw("Submit") == 1)
{
CmdActivateChatClientRPC();
textContainer.SetActive(true);
}
}
[Command]
private void CmdActivateChatClientRPC()
{
foreach (var player in NetworkManager.singleton.client.connection.playerControllers)
{
if (player.IsValid)
{
player.gameObject.GetComponent<YourScriptName>().ActivateChatClientRPC();
}
}
}
Seems that .client.connection.playerControllers was removed.
I guess in that case you would need a custom NetworkManager and keep track of them yourself via OnServerConnect and OnServerDisconnect somewhat like
public CustomNetworkManager : NetworkManager
{
public static readonly HashSet<NetworkConnection> players = new HashSet<NetworkConnection>();
public override void OnServerConnect(NetworkConnection conn)
{
base.OnServerConnect(conn);
players.Add(conn);
}
public override void OnServerDisconnect(NetworkConnection conn)
{
base.OnServerDisconnect(conn);
players.Remove(conn);
}
}
and then instead access
foreach (var player in CustomNetworkManager.players)
{
player.identity.GetComponent<YourScriptName>().ActivateChatClientRPC();
}
How about
client->server: request to activate
and then
server->client: singal
[SyncVar(hook = nameof(AcivateText))]
bool textActivated = false;
[Command]
private void CmdActivateChatClientRPC()
{
ActivateChatClientRPC();
}
void AcivateText(bool oldval, bool newval)
{
textContainer.SetActive(newval);
}

How to network a VR button with PUN in Unity?

I've been working on a solution for weeks now and I think it's time to reach out. I'm trying to make a button that plays a sound when pressed by a controller, everyone will hear that sound. Using VRTK and PlayoVR, I'm able to make a non-networked version where the player can put their hand through a cube, click the trigger from the controller, and it makes a sound.
This is the code for that cube:
namespace VRTK.Examples {
using UnityEngine;
public class Whirlygig : VRTK_InteractableObject
{
public GameObject AudioSource;
public AudioSource LeftSpeaker;
public override void StartUsing(VRTK_InteractUse currentUsingObject =
null)
{
AudioSource.GetComponent<AudioSource>().Play();
}
}
}
Where I get lost is how to network it with Photon Unity Networking. This is what I have:
namespace PlayoVR
{
using UnityEngine;
using VRTK;
using UnityEngine.Video;
using NetBase;
public class PlaySync : Photon.MonoBehaviour
{
public AudioSource LeftSpeaker;
public GameObject Whirlgig;
private bool StartUsing;
// Use this for initialization
void Awake()
{
GetComponent<VRTK_InteractableObject>().InteractableObjectUsed +=
new InteractableObjectEventHandler(DoPlay);
}
void DoPlay(object sender, InteractableObjectEventArgs e)
{
StartUsing = true;
}
// Update is called once per frame
void Update()
{
// Handle firing
if (StartUsing)
{
CmdPlay();
StartUsing = false;
}
}
void CmdPlay()
{
photonView.RPC("NetPlay", PhotonTargets.All);
}
[PunRPC]
void NetPlay()
{
LeftSpeaker.Play();
}
}
}
As you can probably see, I'm a beginner. With this code, when I put my hand in the cube and press the trigger, nothing happens. If anyone can provide any help or even an alternative, I'd be very grateful.
Kind regards,
TheMusiken

Why delegate event show NullReferenceException?

I follow the tutorial write the code ,but still show this error,the script already attach to the game object
This issue caused by the Execution Order of Event Functions.
When I checked logs, OnEnable of GameUI was called before Awake of GameController.
So, GameController.instance is null when you access GameController.instance.OnGameInfoChanged in void OnEnable() in GameUI.cs.
In Unity Manual for Execution Order of Event Functions, it says Awake is before OnEnable, I guess it doesn't guarantee always.
So, I think you'd better to modify GameUI.cs like below.
public class GameUI : MonoBehaviour
{
public Text timeLabel;
private bool isInitialized = false;
void Start()
{
isInitialized = true;
GameController.instance.OnGameInfoChanged += this.OnGameInfoChanged;
}
void OnEnable()
{
if (isInitialized)
GameController.instance.OnGameInfoChanged += this.OnGameInfoChanged;
}
void OnDisable()
{
GameController.instance.OnGameInfoChanged -= this.OnGameInfoChanged;
}
void OnGameInfoChanged(GameType type)
{
//...
}
}

Unity networking. transform.setparent() not working on a clinet side

I am writing a simple multiplayer board game in Unity.
I have the following problem: transport.setparent() not working on a client side.
When I launch a game as a server, everything is OK. When I connect to the server as a client transform.setParent() does nothing.
Here is my code:
public GameObject PlayerPrefab;
private GameObject player;
// Use this for initialization
void Start () {
if (!isLocalPlayer)
{
return;
}
Debug.Log("Spawning.");
CmdSpawn();
}
[Command]
void CmdSpawn()
{
player = Instantiate(PlayerPrefab);
NetworkServer.SpawnWithClientAuthority(player, connectionToClient);
player.transform.SetParent(GameObject.Find("BoardPanel").transform, false);
}
I have found the answer. Here is my solution:
Step 1) Use a SyncVar to synchronise the netID of the parent object between the server and client.
Step 2) When the object is spawned on the client, find the parent using the synchronised netID and set it as your transform's parent.
[Command]
void CmdSpawn()
{
Debug.Log("Spawning.");
player = Instantiate(PlayerPrefab);
player.GetComponent<Player>().ParentNetId = this.netId;
NetworkServer.SpawnWithClientAuthority(player, connectionToClient);
}
And need to add this code in a Player script:
[SyncVar]
public NetworkInstanceId ParentNetId;
public override void OnStartClient()
{
Debug.Log("OnStartClient.");
transform.SetParent(GameObject.Find("BoardPanel").transform, false);
}
Use this to find the parent object:-
Transform parentTransform = PhotonView.Find(parentid).gameObject.transform;
And then call the rpc to make the parent object on the client side like this:-
this.GetComponent().RPC("RPC_DropObject", RpcTarget.AllBuffered, position, rotation, hasParent, parent.gameObject.GetComponent().ViewID);

Multiplayer [SyncEvent] problem with non-player objects

I'm developing a multiplayer game based on turns.
So I have an script named gameController which is the one who has the global timer to alter the turns and also choose which players are attacking and which players are defending in the current turn.
This script is a singleton attached to a gameObject with a network identity on it (This network identity has no checkboxes marked to be controller by the server). I tried to have this game object spawned to all the clients when the server connects and also to have the game object already in the scene (booth cases aren't working).
Well, the main problem is that in the gameController script I have a checker in the update function to check if any new player is connected. In case is connected, it should call a syncEvent named EventOnNewPlayerAddedDelegate (I tried to call it directly, also using [command] and using [ ClientRpc]) to let the players know that they have to call their function named "OnRegisterPlayer", which is a function in their own player script that calls a function on gameController script, passing the player object (I tried via command and rpc also), something like this: GameController.instance.RegisterPlayer(this);
So anyone knows how can I trigger this SyncEvent to register the player to a non-player object controlled by the server?
Thank you very much.
I attach here a brief of booth scripts to make it easier to understand:
GameController:
public class GameController : NetworkBehaviour
{
public delegate void OnNewPlayerAddedDelegate();
[SyncEvent]
public event OnNewPlayerAddedDelegate EventOnNewPlayerAddedDelegate;
List<GamePlayerManager> players = new List<GamePlayerManager>();
public static GameController instance { get; private set; }
float timePerTorn = 30f;
bool isCountdown = false;
[System.NonSerialized]
public float countdownTime;
int roundNumber = 0;
int NumberOfPlayers;
int NumberOfPlayersChanged;
void Awake()
{
Debug.Log("Awaking ");
if (instance != null)
{
Debug.Log("Destoring ");
DestroyImmediate(this);
return;
}
instance = this;
}
// Use this for initialization
void Start()
{
if (!isServer)
{
return;
}
players = new List<GamePlayerManager>();
StartCountdown(5f);//20
}
void Update()
{
if (isServer)
{
if (isCountdown)
{
countdownTime -= Time.deltaTime;
if (countdownTime <= 0)
{
AlterGlobalTurns();
}
}
}
NumberOfPlayers = NetworkManager.singleton.numPlayers;
if (NumberOfPlayersChanged != NumberOfPlayers)
{
Debug.Log("num of players changed ---> " + NumberOfPlayers);
NumberOfPlayersChanged = NumberOfPlayers;
EventOnNewPlayerAddedDelegate();
//RpcNewPlayerAdded();
//CmdNewPlayerAdded();
}
}
[ClientRpc]
void RpcNewPlayerAdded()
{
Debug.Log("---------------- RpcNewPlayerAdded ------------");
EventOnNewPlayerAddedDelegate();
}
[Command]
void CmdNewPlayerAdded()
{
Debug.Log("---------------- CmdNewPlayerAdded ------------");
EventOnNewPlayerAddedDelegate();
}
public void RegisterPlayer(GamePlayerManager player)
{
Debug.Log("player ---> " + player.name);
if (players.Contains(player))
{
return;
}
Debug.Log("players ---> " + players);
players.Add(player);
}
}
PlayerScript:
public class GamePlayerManager : NetworkBehaviour
{
[System.NonSerialized]
public bool isPlayingOnTorn = true;
void Awake()
{
GameController.instance.EventOnNewPlayerAddedDelegate += OnRegisterPlayer;
}
private void Start()
{
if (!isLocalPlayer)
{
return;
}
}
public override void OnStartServer()
{
GameObject gc = (GameObject)Instantiate(NetworkManager.singleton.spawnPrefabs[2], transform.position, transform.rotation);
NetworkServer.Spawn(gc);
}
void OnRegisterPlayer(){
if (isLocalPlayer)
{
//GameController.instance.RegisterPlayer(this);
//RpcRegisterPlayer();
CmdRegisterPlayer();
}
}
[Command]
void CmdRegisterPlayer(){
Debug.Log("-------------Command Register player -------------");
GameController.instance.RegisterPlayer(this);
}
[ClientRpc]
void RpcRegisterPlayer()
{
Debug.Log("------------- RPC REgister Player -------------");
GameController.instance.RegisterPlayer(this);
}
}
I think I already saw the problem here.
The problem is that GameController is spawned by the playerScript(just if the player is also the host) in the function OnStartServer(). So the first problem is that the GameController doesn't detect the host player because it is not a new connections. And the second problem is when a second client is connected, the GameController detect the client connection and send the event signal faster than the client wakeup, so when the client is already working the signal is gone.
I solved the problem deleting this signal and checking directly in the playerScript if the GameController exists then check if the player is already registered.
My question now is that if there is anyway to instance and awake the server objects before the player(Host), which I understand that the answer is "no" because as the player is the host it needs to be running to have the server.
And the second question is if there is anyway or any signal to know that a new player is connected, and wait until it is awaked.
Thank you very much.
You can check the hole thread at Unity forums: https://forum.unity.com/threads/multiplayer-syncevent-problem-with-non-player-objects.589309/