Networking

Spark's networking layer is an abstraction that routes commands through a provider. In single-player, commands execute locally. In multiplayer, a custom provider can route them to a server. Your game logic stays the same either way.

Architecture

The networking system has three pieces:

  1. INetworkProvider: The interface that all providers implement.

  2. NetworkProviderBase: An abstract base class with reflection-based command dispatch.

  3. LocalNetworkProvider: The default provider for single-player/offline games.

INetworkProvider

public interface INetworkProvider
{
    bool IsServer { get; }
    void ExecuteCommand(ICommand command);
    void RegisterHandler<T>(ICommandHandler<T> handler) where T : ICommand;
}
Member
Description

IsServer

Whether this instance has authority. True in single-player, depends on role in multiplayer.

ExecuteCommand(ICommand)

Dispatches a command to its registered handler.

RegisterHandler<T>(handler)

Associates a handler with a command type.

NetworkProviderBase

The abstract base class stores handlers in a Dictionary<Type, object> and uses reflection to invoke the correct Handle() method when a command is executed:

When you create a custom provider, inherit from NetworkProviderBase and override RegisterDefaultHandlers():

LocalNetworkProvider

The default provider used in single-player games. It:

  • Auto-registers at BeforeSceneLoad if no other provider is set

  • Executes commands immediately and synchronously

  • Always reports IsServer => true

  • Registers the built-in DragDropCommandHandler

You don't need to do anything to use it. It's set up automatically.

Setting Up a Network Provider

To use a custom provider (for multiplayer), register it before any plugins try to execute commands:

Listen for provider readiness:

Implementing a Custom Network Provider

Here is a skeleton for a multiplayer provider:

The key idea is that ExecuteCommand on the client sends the command over the network, while the server calls base.ExecuteCommand() to dispatch it locally through the handler.

Command Serialization

For multiplayer, commands need to be serializable. This is why commands should use simple types (strings, ints, floats, bools) and entry IDs instead of direct object references.

Best Practices

  • Design commands for serialization from the start, even if you are building single-player.

  • Use Spark.Network?.ExecuteCommand() with the null-conditional operator. This gracefully handles the case where no provider is set.

  • Register command handlers in your plugin's RegisterPlugin() method.

  • For multiplayer, validate commands on the server. Don't trust client input.

  • Keep the LocalNetworkProvider as your development default. Swap to a multiplayer provider when your networking layer is ready.

Last updated

Was this helpful?