I want to do the following.
When a player joins the room, he should receive all the gameobjects' (with a photonview) locations.
This should happen only once when the player enters the room.
How could this be implemented?
The best solution for sending only one piece of information is to use RPC messages system.
[PunRPC]
void changePos(int x, int y, int z)
{
Debug.Log("new pos =" + x + "," + y + ","+z);
}
PhotonView photonView = PhotonView.Get(this);
photonView.RPC("changePos", PhotonTargets.All, 1,1,1 );
You can read more about RPC messages here: https://doc.photonengine.com/en/pun/current/tutorials/rpcsandraiseevent
EDIT:
I'm guessing you're connecting through:
PhotonNetwork.JoinRoom(this.roomName);
So in the place resposible for connection to the server you can use:
public void OnCreatedRoom()
{
Debug.Log("OnCreatedRoom");
}
public void OnJoinedRoom()
{
Debug.Log("OnJoinedRoom");
RPCserver.Instance.AddNewPlayer(login)
}
And then, you can have a bridge to store all RPC enabled methods:
public class RPCserver : Singleton
{
public List<Player> players = new List<Player>();
public void addNewPlayer(string name)
{
Player p = new Player(name);
players.Add(p);
if( p.isNewPlayer() ) fetchOtherObjectsPositions();
}
private void fetchOtherObjectsPositions(){
// Go through all neccesery objects, and send their position via RPCserver
}
}
Is that make sense ?
Related
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);
}
I try to sync float value between server-client.
On server screen have boss HpBar script with MAXHP value setup in scene.
player prefab have their own boss HP UI and localhp script that will get currently boss HP value from a HpBar script in the server.
when the user press Attack button it will send damage value to HpBar script then it should update to player prefab. but it doesn't sync together. thanks
picture here
https://imgur.com/a/KrYtC3T
localPlayerHPBAR Script:
public class localHpBar : NetworkBehaviour
{
public HpBar serverHp;
public float localhpPoint;
public Image localhpBar;
// Start is called before the first frame update
void Start()
{
serverHp = GameObject.FindObjectOfType<HpBar>();
//Get OBJ of serverHP
}
public void sendDamage(float dmg)
{
serverHp.TakeDamage(dmg); //SendDamageToserver
}
// Update is called once per frame
void Update()
{
//get HP point from server
localhpPoint = serverHp.sum;
localhpBar.fillAmount = localhpPoint;
}
}
SERVER HpBar
public class HpBar : NetworkBehaviour
{
public Image HP;
public float MaxHP;
float currentHP;
public float localhp;
[SyncVar] public float sum;
void Start()
{
//set CURRENT HP
currentHP = MaxHP;
}
[ClientRpc]
void rpcDamage(float dmg)
{
sum = currentHP / MaxHP;
HP.fillAmount = sum;
}
public void TakeDamage(float dmg)
{
//recieve DamageFrom client
currentHP = currentHP - dmg;
rpcDamage(currentHP);
}
}
Note that a [SyncVar] is only synchronized Server → Client(s).
Never in the other direction. Changing this value on the client won't affect the server or other clients.
You are changing sum using a [ClientRpc]. A [ClientRpc] is invoked by the server but executed on all clients. You are not even allowed to call this as a client.
What you rather should do is making the TakeDamage method a [Command] so it is invoked by the client but only executed on the server where you then are allowed to change the synced value like
// This is called by clients but executed on the server
[Command]
public void CmdTakeDamage(float dmg)
{
currentHP -= dmg;
sum = currentHP / MaxHP;
// actually pass on the new sum
rpcDamage(sum);
}
// This is called ONLY by the server but executed on all clients
[ClientRpc]
// also make sure the prefix is capital Rpc otherwise it might not work
void RpcDamage(float newSum)
{
// if you set sum here then you should simply remove the
// [SyncVar] since you already sync it "manually"
// I would actually do this since it is not guaranteed that the syncvar
// transfers its new value before this method is executed
// on the other hand syncvar is additionally synchronized when connecting a new client
// so you might have to do it "manually"
sum = newSum;
HP.fillAmount = sum;
}
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/
I am trying to send data on a simple multiplayer game, using Var1 and Var2 in this example. Var1 should sent and be stored as Var2 in the other user's instance.
The code I have sort of works, except I think the server is sending data to itself and overriding the data it receives, because if I load the client and initialize/send data, the server sees it. Then if I go to the server and initialize/send data, the client sees it. But if I go back to the client and send data, the server no longer sees it. It's like the server is acting as both a client and a server. Am I doing something wrong somewhere?
public class mouseController : NetworkBehaviour
{
GameLoop g;
void Start()
{
GameObject thePlayer = GameObject.Find("Main Camera");
g = thePlayer.GetComponent<GameLoop>();
}
void Update()
{
if (!isLocalPlayer)
{
return;
}
if (isServer)
{
if (g.var1 > 0)
{
Rpcsenddata(g.var1);
}
}
else
{
if (g.var1 > 0)
{
Cmdsenddata(g.var1);
}
}
}
[Command]
void Cmdsenddata(int i)
{
g.var2 = i;
}
[ClientRpc]
public void Rpcsenddata(int i)
{
g.var2 = i;
}
}
https://gyazo.com/b77e2ce35ec5f4d47147894c9430da03
(the data being sent is the card selected by your opponent. The server is the window on the right)
I think I fixed it by adding a !isLocalPlayer check
[ClientRpc]
public void Rpcsenddata(int i)
{
if (!isLocalPlayer) {
g.ehandSelected = i;
}
}
Has anyone got multiple player prefabs working (eg different character classes with different prefabs) on the new Unity UNET Networking?
Finally got it working!
Massive thank you to #ClausKleber for their answer at
http://forum.unity3d.com/threads/how-to-set-individual-playerprefab-form-client-in-the-networkmanger.348337/#post-2256378
Edited working version below, Works with the Network Manager HUD to create and join.
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;
public class NetManagerCustom : NetworkManager
{
// in the Network Manager component, you must put your player prefabs
// in the Spawn Info -> Registered Spawnable Prefabs section
public short playerPrefabIndex;
public override void OnStartServer()
{
NetworkServer.RegisterHandler(MsgTypes.PlayerPrefab, OnResponsePrefab);
base.OnStartServer();
}
public override void OnClientConnect(NetworkConnection conn)
{
client.RegisterHandler(MsgTypes.PlayerPrefab, OnRequestPrefab);
base.OnClientConnect(conn);
}
private void OnRequestPrefab(NetworkMessage netMsg)
{
MsgTypes.PlayerPrefabMsg msg = new MsgTypes.PlayerPrefabMsg();
msg.controllerID = netMsg.ReadMessage<MsgTypes.PlayerPrefabMsg>().controllerID;
msg.prefabIndex = playerPrefabIndex;
client.Send(MsgTypes.PlayerPrefab, msg);
}
private void OnResponsePrefab(NetworkMessage netMsg)
{
MsgTypes.PlayerPrefabMsg msg = netMsg.ReadMessage<MsgTypes.PlayerPrefabMsg>();
playerPrefab = spawnPrefabs[msg.prefabIndex];
base.OnServerAddPlayer(netMsg.conn, msg.controllerID);
Debug.Log(playerPrefab.name + " spawned!");
}
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
MsgTypes.PlayerPrefabMsg msg = new MsgTypes.PlayerPrefabMsg();
msg.controllerID = playerControllerId;
NetworkServer.SendToClient(conn.connectionId, MsgTypes.PlayerPrefab, msg);
}
// I have put a toggle UI on gameObjects called PC1 and PC2 to select two different character types.
// on toggle, this function is called, which updates the playerPrefabIndex
// The index will be the number from the registered spawnable prefabs that
// you want for your player
public void UpdatePC ()
{
if (GameObject.Find("PC1").GetComponent<Toggle>().isOn)
{
playerPrefabIndex = 3;
}
else if (GameObject.Find("PC2").GetComponent<Toggle>().isOn)
{
playerPrefabIndex= 4;
}
}
}
Create a new class that derives from the built-in NetworkManager script. In there, just add a few supporting fields and an override for OnServerAddPlayer().
[SerializeField] Vector3 playerSpawnPos;
[SerializeField] GameObject character1;
[SerializeField] GameObject character2;
// etc.
GameObject chosenCharacter; // character1, character2, etc.
// Instantiate whichever character the player chose and was assigned to chosenCharacter
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId) {
var player = (GameObject)GameObject.Instantiate(chosenCharacter, playerSpawnPos, Quaternion.identity);
NetworkServer.AddPlayer(conn, player, playerControllerId);
}
Reference: http://docs.unity3d.com/Manual/UNetManager.html