Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/justinleemans/uni-networking

Event based networking solution for Unity
https://github.com/justinleemans/uni-networking

client event events game game-development message messages multiplayer networking server signal signals unity unity-networking unity3d-plugin upm upm-package

Last synced: 29 days ago
JSON representation

Event based networking solution for Unity

Awesome Lists containing this project

README

        

# UniNetworking - Event based networking solution for Unity

A custom networking solution with an event based messaging layer. UniNetworking offers a solution to easily establish connections and offers an easy to use and understand messaging layer leaving you in control of what data you want to send back and forth.

UniNetworking is capable of being used in many different configurations like dedicated server and client applications as well as a host client implementation where a client hosts the server in the same application.

Although UniNetworking is designed to work with Unity specifically it is totally independant and does not rely on any Unity specific methods. As such this package could also theoretically be implemented in any other .Net platform of your choice.

# Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Running a server](#running-a-server)
- [Connecting a client](#connecting-a-client)
- [Running the update loop](#running-the-update-loop)
- [Creating messages](#creating-messages)
- [Sending messages](#sending-messages)
- [Receiving messages](#receiving-messages)
- [Transports](#transports)
- [Tcp transport](#tcp-transport)
- [Creating a custom transport](#creating-a-custom-transport)
- [Logging](#logging)
- [Contributing](#contributing)

# Installation

Currently the best way to include this package in your project is through the unity package manager. Add the package using the git URL of this repo: https://github.com/justinleemans/uni-networking.git

# Quick Start

> [!NOTE]
> The quick start guide is not finished and will be updated as the project progresses.

This is a very lightweight networking package. As such a lot of the setup is made easy for you. However the actual architecture of your networking implementation is up to you.

This package can be used to create a client-host setup where one of the clients will host the server on their machine from within the game. But this package could also be used to create dedicated client and server applications.

## Running a server

To run a server you simply first have to create a server instance. When creating a server instance you have the option to choose a transport layer by passing a transport layer instance in the constructor. Currently the default transport layer is TCP. For more info on transports take a look at [transports](#transports).

```c#
IServer server = new Server();
IServer server = new Server(new TcpServerTransport());
```

Once you have your server instance you can start the server. Simply call the method `Start()`. Before starting your server remember to set the correct connection details on your transport. For more info see [transports](#transports).

If you want to stop the server again simply call `Stop()`.

```c#
server.Start();
server.Stop();
```

The server also has two events that can be subscribed to for when a client either connects or disconnects to/from the server.

```c#
server.ClientConnected += OnClientConnected;
server.ClientDisconnected += OnClientDisconnected;
```

Both these events take a delegate with an integer as parameter which represents the connection id that has connected/disconnected.

Finally the server class comes with a method that can be used to close connections remotely, in other words kick the player. This can be done by calling the `CloseConnection(connectionId)` method with the id of the connection you want to close.

```c#
server.CloseConnection(connectionId);
```

## Connecting a client

To connect a client to a server you will first need a client instance. This is practically the same as for the server.

```c#
IClient client = new Client();
IClient client = new Client(new TcpClientTransport());
```

Once you have your instance you can start connecting to a server. For this you can call the method `Connect()`. Before connecting the client remember to set the correct connection details on your transport. For more info see [transports](#transports).

If you want to disconnect your client you can call `Disconnect()`.

```c#
client.Connect();
client.Disconnect();
```

The client also has an event which can be subscribed to for when this client gets disconnected either by disconnecting themself or getting disconnected by server.

```c#
client.ClientDisconnected += OnClientDisconnected;
```

## Running the update loop

To make sure your peer is receiving all communications and managing all connections you have to consistently update the peer by calling the `Tick()` method. This goes for both server and client. It is recommended to call this method from the `FixedUpdate()` method on a MonoBehaviour or through a similar approach. This is because you don't want you communications to be framerate dependant.

## Creating messages

To create a message we make a new class which inherits from the abstract class `Message`, this class is used for all message instances you will be sending and receiving. This class is also where you will define all fields/properties that you want to pass through.

Furthermore all message classes need to include the `Message` attribute above the class with a unique id(int) to identify the message.

```c#
[Message(1)]
public class ExampleMessage : Message
{
}
```

> [!TIP]
> Define all message ids as constants in a class to more easily keep track of what ids are used.

Just like with the signals library you have the option to override the method `OnClear()` which will be called whenever the message class get released back to the message pool. In this library it is extra important to override this method because you don't want to accidentally send values that were set in a different message call.

```c#
public override void OnClear()
{
Foo = default;
}
```

Besides the `OnClear()` method two other overridable methods have been added. `OnSerialize(IWriteablePayload payload)` and `OnDeserialize(IReadablePayload payload)`. These methods are used to read/write the values of fields and properties to/from the payload.

```c#
public override void OnSerialize(IWriteablePayload payload)
{
payload.WriteBool(boolVariable);
payload.WriteFloat(floatVariable);
payload.WriteInt(intVariable);
payload.WriteString(stringVariable);
}
```

```c#
public override void OnDeserialize(IReadablePayload payload)
{
boolVariable = payload.ReadBool();
floatVariable = payload.ReadFloat();
intVariable = payload.ReadInt();
stringVariable = payload.ReadString();
}
```

## Sending messages

To send a message over the network you can either get a message and populate any fields or properties you have on your message class by getting a message with `GetMessage()` and sending it with `SendMessage(message)`.

These methods are available on either your server or client instance depending on which side is sending the message. Keep in mind that when a client sends a message it is the server that will receive it and if server is the one sending a message, all connected clients will receive it.

```c#
var message = client.GetMessage();
message.Foo = "bar";
client.SendMessage(message);
```

Or send it directly with `SendMessage()` without populating fields or properties.

```c#
client.SendMessage();
```

> [!WARNING]
> It is recommended to use the included `GetMessage()` method to retrieve a message instance to avoid filling up the pool and never retrieving from it.

In the case of the server you get an extra set of methods so you can send to specific connections. These methods are the same as the other methods but with an extra parameter.

```c#
server.SendMessage(connectionId);
server.SendMessage(message, connectionId);
```

## Receiving messages

You can subscribe to a message using either the server or client instance and calling `Subscribe(OnMessage)` where the handler is a delegate with a message instance of the given message type as parameter.

```c#
client.Subscribe(OnMessage);
```

```c#
private void OnExampleMessage(ExampleMessage message)
{
}
```

To unsubscribe from a message you make a call similar to subscribing by calling `Unsubscribe(OnMessage)` with the same method as you used to subscribe earlier.

```c#
client.Unsubscribe(OnMessage);
```

Same as with sending message, the server class has a set of extra methods that allow you to see which connection has sent a message. This simply changes the delegate to include a connection id.

```c#
private void OnExampleMessage(ExampleMessage message, int connectionId)
{
}
```

# Transports

The currently implemented and available transports are:
- [Tcp transport](#tcp-transport) (default)

## Tcp transport

The tcp transport uses a tcp protocol for connecting and sending data across a network.

These connection details are part of the transport and can be set through properties when initializing or before starting/connection the peer.

```c#
IServerTransport transport = new TcpServerTransport()
{
Port = 7777,
MaxConnections = 10,
}

transport.Port = 7777;
transport.MaxConnections = 10;
```

```c#
IClientTransport transport = new TcpClientTransport()
{
IpAddress = "127.0.0.1",
Port = 7777,
}

transport.IpAddress = "127.0.0.1";
transport.Port = 7777;
```

## Creating a custom transport

If you want to implement your own transport there is a few things you will have to do. You can take a look at the included [Tcp transport](https://github.com/justinleemans/networking/tree/main/Runtime/Transports/Tcp) as an example.

You will have to create a class implementing the `IServerTransport` interface for the server implementation. This will require you to implement the following events, properties and/or methods.
- `Action ClientConnected` which is an event that should be called when a new connection is made. Should return an integer which represents the id of the connection.
- `Action ClientDisconnected` which is an event that should be called when a connection is closed. Should return the id of the connection that has been closed.
- `bool IsRunning` which is a property that returns if the server is currently running.
- `IReadOnlyCollection ConnectionIds` which is a read only collection of all connection ids that are currently in use on the server.
- `void Start()` which is to start the server.
- `void Stop()` which is to stop the server.
- `void CloseConnection(int connectionId)` which is a method that can be used to close a connection.
- `void Tick()` this method is the update loop for your transport, you will use this for checking wether you are able to receive a message.
- `void Send(Payload payload)` which takes an instance of payload to send to all connections.
- `void Send(Payload payload, int connectionId)` which is the same method as before but send the payload to a specified connection.
- `void Receive(Action onMessageReceived)` which is called to check if a message can be received. Takes a callback method that should be called if a message has been received.

Next you will have to create a class implementing the `IClientTransport` interface. This will require you to implement these events, properties and/or methods.
- `Action ClientDisconnected` which is an event that should be raised when this client has been disconnected.
- `bool IsConnected` which is a property which returns whether the client is currently connected to a server.
- `void Connect()` which is used to connect this client to a server.
- `void Disconnect()` which is to disconnect this client from the server.
- `void Tick()` this method is the update loop for your transport, you will use this for checking wether you are able to receive a message.
- `void Send(Payload payload)` which takes an instance of payload to send to the server.
- `void Receive(Action onMessageReceived)` which is called to check if a message can be received. Takes a callback method that should be called if a mesage has been received.

# Logging

UniNetworking comes with a custom logger. This is one of the additions made to keep this system independant from whatever platform it is used on.

To enable logging simply register your preferred log methods by calling the `SetLogMethod()` method on the `NetworkLogger` class. This method takes two arguments. First an enum of type `LogLevel` which determines at which method should be used for which severity. And also a method that takes a string argument for the actual log method. The available levels are `Log`, `Warning` and `Error` and ideally should all be provided their own methods.

```c#
NetworkLogger.SetLogMethod(LogLevel.Log, Debug.Log);
```

If you need to disable logging for some reason than you can do that by toggling the `IsEnabled` property on the `NetworkLogger` class.

# Contributing

Currently I have no set way for people to contribute to this project. If you have any suggestions regarding improving on this project you can make a ticket on the GitHub repository or contact me directly.