MKSQD / Cube

Scalable high level network library for Unity
MIT License
13 stars 0 forks source link
eventual-consistency networking replication udp unity3d

Cube

Cube tries to do eventual consistency based object replication for Unity and nothing more.

Eventual consistency means unreliable packets only - inspired by GDC Vault: I Shot You First! Gameplay Networking in Halo: Reach, which makes it scalable, robust to bad network conditions and simple.

Apart from replication, basic features required by every network game are provided, such as low level network api abstraction, rpcs-as-method-calls via Cecil assembly patching and level loading. In editor, single instance, client+server development is possible.

State

Far from production ready. Networking is hard. The library works, it's simple, robust and hackable. But there are a lot of spots were it's far from good enough. All patches, pull requests and feedback welcome.

Getting Started

Clone the git repository into your Assets folder.

Put CubeClient and CubeServer components on 2 GameObjects in the scene.

Replication

A Replica is replicated from the server to all clients. Replicas must always be instances of prefabs for Cube to be able to create client instances of them.

Create a new GameObject in the scene. Add the Cube/Replica component to mark it as an Replica. Add the Cube/ReplicaTransform component to keep their transforms synchronized.

Then create a prefab TestReplica from it by dragging the GameObject into the project explorer. Delete the original instance.

Create a new script TestServerGame:

using Cube.Networking;
using Cube.Replication;
using Cube.Transport;
using UnityEngine;

public class TestServerGame : ServerGame {
    public GameObject prefab;

    protected override void OnNewIncomingConnection(Connection connection, BitStream bs) {
        // Create a new ReplicaView for this Connection
        var view = new GameObject("ReplicaView " + connection);
        view.transform.parent = transform;

        var rw = view.AddComponent<ReplicaView>();
        rw.connection = connection;

        server.replicaManager.AddReplicaView(rw);

        // Instantiate some Replica
        server.replicaManager.InstantiateReplica(prefab);
    }
}

Replace the ServerGame component on the ServerGame scene GameObject. Assign TestReplica to it's prefab field.

A ReplicaView observes Replicas for a connection. Its position and settings is used to scope and priorize which Replicas to send. Without a ReplicaView the client will not receive any Replicas.

Start the game now and you should see the Replica prefab being replicated. Try to move around the server-side instance in the editor.

For the client to instantiate a different prefab, rename your prefab to Server_TestReplica and create a new prefab variant Client_TestReplica (The name prefixes Server_ and Client_ are important).

ReplicaBehaviour instead of MonoBehaviour

ReplicaBehaviour derives from MonoBehaviour and is used to implement multiplayer related functionaly on a Replica. The server/client members can be used to access the Cube instance the Replica is part of. Rpcs are only available for ReplicaBehaviours.

using Cube.Replication;
using UnityEngine;

public class Test : ReplicaBehaviour {
    void DoTest() {
        if (!isServer)
            return;

         RpcTest();
    }

    [ReplicaRpc(RpcTarget.Client)]
    void RpcTest() {
        Debug.Log("Client rpc called");
    }
}

Replica ownership

Each Replica has an owning (Replica.owner) connection. Assign ownership with Replica.AssignOwnership and take it away with Replica.TakeOwnership. Only the server can set and remove ownership. Ownership information is sent to the owning client.

ReplicaRpc

ReplicaBehaviours can send unreliable rpcs. Rpcs are prioritized aggressively, so never rely on these to transmit actual gameplay state. Instead, these should be used for additional, optional effects and cues.

Rpcs can only be private functions with an [ReplicaRpc(...)] attribute and its name starting with Rpc.

using Cube.Replication;
using UnityEngine;

public class TestReplica : ReplicaBehaviour {
    void Update() {
        if (!isServer)
            return;

        RpcTest();
    }

    [ReplicaRpc(RpcTarget.Client)]
    void RpcTest() {
        Debug.Log("Client rpc called");
    }
}