Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Networking Chatting with new API

Discussion in '5.1 Beta' started by R0w4n, May 25, 2015.

  1. R0w4n

    R0w4n

    Joined:
    May 21, 2015
    Posts:
    5
    Hello,

    I'm trying to create a chat system but are having trouble getting my messages sent over the network due to the API update, my code follows the example of Brent Farris' tutorial on Unity3D Networking 8: Simple Chat System.

    Due to the API update I know that my SendMessage() does not work as RPC is not a thing anymore, this is what the old code looked like:

    Old code:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public class Chat : MonoBehaviour {
    6.  
    7.     //Chat History
    8.     public List<string> chatHistory = new List<string>();
    9.  
    10.     //Keeps track of current message
    11.     private string currentMessage = String.Empty;
    12.  
    13.     private void SendMessage() {
    14.         if (string.IsNullOrEmpty(currentMessage.Trim()))
    15.         {
    16.             networkView.RPC("ChatMessage", RPCMode.AllBuffered, new object[] { currentMessage }); //OBVIOUSLY DOES NOT WORK
    17.             currentMessage = String.Empty;
    18.         }
    19.     }
    20.  
    21.     private void BottomChat() {
    22.         currentMessage = GUI.TextField(new Rect(0, Screen.height - 20, 175, 20), currentMessage);
    23.         if (GUI.Button(new Rect(200, Screen.height - 20, 75, 20), "Send"))
    24.         {
    25.             SendMessage();
    26.         }
    27.         GUILayout.Space(15);
    28.         for (int i = chatHistory.Count - 1; i >= 0; i--)
    29.             GUILayout.Label(chatHistory[i]);
    30.     }
    31.  
    32.     private void TopChat() {
    33.         GUILayout.Space(15);
    34.         GUILayout.BeginHorizontal(GUILayout.Width(250));
    35.         currentMessage = GUILayout.TextField(currentMessage);
    36.         if (GUILayout.Button("Send"))
    37.         {
    38.             SendMessage();
    39.         }
    40.         GUILayout.EndHorizontal();
    41.         foreach (string c in chatHistory)
    42.             GUILayout.Label(c);
    43.     }
    44.  
    45.  
    46.     //Chatbox
    47.     private void OnGUI() {
    48.         BottomChat();
    49.     }
    50.  
    51.     [RPC]
    52.     public void ChatMessage(string message)
    53.     {
    54.         chatHistory.Add(message);
    55.     }
    56. }
    Obviously Unity complained and wanted to run the API Updater but that did not help the situation:

    API Updater (still not working):
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public class Chat : MonoBehaviour {
    6.  
    7.     //Chat History
    8.     public List<string> chatHistory = new List<string>();
    9.  
    10.     //Keeps track of current message
    11.     private string currentMessage = String.Empty;
    12.  
    13.     private void SendMessage() {
    14.         if (string.IsNullOrEmpty(currentMessage.Trim()))
    15.         {
    16.             GetComponent<NetworkView>().RPC("ChatMessage", RPCMode.AllBuffered, new object[] { currentMessage }); //OBVIOUSLY DOES NOT WORK
    17.             currentMessage = String.Empty;
    18.         }
    19.     }
    20.  
    21.     private void BottomChat() {
    22.         currentMessage = GUI.TextField(new Rect(0, Screen.height - 20, 175, 20), currentMessage);
    23.         if (GUI.Button(new Rect(200, Screen.height - 20, 75, 20), "Send"))
    24.         {
    25.             SendMessage();
    26.         }
    27.         GUILayout.Space(15);
    28.         for (int i = chatHistory.Count - 1; i >= 0; i--)
    29.             GUILayout.Label(chatHistory[i]);
    30.     }
    31.  
    32.     private void TopChat() {
    33.         GUILayout.Space(15);
    34.         GUILayout.BeginHorizontal(GUILayout.Width(250));
    35.         currentMessage = GUILayout.TextField(currentMessage);
    36.         if (GUILayout.Button("Send"))
    37.         {
    38.             SendMessage();
    39.         }
    40.         GUILayout.EndHorizontal();
    41.         foreach (string c in chatHistory)
    42.             GUILayout.Label(c);
    43.     }
    44.  
    45.  
    46.     //Chatbox
    47.     private void OnGUI() {
    48.         BottomChat();
    49.     }
    50.  
    51.     [RPC]
    52.     public void ChatMessage(string message)
    53.     {
    54.         chatHistory.Add(message);
    55.     }
    56. }
    Firstly I'm thinking this needs to be a NetworkBehaviour (using UnityEngine.Networking;) instead of MonoBehaviour and [RPC] could be [ClientCallback] ?

    But I have NO IDEA what I am talking about, I am just guessing and I have no idea how to fix SendMessage function.

    Hopefully you are smarter than me and can see what I am doing wrong...
    I appreciate your help!
     
    Last edited: May 25, 2015
  2. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    OK, so in a nutshell, you have two things for client/server communication.

    There's Commands. These are sent from client to server.

    Code (csharp):
    1.  
    2. void SendMessage()
    3. {
    4.     if( string.IsNullOrEmpty( currentMessage.Trim() ) ) return;
    5.  
    6.     CmdChatMessage( currentMessage );
    7.     currentMessage = "";
    8. }
    9.  
    10. [Command]
    11. void CmdChatMessage( string message )
    12. {
    13.     // this will be called on the server
    14. }
    15.  
    To send a Command, you just decorate the method with a [Command] attribute and prefix it with "Cmd", then just call the method on the client.

    Then there's RPCs, which are sent from server to client.

    Code (csharp):
    1.  
    2. [Command]
    3. void CmdChatMessage( string message )
    4. {
    5.     RpcReceiveChatMessage( message );
    6. }
    7.  
    8. [ClientRPC]
    9. void RpcReceiveChatMessage( string message )
    10. {
    11.     // this will be called on the client
    12. }
    13.  
    They work pretty much the same, but you use the [ClientRPC] attribute and prefix it with "Rpc".

    This is my understanding of the network stuff so far, anyway.

    The cool thing is that Unity rewrites the IL I think, so that all you have to do is call the method and it automatically sends it over the network and calls it on the receiving end.
     
  3. R0w4n

    R0w4n

    Joined:
    May 21, 2015
    Posts:
    5
    Thanks for your reply PhobicGunner!
    I took your example and updated my code, now the host is able to send messages across the network but the message is being spammed :3
    Also clients get this error when they try to chat:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using UnityEngine.Networking;
    5.  
    6. public class Chat : NetworkBehaviour {
    7.  
    8.     //Chat History
    9.     public List<string> chatHistory = new List<string>();
    10.  
    11.     //Keeps track of current message
    12.     private string currentMessage = String.Empty;
    13.    
    14.     void SendMessage() {
    15.         if (string.IsNullOrEmpty(currentMessage.Trim())) return;
    16.         {
    17.             CmdChatMessage(currentMessage);
    18.             currentMessage = "";
    19.         }
    20.     }
    21.  
    22.     private void BottomChat() {
    23.         currentMessage = GUI.TextField(new Rect(0, Screen.height - 20, 175, 20), currentMessage);
    24.         if (GUI.Button(new Rect(200, Screen.height - 20, 75, 20), "Send"))
    25.         {
    26.             SendMessage();
    27.         }
    28.         GUILayout.Space(15);
    29.         for (int i = chatHistory.Count - 1; i >= 0; i--)
    30.             GUILayout.Label(chatHistory[i]);
    31.     }
    32.  
    33.     private void TopChat() {
    34.         GUILayout.Space(15);
    35.         GUILayout.BeginHorizontal(GUILayout.Width(250));
    36.         currentMessage = GUILayout.TextField(currentMessage);
    37.         if (GUILayout.Button("Send"))
    38.         {
    39.             SendMessage();
    40.         }
    41.         GUILayout.EndHorizontal();
    42.         foreach (string c in chatHistory)
    43.             GUILayout.Label(c);
    44.     }
    45.  
    46.  
    47.     //Chatbox
    48.     private void OnGUI() {
    49.         BottomChat();
    50.         //TopChat();
    51.     }
    52.  
    53.     [Command]
    54.     void CmdChatMessage(string message)
    55.     {
    56.         RpcReceiveChatMessage(message);
    57.         chatHistory.Add(message);
    58.         CmdChatMessage(message);
    59.     }
    60.    
    61.     [ClientRpc]
    62.     void RpcReceiveChatMessage(string message)
    63.     {
    64.         chatHistory.Add(message);
    65.     }
    66. }
    I appreciate your help a lot!
     
  4. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    Keep in mind the body of this function will only ever be executed on the server, and Commands can only be sent from client to server (not the other way around). Plus, even if you could, this would result in an infinite loop as the function is calling itself.
    So remove that line I marked above.
     
  5. AlwaysBeCoding247

    AlwaysBeCoding247

    Joined:
    Jan 27, 2014
    Posts:
    41
    I was just thinking to create a simple chat today based from the same video Rowan has here but with the new networking. I was thinking that a SyncListString should handle this without needing to do the ClientRpc thing and all that. The command with the message string would get sent, do any verification server side, and then all client list get the new updated values like a SyncVar. I am going to play with it and see
     
  6. R0w4n

    R0w4n

    Joined:
    May 21, 2015
    Posts:
    5
    Thanks, that was a stupid mistake, I corrected it but the message is still sent twice by HOST ONLY, clients still cannot chat, have not been able to figure that out yet.

    How are you coming along, have you progressed further than me?
     
  7. AlwaysBeCoding247

    AlwaysBeCoding247

    Joined:
    Jan 27, 2014
    Posts:
    41
    @R0w4n I just played with it a little like an hour ago. I do have messages being sent in both directions now using a SyncListString. I am still sorting how to display the list properly since I am using some if(isLocalPlayer) in OnGUI to get it to work. The messages arrive and display on clients and the server/host but following that tutorial I have a disconnect between the messages the client himself typed and sent and the new messages arriving, they start to overlap from the way I am using isLocalPlayer in OnGUI right now. Should be a simple fix once my brain isnt trying to process it at 330am. But I just wanted to get the basic functionality of using the SyncListString to work and it did. I'll play with it more to get the GUI junk sorted and let you know if you havent found your own solution first. I was also initially having the issue with only the server/host being able to send messages across the network and clients messages not reaching the server and other things. I am using a command to send the chat message to the server, server updates list, changes are reflected on clients and host automagically.
     
  8. AlwaysBeCoding247

    AlwaysBeCoding247

    Joined:
    Jan 27, 2014
    Posts:
    41
    Forgot to mention I have the script attached to the player prefab and not the camera in this case, that allows the sending of the commands and checking for local player. I am attempting to get the rest sorted out now
     
  9. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    @PhobicGunner Anyone ever tell you you look exactly like Morgan Freeman? ;)
     
  10. AlwaysBeCoding247

    AlwaysBeCoding247

    Joined:
    Jan 27, 2014
    Posts:
    41
    @R0w4n dump all that other crap I was saying. I seemingly had communication going in both directions but it was not correct and setup for disaster. I stripped it down and found that simply sending NetworkMessage's with a string was a simple approach and got the outcome I wanted. I am sure a better/simpler way is possible and one will come up later but here is the run down after reading up on sending messages.. This is based on the first part of that video you watched, you can move it around to get the "rpg" bottom chat and stuff

    I created a class for my message types, just chat in this case..

    Code (CSharp):
    1. public class MyMessages
    2. {
    3.  
    4.     public enum MyMessageTypes
    5.     {
    6.         CHAT_MESSAGE = 1000
    7.     }
    8.  
    9.     public class ChatMessage : MessageBase
    10.     {
    11.         public string message;
    12.     }
    13.  
    14. }

    In my NetworkManager implementation I used server and client virtual methods to hook into their setup process to register both for my new message type..

    Code (CSharp):
    1.     // hook into NetworkManager client setup process
    2.     public override void OnStartClient(NetworkClient mClient)
    3.     {
    4.         base.OnStartClient(mClient); // base implementation is currently empty
    5.  
    6.         mClient.RegisterHandler((short)MyMessages.MyMessageTypes.CHAT_MESSAGE, OnClientChatMessage);
    7.     }
    8.  
    9.  
    10.  
    11.     // hook into NetManagers server setup process
    12.     public override void OnStartServer()
    13.     {
    14.         base.OnStartServer(); //base is empty
    15.         NetworkServer.RegisterHandler((short)MyMessages.MyMessageTypes.CHAT_MESSAGE, OnServerChatMessage);
    16.     }

    This is part from OnGUI from the tutorial video you are watching you should recognize, just with the new networking hooked in..

    Code (CSharp):
    1.          GUILayout.BeginHorizontal(GUILayout.Width(250));
    2.  
    3.             currentMessage = GUILayout.TextField(currentMessage);
    4.  
    5.             if (GUILayout.Button("Send"))
    6.             {
    7.                 if (!string.IsNullOrEmpty(currentMessage))
    8.                 {
    9.                     MyMessages.ChatMessage msg = new MyMessages.ChatMessage();
    10.                     msg.message = currentMessage;
    11.                     NetworkManager.singleton.client.Send((short) MyMessages.MyMessageTypes.CHAT_MESSAGE, msg);
    12.  
    13.                     currentMessage = String.Empty;
    14.                 }
    15.             }
    16.  
    17.          GUILayout.EndHorizontal();
    So client sends message to server and the server callback we registered before gets called, we do a check to make sure they dont think our game sucks, or any validation you may want to do maybe, and if not then send from server to all clients, including the one who actually sent the message in this case..

    Code (CSharp):
    1.  private void OnServerChatMessage(NetworkMessage netMsg)
    2.     {
    3.         var msg = netMsg.ReadMessage<StringMessage>();
    4.         Debug.Log("New chat message on server: " + msg.value);
    5.  
    6.         MyMessages.ChatMessage chat = new MyMessages.ChatMessage();
    7.         chat.message = msg.value;
    8.  
    9.         if (msg.value == "Dam this game sucks")
    10.         {
    11.             return;
    12.         }
    13.         else
    14.         {
    15.             NetworkServer.SendToAll((short)MyMessages.MyMessageTypes.CHAT_MESSAGE, chat);
    16.         }
    17.     }
    Now the client chat callback gets called after the server sends us the new chat message and we add it to local List of strings that is holding the chat history..

    Code (CSharp):
    1.   private void OnClientChatMessage(NetworkMessage netMsg)
    2.     {
    3.         var msg = netMsg.ReadMessage<StringMessage>();
    4.         Debug.Log("New chat message on client: " + msg.value);
    5.  
    6.         chat.Add(msg.value);
    7.     }
    Of course then you have your OnGUI loop like the tutorial that is adding a new label for each new chat message in the List..

    Code (CSharp):
    1. foreach (var msg in chat)
    2.      {
    3.             GUILayout.Label(msg);
    4.      }

    This is with the list and OnGUI stuff being inside the same script that runs the NetworkManager. I would separate things a bit if I were actually trying to put chat in my game right now, including using the new GUI and not OnGUI. Hope that helps you out a bit. It will at least have your chat working the same as it was from the video but using the new networking code
     
  11. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    that is a nice write up!
     
    AlwaysBeCoding247 likes this.