Hey I'm using the unity lobby asset (the one in the asset store). I have edited it so players can click a button and choose what character they would like to be (changes prefab). This is fine as it works but clients cant choose a prefab they get given the same one as the host... Any help / ideas would be great. Been on this for like 4 weeks.... Thanks alot if someone responds! CODE: Spoiler: C# networkmanager Code (CSharp): using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using UnityEngine.Networking; using UnityEngine.Networking.Types; using UnityEngine.Networking.Match; using System.Collections; using UnityEngine.Networking.NetworkSystem; namespace Prototype.NetworkLobby { public class LobbyManager : NetworkLobbyManager { public Button player1Button; public Button player2Button; int avatarIndex = 0; public Canvas characterSelectionCanvas; static short MsgKicked = MsgType.Highest + 1; static public LobbyManager s_Singleton; [Header("Unity UI Lobby")] [Tooltip("Time in second between all players ready & match start")] public float prematchCountdown = 5.0f; [Space] [Header("UI Reference")] public LobbyTopPanel topPanel; public RectTransform mainMenuPanel; public RectTransform lobbyPanel; public LobbyInfoPanel infoPanel; public LobbyCountdownPanel countdownPanel; public GameObject addPlayerButton; protected RectTransform currentPanel; public Button backButton; public Text statusInfo; public Text hostInfo; //Client numPlayers from NetworkManager is always 0, so we count (throught connect/destroy in LobbyPlayer) the number //of players, so that even client know how many player there is. [HideInInspector] public int _playerNumber = 0; //used to disconnect a client properly when exiting the matchmaker [HideInInspector] public bool _isMatchmaking = false; protected bool _disconnectServer = false; protected ulong _currentMatchID; protected LobbyHook _lobbyHooks; void Start() { s_Singleton = this; _lobbyHooks = GetComponent<Prototype.NetworkLobby.LobbyHook>(); currentPanel = mainMenuPanel; backButton.gameObject.SetActive(false); GetComponent<Canvas>().enabled = true; DontDestroyOnLoad(gameObject); player1Button.onClick.AddListener(delegate { AvatarPicker(player1Button.name); }); player2Button.onClick.AddListener(delegate { AvatarPicker(player2Button.name); }); SetServerInfo("Offline", "None"); } void AvatarPicker(string buttonName) { switch (buttonName) { case "Player1": avatarIndex = 0; break; case "Player2": avatarIndex = 1; break; } playerPrefab = spawnPrefabs[avatarIndex]; gamePlayerPrefab = spawnPrefabs[avatarIndex]; } public override void OnLobbyClientSceneChanged(NetworkConnection conn) { if (SceneManager.GetSceneAt(0).name == lobbyScene) { if (topPanel.isInGame) { ChangeTo(lobbyPanel); if (_isMatchmaking) { if (conn.playerControllers[0].unetView.isServer) { backDelegate = StopHostClbk; } else { backDelegate = StopClientClbk; } } else { if (conn.playerControllers[0].unetView.isClient) { backDelegate = StopHostClbk; } else { backDelegate = StopClientClbk; } } } else { ChangeTo(mainMenuPanel); } topPanel.ToggleVisibility(true); topPanel.isInGame = false; } else { ChangeTo(null); Destroy(GameObject.Find("MainMenuUI(Clone)")); //backDelegate = StopGameClbk; topPanel.isInGame = true; topPanel.ToggleVisibility(false); } } public void ChangeTo(RectTransform newPanel) { if (currentPanel != null) { currentPanel.gameObject.SetActive(false); } if (newPanel != null) { newPanel.gameObject.SetActive(true); } currentPanel = newPanel; if (currentPanel != mainMenuPanel) { backButton.gameObject.SetActive(true); } else { backButton.gameObject.SetActive(false); SetServerInfo("Offline", "None"); _isMatchmaking = false; } } public void DisplayIsConnecting() { var _this = this; infoPanel.Display("Connecting...", "Cancel", () => { _this.backDelegate(); }); } public void SetServerInfo(string status, string host) { statusInfo.text = status; hostInfo.text = host; } public delegate void BackButtonDelegate(); public BackButtonDelegate backDelegate; public void GoBackButton() { backDelegate(); topPanel.isInGame = false; } // ----------------- Server management public void AddLocalPlayer() { TryToAddPlayer(); } public void RemovePlayer(LobbyPlayer player) { player.RemovePlayer(); } public void SimpleBackClbk() { ChangeTo(mainMenuPanel); } public void StopHostClbk() { if (_isMatchmaking) { matchMaker.DestroyMatch((NetworkID)_currentMatchID, 0, OnDestroyMatch); _disconnectServer = true; } else { StopHost(); } ChangeTo(mainMenuPanel); } public void StopClientClbk() { StopClient(); if (_isMatchmaking) { StopMatchMaker(); } ChangeTo(mainMenuPanel); } public void StopServerClbk() { StopServer(); ChangeTo(mainMenuPanel); } class KickMsg : MessageBase { } public void KickPlayer(NetworkConnection conn) { conn.Send(MsgKicked, new KickMsg()); } public void KickedMessageHandler(NetworkMessage netMsg) { infoPanel.Display("Kicked by Server", "Close", null); netMsg.conn.Disconnect(); } //=================== public override void OnStartHost() { base.OnStartHost(); ChangeTo(lobbyPanel); backDelegate = StopHostClbk; SetServerInfo("Hosting", networkAddress); } public override void OnMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo) { base.OnMatchCreate(success, extendedInfo, matchInfo); _currentMatchID = (System.UInt64)matchInfo.networkId; } public override void OnDestroyMatch(bool success, string extendedInfo) { base.OnDestroyMatch(success, extendedInfo); if (_disconnectServer) { StopMatchMaker(); StopHost(); } } //allow to handle the (+) button to add/remove player public void OnPlayersNumberModified(int count) { _playerNumber += count; int localPlayerCount = 0; foreach (PlayerController p in ClientScene.localPlayers) localPlayerCount += (p == null || p.playerControllerId == -1) ? 0 : 1; addPlayerButton.SetActive(localPlayerCount < maxPlayersPerConnection && _playerNumber < maxPlayers); } // ----------------- Server callbacks ------------------ //we want to disable the button JOIN if we don't have enough player //But OnLobbyClientConnect isn't called on hosting player. So we override the lobbyPlayer creation public override GameObject OnLobbyServerCreateLobbyPlayer(NetworkConnection conn, short playerControllerId) { GameObject obj = Instantiate(lobbyPlayerPrefab.gameObject) as GameObject; LobbyPlayer newPlayer = obj.GetComponent<LobbyPlayer>(); newPlayer.ToggleJoinButton(numPlayers + 1 >= minPlayers); for (int i = 0; i < lobbySlots.Length; ++i) { LobbyPlayer p = lobbySlots[i] as LobbyPlayer; if (p != null) { p.RpcUpdateRemoveButton(); p.ToggleJoinButton(numPlayers + 1 >= minPlayers); } } return obj; } public override void OnLobbyServerPlayerRemoved(NetworkConnection conn, short playerControllerId) { for (int i = 0; i < lobbySlots.Length; ++i) { LobbyPlayer p = lobbySlots[i] as LobbyPlayer; if (p != null) { p.RpcUpdateRemoveButton(); p.ToggleJoinButton(numPlayers + 1 >= minPlayers); } } } public override void OnLobbyServerDisconnect(NetworkConnection conn) { for (int i = 0; i < lobbySlots.Length; ++i) { LobbyPlayer p = lobbySlots[i] as LobbyPlayer; if (p != null) { p.RpcUpdateRemoveButton(); p.ToggleJoinButton(numPlayers >= minPlayers); } } } public override bool OnLobbyServerSceneLoadedForPlayer(GameObject lobbyPlayer, GameObject gamePlayer) { //This hook allows you to apply state data from the lobby-player to the game-player //just subclass "LobbyHook" and add it to the lobby object. if (_lobbyHooks) _lobbyHooks.OnLobbyServerSceneLoadedForPlayer(this, lobbyPlayer, gamePlayer); return true; } // --- Countdown management public override void OnLobbyServerPlayersReady() { bool allready = true; for (int i = 0; i < lobbySlots.Length; ++i) { if (lobbySlots[i] != null) allready &= lobbySlots[i].readyToBegin; } if (allready) StartCoroutine(ServerCountdownCoroutine()); } public IEnumerator ServerCountdownCoroutine() { float remainingTime = prematchCountdown; int floorTime = Mathf.FloorToInt(remainingTime); while (remainingTime > 0) { yield return null; remainingTime -= Time.deltaTime; int newFloorTime = Mathf.FloorToInt(remainingTime); if (newFloorTime != floorTime) {//to avoid flooding the network of message, we only send a notice to client when the number of plain seconds change. floorTime = newFloorTime; for (int i = 0; i < lobbySlots.Length; ++i) { if (lobbySlots[i] != null) {//there is maxPlayer slots, so some could be == null, need to test it before accessing! (lobbySlots[i] as LobbyPlayer).RpcUpdateCountdown(floorTime); } } } } for (int i = 0; i < lobbySlots.Length; ++i) { if (lobbySlots[i] != null) { (lobbySlots[i] as LobbyPlayer).RpcUpdateCountdown(0); } } ServerChangeScene(playScene); } // ----------------- Client callbacks ------------------ public override void OnClientConnect(NetworkConnection conn) { IntegerMessage msg = new IntegerMessage(avatarIndex); base.OnClientConnect(conn); infoPanel.gameObject.SetActive(false); conn.RegisterHandler(MsgKicked, KickedMessageHandler); if (!NetworkServer.active) {//only to do on pure client (not self hosting client) ChangeTo(lobbyPanel); backDelegate = StopClientClbk; SetServerInfo("Client", networkAddress); } } public override void OnClientDisconnect(NetworkConnection conn) { base.OnClientDisconnect(conn); ChangeTo(mainMenuPanel); } public override void OnClientError(NetworkConnection conn, int errorCode) { ChangeTo(mainMenuPanel); infoPanel.Display("Cient error : " + (errorCode == 6 ? "timeout" : errorCode.ToString()), "Close", null); } } }
The server is the one to spawn the player. So it you can't just change the prefab client sided. What I would do is to spawn some temporary player. Like a player object with only a camera. Then send over the character you want to the server once the game has started. Then replace the player object with a new one. https://docs.unity3d.com/ScriptReference/Networking.NetworkServer.ReplacePlayerForConnection.html
I have started trying this ahaha I made the player prefab spawn an empty object with a UI that has 2 options etc.... for the characters. But I don't know how to spawn them once they are clicked... ill try to figure out this replace player but you have any other clues? BTW THANKS VERY MUCH FOR RESPONDING!
Elaborate? My idea was to have a generic player object with nothing other than a script and a camera on. And In the script on the start method you basically replace the object with the selected character. Just one way to do it. EDIT: I understand what you mean now. In the button presses, you want to call a command. In the command you want to spawn the character. Then you want to use the ReplacePlayerForConnection to make that the active player.
I have a function: Code (CSharp): [Command] void CmdReplaceMe(GameObject Player1Object) { NetworkServer.Spawn(Player1Object); NetworkServer.ReplacePlayerForConnection(connectionToClient, Player1Object, playerControllerId); } I then in the button click function: Code (CSharp): public void character1click() { // play that other function Destroy(PlayerSpawn); }
You have to instantiate the object on the server then spawn it also on server. You cant instantiate on Client and expect it to be on the server.
Code (CSharp): [Command] void CmdReplacePlayer(GameObject PlayerPrefab) { Network.Instantiate(PlayerPrefab, transform.position, transform.rotation, 0); NetworkServer.ReplacePlayerForConnection(connectionToClient, PlayerPrefab, playerControllerId); Thats it isn't it? Also thanks for your time, this is helping a lot.
uhm. You should probably read up the documentation a bit more about how UNET works. But here is ish how you would do it. Then you need to pass a int or something to the command and replace the prefab with a Array of prefabs where the int is the index that the client wants. Code (CSharp): [Command] void Cmd_ReplacePlayer() { //Player prefab is a global variable that refers to a prefab. //This line spawns the prefab just like any signle player game on the server. GameObject go = Instantiate(PlayerPrefab, transform.position, transform.rotaion); //Then this just tells all the clients to spawn the same object. NetworkServer.Spawn(go); //Then we try to replace it. And if it succeeded. if(NetworkServer.ReplacePlayerForConnection(connectionToClient, go, playerControllerId)) { //We destroy the current player across all instances NetworkServer.Destroy(gameObject); } }
In the Network manager I have: Code (CSharp): int avatarIndex = 0; Code (CSharp): void AvatarPicker(string buttonName) { switch (buttonName) { case "Player1": avatarIndex = 0; break; case "Player2": avatarIndex = 1; break; } Code (CSharp): playerPrefab = spawnPrefabs[avatarIndex]; gamePlayerPrefab = spawnPrefabs[avatarIndex]; That makes it so before players are spawned they can pick what class they would like to be. But all clients are the host etc. Is there a way I can take that number from that code and put it in the spawn code: Code (CSharp): [Command] void Cmd_ReplacePlayer() { //Player prefab is a global variable that refers to a prefab. //This line spawns the prefab just like any signle player game on the server. GameObject go = Instantiate(PlayerPrefab, transform.position, transform.rotaion); //Then this just tells all the clients to spawn the same object. NetworkServer.Spawn(go); //Then we try to replace it. And if it succeeded. if (NetworkServer.ReplacePlayerForConnection(connectionToClient, go, playerControllerId)) { //We destroy the current player across all instances NetworkServer.Destroy(gameObject); } }
Just make an array of all the player prefabs on the script I corrected for you. Then pass the index you would like as a argument to the command. Atleast that's one way to do it.
where do I need to put this code? on an emptyObject in the scene I want to spawn, or do I need to set this script on a prefab and set it as player prefab in the network manager