Moving players

Synchronizing movement of rigidbodies controlled by clients

Input in Elympics is processed in server-authoritative manner. That means it has to be sent to the server before it can be applied.
Find out how you can employ this mechanism in your game to allow players to control their characters.

Starting point

The scene that is your starting point should contain the Elympics prefab, two uncontrolled player characters and optionally some basic environment (e.g. a horizontal plane for 3D objects to “walk” on).

The original scene hierarchy

Input handler script

For Elympics to acknowledge player input we have to prepare a script implementing IInputHandler interface. Let’s name it InputController:

public class InputController : MonoBehaviour, IInputHandler
{
	public void OnInputForClient(IInputWriter inputSerializer)
	{
		// TODO
	}

	public void OnInputForBot(IInputWriter inputSerializer)
	{
		// TODO
	}
}

Gathering the input

Elympics expects us to serialize game input using an instance of IInputWriter provided as the only argument of OnInputForClient and OnInputForBot methods.

OnInputForClient is responsible for providing the input for characters controlled by human players. For now, we’ll only serialize the value of a single axis.

public void OnInputForClient(IInputWriter inputSerializer)
{
	inputSerializer.Write(Input.GetAxis("Horizontal"));
}

OnInputForBot provides the input for bot-controlled characters. It may remain empty if bots are not used or no actions are expected from them:

public void OnInputForBot(IInputWriter inputSerializer)
{ }

Applying the input

After the input is sent to the server, it can be retrieved as IInputReader using TryGetInput method of ElympicsBehaviour property of ElympicsMonoBehaviour class. ElympicsMonoBehaviour extends MonoBehaviour provided by Unity. Aside from allowing the script to be attached as a component, it makes Elympics-specific properties and methods available.

Calls to TryGetInput are allowed only within ElympicsUpdate method, so we need to implement IUpdatable interface as well.

After swapping the parent class and implementing a new interface, our script looks as follows:

public class InputController : ElympicsMonoBehaviour, IInputHandler, IUpdatable
{
	// [...]

	public void ElympicsUpdate()
	{
		// TODO
	}
}

One last thing to consider is how we identify characters. We could put a serialized field in our InputController and then fill it with player IDs (starting from 0) after attaching the script to scene objects. But there’s a less error-prone method – using PredictableFor property available on ElympicsMonoBehaviour. The reason for this is explained in the next step).

The identifier is used when calling TryGetInput. Input will only be available on the server and on the client which provided it – if TryGetInput can’t access the data, it returns false.

public void ElympicsUpdate()
{
	var horizontalMovement = 0.0f;

	if (ElympicsBehaviour.TryGetInput(PredictableFor, out var inputDeserializer))
	{
		inputDeserializer.Read(out horizontalMovement);
	}

	// TODO

}

Having implemented all Elympics-related stuff, we can now proceed to actually use the input, changing velocity of associated rigidbody.

The final script is provided below:

using UnityEngine;
using Elympics;

[RequireComponent(typeof(Rigidbody))]
public class InputController : ElympicsMonoBehaviour, IInputHandler, IUpdatable
{
	private Rigidbody _rigidbody;
	
	public void Awake()
	{
		_rigidbody = GetComponent<Rigidbody>();
	}

	public void OnInputForClient(IInputWriter inputSerializer)
	{
		inputSerializer.Write(Input.GetAxis("Horizontal"));
	}

	public void OnInputForBot(IInputWriter inputSerializer)
	{ }

	public void ElympicsUpdate()
	{
		var horizontalMovement = 0.0f;

		if (ElympicsBehaviour.TryGetInput(PredictableFor, out var inputDeserializer))
		{
				inputDeserializer.Read(out horizontalMovement);
		}

		_rigidbody.velocity = new Vector3(horizontalMovement, _rigidbody.velocity.y, _rigidbody.velocity.z);
	}
}
  
using UnityEngine;
using Elympics;

[RequireComponent(typeof(Rigidbody2D))]
public class InputController : ElympicsMonoBehaviour, IInputHandler, IUpdatable
{
	private Rigidbody2D _rigidbody;

	public void Awake()
	{
		_rigidbody = GetComponent<Rigidbody2D>();
	}

	public void OnInputForClient(IInputWriter inputSerializer)
	{
		inputSerializer.Write(Input.GetAxis("Horizontal"));
	}

	public void OnInputForBot(IInputWriter inputSerializer)
	{ }

	public void ElympicsUpdate()
	{
		var horizontalMovement = 0.0f;

		if (ElympicsBehaviour.TryGetInput(PredictableFor, out var inputDeserializer))
		{
			inputDeserializer.Read(out horizontalMovement);
		}

		_rigidbody.velocity = new Vector3(horizontalMovement, _rigidbody.velocity.y, _rigidbody.velocity.z);
	}
}
  

Attaching the script

All that’s left is to add our script component to character game objects. Each object requires its own script as it is a separate synchronized entity.

Attaching any script inheriting from ElympicsMonoBehaviour results in ElympicsBehaviour being attached as well, displaying a friendly editor:

Elympics Behaviour editor

ElympicsBehaviours identify synchronized game objects (each using unique network ID) and decides who can predict their state. The latter setting is particularly important. Depending on “Predictable for:” value, the state of objects may be simulated by clients before the authoritative version is received from the server.

Characters controlled by players should be predictable to players controlling them – input can be applied immediately giving seamless experience. More about this concept can be found here.

And so, predictability for characters looks like this:

Predictability settings for the first character

Predictability settings for the second character

The predictability setting is accessible in ElympicsMonoBehaviour using PredictableFor property which we used in the previous step. Because we need to set it either way, providing an additional serialized field for associated player ID would only introduce unnecessary mess.

Result

With the input handler script implemented we’re halfway there!

Partially synchronized movement

The server runs a perfect simulation and clients predict what they can. Now we only need to consider data sent from the server to clients. This page describes how Elympics network data exchange looks in detail.

Rigidbody synchronization

Synchronization of many Unity-provided components (including rigidbodies) can be enabled in a trouble-free way using ready-made Elympics synchronizers (script components).

Elympics Behaviour (added automatically in the previous section) detects which synchronizers are suitable for its game object and displays a handy button for adding/removing them. Let’s add a rigidbody synchronizer for each character:

Adding a rigidbody synchronizer

Result

That’s it! After adding the synchronizer everything works as expected:

Fully synchronized movement

Further steps – extending the input

Synchronizing just a single axis may be not enough. Let’s see how we could extend our input handler script to move characters in four directions by reading “Vertical” axis.

First, we need to serialize the new input in OnInputForClient method:

public void OnInputForClient(IInputWriter inputSerializer)
{
	inputSerializer.Write(Input.GetAxis("Horizontal"));
	inputSerializer.Write(Input.GetAxis("Vertical"));
}

And then, we have to handle that value in ElympicsUpdate:

public void ElympicsUpdate()
{
	var horizontalMovement = 0.0f;
	var verticalMovement = 0.0f;

	if (ElympicsBehaviour.TryGetInput(PredictableFor, out var inputDeserializer))
	{
		inputDeserializer.Read(out horizontalMovement);
		inputDeserializer.Read(out verticalMovement);
	}

	_rigidbody.velocity = new Vector3(horizontalMovement, _rigidbody.velocity.y, verticalMovement);
}

Synchronizers don’t need to be upated in any way, they already synchronize all the required values.

That’s all! The final effect is presented below:

Fully synchronized movement

Resources