UNET LLAPI guide

This will be a short usage guide on LLAPI and using the MessageBase for sendign data. Assuming that you already have prior knowledge on how to start a connection and send data. If you don’t then please refer to unity manual.

Let’s start with making a NetworkManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
using System;
using UnityEngine;
using UnityEngine.Networking;

namespace Networking
{
public class Manager : MonoBehaviour
{
private int hostId = -1;

protected int reliable;
protected int unreliable;
protected HostTopology topology;
protected ConnectionConfig config;

private NetworkWriter writer = new NetworkWriter();
private NetworkReader reader = new NetworkReader();
private byte[] buffer = new byte[1024];

public virtual void Awake()
{
DontDestroyOnLoad(gameObject);
Init();
}

private void Init()
{
NetworkTransport.Init();
config = new ConnectionConfig();
reliable = config.AddChannel(QosType.ReliableSequenced);
unreliable = config.AddChannel(QosType.Unreliable);
topology = new HostTopology(config, 10);
}

protected virtual void Update()
{
if (!NetworkTransport.IsStarted) return;
if (hostId == -1) return;
while (ReceiveEvents()) ;
}

public void StartServer(int port)
{
hostId = NetworkTransport.AddHost(topology, port);
}

public void StartClient(int port)
{
StartClient("127.0.0.1", port, port + 1);
}

public void StartClient(string address, int port)
{
StartClient(address, port, port);
}

public void StartClient(string address, int port, int localPort)
{
hostId = NetworkTransport.AddHost(topology, localPort, null);

byte error;
NetworkTransport.Connect(hostId, address, port, 0, out error);
}

public void StartHost(int port)
{
StartClient("127.0.0.1", port);
}


private bool ReceiveEvents()
{
byte error;
int incConnectionId, incChannelId, incReceivedSize, incHostId;

var networkEvent = NetworkTransport.ReceiveFromHost(hostId, out incConnectionId, out incChannelId, buffer,
(ushort)buffer.Length, out incReceivedSize, out error);

if (error != 0)
{
Debug.LogError("Error: " + error);
// you should handle errors here for example
// player timing out is considered an error code 6
if(error == 6)
{
OnPlayerDisconnected(incConnectionId);
}
return false;
}

switch (networkEvent)
{
case NetworkEventType.Nothing:
return false;
case NetworkEventType.ConnectEvent:
OnPlayerConnected(incConnectionId);
break;
case NetworkEventType.DataEvent:
reader = new NetworkReader(buffer);
ushort size = reader.ReadUInt16();
short type = reader.ReadInt16();
OnDataReceived(incConnectionId, reader, size, type);
break;
case NetworkEventType.DisconnectEvent:
OnPlayerDisconnected(incConnectionId);
break;
case NetworkEventType.BroadcastEvent:
// TODO
break;
default:
throw new ArgumentOutOfRangeException();
}
return true;
}

protected virtual void OnPlayerConnected(int id)
{
}

protected virtual void OnPlayerDisconnected(int id)
{
}

protected virtual void OnDataReceived(int id, NetworkReader reader, ushort size, short type)
{
}

protected void SendNetworkMessage(int id, int channel, MessageBase message)
{
byte error;
Debug.Log(string.Format("Sending message {0} to connection {1}", message.GetType().Name, id));
message.Serialize(writer);
NetworkTransport.Send(hostId, id, channel, writer.AsArray(), writer.Position, out error);
}

private void OnDestroy()
{
NetworkTransport.Shutdown();
}
}
}

Setting hostId to -1 so we could know when we are not using the transport. You could use 0 as well because first connection id is 1. After this you want to receive events from transport layer. We put all of them in a switch. I left out BroadcastEvent as this is for searching for active hosts on LAN. We place virtual methods to get called on connect/disconnect and to shutdown transport when the manager is destroyed.

NetworkTransport.AddHost – will open port for incoming and outgoing communications
NetworkTransport.Connect – connects to a remote open host

You cannot reuse the same port on different connections on the same machine. Once it is occupied you have to use a different one. When starting local client and server you need to open one port for server communications and then a different one for client communication but direct that to the servers port.

A good starting point on organizing your data sent is extending MessageBase.
It as good practice to name all messages with a prefix of CS or SC if they are to be used only one way. In my own project I would treat these messages when sent from opposite side than expected as a request for info. If SC is received on server then we can send that message to client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using Networking;
using UnityEngine.Networking;

namespace MyNetworking
{
public class MessageTypes
{
public const short CSHello = MsgType.Highest + 1;
public const short SCGoodbye = MsgType.Highest + 2;
}

public class CSHello : MessageBase
{
public string greeting = "Hello!";

public override void Deserialize(NetworkReader reader)
{
greeting = reader.ReadString();
}

public override void Serialize(NetworkWriter writer)
{
writer.StartMessage(MessageTypes.CSHello);
writer.Write(greeting);
writer.FinishMessage();
}
}

public class SCGoodbye : MessageBase
{
public SCGoodbye()
{

}

public SCGoodbye(string greeting)
{
this.greeting = greeting;
}

public string greeting = "Goodbye!";

public override void Deserialize(NetworkReader reader)
{
greeting = reader.ReadString();
}

public override void Serialize(NetworkWriter writer)
{
writer.StartMessage(MessageTypes.SCGoodbye);
writer.Write(greeting);
writer.FinishMessage();
}
}
}

We define the message id as a short and then extend MessageBase. You define some variable you want to send and Serialize/Deserialize methods. For Serialize you should always StartMessage and FinishMessage. The following will add message id and size to the NetworkWriter. As for Deserialize it is enough to just read all input.

Let’s now reuse our Manager and set up connection between client and server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Networking;
using UnityEngine.Networking;

namespace MyNetworking
{
public class Server : Networking.Manager
{
public string description = "Empty server name";
public int port = Info.port;

public void SetupServerInfo(string description, int port)
{
this.description = description;
this.port = port;
}

protected override void OnDataReceived(int id, NetworkReader reader, ushort size, short type)
{
switch (type)
{
case MessageTypes.CSHello:
CSHello hello = new CSHello();
hello.Deserialize(reader);
OnHello(id, hello);
break;
case MessageTypes.SCGoodbye:
break;
}
}

public void OnHello(int id, CSHello hello)
{
Debug.Log("Received Hello message from client saying: " + hello.greeting);
SCGoodbye bye = new SCGoodbye("It is all about Transport layer!!!");
SendNetworkMessage(id, reliable, bye);
}
}
}

When server receives a message with id of CSHello it will then deserialize it and send a new SCGoodbye to client.

Here is client part:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Networking;
using UnityEngine.Networking;


namespace MyNetworking
{
public class Client : Networking.Manager
{
protected override void OnDataReceived(int id, NetworkReader reader, ushort size, short type)
{
switch (type)
{
case MessageTypes.SCGoodbye:
SCGoodbye bye = new SCGoodbye();
bye.Deserialize(reader);
OnGoodbye(bye);
break;
}
}

public void OnGoodbye(SCGoodbye bye)
{
Debug.Log("Received from server: " + bye.greeting);
}

protected override void OnPlayerConnected(int id)
{
Debug.Log("Client: successfully connected...");
CSHello hello = new CSHello();
hello.greeting = "I love UNET HLAPI!";
SendNetworkMessage(id, reliable, hello);
}
}
}

When we establish connection to server it sends a CSHello in OnPlayerConnected and after receiving SCGoodbye from server we will log it to console.

Test it out!

In your scene create new gameobject and attach Server and Client components. Set up two UI Buttons and to one add StartServer() from Server component and from Client StartClient. In the string field write a port number (both should be matching).

Now this is by no means a very good implementation. But for anyone starting out it should do good enough as a starting point on how to organize the code and use MessageBase.