LiteNetLib - TextGuySemicolon/VPPMultiplayer GitHub Wiki
Lite reliable UDP library for .NET Framework 3.5, Mono, .NET Core 2.0, .NET Standard 2.0. more information
NetManager is the main class for all network operations. Can be used as client and/or server.
You may want to create separate scene to run different NetManager class for client/server. They're gonna have different operations for the network events, to handle communications between server/client.
To start a NetManager on client/server.
NetManager netManager = new NetManager(netEventListener);
netManager.Start();
A NetManager is able to connect to any other NetManager using Connect functions, a "IPEndPoint/address" and "port" is required, you may attach a "key" or "connectionData" as additional data to do other operations on connecting. (ie, private room, etc..)
//NetManager.cs
public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData)
public NetPeer Connect(IPEndPoint target, string key)
public NetPeer Connect(string address, int port, NetDataWriter connectionData)
public NetPeer Connect(string address, int port, string key)
Example: On the dedicated server room, only the clients is required to connect to server, and all clients within a room is connected to a same server. (Star Topology)
One of the most important part about NetManager are its network callback events, it's the main way for us to process network data and handle important network operations like "OnPeerConnected" or "OnNetworkReceive".
public interface INetEventListener
{
void OnPeerConnected(NetPeer peer);
void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo);
void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod);
void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType);
void OnNetworkLatencyUpdate(NetPeer peer, int latency);
void OnConnectionRequest(ConnectionRequest request);
}
when we construct a NetManager, we construct it using a "INetEventListener" class, we can easily create network event listeners with INetEventListener. A simple way to create INetEventListener is to simply implement the INetEventListener interface on the server/client class.
and start the NetManager with "this" keyword.
//YourClient.cs or YourServer.cs
netManager.Start(this);
it's okay to leave some of the functions in INetEventListener blank as they're all callbacks. However, to get a better control and readability of code. We can use a EventBasedNetListener that contains "event" for each network callback events. and you can simple subscribe your callback functions to it.
public void Start()
{
EventBasedNetListener netEventListener = new EventBasedNetListener();
netEventListener.PeerConnectedEvent += OnPeerConnected; //your callback function
NetManager netManager = new NetManager(netEventListener);
}
in the space of network, all data transmission is packed as a network packet, in LiteNetLib, a packet is constructed with NetDataWriter when sending, and NetDataReader when receiving.
- NetDataWriter: used to write/serialize data when sending packet
- NetDataReader: used to read/deserialize data from received packet
when sending packet, the packet is send using NetDataWriter. when receiving packet, the packet is received using NetDataReader. We can also send/receive unique data type or class/struct using NetPacketProcessor. [read more about NetPacketProcessor] (https://revenantx.github.io/LiteNetLib/articles/netserializerusage.html)
When we want to write data into a "NetDataWriter", we can simply use the built-in Put functions to write common data to a "NetDataWriter". to know what data type is supported, check out their doc.
_writer = new NetDataWriter();
byte _data1;
float _data2;
int _data3;
_writer.Put(_data1);
_writer.Put(_data2);
_writer.Put(_data3);
When we read data from a "NetDataReader", we can use the built-in Get functions, that works similar to the Put functions.
//_reader is a "NetDataReader"
var _data1 = _reader.GetByte(); //byte
var _data2 = _reader.GetFloat(); //float
var _data3 = _reader.GetInt(); //int
one thing to note is that we want to call the "Get" functions in the exact same order of how we call the "Put" functions.
LiteNetLib also allowed us to put/get unique class on the "NetDataWriter" and "NetDataReader". However there's an extra step we need to deal with using "NetPacketProcessor".
When we want to write a class into a "NetDataWriter", we write the class using a "NetPacketProcessor".
NetDataWriter _write = new NetDataWriter();
NetPacketProcessor _processor = new NetPacketProcessor();
_processor.Write(_writer, _packet);
To read class from a "NetDataReader", we use the "NetPacketProcessor" to register a callback for the specific class.
first, we create a class that represent a packet.
class SamplePacket
{
//your data
}
in the NetManager script, we create a NetPacketProcessor and register a callback function for that "SamplePacket" class.
_processor = new NetPacketProcessor();
_processor.SubscribeReusable<SamplePacket, NetPeer>(OnSamplePacketReceived); //NetPeer is the parameter of the callback function.
then we can call the callback function when we received network packet.
public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
{
// Deserializes packet and calls the handler registered in constructor
_processor.ReadAllPackets(reader, peer); //this will call the registered callback function.
}
When we use a struct in "NetDataWriter" and "NetDataReader", we need to implement "INetSerializable" interface.
struct SampleStruct : INetSerializable
{
public int Value1;
public string Value2;
public void Serialize(NetDataWriter writer)
{
writer.Put(Value1);
writer.Put(Value2);
}
public void Deserialize(NetDataReader reader)
{
Value1 = reader.GetInt();
Value2 = reader.GetString();
}
}
when creating a struct that implements "INetSerializable", we have to manually implement the "Serialize()" and "Deserialize()" functions, to read or write a struct onto "NetDataReader" and "NetDataWriter", it has the same exact structure as how we read or write a class, only difference is we use a separate function for "Write" and "Subcribe" function.
To register:
_processor = new NetPacketProcessor();
//use "SubscribeNetSerializable" instead of "SubscribeReusable"
_processor.SubscribeNetSerializable<SamplePacket>(OnSamplePacketReceived);
then we can call the callback function exactly like how we did above.
public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
{
// Deserializes packet and calls the handler registered in constructor
_processor.ReadAllPackets(reader); //this will call the registered callback function.
}
To write:
SampleStruct _sample = new SampleStruct();
//use "WritePacketSerializable" instead of "Write"
_writer.WritePacketSerializable(_packet, _processor);
"NetPacketProcessor" doesn't support nested structs or classes, but you can register your own custom type processors. It is one of the main reason to use a "INetSerializable".
we just need to register the nested type of the class/struct, and the "NetPacketProcessor" will handle the corresponding class/struct used in any of your packet read using the "NetPacketProcessor".
netPacketProcessor = new NetPacketProcessor();
netPacketProcessor.RegisterNestedType<MyType>(); // Serialization handled automatically thanks to INetSerializable
for more information about "NetPacketProcessor", check out more about NetPacketProcessor.
there are many ways to send packet to a connected peer. (client/server) for more
NetPeer.Send(NetDataWriter writer, DeliveryMethod options) //send to a specific peer
NetPeer.SendToAll(NetDataWriter writer, DeliveryMethod options) //send to all connected peers
NetPeer.SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer) //to exclude a peer
and the "NetPeer" that we sent packet to will receive the data in the "OnNetworkReceive" network callback event.
void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType)
{
//the "NetPacketReader" reader will contain data received.
//do your logic here
}