Teleport is a real-time multiplayer framework for Unity.
- Open your project manifest file (
MyProject/Packages/manifest.json
). - Add
"com.debox.teleport": "https://github.com/debox-dev/Teleport.git"
to thedependencies
list. - Open or focus on Unity Editor to resolve packages.
- Unity 2019 or higher.
- Easy to set-up
- Unlimited client connections (As much as your network can handle)
- Up to 4 channels per Transport
- Multiple transports per project (If you need more than 4 channels)
- CRC checking
- Sequencing
- Retransmitting missing packets
- Packet buffering
- Built-in Server-Time synchronization to clients
- Client-side delayed interpolation
- Prefab spawning mechanism
- State synchronization mechanism
- Tests
Teleport allows us to test the client and server simultaneously in the same scene, we will use this ability to quickly set up a working network scene
- Open your project
- Add
"com.debox.teleport": "https://github.com/debox-dev/Teleport.git"
to thedependencies
list. of yourmanifest.json
, see "Installation instructions" for more information - Add an empty GameObject to your scene and name it "TeleportManager"
- Add the "TeleportManager" componend to the empty GameObject
- Choose a prefab you'd like to synchronize. Any prefab will do. There is no need to add anything to the prefab.
- Drag the prefab to the Prefab Spawners list of the
TeleportManager
component - Create a connection script
NetworkStarter.cs
(See example below) - Create a new empty GameObject in your scene and name it "NetworkStarter"
- Place the
NetworkStarter
component on the "NetworkStarter" GameObject - Drage your prefab to the
NetworkStarter
component in the inspector - Press play to see your prefab spawned for both server and client
// NetworkStarter.cs
using System.Collections;
using UnityEngine;
using DeBox.Teleport.Unity;
using DeBox.Teleport;
public class NetworkStarter : MonoBehaviour
{
[SerializeField] private GameObject _spawnedPrefab = null;
private IEnumerator Start()
{
yield return new WaitForSeconds(1);
TeleportManager.Main.StartServer();
TeleportManager.Main.ConnectClient();
Debug.Log("Waiting for client to authenticate...");
while (TeleportManager.Main.ClientState != TeleportClientProcessor.StateType.Connected)
{
yield return null;
}
Debug.Log("Client to authenticated!");
for (var i = 0; i < 10; i++)
{
var spawnPosition = Vector3.one * i;
TeleportManager.Main.ServerSideSpawn(_spawnedPrefab, spawnPosition);
}
}
}
Teleport automagically syncs the server time to the clients
Debug.Log("Actual server time is " + TeleportManager.Main.ServerSideTime);
Debug.Log("Predicted server time is " + TeleportManager.Main.ClientSideServerTime);
You can and should create your own data messages. Teleport supplies an API for common events in the lifetime of the message
Use the DeBox.Teleport.BaseTeleportMessage
class
Example
public class MyMessage : BaseTeleportMessage
{
public Vector3 Position { get; private set; }
public override byte MsgTypeId { get { return TeleportMsgTypeIds.Highest + 1; } }
// Deserialization constructor
public MyMessage() {}
// Actual constructor
public MyMessage(Vector3 position)
{
Position = position;
}
public override void Serialize(DeBox.Teleport.Core.TeleportWriter writer)
{
base.Serialize(writer);
// Write a compressed Vector3
writer.Write(Position, FloatCompressionTypeShort.Short_Two_Decimals);
}
public override void Deserialize(DeBox.Teleport.Core.TeleportReader reader)
{
base.Deserialize(reader);
// Read a compressed Vector3
Position = reader.ReadVector3(FloatCompressionTypeShort.Short_Two_Decimals);
}
public override void OnArrivalToClient()
{
base.OnArrivalToClient();
Debug.Log("The sent position is " + Position);
GameObject.FindObjectOfType<Actor>().transform.position = Position;
}
}
On the client
TeleportManager.Main.RegisterClientMessage<MyMessage>();
On the server
TeleportManager.Main.RegisterClientMessage<MyMessage>();
On both
TeleportManager.Main.RegisterTwoWayMessage<MyMessage>();
- PreSendServer - Called before the server sends the message, use this for setup of the message
- PreSendClient - Called beofer the client sends the message, use this for setup of the message
- PostSendServer - Called after the server sent the message, use this for sending follow up messages or any clean-up actions
- PostSendClient - Called after the client sent the message, use this for sending follow up messages or any clean-up actions
- OnArrivalToClient - Called as soon as the client receives the message, after it was deserialized
- OnArrivalToServer - Called as soon as the server receives the message, after it was deserialized
- To all clients
TeleportManager.Main.SendToAllClients(new MyMessage(Vector3.one));
- To a specific client
TeleportManager.Main.SendToClient(clientId, new MyMessage(Vector3.one));
- To multiple, specific clients
TeleportManager.Main.SendToClients(new MyMessage(Vector3.one), channelId: 0, clientIdsArray);
- To all clients, except specific clients
TeleportManager.Main.SendToAllExcept(new MyMessage(Vector3.one), channelId: 0, clientIdsArray);
TeleportManager.Main.SendToServer(new MyMessage(Vector3.one), channelId: 0);
The fixed packet prefix is a fixed value that always prepends the packet header This value is used by Teleport to identify where a packet may start in case of data consistency errors.
For example - If a packet arrived with a header error, there is no way to know where the next packet starts, so Teleport must search for the fixed prefix to understand where the next packet is. If it finds something that looks like a packet, but is now - we expect it to fail the CRC check and so Teleport will continue to search onward.
This is the channel id of the packet. Each channel may process the packet data differently so it is important for the system to know which channel should handle this packet
This is the CRC of the data. Teleport sums up the bytes of the data, modded by the number 15 (0b1111) - resulting in a 4 bit number.
The header of the packet is critical as it tells the system how long the packet is. If the packet header is damaged, Teleport may try to read an infinetly long packet. Because the data CRC depends on reading the entire packet, we CRC the header separately in order to quickly check if we can receive the data, or immediately dispose this packet.
The header CRC is the sum of the bytes of the following items
- Fixed packet prefix
- Channel Id
- Data CRC
- Data Length (With the Header CRC bits set to zero)
This is the length of the data. It is a ushort trimmed to 1.5 bytes (12 bit). This results in a maximum of 4096 bytes (4K) per packet. This is sufficient enougth and even if it is not, this can be resolved by breaking down data to smaller packets at the channel level (See AggregatingTeleportChannel, not yet tested)
This is the actual data of the packet. If the CRC mismatches we will dispose the data altogether
BYTE 1 +-+[] FIXED PACKET PREFIX (2 bits)
| []
|
| [] CHANNEL ID (2 bits)
| []
|
| [] DATA CRC (4 bits)
| []
| []
+-+[]
BYTE 2 +-+[] HEADER CRC (4 bits)
| []
| []
| []
|
| [] DATA LENGTH (12 bits)
| []
| []
+-+[]
BYTE 3 +-+[]
| []
| []
| []
| []
| []
| []
+-+[]
Teleport hands out a simple way to compress your floats, Vector2, Vector3, Vector4 and Quaternions
Use this to shorten 4 byte floats to 2 bytes
FloatCompressionTypeShort.Short_One_Decimals
converts a float to a short with one decimal.
Good for rough numbers that do not require too much accuracy, and can be large (4 digits.)
For example: 4.12551f => 41 => 4.1
Minimum Value: -3276.8 Maximum Value: 3276.7
FloatCompressionTypeShort.Short_Two_Decimals
converts a float to a short with two decimals
The resulting number can be a most three digits long before the decimal.
For example: 4.12551f => 412 => 4.12
Minimum Value: -327.68 Maximum Value: 327.67
FloatCompressionTypeShort.Short_Three_Decimals
converts a float to a short with three decimals.
Good for Vectors that have at most the magnitude of 1; for example directions.
For example: 4.12551f => 4125 => 4.1245
Minimum Value: -32.768 Maximum Value: 32.767
Use char compression for really really small floats in order to trim 4 bytes to 1 byte
FloatCompressionTypeChar.Char_One_Decimal
Minimum Value: -12.7 Maximum Value: 12.8
FloatCompressionTypeChar.Char_Two_Decimals
Good for Vectors that have at most the magnitude of 1; for example directions.
Minimum Value: -1.27 Maximum Value: 1.28
writer.Write(Position, FloatCompressionTypeShort.Short_Two_Decimals);
writer.Write(Rotation, FloatCompressionTypeShort.Short_Two_Decimals);
Position = reader.ReadVector3(FloatCompressionTypeShort.Short_Two_Decimals);
Rotation = reader.ReadQuaternion(FloatCompressionTypeShort.Short_Two_Decimals);
You may serialize a message personally for each client
- Override GetDeliveryTarget
1.1. return DeliveryTargetType.PerConnection
- Override SerializeForClient
2.1. Make sure to return true for clients that should receive this message
2.2. Make sure to return false for clients that should not receive this message
- Optionally override PreSendServerForClient
3.1. Make sure to return true for clients that should receive this message
3.2. Make sure to return false for clients that should not receive this message
public class MyMessage : BaseTeleportMessage
{
public Vector3 Position { get; private set; }
public override byte MsgTypeId { get { return TeleportMsgTypeIds.Highest + 1; } }
// Deserialization constructor
public MyMessage() {}
// Actual constructor
public MyMessage(Vector3 position)
{
Position = position;
}
public override DeliveryTargetType GetDeliveryTarget()
{
return DeliveryTargetType.PerConnection;
}
// We don't have to implement Serialize now that we are PerConnection
public override void Serialize(DeBox.Teleport.Core.TeleportWriter writer)
{
throw new Exception("Don't expect this to be called!");
}
public override bool SerializeForClient(TeleportWriter writer, uint clientId)
{
// Only the second client
if (GameUtils.CanPlayerSeeObjects(clientId))
{
writer.Write(Position);
return true;
}
return false;
}
public override void Deserialize(TeleportReader reader)
{
Position = reader.ReadVector3();
}
}