Easy Unity Network (EuNet) is a network solution for multiplayer games.
Supports Server-Client, Peer to Peer communication using TCP, UDP, and RUDP protocols.
In the case of P2P (Peer to Peer), supports hole punching and tries to communicate directly as much as possible, and if it is impossible, automatically relayed through the server.
Great for developing Action MORPG, MOBA, Channel Based MMORPG, Casual Multiplayer Game (e.g. League of Legends, Among Us, Kart Rider, Diablo, etc.).
Produced based on .Net Standard 2.0, multiplatform supported(Windows, Linux, Android, iOS, etc.), and is optimized for .Net Core-based servers and Unity3D-based clients.
RPC(Remote procedure call) can be used to call remote functions and receive return values.
There is no overhead as it serializes at high speed and calls remote functions.
Work efficiency increases as there is no work to create a message every time.
EuNet-Starter | EuNet-Tanks |
---|---|
https://github.com/zestylife/EuNet-Starter | https://github.com/zestylife/EuNet-Tanks |
Google Play | |
Channels | Transmission guarantee | Not duplicate | Order guarantee |
---|---|---|---|
TCP | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
Unreliable UDP | :x: | :x: | :x: |
Reliable Ordered UDP | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
Reliable Unordered UDP | :heavy_check_mark: | :heavy_check_mark: | :x: |
Reliable Sequenced UDP | :heavy_check_mark:(Last order) | :heavy_check_mark: | :heavy_check_mark: |
Sequenced UDP | :x: | :heavy_check_mark: | :heavy_check_mark: |
We need three projects
See EuNet-Starter for an example
PM> Install-Package EuNet.CodeGenerator.Templates
CodeGen/EuNet.Rpc.CodeGen.cs
file was created.PM> Install-Package EuNet
Solution Explorer -> [User Project] -> References -> Add Reference -> [Add Common project]
After Unity 2019.3.4f1, Unity 2020.1a21, that support path query parameter of git package. You can add package from UPM (Unity Package Manager)
https://github.com/zestylife/EuNet.git?path=src/EuNet.Unity/Assets/Plugins/EuNet
If you want to add a specific release version, add #version
after the url. ex) version 1.1.13
https://github.com/zestylife/EuNet.git?path=src/EuNet.Unity/Assets/Plugins/EuNet#1.1.13
RPC(Remote procedure call) can be used to call remote functions and receive return values.
There is no overhead as it serializes at high speed and calls remote functions.
Work efficiency increases as there is no work to create a message every time.
// Declaring login rpc interface
public interface ILoginRpc : IRpc
{
Task<int> Login(string id, ISession session);
Task<UserInfo> GetUserInfo();
}
// Generate Rpc code using EuNetCodeGenerator and use it in server and client
// User session class inherits Rpc Interface (ILoginRpc)
public partial class UserSession : ILoginRpc
{
private UserInfo _userInfo = new UserInfo();
// Implement Rpc Method that client calls
public Task<int> Login(string id, ISession session)
{
if (id == "AuthedId")
return Task<int>.FromResult(0);
return Task<int>.FromResult(1);
}
// Implement Rpc Method that client calls
public Task<UserInfo> GetUserInfo()
{
// Set user information
_userInfo.Name = "abc";
return Task<UserInfo>.FromResult(_userInfo);
}
}
private async UniTaskVoid ConnectAsync()
{
var client = NetClientGlobal.Instance.Client;
// Trying to connect. Timeout is 10 seconds.
var result = await client.ConnectAsync(TimeSpan.FromSeconds(10));
if(result == true)
{
// Create an object for calling login Rpc
LoginRpc loginRpc = new LoginRpc(client);
// Call the server's login function (UserSession.Login)
var loginResult = await loginRpc.Login("AuthedId", null);
Debug.Log($"Login Result : {loginResult}");
if (loginResult != 0)
return;
// Call the server's get user information function (UserSession.GetUserInfo)
var userInfo = await loginRpc.GetUserInfo();
Debug.Log($"UserName : {userInfo.Name}");
// UserName : abc
}
else
{
// Fail to connect
Debug.LogError("Fail to connect server");
}
}
Object serialization is required to use Rpc. There are two ways to serialize objects.
Declaring the NetDataObject
Attribute makes the class serializable.
All public objects are serialized.
Declaring the [IgnoreMember]
Attribute does not serialize it.
[NetDataObject]
public class DataClass
{
// Serializable
public int Int;
// Serializable
public int Property { get; set; }
// Ignore
public int PropertyOnlyGet { get; }
// Ignore
private int IntPrivate;
// Ignore
protected int IntProtected;
// Ignore
[IgnoreMember]
public int IgnoreInt;
// Ignore
[IgnoreMember]
public int IgnoreProperty { get; set; }
}
Implement serialization manually by inheriting INetSerializable
.
You have to code, but it's the fastest and most flexible.
public class InterfaceSerializeClass : INetSerializable
{
public int Value;
public string Name;
public void Serialize(NetDataWriter writer)
{
writer.Write(Value);
writer.Write(Name);
}
public void Deserialize(NetDataReader reader)
{
Value = reader.ReadInt32();
Name = reader.ReadString();
}
}
GameClient.cs
file
using Common.Resolvers;
using EuNet.Core;
using EuNet.Unity;
using System.Threading.Tasks;
public class GameClient : Singleton
public NetClientP2p Client => _client.ClientP2p;
protected override void Awake()
{
base.Awake();
_client = GetComponent<NetClientP2pBehaviour>();
Client.OnConnected = OnConnected;
Client.OnClosed = OnClosed;
Client.OnReceived = OnReceive;
// Register automatically generated resolver.
//CustomResolver.Register(GeneratedResolver.Instance);
// If you generated RpcService, register it.
//Client.AddRpcService(new GameScRpcService());
}
public Task<bool> ConnectAsync()
{
// Try to connect server. All functions can be accessed with Client instance
return Client.ConnectAsync(TimeSpan.FromSeconds(10));
}
private void OnConnected()
{
// Connected
}
private void OnClosed()
{
// Disconnected
private Task OnReceive(NetDataReader reader)
{
// Received data. No need to use when using RPC
return Task.CompletedTask;
}
}
### How to use Rpc
Rpc is service that can call remote procedures.
EuNet's Rpc is a function call service between the server and the client.
When you declare an interface that inherits the IRpc interface, calls and service codes are automatically generated.
* Create Rpc Interface in `Common` project.
* Build project.
```csharp
using EuNet.Rpc;
using System.Threading.Tasks;
namespace Common
{
// Inherit IRpc for Rpc
public interface IGameCsRpc : IRpc
{
// Login Rpc
Task<int> Login(string id);
}
}
Server
project, register RpcService when creating a server ._server.AddRpcService(new GameCsRpcServiceSession());
In the Server
project, UserSession
class inherits from IGameCsRpc
.
public partial class UserSession : IGameCsRpc
{
public Task<int> Login(string id)
{
return Task.FromResult(0);
}
}
In the Client
project, call Rpc.
// Rpc callable object
var rpc = new GameCsRpc(_client.Client, null, TimeSpan.FromSeconds(10));
// Call Rpc Login
var loginResult = await rpc.Login("MyId");
Debug.Log(loginResult);
ViewRpc is a technology that makes peer-to-peer communication between NetView Components as Rpc.
By adding a NetView Component to the GameObject, you can call functions of the same NetView Component (same ViewId) that exist on different clients.
For example, if you shoot a cannon from a red tank, the other user's red tank will also fire.
1:1 or 1:N call is possible, and in case of 1:N, return value can not be received.
Some platforms do not allow runtime code generation. Therefore, any managed code which depends upon just-in-time (JIT) compilation on the target device will fail. Instead, you need to compile all of the managed code ahead-of-time (AOT). Often, this distinction doesn’t matter, but in a few specific cases, AOT platforms require additional consideration.
See more
https://docs.unity3d.com/2019.4/Documentation/Manual/ScriptingRestrictions.html
There is a problem when serializing generic objects as AOT cannot generate code So, you need to provide a hint so that AOT can generate the code.
Class for serialize (In Common
project)
[NetDataObject]
public class DataClass
{
public Tuple<int,string> TupleData;
public Dictionary<int,string> DictionaryData;
}
Hint function (In Client
unity project)
private void UsedOnlyForAOTCodeGeneration()
{
// Hints for using <int,string> in TupleFormatter<T,T>
new TupleFormatter<int, string>();
// Hints for using <int,string> in DictionaryFormatter<T,T>
new DictionaryFormatter<int, string>();
// Exception!
throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
}