Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

UNET sync BOTH players UI across network

Discussion in 'UNet' started by KingTooTall, Nov 30, 2018.

  1. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Hello,
    I have a 2 player game, One HOST, and One CLIENT. I have them picking their correct player model they want when connecting. On the HOST side i can setup the HEALTH, player picture (AVATAR) UI...The health system is NOT connected to either player, it is on a script on a empty game object in my hierarchy as is my game controller, spawncontroller, sound controller etc etc etc. Each player has their its own UI element with their avatar and then their health bars below the avatar. I have the client on the host game screen setup also with the correct picture and health markers etc. on my CLIENT side, NONE of the UI is syncing and or I do not know how to do that.. at all.. I've read ALL the networking docs 90 times and I'm sorry if its either to complex a question or to simple, but I cannot seem to find a straight answer anywhere that fits my situation. I can and will post Code if needed, but I'm not even sure of the "HOW to go about" setting the client side UI to match the hosts. Some hand holding would be great.

    CLIENT VIEW:
    CLIENT.jpg
    HOST VIEW:
    HOST.jpg
     
  2. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    I'd just send a Unet Message to the client telling it what UI options to go with.
     
  3. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    HOW would i pass a message referencing a sprite image? and how do i tell the client to do that as i already have the host setup...again sorry for the possible noob questions, trying to break this down into something i can roll on my own.

    Thanks
    King
     
  4. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    If you have a list or array of sprites, send the index of that sprite in the list. You then do all the work in the handling method when receiving that type of message.
     
  5. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    I jdont have a list or array, I just attached the sprite in a public variable ON the player game object, So its looking like the better method here is just to have a list of player avatar's and just update the index with a syncvar?
     
  6. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    Yeah you can do it that way. You haven't shown what you're currently trying to do as far as syncing this, so my previous comment was just based on my best guess of what you were doing.
     
  7. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    NETWORK MANAGER:

    Code (CSharp):
    1.         public override void OnClientConnect(NetworkConnection conn)
    2.         {
    3.             //Get the CLIENTS pirate choice index
    4.             curPlayerPrefabIndex = KnifeFighterGameController.Instance.globalPlayerManager.MP_PirateIndexToSpawn();
    5.             // Create message to set the player
    6.             IntegerMessage msg = new IntegerMessage(curPlayerPrefabIndex);
    7.             // Call Add player and pass the message
    8.             ClientScene.AddPlayer(conn, 0, msg);
    9.         }
    10.  
    11. // Server
    12. public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader)
    13. {
    14.     // Read client message and receive index
    15.     if(extraMessageReader != null)
    16.     {
    17.         var stream = extraMessageReader.ReadMessage<IntegerMessage>();
    18.         curPlayerPrefabIndex = stream.value;
    19.     }
    20.     //Select the prefab from the spawnable objects list
    21.     tmpPrefabGO = KnifeFighterSpawnController.Instance.objectPooler.allAvailablePlayerPrefabs[curPlayerPrefabIndex];
    22.     tempPlayerGO = KnifeFighterSpawnController.Instance.MP_CreatePlayer(tmpPrefabGO);
    23.  
    24.     //Save the player to a list
    25.     KnifeFighterGameController.Instance.globalPlayerManager.MP_SetNetworkedPlayerGO(tempPlayerGO);
    26.     //Get all scripts and INIT player stats (plankside, health system etc)
    27.     KnifeFighterGameController.Instance.globalPlayerManager.MP_SetupPlayers();
    28.     // Add player object for connection
    29.     NetworkServer.AddPlayerForConnection(conn, tempPlayerGO, playerControllerId);
    30. }
    in my GLOBAL PLAYER MANAGER that does all the setup of plankside,health side, avatar, etc....
    Code (CSharp):
    1.  
    2. public void MP_SetupPlayers()
    3. {
    4.     //Make sure BOTH players have joined the game
    5.     if(nwPlayers.Count < 2)
    6.     {
    7.         return;
    8.     }
    9.     //Gett all Lists setup with player info needed
    10.     for(int i=0; i<numOfPlayers;i++)
    11.     {
    12.         AddPlayerTransform(nwPlayers[i].transform,i);
    13.         AddPlayerScript(nwPlayers[i].transform.GetComponent<KnifeFighterPlayer>(),i);
    14.         AddPlayerManagerScript(nwPlayers[i].transform.GetComponent<KnifeFighterPlayerManager>(),i);
    15.         playerScripts[i].currentRagDollScript = nwPlayers[i].transform.GetComponent<KnifeFighterRagDoll>();
    16.     }
    17.     //Init Player stats (plankside, max health,health system, etc)
    18.     MP_InitPlayerStats();
    19. }
    20.  
    21.  
    22. private void MP_InitPlayerStats()
    23. {
    24. /* singlePlayerOptions [0] = curPirateIndex; [1] = plankSide; [2] = maxHealth; [3] = level; */
    25.     for ( int i = 0; i < numOfPlayers; i++ )
    26.     {
    27.         //Make sure we are clearing the variable
    28.         tempTr = null;
    29.         tempPManager = null;
    30.         tempPlayerScript = null;
    31.         //Get the player that was spawned transform
    32.         tempTr = playerTransforms[i];
    33.         //Get the PlayerManager script
    34.         tempPManager = GetPlayerManagerScript(i);
    35.         tempPlayerScript = tempPManager.playerScript;
    36.         //id of ZERO is the always the HUMAN PLAYER, in a single player game
    37.         if(i == 0 && singlePlayerOptions[1] == (int)PlankSide.Random)
    38.         {
    39.             //Random choice
    40.             singlePlayerOptions[1] = Random.Range((int)PlankSide.Left,(int)PlankSide.Random);
    41.         }
    42.         //Set the correct plankside for each of the players depending on what side the human player chose
    43.         if(i == 0 && singlePlayerOptions[1] == (int)PlankSide.Left || i == 1 && clientPlankSide == (int)PlankSide.Left)
    44.         {
    45.             if(i ==0)
    46.             {
    47.                 clientPlankSide = (int)PlankSide.Right;
    48.             }
    49.             //1 = left plank  / -1 = right plank
    50.             tempTr.localPosition  = KnifeFighterGameController.Instance.islandController.currentIslandManager.currentLevelManager.GenerateRandomPosOnPlank(1);
    51.             // Get the level parent (cam parent) rotation so we can modify our players with the correct start rotations              
    52.             Vector3 tempRot = KnifeFighterGameController.Instance.gameCamController.GetCurrentCamParentRotation();
    53.             //set player rotation to 90 degrees (left plank) plus the camera rotation
    54.             tempTr.localRotation = Quaternion.Euler(0,tempRot.y+90f,0);
    55.             //Used to Set the players correct health system (left player = left side health system)
    56.             tempPManager.SetPlayerHealthSystemScript(KnifeFighterGameController.Instance.UIControl.LeftPlayerHealthSystem);
    57.             //Get the players Avatar picture for the UI
    58.             KnifeFighterGameController.Instance.UIControl.SetPlayerAvatarPicture(tempPManager.GetPlayerAvatar(),PlankSide.Left);
    59.             tempPManager.playerScript.plankSideChoice = PlankSide.Left;
    60.         }
    61.         if(i == 0 && singlePlayerOptions[1] == (int)PlankSide.Right || i == 1 && clientPlankSide == (int)PlankSide.Right)
    62.         {
    63.             if(i ==0)
    64.             {
    65.                 clientPlankSide = (int)PlankSide.Left;
    66.             }
    67.             //1 = left plank  / -1 = right plank
    68.             //Transform generatedXform = KnifeFighterGameController.Instance.islandController.currentIslandManager.currentLevelManager.GenerateRandomPosOnPlank(-1);
    69.             //tempTr.localPosition  = generatedXform.TransformPoint(Vector3.zero);
    70.             tempTr.localPosition  = KnifeFighterGameController.Instance.islandController.currentIslandManager.currentLevelManager.GenerateRandomPosOnPlank(-1);
    71.             Vector3 tempRot = KnifeFighterGameController.Instance.gameCamController.GetCurrentCamParentRotation();
    72.             //Add -90 degrees to rotation PLUS the level rotation
    73.             tempTr.localRotation = Quaternion.Euler(0,tempRot.y-90f,0);
    74.             //Used to Set the players correct health system (right player = right side health system)
    75.             tempPManager.SetPlayerHealthSystemScript(KnifeFighterGameController.Instance.UIControl.RightPlayerHealthSystem);
    76.             //Get the players Avatar picture for the UI
    77.             KnifeFighterGameController.Instance.UIControl.SetPlayerAvatarPicture(tempPManager.GetPlayerAvatar(),PlankSide.Right);
    78.             tempPManager.playerScript.plankSideChoice = PlankSide.Right;
    79.         }
    80.  
    81.     //Set the max health for this player from menu options
    82.     tempPManager.playerStatManager.SetMaxHealth(singlePlayerOptions[2]);
    83.     tempPManager.playerScript.isAIControlled = false;
    84.     //Set player INDEX in currentPlayerPrefabs
    85.     tempPManager.playerScript.SetID(i);
    86.     tempPManager.playerScript.SetUserInput(false);
    87.     tempPManager.playerScript.tp_Animator.animController.SetBool ("autoDie",false);
    88.     //tempPManager.playerScript.GetFixedTargetForPlayer(i);
    89.     //Max Health MUST be set  before we Init the health system
    90.     tempPManager.InitPlayerHealthSystem();
    91.     //Set the players default input device to the KEYBOARD for now
    92.     //tempPManager.playerScript.SetInputDevice(tempPManager.keyBoardInput);
    93.     //Set the players Weapon system
    94.     tempPManager.playerScript.SetPlayerWeaponSystem(tempPManager.playerWeaponSystem);
    95.     //Causes MORE ammo to be made each time its run
    96.     tempPManager.playerWeaponSystem.InitAllPlayerWeapons();
    97.     //Tell weapon control who we are (so all weapons can tell projectiles who sent them)
    98.     tempPManager.playerWeaponSystem.SetOwner(i);
    99.     //Tell projectiles who sent them
    100.     tempPManager.playerWeaponSystem.SetAmmoOwner();
    101.     //Set default LOCAL rotation of ALL ammo to the players plank side rotation
    102.     tempPManager.playerWeaponSystem.MatchAmmoRotationToPlayer(tempTr.localRotation);
    103.     //Set the players current weapon script
    104.     tempPManager.playerScript.SetPlayerCurrentWeaponScriptTo(tempPManager.playerWeaponSystem.GetCurrentWeaponScript());
    105.     //which way  we are supposed to move depends on what plankside we are on (pos = left neg = right)
    106.     tempPManager.playerScript.strafeDirection = Vector3.Cross(KnifeFighterGameController.Instance.gameCamController.currentCamXform.forward , tempPManager.playerScript.playerXform.forward);
    107.     }
    108.     numOfPlayersDead = 0;
    109.     isOnePlayerDone = false;
    110.     //KnifeFighterGameController.Instance.StartMPGame();
    111. }
    112.  
    113.  
    So I still have no idea how I would "Init" the clients side UI. COmmands? RPc's? Im happy to explain my hacky code also if needed lol !
    Thanks
     
  8. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Anyone? I guess I didn't realize that Sharing the same UI for all connected users was so elusive!? I'm not sure if I asked the question correctly either, but I've tried commands, rpcs, etc and I'm missing something. How would someone else approach my problem? Thanks
     
  9. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    I went back and tried to have the UI update itself with a syncvar, but NOTHING is still showing on the client side for UI...
    How do other people do shared UI canvas'es?
     
  10. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    A call to this method sets the correct player avatar picture UI on the HOST ONLY., this script is attached to a gameobject in my hierarchy as the universal Player UI Controller.
    It would seem to me that currentAvatarController is NOT getting updated on the CLIENT machine....
    Code (CSharp):
    1.  
    2. public void UpdateAvatarPicture(int newAvatarIdx,PlankSide plankSide)
    3. {
    4.     if(plankSide == PlankSide.Left)
    5.     {
    6.         currentAvatarController = leftAvatarController;
    7.     }
    8.     if(plankSide == PlankSide.Right)
    9.     {
    10.         currentAvatarController = rightAvatarController;
    11.     }
    12.     //Update the avatar pic to the NEW picture on HOST
    13.     currentAvatarController.ChangeAvatarTo(newAvatarIdx);
    14. }

    My current Avatar picture controller takes an index and looks through the list of available avatars and updates the image for the corresponding player side. Again on the HOST only. This script is attached to gameobject the universal Player UI Controller as a child...

    Code (CSharp):
    1. [SyncVar (hook = "UpdateAvatarPic")]public int avatarIndex;
    2.  
    3. //Call this to make controller auto update to the new avatar
    4. public void ChangeAvatarTo(int tmpIndex)
    5. {
    6.     avatarIndex = tmpIndex;
    7. }
    8.  
    9. private void UpdateAvatarPic(int spriteIndex)
    10. {
    11.     print("THE HOoOOOOOK just got called");
    12.     avatarIndex = spriteIndex;
    13.     destinationImageSlot.sprite = KnifeFighterGameController.Instance.globalPlayerManager.playerAvatars[avatarIndex];
    14.     destinationImageSlot.fillAmount = 1;
    15.    
    16. }
    I have tried RPC and CMD's back and forth 100 ways to no avail. Can anyone tell me how i get the "currentAvatarController" to update on the CLIENT side so my Avatar pic controller script knows which side and picture to update? I'm sorry to keep reposting, but as I make changes and try new things I thought it may help is anyone can take a look and make some suggestions?
    Thanks Again,
    King
     
  11. Cranom

    Cranom

    Joined:
    Jan 31, 2018
    Posts:
    26
    I take it that you don't even see your log from "UpdateAvatarPic" on the Client, otherwise you would know the problem comes from somewhere else. Other than that, and not knowing where you are calling "UpdateAvatarPicture" from, i also suppose that you make this call probably too soon for the SyncVar on the client to be available for update. (Remember that syncvar do not update on their own for late clients to the party!)

    That makes for a lot of guessing and not much help, so since i hate syncvar, i'll give you an exemple with Command/ClientRpc tested in a bare bones project.

    So here is the script used in the player prefab set as localPlayerAuthorities:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.Networking;
    4.  
    5. public class PlayerScript : NetworkBehaviour {
    6.  
    7.     static List<PlayerScript> playersRepository = new List<PlayerScript> ();
    8.     public int avatarIdx = -1;
    9.  
    10.     void Awake(){
    11.         playersRepository.Add (this);
    12.     }
    13.     void OnDestroy(){
    14.         playersRepository.Remove (this);
    15.     }
    16.  
    17.     public override void OnStartAuthority (){
    18.         base.OnStartAuthority ();
    19.  
    20.         //Get your local avatar's index from where you stored it instead of this random
    21.         avatarIdx = Random.Range(0,100);
    22.         // => Call setup function for each player on their own side
    23.  
    24.         //Server does not share directly because he's likely the first up and running
    25.         if (!isServer)
    26.             CmdShareAvatar (avatarIdx);
    27.     }
    28.  
    29.     [Command] void CmdShareAvatar (int idx){
    30.         //Update sent value for clone on server
    31.         avatarIdx = idx;
    32.  
    33.         /* Send back to all, the current values from other players if available...
    34.          * because if a player comes late he sees others in their default state */
    35.         foreach (PlayerScript ps in playersRepository) {
    36.             if (ps.avatarIdx > -1)
    37.                 ps.RpcShareAvatar (ps.avatarIdx);
    38.         }
    39.     }
    40.  
    41.     [ClientRpc] public void RpcShareAvatar(int idx){
    42.         //We do not update the player with authority
    43.         if (!hasAuthority) {
    44.             avatarIdx = idx;
    45.             // => Call setup function for a given player clone
    46.         }
    47.  
    48.         //Make a DevelopmentBuild to see beautifull red lines and confirm values
    49.         Debug.LogError ("I'm a (" +
    50.             (isServer ? "Server" : "Client") + "/" +
    51.             (hasAuthority ? "Player" : "Clone") +
    52.             ") with avatar index of " + avatarIdx);
    53.     }
    54. }
    Replace the two lines...
    // => Call setup function

    Each players perform their setup on their own side then inform the server to relay the info to its clones on other clients.
    You can then just replace the integer being shared by your own struct containing whatever data you deem necessary.

    Hope it helps!
     
    KingTooTall likes this.
  12. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    First off, I have been dealing with this for honestly 2 weeks now, and can agree with you on using syncvar's kind of as a last resort, lol. Ironically i rewrote my code before you posted yours to get rid of the syncvar hooks and used commands and rpc''s but it seem your key element that I was getting wrong was more in your explanation of the WHY that helps me immensely (and the code is also a big TY for as most dont like to share their code). I'll try and implement your method this afternoon and post back my results! UPDATEPICTURE was in part of the setup(basically player init values, plankside, rotation for plankside choice, setting of all the Controller scripts that handle the Global player setup and ui setup based on what side what player is on etc etc) code that got called right after OnServerAddPlayer() added both players to the scene.
     
  13. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Ok, so I have an understanding of the code you provided and it was basically a drop in method(s). However, I cannot get my Indexes correctly set before OnStartAuthority() gets called so it is still showing up blank on the client side unless i hard code a valid index for the avatar picture and valid index for the plankside choice.... how do I get the HOST plank side choice and pass that to the client so the CLIENT knows he is supposed to be on the OPPOSITE plank? I do that in my global player manager and it works on the HOST side, but maybe im calling something out of order? im sorry for so much code, but its not complex, although i thought it may help in some one pointing out what an idiot i am here.. lol

    NetworkMANAGER:
    Code (CSharp):
    1.  
    2. public override void OnStartHost()
    3. {
    4.     print("We just OnStartHost");
    5.     //Make sure client will be on the OPPOSITE PLANKSIDE
    6.     if(hostPlayerPlankSideChoice == (int)PlankSide.Left)
    7.     {
    8.         clientPlayerPlankSide =  (int)PlankSide.Right;
    9.     }
    10.     else
    11.     {
    12.         clientPlayerPlankSide =  (int)PlankSide.Left;
    13.     }
    14.    
    15. }
    16.  
    17. //Called on client when connect
    18. public override void OnClientConnect(NetworkConnection conn)
    19. {
    20. print("THIS IS WHERE A OnClientConnect  START GOES");
    21.     //Get the LOCAL player prefab you want to have for this player. (all this does is lookup my list of player prefabs INDEX and kick back the one your requesting)
    22.     curPlayerPrefabIndex = KnifeFighterGameController.Instance.globalPlayerManager.MP_PirateIndexToSpawn();
    23.     // Create message to set the player
    24.     IntegerMessage msg = new IntegerMessage(curPlayerPrefabIndex);
    25.     // Call Add player and pass the message
    26.     ClientScene.AddPlayer(conn, 0, msg);
    27.    
    28. }
    29.  
    30. public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader)
    31. {
    32.    
    33.     // Read client message and receive index
    34.     if(extraMessageReader != null)
    35.     {
    36.         var stream = extraMessageReader.ReadMessage<IntegerMessage>();
    37.         curPlayerPrefabIndex = stream.value;
    38.     }
    39.     //Select the prefab from the spawnable objects list FROM the local machine index that we created with our message earlier
    40.     tmpPrefabGO = KnifeFighterSpawnController.Instance.objectPooler.allAvailablePlayerPrefabs[curPlayerPrefabIndex];
    41.     //Spawn my player GO from the selected prefab
    42.     tempPlayerGO = KnifeFighterSpawnController.Instance.MP_CreatePlayer(tmpPrefabGO);
    43.     //Save the player to a list so we can do stuff to both players later
    44.     KnifeFighterGameController.Instance.globalPlayerManager.MP_AddNetworkedPlayerGO(tempPlayerGO);
    45.     //Init of lists used for health, avatar, manager scripts etc.
    46.     KnifeFighterGameController.Instance.globalPlayerManager.MP_GetPlayerListsSetup();
    47.     //KnifeFighterGameController.Instance.globalPlayerManager.MP_InitPlayerStats(tempPlayerGO.transform);
    48.     // Add player object for connection
    49.     NetworkServer.AddPlayerForConnection(conn, tempPlayerGO, playerControllerId);
    50.     print("THIS IS WHERE A OnServerAddPlayer  DONE");
    51. }
    GLOBAL PLAYER MANAGER:
    Code (CSharp):
    1.  
    2. public void MP_GetPlayerSettings ()
    3. {
    4.     //Clear  option list as we re-read stats each new game
    5.     singlePlayerOptions.Clear();
    6.     /* singlePlayerOptions [0] = curPirateIndex; [1] = plankSide; [2] = maxHealth; [3] = level; */
    7.     singlePlayerOptions.AddRange(KnifeFighterGameController.Instance.gameMenuController.SP_ReadPlayerStats());
    8. }
    9.  
    10. public void MP_GetPlayerListsSetup()
    11. {
    12.     //Make sure BOTH players have joined the game
    13.     if(nwPlayers.Count < 2)
    14.     {
    15.         return;
    16.     }
    17.     //Gett all Lists setup with player info needed
    18.     for(int i=0; i<nwPlayers.Count;i++)
    19.     {
    20.         AddPlayerTransform(nwPlayers[i].transform,i);
    21.         AddPlayerScript(nwPlayers[i].transform.GetComponent<KnifeFighterPlayer>(),i);
    22.         AddPlayerManagerScript(nwPlayers[i].transform.GetComponent<KnifeFighterPlayerManager>(),i);
    23.         playerScripts[i].currentRagDollScript = nwPlayers[i].transform.GetComponent<KnifeFighterRagDoll>();
    24.     //    playerAvatars.Add(playerScripts[i].playerAvatarImage.sprite);
    25.     }
    26.     //Init Player stats (plankside, max health,health system, etc)
    27.     MP_InitPlayerStats();
    28.    
    29. }
    30.  
    31. private void MP_InitPlayerStats()
    32. {
    33.     /* singlePlayerOptions [0] = curPirateIndex; [1] = plankSide; [2] = maxHealth; [3] = level; [4] = curAvatarIndex; */
    34.     print("MP_InitPlayerStats Just Got Called!");
    35.     for(int i = 0; i <  nwPlayers.Count; i++)//numOfPlayers; i++)
    36.     {
    37.         //Make sure we are clearing the variable
    38.         tempTr = null;
    39.         tempPManager = null;
    40.         //Get the player that was spawned transform
    41.         tempTr = playerTransforms[i];
    42.         //Get the PlayerManager script
    43.         tempPManager = GetPlayerManagerScript(i);
    44.         //id of ZERO is the always the HUMAN PLAYER, in a single player game
    45.         if(i == 0 && singlePlayerOptions[1] == (int)PlankSide.Random)
    46.         {
    47.             //Random choice
    48.             singlePlayerOptions[1] = Random.Range((int)PlankSide.Left, (int)PlankSide.Random);
    49.         }
    50.         //Set the correct plankside for each of the players depending on what side the human player chose
    51.         if(i == 0 && singlePlayerOptions[1] == (int)PlankSide.Left || i == 1 && clientPlankSide == (int)PlankSide.Left)
    52.         {
    53.             if(i == 0)
    54.             {
    55.                 clientPlankSide = (int)PlankSide.Right;
    56.             }
    57.             tempPManager.playerScript.SetID(i);
    58.             //1 = left plank  / -1 = right plank
    59.             tempTr.localPosition = KnifeFighterGameController.Instance.islandController.currentIslandManager.currentLevelManager.GenerateRandomPosOnPlank(1);
    60.             // Get the level parent (cam parent) rotation so we can modify our players with the correct start rotations              
    61.             Vector3 tempRot = KnifeFighterGameController.Instance.gameCamController.GetCurrentCamParentRotation();
    62.             //set player rotation to 90 degrees (left plank) plus the camera rotation
    63.             tempTr.localRotation = Quaternion.Euler(0, tempRot.y + 90f, 0);
    64.             //Used to Set the players correct health system (left player = left side health system)
    65.             tempPManager.SetPlayerHealthSystemScript(KnifeFighterGameController.Instance.UIControl.LeftPlayerHealthSystem);
    66.             //Get the players Avatar picture for the UI to a temp variable
    67.             tempPManager.playerScript.plankSideChoice = PlankSide.Left;
    68.             tempPManager.playerScript.syncedPlankSideChoice = (int)PlankSide.Left;
    69.             tempPManager.playerScript.avatarIdx = singlePlayerOptions[4];
    70.             //Set the host side avatar
    71.             KnifeFighterGameController.Instance.UIControl.playerUIController.UpdateAvatarPicture(singlePlayerOptions[4], PlankSide.Left);
    72.             //client
    73.         }
    74.  
    75.         if(i == 0 && singlePlayerOptions[1] == (int)PlankSide.Right || i == 1 && clientPlankSide == (int)PlankSide.Right)
    76.         {
    77.             if(i == 0)
    78.             {
    79.                 clientPlankSide = (int)PlankSide.Left;
    80.             }
    81.             tempPManager.playerScript.SetID(i);
    82.             //1 = left plank  / -1 = right plank
    83.             tempTr.localPosition = KnifeFighterGameController.Instance.islandController.currentIslandManager.currentLevelManager.GenerateRandomPosOnPlank(-1);
    84.             Vector3 tempRot = KnifeFighterGameController.Instance.gameCamController.GetCurrentCamParentRotation();
    85.             //Add -90 degrees to rotation PLUS the level rotation
    86.             tempTr.localRotation = Quaternion.Euler(0, tempRot.y - 90f, 0);
    87.             //Used to Set the players correct health system (right player = right side health system)
    88.             tempPManager.SetPlayerHealthSystemScript(KnifeFighterGameController.Instance.UIControl.RightPlayerHealthSystem);
    89.             //Get the players Avatar picture for the UI
    90.             tempPManager.playerScript.plankSideChoice = PlankSide.Right;
    91.             tempPManager.playerScript.syncedPlankSideChoice = (int)PlankSide.Right;
    92.             tempPManager.playerScript.avatarIdx = singlePlayerOptions[4];
    93.             //Set the host side avatar
    94.             KnifeFighterGameController.Instance.UIControl.playerUIController.UpdateAvatarPicture(singlePlayerOptions[4], PlankSide.Right);
    95.             //client
    96.         }
    97.         //Set the max health for this player from menu options
    98.         tempPManager.playerStatManager.SetMaxHealth(singlePlayerOptions[2]);
    99.         tempPManager.playerScript.isAIControlled = false;
    100.         //Set player INDEX in currentPlayerPrefabs
    101.         tempPManager.playerScript.SetID(i);
    102.         tempPManager.playerScript.SetUserInput(false);
    103.         tempPManager.playerScript.tp_Animator.animController.SetBool("autoDie", false);
    104.         //    tempPManager.playerScript.GetFixedTargetForPlayer(i);
    105.         //Max Health MUST be set  before we Init the health system
    106.         tempPManager.InitPlayerHealthSystem();
    107.         //Set the players default input device to the KEYBOARD for now
    108.         //tempPManager.playerScript.SetInputDevice(tempPManager.keyBoardInput);
    109.         //Set the players Weapon system
    110.         tempPManager.playerScript.SetPlayerWeaponSystem(tempPManager.playerWeaponSystem);
    111.         //Causes MORE ammo to be made each time its run
    112.         tempPManager.playerWeaponSystem.InitAllPlayerWeapons();
    113.         //Tell weapon control who we are (so all weapons can tell projectiles who sent them)
    114.         tempPManager.playerWeaponSystem.SetOwner(i);
    115.         //Tell projectiles who sent them
    116.         tempPManager.playerWeaponSystem.SetAmmoOwner();
    117.         //Set default LOCAL rotation of ALL ammo to the players plank side rotation
    118.         tempPManager.playerWeaponSystem.MatchAmmoRotationToPlayer(tempTr.localRotation);
    119.         //Set the players current weapon script
    120.         tempPManager.playerScript.SetPlayerCurrentWeaponScriptTo(tempPManager.playerWeaponSystem.GetCurrentWeaponScript());
    121.         //which way  we are supposed to move depends on what plankside we are on (pos = left neg = right)
    122.         tempPManager.playerScript.strafeDirection = Vector3.Cross(KnifeFighterGameController.Instance.gameCamController.currentCamXform.forward, tempPManager.playerScript.playerXform.forward);
    123.     }
    124.     numOfPlayersDead = 0;
    125.     isOnePlayerDone = false;
    126.     KnifeFighterGameController.Instance.StartMPGame();
    127. }
    128.  
    129. public void MP_AddNetworkedPlayerGO(GameObject tempPlayer)
    130. {
    131.     //Add the new player to
    132.     nwPlayers.Add(tempPlayer);
    133. }
    134.  
    135. public int MP_PirateIndexToSpawn()
    136. {
    137.     return singlePlayerOptions[0];
    138. }
    139.  
    140. public int MP_PirateAvatarIndex()
    141. {
    142.     return singlePlayerBotOptions[4];
    143. }

    Player UI manager:
    Code (CSharp):
    1.  
    2. public void UpdateAvatarPicture(int newAvatarIdx,PlankSide plankSide)
    3. {
    4.     print("UpdateAvatarPicture newAvatarIdx =  "+newAvatarIdx);
    5.     print("UpdateAvatarPicture plankSide =  "+plankSide);
    6.     if(plankSide == PlankSide.Left)
    7.     {
    8.         currentAvatarController = leftAvatarController;
    9.     }
    10.     if(plankSide == PlankSide.Right)
    11.     {
    12.         currentAvatarController = rightAvatarController;
    13.     }
    14.     //Update the avatar pic to the NEW picture on HOST
    15.     currentAvatarController.ChangeAvatarTo( newAvatarIdx);
    16. }
    17.  
    AvatarController:
    Code (CSharp):
    1.  
    2. //Call this to make controller auto update to the new avatar
    3. public void ChangeAvatarTo(int tmpIndex)
    4. {
    5.     print("TmpIndex = "+tmpIndex);
    6.     avatarIndex = tmpIndex;
    7.     destinationImageSlot.sprite = KnifeFighterSpawnController.Instance.objectPooler.allAvailablePlayerAvatars[tmpIndex];
    8.     destinationImageSlot.fillAmount = 1;
    9. }
    10.  
    My Player:
    Code (CSharp):
    1.  
    2. void OnDestroy(){
    3.         playersRepository.Remove (this);
    4.     }
    5.  
    6. //Called BEFORE OnStartAuthority
    7. public override void OnStartClient()
    8. {
    9.     base.OnStartClient();
    10.     print("OnStartClient ");
    11. }
    12.  
    13. public int avatarIdx = -1;
    14. //Called AFTER OnStartClient and/or OnStartHost
    15. public override void OnStartAuthority ()
    16. {
    17.     base.OnStartAuthority ();
    18.     print("OnStartAuthority ");
    19.     //Get your local avatar's index from where you stored it instead of this random
    20.      avatarIdx =1;
    21.     // => Call setup function for each player on their own side
    22.     KnifeFighterGameController.Instance.UIControl.playerUIController.UpdateAvatarPicture(0,(PlankSide)0);
    23.     KnifeFighterGameController.Instance.UIControl.playerUIController.UpdateAvatarPicture(1,(PlankSide)1);
    24.     //Server does not share directly because he's likely the first up and running
    25.     if (!isServer)
    26.         CmdShareAvatar (avatarIdx);
    27. }
    28.  
    29.    [Command] void CmdShareAvatar (int idx){
    30.         //Update sent value for clone on server
    31.         avatarIdx = idx;
    32.         /* Send back to all, the current values from other players if available...
    33.          * because if a player comes late he sees others in their default state */
    34.         foreach (KnifeFighterPlayer ps in playersRepository) {
    35.             if (ps.avatarIdx > -1)
    36.                 ps.RpcShareAvatar (ps.avatarIdx);
    37.         }
    38.     }
    39.     [ClientRpc] public void RpcShareAvatar(int idx){
    40.         //We do not update the player with authority
    41.         if (!hasAuthority) {
    42.             avatarIdx = idx;
    43.             // => Call setup function for a given player clone
    44.             KnifeFighterGameController.Instance.UIControl.playerUIController.UpdateAvatarPicture(avatarIdx,(PlankSide)syncedPlankSideChoice);
    45.         }
    46.         //Make a DevelopmentBuild to see beautifull red lines and confirm values
    47.         Debug.LogError ("I'm a (" +
    48.             (isServer ? "Server" : "Client") + "/" +
    49.             (hasAuthority ? "Player" : "Clone") +
    50.             ") with avatar index of " + avatarIdx);
    51.     }
    ..Will make an avatar show on BOTH client left and right side avatar picture slots...
     
  14. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    King,

    I am late to the party, and writing this at work, so I don't have my code in front of me.
    For myself, I was trying to sync player names across all players, and this may help regarding using SyncVars, if that's how you decide to go.

    THE most crucial aspects of SyncVars are:
    A) They must be called on the server, and,
    B) If you use a hook, the remote values are NOT automatically updated, inside the hook you must set the local module-level variable yourself, and,
    C) MOST IMPORTANTLY, a syncvar does NOT get synced when a new player connects!

    For myself:
    I have a network manager where the player enters their name before they click HOST or CONNECT.
    When the player starts the HOST or connects as a CLIENT, I call a [Command] in OnStartLocalPlayer() that sets the local synced module-level variable to its proper value.
    Also, in OnStartClient() [I think that's the one], I also update the local module-level variable to its proper value; that takes care of (C).

    I know this is confusing, and I'm not explaining it well, but if you want to try SyncVars again, let me know and I can send you snippets of actual code so I'm certain not to misrepresent something.

    -- Paul
     
  15. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Paul, Thank you for the reply,
    Im not against using syncvars or commands, either, or both, I seem to be having to most trouble of WHERE and WHEN to execute things (as you were trying to explain above). Im thinking to myself this whole time also, in its simplest form, that I basically (for now) only need to make sure the client and host "trade" 2 variables, one is a plank side choice (left or right) and the other is how to set which player avatar index that was chosen by whoever connects (only 2 players in this game, host/client and a client).and then filter that down to the Clones properly. Cranom's code was part of what i was trying to figure out as far as "filtering down" to the remote client side, but then its appears I also need a way for EACH player whoever joins sets THEIR avatar pic index FIRST, then filters it down. Again, Paul, I'm slowly "getting it" but by all means please post any code you wish, that helps me when i can see it in "action".
    Thanks
    King
     
  16. Cranom

    Cranom

    Joined:
    Jan 31, 2018
    Posts:
    26
    imgodot is right on the synvars and the list might not even be full, they require so many manual corrections that they are a pain in the ass.

    King you mention having trouble with the "WHERE and WHEN" and that's normal, there is a bunch of functions to call from in Networking and NetworkManager, some are unclear in their descriptions, some are not triggered in their specified orders, some others are even bugged...
    For starter you're probably better off not making overrides in the NetworkManager, just use it as is, with the playerprefab auto create checked in the inspector. Because trying to setup a player the instant he's added won't work the way you do it i think, since spawning is made from the server the objet will first exist there, then will exist on the clients (not at the same time), then the authority will be set. And that's just at this moment that you can safely exchange data.

    About my previous exemple; i was showing you a way to contact and share informations as soon as possible between the client and the server by having the player with authority communicate when ready to do so. But you can just remove the first direct call made to the server and replace it by a local function stating to the player "we are ready to go, what are your orders good sir?" and await the user input before sending the data.

    Here is the first exemple extended with a color collection (representing your avatar collection) and a textmesh (for the avatar) displayed to the wanted side by the server.

    Code (CSharp):
    1. public class PlayerScript : NetworkBehaviour {
    2.  
    3.     static List<PlayerScript> _playersRepository = new List<PlayerScript> ();
    4.     int _avatarIdx = 0;
    5.     public int AvatarIdx {
    6.         get { return _avatarIdx; }
    7.     }
    8.     /* I'm using a collection of colors for ease of use in this exemple...
    9.      * but it's exaclty the same principle, just change color to sprite */
    10.     static Color[] _colorsRepository = {
    11.         Color.clear,
    12.         Color.black,
    13.         Color.white,
    14.     };
    15.     /* In this case the avatar takes form in a textmesh's color.
    16.     * In your case, this would be a sprite or image link to the GUI */
    17.     TextMesh _myText;
    18.  
    19.  
    20.     void Awake(){
    21.         //Setup for visual feedbacks
    22.         _myText = gameObject.AddComponent<TextMesh> ();
    23.         _myText.anchor = TextAnchor.UpperCenter;
    24.         _myText.fontSize = 100;
    25.         _myText.characterSize = 0.05f;
    26.         _myText.text = " (Clone on Client)";
    27.  
    28.         /* Display the avatar with default val, to be invisible until update from...
    29.          * either us if we have authority or the server passing info from others */
    30.         DisplayAvatar ();
    31.         enabled = false;
    32.         _playersRepository.Add (this);
    33.     }
    34.     void OnDestroy(){
    35.         _playersRepository.Remove (this);
    36.     }
    37.     public override void OnStartServer (){
    38.         base.OnStartServer ();
    39.         _myText.text = _myText.text.Replace ("Client", "Server");
    40.     }
    41.     public override void OnStartAuthority (){
    42.         base.OnStartAuthority ();
    43.         _myText.text = _myText.text.Replace ("Clone", "You");
    44.         enabled = true;
    45.     }
    46.     public override void OnStartLocalPlayer (){
    47.         base.OnStartLocalPlayer ();
    48.         _myText.anchor = TextAnchor.LowerCenter;
    49.     }
    50.  
    51.  
    52.     //Only being checked for player with authority
    53.     void Update(){
    54.         if (!Input.anyKeyDown)
    55.             return;
    56.  
    57.         //We take the player's input, be they client or server for avatar index
    58.         if (Input.GetKeyDown (KeyCode.Keypad1)) {
    59.             _avatarIdx = 1;
    60.             DisplayAvatar ();
    61.             CmdShareAvatar (_avatarIdx);
    62.         } else if (Input.GetKeyDown (KeyCode.Keypad2)) {
    63.             _avatarIdx = 2;
    64.             DisplayAvatar ();
    65.             CmdShareAvatar (_avatarIdx);
    66.         }
    67.  
    68.         //It seems that you wish for the plank side to be server dominant
    69.         if (!isServer)
    70.             return;
    71.  
    72.         if (Input.GetKeyDown (KeyCode.LeftArrow)) {
    73.             //Since it's the server we can call Rpc direclty for each players
    74.             RpcSetSide (5);
    75.             if(_playersRepository.Count==2)
    76.                 _playersRepository [1].RpcSetSide (3);
    77.         } else if (Input.GetKeyDown (KeyCode.RightArrow)) {
    78.             RpcSetSide (3);
    79.             if(_playersRepository.Count==2)
    80.                 _playersRepository [1].RpcSetSide (5);
    81.         }
    82.     }
    83.  
    84.     void DisplayAvatar(){
    85.         _myText.color = _colorsRepository [_avatarIdx];
    86.     }
    87.  
    88.     [Command] void CmdShareAvatar (int idx){
    89.         //Update sent value for clone on server
    90.         _avatarIdx = idx;
    91.  
    92.         /* Send back to all, the current values from other players if available...
    93.              * because if a player comes late he sees others in their default state */
    94.         foreach (PlayerScript ps in _playersRepository) {
    95.             if (ps.AvatarIdx > 0)
    96.                 ps.RpcShareAvatar (ps.AvatarIdx);
    97.         }
    98.     }
    99.  
    100.     [ClientRpc] public void RpcShareAvatar(int idx){
    101.         //We do not update the player with authority
    102.         if (!hasAuthority) {
    103.             _avatarIdx = idx;
    104.             DisplayAvatar ();
    105.         }
    106.     }
    107.  
    108.     [ClientRpc] public void RpcSetSide(int idx){
    109.         _myText.anchor = (TextAnchor)idx;
    110.     }
    111. }
    1- So now instead of sending right away the avatar index we do as if knowing nothing, when the authority start we enable the update loop and wait for input from the user.
    2- Both client and host can change their color with keypad 1 & 2, it could have been buttons, or we could have call a variable holding the choice in memory, whatever suits you.
    3- And only the server can choose side and change accordingly the client with the arrows.

    At step 1 both the host and client are in the game and didn't make input: The screen is deep blue.
    At step 2, let's say server push keypad1 and client push keypad2, we get:
    ex2.png
    At step 3, the server push the left arrow and both get in place:
    ex3.png
     
  17. KingTooTall

    KingTooTall

    Joined:
    Oct 17, 2014
    Posts:
    61
    Cranom, I did get it to work before your code was posted using a keypress to populate both host and client on the client side of things. I was trying to avoid using a button press or anything like that when a client joins. I just want it all to "auto" configure the health and avatar pictures ONCONNECT. I DID get that to work also, but ha, I'm not sure why its working!? lol I'll clean up the code and repost later today in case others want to see how im accomplishing what im trying to do and maybe get some insight from you guys as to the "WHY" it works the way i have it setup. I am using commands and rpc's, but also with a combo of SyncVars. To be continued....

    King
     
  18. imgodot

    imgodot

    Joined:
    Nov 29, 2013
    Posts:
    212
    King, Sorry I did not respond with the code I promised. Life got in the way. But, in honor of the new year, and with the hope that you, or anyone else, might profit from what I learned, here is what I did to get syncvars to work across host and client side. In my program, I only have two players, one runs on the HOST/server computer and one on a remote/client computer. I have both player names displayed on the screen on the host/client and the remote/client.

    The player object script has a syncvar defined (with a "hook" event).
    Here is the syncvar definition::
    Code (CSharp):
    1.     [SyncVar(hook = "OnSyncVarChange_PlayerName")]
    2.     public string m_PlayerName;
    3.  
    Here is the hook event:
    Code (CSharp):
    1.     // SyncVar HOOK:
    2.     void OnSyncVarChange_PlayerName(string argPlayerName) {
    3.         //===========================================================================
    4.         // NOTE 1: The SyncVar hook is also called manually from "OnStartClient()".
    5.         // NOTE 2: Using a HOOK means that the value is NOT propagated automatically.
    6.         //         That is why the mod-level variable is assigned below.
    7.         //===========================================================================
    8.         // Set the mod-level player# and assign it to the player GO name.
    9.         //===========================================================================
    10.  
    11.         if (argPlayerName.Trim() == "") return;
    12.  
    13.         m_PlayerName = argPlayerName;
    14.  
    15.         if (isServer) {
    16.             if (isLocalPlayer) {
    17.                 GameObject.Find("PlayerName0").GetComponent<TextMesh>().text = "Player 1" + ": " + argPlayerName;
    18.             } else {
    19.                 GameObject.Find("PlayerName1").GetComponent<TextMesh>().text = "Player 2" + ": " + argPlayerName;
    20.             }
    21.         } else {
    22.             if (isLocalPlayer) {
    23.                 GameObject.Find("PlayerName1").GetComponent<TextMesh>().text = "Player 2" + ": " + argPlayerName;
    24.             } else {
    25.                 GameObject.Find("PlayerName0").GetComponent<TextMesh>().text = "Player 1" + ": " + argPlayerName;
    26.             }
    27.         }
    28.     }
    Here is a command in the player object script. This is only called from the "OnStartLocalPlayer()" method:
    Note: The player name passed into this method comes from the NetworkManager UI.
    Code (CSharp):
    1.     [Command]
    2.     public void Cmd_AssignPlayerPropertiesOnServer(string argPlayerName, int argPlayerIndex ...) {
    3.                 // This COMMAND is called from "OnStartLocalPlayer()" for a "local" player.
    4.         // The name passed into this method is accurate for that player.
    5.         // This COMMAND takes the player name, index# and spawn position and assigns them to mod-level variables.
    6.         // That name and index# values (set here on the SERVER) are propagated to the client-side
    7.         // so they are all in sync between the object on the HOST and the object on the remote CLIENT.
    8.  
    9.         m_PlayerName = argPlayerName;
    10.     }
    Here is the code in the player "OnStartClient()" event:
    Code (CSharp):
    1.     public override void OnStartClient() {
    2.         // Note: This code will not run until a player connects as a client.
    3.         //===========================================================================
    4.         // The SyncVar hook MUST be called here, because the HOOK does not get
    5.         // called for the server-controlled player object when the client connects.
    6.         // NOTE 1: This is an oddity, but not a bug, more a feature limitation.
    7.         // NOTE 2: The same limitation applies to RPC calls. An RPC that is called
    8.         //         before the client connects ONLY gets called for the local client
    9.         //         player object with authority and NOT for the server's remote
    10.         //         player avatar.
    11.         //===========================================================================
    12.  
    13.         OnSyncVarChange_PlayerName(m_PlayerName);
    14.     }