Home
Clone this wiki locally
🔥 Warning: Unity's UNET has been deprecated, so I rewrote this asset to work with Mirror (which is better then UNET ever was): https://assetstore.unity.com/packages/tools/network/mirror-129321
Easy Unet/Mirror Packets
🔥 Warning: Unity's UNET has been deprecated, so I rewrote this asset to work with Mirror (which is better then UNET ever was): https://assetstore.unity.com/packages/tools/network/mirror-129321
What is this?
Easy Packets (EP) is an Unity asset which makes it more convenient to send and handle messages in your Unet HLAPI applications. Unet RPC's make everything ugly and complex, and standard messages lack a lot of convenient functionality, which is why I made this asset. I've been using it for quite a while, and I can't imagine working with Unet HLAPI without it.
This packet is not something BIG that you add to your games - it's something extremelly small, simple, and makes it a lot more enjoying to write networking code.
Why would you use it?
- Can be used as framework/base for building your networked applications
- Allows replying to messages directly (main reason why I wrote this)
- Messages can be created on the fly (written directly to writers)
- On the server, it is easy to see who sent a message (which peer) and modify it's state
- Uses same transportation method as Unet HLAPI messages
- It makes your code beautiful (check out the examples)
- It's small, robust and performant
How it works?
Under the hood, EP registers a custom binary message handler on client and server, and all of the EP packets are sent as standard Unet HLAPI messages.
Setup
🔥 Warning: Because asset was rewritten to support Mirror instead of UNET, you'll need to import mirror asset first https://assetstore.unity.com/packages/tools/network/mirror-129321
All you need to do before using EP is to setup the NetworkManager. This can be done by calling API methods from overriden NetworkManager methods.
You can see how it's done in SpaceApple/EasyPackets/Scripts/EpNetworkManager.cs file
API
All the code is located at SpaceApple.EasyPackets namespace
using SpaceApple.EasyPackets;Because both client and server code is written within the same application, for convenience, API is divided into two parts:
-
Ep.Client- should be used on the client. For example, if you want to send a message from client to server, useEp.Client.Send(), which would translate to something like "Easy Packets Client Sends a message to..." -
Ep.Server- used on the server.
Server API
- (event)
Ep.Server.Started- invoked when server starts - (event)
Ep.Server.ClientJoined- invoked when client joins the server - (event)
Ep.Server.ClientLeft- invoked when client leaves the server -
Ep.Server.IsServerRunning- true, if server is running -
Ep.Server.Peers- dictionary of peers, connected to server. Key - peer id -
Ep.Server.PeersByConnection- dictionary of peers, connected to server. Key - UNET/Mirror connection id -
Ep.Server.GetPeer((int)peerId)- retrieves a peer by peer id -
Ep.Server.GetPeer((NetworkConnection)) connection- retrieves a peer by UNET/Mirror connection -
Ep.Server.SetHandler(opCode, handler)- sets a handler for messages with given opCode -
Ep.Server.RemoveHandler(opCode, (optional)handler)- removes a handler. If handler is provided via parameters, it will only remove it if it's the exact same handler
Server Peers
Clients connected to the server are referred to as peers.
- (event)
peer.Disconnected- invoked, when peer disconnects from server -
peer.Connection-NetworkConnectionof this peer -
peer.State- state object of the peer (you'll find examples below)-
peer.State.Set((string) key, (object) value)- saves an object in peer state -
peer.State.Get((string) key, (optional) (object) defaultvalue)- returns an object from peer state -
peer.State.Get<Type>((string) key, (optional) (object) defaultvalue)- returns an object from peer state, and casts it to a given type -
peer.State.Set(extension)- saves a state object without any key (stored byType) -
peer.State.Get<Type>()- returns a state object of the specific type (must be first set by the above method) -
peer.State.Has<Type>- checks if state object is saved by type -
peer.State.Has(string key)- checks if state object of a given key is saved
-
-
peer.Send(opCode, ...)- sends a message from server to this peer.
Client API
- (event)
Ep.Client.Connected- invoked on client, when it connects to server - (event)
Ep.Client.Disconnected- invoked on client, when it disconnects from server -
Ep.Client.Send(opCode, ...)- sends a message to server (look bellow for explanations and examples) -
Ep.Client.SetHandler(opCode, handler)- sets a handler for messages with given opCode -
Ep.Client.RemoveHandler(opCode, (optional) handler)- removes a handler. If handler is provided via parameters, it will only remove it if it's the exact same handler -
Ep.Client.DoWhenConnected(callback)- Invokes a callback when connection to server is established, or instantly, if already connected
Messaging API
Messaging API is the core of the EasyPackets asset - through it, you can easily send messages, handle and respond to them.
Send Messages
- opCode - any number, by which we can choose what kind of message to handle
- responseHandler - method/function, called when a message is received
From client to server
// ------------------------------------------------------
// Without requesting a response
Ep.Client.Send((short) opCode, writeMethod);
Ep.Client.Send((short) opCode, writeMethod, channel);
Ep.Client.Send((short) opCode, INetSerializable);
Ep.Client.Send((short) opCode, INetSerializable, channel);
// Example
Ep.Client.Send(10, w => {w.Write(10);});
// ------------------------------------------------------
// With response
Ep.Client.Send((short) opCode, writeMethod, responseHandler);
Ep.Client.Send((short) opCode, writeMethod, responseHandler , channel);
Ep.Client.Send((short) opCode, INetSerializable, responseHandler);
Ep.Client.Send((short) opCode, INetSerializable, responseHandler, channel);
// Example
Ep.Client.Send(10, w => {}, response => {});From server to client
Messages from server to client are sent directly to connected client (peer)
// ------------------------------------------------------
// Without requesting a response
peer.Send((short) opCode, writeMethod);
peer.Send((short) opCode, writeMethod, channel);
peer.Send((short) opCode, INetSerializable);
peer.Send((short) opCode, INetSerializable, channel);
// Example
peer.Send(10, w => {w.Write(10);});
// ------------------------------------------------------
// With response
peer.Send((short) opCode, writeMethod, responseHandler);
peer.Send((short) opCode, writeMethod, responseHandler , channel);
peer.Send((short) opCode, INetSerializable, responseHandler);
peer.Send((short) opCode, INetSerializable, responseHandler, channel);
// Example
peer.Send(10, w => {}, response => {});Handle Messages
- opCode - any number, by which we can choose what kind of message to handle
- handler - method/function, called when a message is received
On Client
// Set handler
Ep.Client.SetHandler(opCode, handler)
// Example
Ep.Client.SetHandler(0, (message) => {});On Server
// Set handler
Ep.Server.SetHandler(opCode, handler)
// Example
Ep.Server.SetHandler(0, (message) => {});API Examples
Sending and handling messages
From Client to Server
Sending a simple message
// Send a regular message from client to server
// (there are more overloaded methods for Send. Make sure to check them out)
Ep.Client.Send(0, writer => writer.Write("Text message"));
// Handle the message on server
Ep.Server.SetHandler(0, message =>
{
// This is called when server receives a message
var text = message.Reader.ReadString();
Debug.Log("Message received: " + text);
});Sending a message which expects a response
// 1. Send the message
Ep.Client.Send(1, writer => writer.Write("Text"), response =>
{
// 3. Response received
Debug.Log("Response received: " + response.Status + " " + response.ToString());
});
// Register handler
Ep.Server.SetHandler(1, message =>
{
// 2. Receive the message and respond
message.Respond(ResponseStatus.Success, "Response text");
});From Server To Client
There can be many players connected to a server, so you'll need to send messages to them directly. Each player is represented by EpPeer instance, which has necessary Send methods to send messages to them.
// --------------------------------------------------------
// Listen to event
Ep.Server.ClientJoined += peer =>
{
// A new client has joined, so we can send it a message
peer.Send(0, writer => writer
.Write("Username") // You can pack as many different values as you need
.Write(Vector3.up) // Like vectors
.Write(10)); // Or numbers
};
// --------------------------------------------------------
// Handle message on the client
Ep.Client.SetHandler(0, message =>
{
// Message received, let's handle it and read all the data
var username = message.Reader.ReadString();
var position = message.Reader.ReadVector3();
var health = message.Reader.ReadInt32();
});Typesafe messages / packets
In case you don't like to push stuff to network readers/writers, and if you want more type-safety, you can send instances of classes that implement INetSerializable interface.
Here's an example of implementation
using SpaceApple.NetworkingCore;
public class RegisterPacket : INetSerializable
{
public string Username;
public string Password;
public void Serialize(NetWriter writer)
{
writer.Write(Username);
writer.Write(Password);
}
public void Deserialize(NetReader reader)
{
Username = reader.ReadString();
Password = reader.ReadString();
}
}And you would send a message like this:
// Create the message
var registerPacket = new RegisterPacket()
{
Username = "Username",
Password = "Password"
};
// Send a message from client
Ep.Client.Send(5, writer => writer.Write(registerPacket));
// Handle the message on server
Ep.Server.SetHandler(5, message =>
{
var packet = message.Reader.Deserialize<RegisterPacket>();
Debug.Log("User is trying to register: " + packet.Username);
});Peer State
When server receives all kinds of messages, sooner or later, you'll need to change or save the state of the peer - for example, when a player picks a username, it's good to be able to save it and use it when you want to spawn a client.
Simple string-key'ed property
// Handle a message from client
// We can imagine this to be "identification" request
Ep.Server.SetHandler(6, message =>
{
// Get the client who send the message
var peer = message.Peer;
// Read the username from the message
var username = message.Reader.ReadString();
// Save the username
peer.State.Set("username", username);
});
// Handle another message from client
Ep.Server.SetHandler(7, message =>
{
// Get the client who send the message
var peer = message.Peer;
// Get its username (if user has identified before)
var username = message.Peer.State.Get<string>("username");
});Or you can use an object as state
First, create a class which will represent the state
/// <summary>
/// Custom object for player state
/// </summary>
public class MyPlayerState
{
public string Username { get; set; }
public int SkinId { get; set; }
public Dictionary<string, string> OtherValues { get; set; }
public MyPlayerState()
{
OtherValues = new Dictionary<string, string>();
}
}Then just set the state
// For example, set the default state when cliet joins
Ep.Server.ClientJoined += peer =>
{
// Set the state
peer.State.Set(new MyPlayerState());
// Modify the state
peer.State.Get<MyPlayerState>().Username = "Guest";
};Example Game
EasyPackets usage example is located at SpaceApple/EasyPackets.Example
In this example, you will see how standard UNet/Mirror functionality is used together with EP
How to run it
- Open the scene
EasyPackets.Example - Run it
- Hit LAN Host(H)
- Click "Spawn" button
- (To connect more clients, make a build, run it and click "LAN Client(C)")
Explanation of what I've tried to demonstrate
I've added this example in case you didn't know how to use this asset in your own games.
When a player is spawned, he can collect coins. If he dies - he will not lose his coins, because they are saved on peer state (state of the connection).
Player can use this coins in the shop, to buy "Cubes". This is done by sending requests from client to server, to buy a specific cube.
These cubes are saved as SyncVar's, so they are lost when player dies.
If you want more specific examples - feel free to send me an e-mail ;)
Optimize Channels
In most cases, we don't care if our messages are sequenced - we only want them to be reliable, and this improves the performance.
By default, NetworkManager comes with 2 channels Reliable Sequenced and Unreliable, so we can add a new one, Reliable (please note newly created channel ID):
Change the default channel
Open SpaceApple/EasyPackets/Scripts/Ep.cs and set the channel to be used:
/// <summary>
/// Change this value, if you want messages to be sent to another channel by default
/// </summary>
public const int DefaultChannel = 2;
