'Simple' networking

Hey guys,
I am just starting out with networking and have a small project for which I would like to use to test out networking. It is a typing game in which you kill enemies.
What I was thinking is that I need to be able to create & join a simple match. (Using IP for now, no matchmaking). Then we load the game scene and each player has a super simple hud with the amount of points that each player has accumulated, updating every 0.1s for example. Then when the first player completes the level, start a countdown (20s), after which every player stops and a leaderboard is shown. (or when every player finishes)

In Short:

  • Connect Players using IP for now. (Client hosted)
  • Play level (played locally, only scores sent), only needing to send the scores to each player at a set interval.
  • Complete level, showing leaderboard
  • Quit to main menu

Are there any resources or short scripts that could be shared to point me in the right direction.

I realise that there are already some resources about Unity networking, however, they are mostly about games where all of the players are in a single scene. Levels can be local, all I need is to send the scores.

Bump!

Bumpetty!

I’m not entirely sure what you’re asking about, but TcpClient and TcpListener would be important.
Your first post says Client hosted, but I presume you mean that the client acts as a server.
So, the other players can input an ip address and to the host and join the game. They send in their scores, and the server relays the scores to the other players.
The rest of it would run like a local game, I’d imagine (other than maybe waiting for scores/something to say the round’s finished).
Is there some more specific information you’re seeking?

Thanks, couple of helpful key words in there.
Something I forgot to mention was that I would need to send a text file to the other players. (Or a string array). This is because it is a typing-style game. Is there any tips for this or will I find out such things when researching things like TcpClient etc.

When you send, there are a few ways. I usually use Socket.Send(byte [ ]).
There are variations to that (it’s TcpClient.client) I think. There’s also using a stream, and what not, which I believe just wraps around the same thing…
In any event, in direct response to your “sending a string” - you can of course do that. You convert to ascii to send, and convert back to utf-8 on receipt. That’s how I do/have, anyways.
You could send a file, also - just whatever suits ya. With some exceptions, sending as a string would be basically the same as a file.
Also, to expand on some keywords, if you find them helpful, I like using ‘Socket.Select’. In Unity, you can run this in Update, or a coroutine, I’m sure would work, too. Just set the timeout to 0 (zero) seconds. I don’t think you’ll need anymore options to look into for this rather simple game you’re describing.

Thanks. I created the barebones now.
Here are my scripts so far in case anyone is interested. Now I just have to keep developing it.
Host:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using UnityEngine;

public class TarnationHost : MonoBehaviour
{
    [SerializeField] private string serverIp = "127.0.0.1";
    [SerializeField] private int serverPort = 2345;

    /// <summary>
    /// Server
    /// </summary>
    TcpListener listener;
    StreamWriter writer;

    /// <summary>
    /// It appears when to connect a StreamReader to a client it must continue using that specific StreamReader?
    /// </summary>
    List<TcpClient> connectedClients = new List<TcpClient>();
    List<StreamReader> connectedClientReaders = new List<StreamReader>();

    private void Awake()
    {
        listener = new TcpListener(IPAddress.Parse(serverIp), serverPort);
    }

    public void StartServer()
    {
        Debug.Log("[Server] Started Server!");
        listener.Start();

        StartCoroutine(AcceptConnections());
        StartCoroutine(AcceptMessages());
    }

    public void StopAcceptingConnections()
    {
        StopCoroutine(AcceptConnections());
    }

    /// <summary>
    /// I would send the scores to the players here, to display on the UI
    /// </summary>
    private void SendMessageToClients(string message)
    {
        for (int i = 0; i < connectedClients.Count; i++)
        {
            // TODO: Send data to all clients of each others scores
            // Data I need: Usernames, scores.
            //connectedClients[i].GetStream().Write();
        }
    }

    /// <summary>
    /// Accept any pending connection requests
    /// </summary>
    private IEnumerator AcceptConnections()
    {
        while (true)
        {
            if (listener.Pending() == true)
            {
                TcpClient client = listener.AcceptTcpClient();
                StreamReader clientSr = new StreamReader(client.GetStream());

                connectedClients.Add(client);
                connectedClientReaders.Add(clientSr);
            }

            yield return null;
        }
    }

    /// <summary>
    /// Read any incoming messages
    /// </summary>
    private IEnumerator AcceptMessages()
    {
        while (true)
        {
            for (int i = 0; i < connectedClients.Count; i++)
            {
                string message = connectedClientReaders[i].ReadLine();

                if (string.IsNullOrEmpty(message) == false)
                {
                    OnNetworkMessageRecieved(connectedClients[i], message);
                }
            }

            yield return null;
        }
    }

    private void OnNetworkMessageRecieved(TcpClient client, string message)
    {
        Debug.Log("[Server] Message Recieved: " + message);
    }
}

Client:

using System.IO;
using System.Net.Sockets;
using UnityEngine;

public class TarnationClient : MonoBehaviour
{
    [SerializeField] private string serverIp = "127.0.0.1";
    [SerializeField] private int serverPort = 2345;

    /// <summary>
    /// Client
    /// </summary>
    TcpClient client = new TcpClient();
    StreamWriter writer;

    public void ConnectToServer()
    {
        client.Connect(serverIp, serverPort);
        SendMessageToServer("Woop");
    }

    private void SendMessageToServer(string message)
    {
        using (StreamWriter writer = new StreamWriter(client.GetStream()))
        {
            writer.AutoFlush = true;
            writer.WriteLine(message);
            Debug.Log("[Client] Message sent to server!");
        }
    }
}

Update:
I changed my Client code to:

using System.IO;
using System.Net.Sockets;
using UnityEngine;

public class TarnationClient : MonoBehaviour
{
    [SerializeField] private string serverIp = "127.0.0.1";
    [SerializeField] private int serverPort = 2345;

    /// <summary>
    /// Client
    /// </summary>
    TcpClient client = new TcpClient();
    StreamWriter writer;

    public void ConnectToServer()
    {
        client.Connect(serverIp, serverPort);
        SendMessageToServer("Woop");
        SendMessageToServer("Poop");

        writer = new StreamWriter(client.GetStream());
        writer.AutoFlush = true;
    }

    private void SendMessageToServer(string message)
    {
        writer.WriteLine(message);
        Debug.Log("[Client] Message sent to server!");
    }
}

Where instead of creating a new StreamWriter everytime I send a message, I create it on connection to the server and use that. Howver, this causes a crash within Unity and I dont know why.
I changed it because with the setup posted in my previous message I get the error:
Cannot write to stream

ArgumentException: Can not write to stream
System.IO.StreamWriter…ctor (System.IO.Stream stream, System.Text.Encoding encoding, Int32 bufferSize) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.IO/StreamWriter.cs:95)
System.IO.StreamWriter…ctor (System.IO.Stream stream)
(wrapper remoting-invoke-with-check) System.IO.StreamWriter:.ctor (System.IO.Stream)
TarnationClient.SendMessageToServer (System.String message) (at Assets/TarnationClient.cs:25)
TarnationClient.ConnectToServer () (at Assets/TarnationClient.cs:20)
UnityEngine.Events.InvokableCall.Invoke (System.Object[ ] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:154)
UnityEngine.Events.InvokableCallList.Invoke (System.Object[ ] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:637)
UnityEngine.Events.UnityEventBase.Invoke (System.Object[ ] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:773)
UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:52)
UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:36)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:45)
UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:50)
UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:261)
UnityEngine.EventSystems.EventSystem:Update()

All or any help would be greatly appreciated,
Thanks.

This is an awful lot of boilerplate code just to send networked messages. Is there any reason you’re not using Unity’s built-in networking library?

Purely lack of experience, I knew it existed just didnt go much further. I will mess about with that too.
I assume these 2 docs are what I need:
https://docs.unity3d.com/ScriptReference/Networking.NetworkManager.html
https://docs.unity3d.com/ScriptReference/Network.html

And here: https://docs.unity3d.com/Manual/UNetOverview.html

UNet is good, too, of course. Your project sounded like it needed just simple requirements. Maybe I should have pointed you to that, instead. I like the other way… maybe I should have listed both options. :slight_smile:
In any event, it’s not so much code to write the simple program you’re describing, so I don’t see it as a big deal…
Did you get it working (with either)?

I got it mostly working with both actually.
Just tinkering with the UNet one.
This is what I have for UNet at the moment.
Host:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.NetworkSystem;

#pragma warning disable 0414
public class NetworkManager : MonoBehaviour
{
    [SerializeField] private string serverIp = "127.0.0.1";
    [SerializeField] private int serverPort = 2345;

    Dictionary<string, int> playerScores = new Dictionary<string, int>();

    private void StartServer()
    {
        NetworkServer.RegisterHandler(MsgType.Connect, OnClientConnected);
        NetworkServer.RegisterHandler(MsgType.Error, OnErrorOccured);
        NetworkServer.RegisterHandler(100, OnScoreMessage);

        NetworkServer.Listen(serverPort);
    }

    private void OnScoreMessage(NetworkMessage netMsg)
    {
        PlayerScore player = netMsg.ReadMessage<PlayerScore>();
        playerScores[player.username] = player.score;
    }

    private void OnErrorOccured(NetworkMessage netMsg)
    {
        ErrorMessage error = netMsg.ReadMessage<ErrorMessage>();
        Debug.Log("[Server] Error: " + error.errorCode);
    }

    private void OnClientConnected(NetworkMessage netMsg)
    {
        Debug.Log("[Server] New Client Connected!");
    }
}

Client:

using Tarnation.Shared.Managers;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.NetworkSystem;

public class NetworkClientPlayer : MonoBehaviour
{
    [SerializeField] private string serverIp = "127.0.0.1";
    [SerializeField] private int serverPort = 2345;

    NetworkClient client = new NetworkClient();

    public void ConnectToServer ()
    {
        client.RegisterHandler(MsgType.Connect, OnClientConnected);
        client.RegisterHandler(MsgType.Error, OnErrorOccured);

        client.Connect(serverIp, serverPort);
    }

    private void SendScoreToServer()
    {
        if (client.isConnected == false || StatManager.instance == null)
        {
            return;
        }

        PlayerScore player = new PlayerScore() {
            username = GameManager.instance.playfabUsername,
            score = StatManager.instance.score
        };

        client.Send(100, player);
    }

    private void OnErrorOccured(NetworkMessage netMsg)
    {
        ErrorMessage error = netMsg.ReadMessage<ErrorMessage>();
        Debug.Log("[Client] Error: " + error.errorCode);
    }

    private void OnClientConnected(NetworkMessage netMsg)
    {
        Debug.Log("[Client] Connected to Server!");
    }
}