Author Topic: How to create Drop In Drop Out functionality in a network game using TNet ?  (Read 3846 times)

troglodescu

  • Guest
First of all,

Like NGUI, TNet is very useful for the network hassle. I still have some issues  that I'm sure have a clean solution, but I just couldn't figure it out yet.

Issue 1:

My game is pretty straightforward. You press play and automaticaly a local server is started with UDP broadcast and the player starts playing. Anyone can see the list of local servers and can join. Everyting works perfectly, it was very nice and easy to implement this with TNet. The problem arises when using TNManager.Create(). The host who first joined the game is created just fine and when another player joins, the host sees another instance being created just as it should. The problem is on the client side where  there is only one instance of the player (the client does not see the server player even though it is connected). I was hoping that TNManager.Create() would be buffered like Network.Instantiate but it seems that it doesn't work like that.
This is the basic code that does this. Is there a way I can implement the desired functionality ?

  1. void Awake()
  2.     {
  3.         TNManager.Create(prefabPlayer, playerSpawnPosition.position, playerSpawnPosition.rotation);
  4.     }
  5.  

Issue 2:

I seem to experience some perfomance problems when using SendQuickly to send info for the players to sync over the network. It works fine but FPS drops dramatically when there is a network connection. I guess using SendQuickly in every frame is much too costly. What's the best solution ? Sending with a specified send rate like Unity uses for networkViews and interpolating ? (about 15 miliseconds) or is there another way ?

here is the script that synchronizes the players on the netowrk

  1. using UnityEngine;
  2. using TNet;
  3. using System.Collections;
  4. public class NetworkPuppetSync : TNBehaviour
  5. {
  6.    
  7.  
  8.     public Puppet whatToControl;
  9.  
  10.    
  11.  
  12.  
  13.     [RFC(1)]
  14.     void SetMoveDir(Vector3 aMoveDir)
  15.     {
  16.         whatToControl.moveDirection = aMoveDir;
  17.     }
  18.  
  19.     [RFC(2)]
  20.     void SetLookDir(Vector3 aLookDir)
  21.     {
  22.         whatToControl.lookingDirection = aLookDir;
  23.     }
  24.     [RFC(3)]
  25.     void SetPost(Vector3 aPos)
  26.     {
  27.         if ((whatToControl.xForm.position - aPos).sqrMagnitude > 0.1F)
  28.         {
  29.             whatToControl.xForm.position = aPos;
  30.         }
  31.     }
  32.  
  33.     [RFC(4)]
  34.     void EquipPlayer(string slot1, string slot2, string melee)
  35.     {
  36.         Debug.Log("IN EquipPlayer");
  37.         GameplayController.Instance.playerEquipper.EquipPlayer(whatToControl as PlayerPuppet, slot1, slot2, melee);
  38.     }
  39.  
  40.  
  41.     bool remote = false;
  42.  
  43.     void Awake()
  44.     {
  45.         remote = !TNManager.isThisMyObject;
  46.         if (!remote)
  47.         {
  48.             GameplayController.Instance.RegisterForControl(whatToControl as PlayerPuppet);
  49.         }
  50.     }
  51.    
  52.     void Update()
  53.     {
  54.         if (remote)
  55.         {
  56.             return;
  57.         }
  58.         tno.SendQuickly(1, Target.Others, whatToControl.moveDirection);
  59.         tno.SendQuickly(2, Target.Others, whatToControl.lookingDirection);
  60.         tno.SendQuickly(3, Target.Others, whatToControl.xForm.position);
  61.     }
  62.    
  63.  
  64. }
  65.  

Thank you and sorry for the long post :)

troglodescu

  • Guest
I managed to fix issue 1 by a weird and probably not so nice method: I stopped using TNManager.Create(), instead I just used classic Unity.Instantiate that is called through [RFC]. I think the author of TNet would not approve of my code :)
 It works nonetheless.

Anyway, this is the code. Tell me what you think of it and if could have used TNet functionality better.
  1.  void Awake()
  2.     {
  3.  
  4.         tno.Send(1, Target.AllSaved,
  5.             TNManager.playerID,
  6.  
  7.             SavedPrefs.GetString(Prefs.CATEGORIES[0], Prefs.SMG),
  8.             SavedPrefs.GetString(Prefs.CATEGORIES[1], Prefs.NONE),
  9.             SavedPrefs.GetString(Prefs.CATEGORIES[2], Prefs.KNIFE));
  10.         tno.Send(2, Target.AllSaved, TNManager.playerID);
  11.  
  12.        
  13.  
  14.         Debug.Log(TNServerInstance.localAddress + " players = " + TNManager.players.size);
  15.     }
  16.  
  17.  
  18.  
  19.     [RFC(1)]
  20.     void SpawnPlayer(int aPlayerID, string slot1, string slot2, string melee)
  21.     {
  22.         Debug.Log("IN SpawnRemote aID = " + aPlayerID + " slot1 = " + slot1);
  23.         PlayerPuppet pp = (Instantiate(prefabPlayer, playerSpawnPosition.position, playerSpawnPosition.rotation) as GameObject).GetComponent<PlayerPuppet>();
  24.  
  25.         playerEquipper.EquipPlayer(pp, slot1, slot2, melee);
  26.  
  27.  
  28.         TNObject to = pp.gameObject.GetComponent<TNObject>();
  29.        
  30.         to.uid = (uint)(aPlayerID);
  31.  
  32.         if (aPlayerID == TNManager.playerID)
  33.         {
  34.             pp.GetComponent<NetworkPuppetSync>().remote = false;
  35.             RegisterForControl(pp);
  36.         }
  37.         else
  38.         {
  39.             pp.GetComponent<NetworkPuppetSync>().remote = true;
  40.         }
  41.  
  42.     }
  43.     [RFC(2)]
  44.     void RequestPlayerDataFromOthers(int aPlayerID)
  45.     {
  46.         Debug.Log("IN RequestPlayerDataFromOthers " + aPlayerID.ToString());
  47.         if (aPlayerID != TNManager.playerID)
  48.         {
  49.            
  50.             tno.Send(1, TNManager.GetPlayer(aPlayerID),
  51.                 TNManager.playerID,
  52.  
  53.                 SavedPrefs.GetString(Prefs.CATEGORIES[0], Prefs.SMG),
  54.                 SavedPrefs.GetString(Prefs.CATEGORIES[1], Prefs.NONE),
  55.                 SavedPrefs.GetString(Prefs.CATEGORIES[2], Prefs.KNIFE));
  56.         }
  57.     }
  58.  

Unfortunately, I still have to deal with Issue 2. I can't seem to figure out why performance is drastically reduced when sending data over the network. By using Unity's multiplayer with OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info), the same data was sent and performance was not affected.

I've revised a little the sync code to not send in every frame.

Position, movement direction and look direction are sent with a rate of 0.1F

The rest of the data is just bools that I send only when their value changes.

Any help for the performance issue would be great.

Here's the code.

  1. using System.Collections;
  2.  
  3. using TNet;
  4.  
  5. using UnityEngine;
  6.  
  7. public class NetworkPuppetSync : TNBehaviour
  8. {
  9.     #region Fields Arranged Nicely :)
  10.  
  11.     public Puppet whatToControl;
  12.  
  13.     public float sendRate = 0.1F;
  14.  
  15.     public bool remote = false;
  16.  
  17.     float timer = 0;
  18.  
  19.     bool commandAimWeapon;
  20.     bool commandDodge;
  21.     bool commandMeleeAttack;
  22.     bool commandReloadWeapon;
  23.     bool commandShoot;
  24.     bool commandSprint;
  25.     bool commandSwitchWeapon;
  26.     bool commandVault;
  27.  
  28.     #endregion Fields Arranged Nicely :)
  29.  
  30.     #region Methods
  31.  
  32.    
  33.     void CheckBool(ref bool aBool, bool aNewBool, byte rfc)
  34.     {
  35.         if (aBool != aNewBool)
  36.         {
  37.             aBool = aNewBool;
  38.             tno.Send(rfc, Target.Others, aBool);
  39.         }
  40.     }
  41.  
  42.     [RFC(12)]
  43.     void SetCommandAimWeapon(bool aValue)
  44.     {
  45.         whatToControl.commandAimWeapon = aValue;
  46.     }
  47.     [RFC(8)]
  48.     void SetCommandDodge(bool aValue)
  49.     {
  50.         whatToControl.commandDodge = aValue;
  51.     }
  52.     [RFC(6)]
  53.     void SetCommandMeleeAttack(bool aValue)
  54.     {
  55.         whatToControl.commandMeleeAttack = aValue;
  56.     }
  57.     [RFC(10)]
  58.     void SetCommandReloadWeapon(bool aValue)
  59.     {
  60.         whatToControl.commandReloadWeapon = aValue;
  61.     }
  62.     [RFC(5)]
  63.     void SetCommandShoot(bool aValue)
  64.     {
  65.         whatToControl.commandShoot = aValue;
  66.     }
  67.     [RFC(11)]
  68.     void SetCommandSprint(bool aValue)
  69.     {
  70.         whatToControl.commandSprint = aValue;
  71.     }
  72.     [RFC(9)]
  73.     void SetCommandSwitchWeapon(bool aValue)
  74.     {
  75.         whatToControl.commandSwitchWeapon = aValue;
  76.     }
  77.     [RFC(7)]
  78.     void SetCommandVault(bool aValue)
  79.     {
  80.         whatToControl.commandVault = aValue;
  81.     }
  82.     [RFC(2)]
  83.     void SetLookDir(Vector3 aLookDir)
  84.     {
  85.         whatToControl.lookingDirection =
  86.             //Vector3.Lerp(
  87.             //whatToControl.lookingDirection
  88.             //,
  89.             aLookDir
  90.             //, Time.deltaTime)
  91.             ;
  92.     }
  93.     [RFC(1)]
  94.     void SetMoveDir(Vector3 aMoveDir)
  95.     {
  96.         whatToControl.moveDirection =
  97.             //Vector3.Lerp(
  98.             //whatToControl.moveDirection
  99.             //,
  100.             aMoveDir
  101.             //, Time.deltaTime)
  102.             ;
  103.     }
  104.     [RFC(3)]
  105.     void SetPos(Vector3 aPos)
  106.     {
  107.         if ((whatToControl.xForm.position - aPos).sqrMagnitude > 0.1F)
  108.         {
  109.             whatToControl.xForm.position = Vector3.Lerp(whatToControl.xForm.position, aPos, Time.deltaTime);
  110.         }
  111.     }
  112.     void Update()
  113.     {
  114.         if (remote)
  115.         {
  116.             return;
  117.         }
  118.  
  119.         if (timer > sendRate)
  120.         {
  121.             timer = 0;
  122.             tno.SendQuickly(1, Target.Others, whatToControl.moveDirection);
  123.             tno.SendQuickly(2, Target.Others, whatToControl.lookingDirection);
  124.  
  125.         }
  126.         tno.SendQuickly(3, Target.Others, whatToControl.xForm.position);
  127.         timer += Time.deltaTime;
  128.  
  129.         CheckBool(ref commandShoot, whatToControl.commandShoot,5);
  130.         CheckBool(ref commandMeleeAttack, whatToControl.commandMeleeAttack, 6);
  131.         CheckBool(ref  commandVault, whatToControl.commandVault, 7);
  132.         CheckBool(ref  commandDodge, whatToControl.commandDodge, 8);
  133.         CheckBool(ref  commandSwitchWeapon, whatToControl.commandSwitchWeapon, 9);
  134.         CheckBool(ref  commandReloadWeapon, whatToControl.commandReloadWeapon, 10);
  135.         CheckBool(ref  commandSprint, whatToControl.commandSprint, 11);
  136.         CheckBool(ref  commandAimWeapon, whatToControl.commandAimWeapon, 12);
  137.     }
  138.  
  139.     #endregion Methods
  140. }
  141.  

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
1. TNManager.Create is a buffered call, but you need to make sure that the object you are trying to create is referenced on the TNManager's list of objects, or it can't be buffered.

2. Sending data every frame is... insane. What if you're on a computer with 1000 FPS? That's 1000 packets per second? You will flood the socket. Obvious bandwidth issues aside, When you flood the socket, it will flat out stop all communication for a few seconds. You need to send data periodically. If you're not sure how that's done, look inside TNAutoSync. You can use that class to automatically sync any property across the network without any coding.

troglodescu

  • Guest
Thank you for the reply ArenMook,

1. I did put the object I wanted to create in the list of objects in the TNManager. My TNmanager is in the MainMenuScene and it remains present in the GameplayScene. I'm sure I'm missing something else.

2. I will check out TNAutoSync to see how it's done.

3. Short question just to make sure I got it right: TNObject.Send is the equivalent with Unity's networkView.RPC("") and TNObject.SendQuickly is equivalent with Unity's OnSynchronizeNetworkView, right ?

Thank you again for the help

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
3. Both are equivalent to networkView.RPC. Send() sends via TCP, while SendQuickly may or may not send via UDP -- meaning the packet may not arrive (rare), or arrive in a wrong order (even more rare). SendQuickly is ideal for frequent updates -- such as position updates -- that happen several times per second and you don't care if one packet update gets lost from time to time.