ExampleGameGuidePart03UpgradingToSynchronizable
Upgrading the PlayerController from part 2 to a Synchronizable, so players who join later also see who is blinking.
In
Part 2 we made players blink red using an RPC.
That works for everyone who is in the Room when the blink starts, but try this: hold Space on one instance, then join the Room with a second instance.
The new player sees a white square, even though the first player is still blinking.
Why the new player misses the blink
An RPC is a one-off event.
BroadcastRemoteMethod("BlinkRpc", true) is sent once, to the users that are in the Room at that moment.
A user who joins later never receives it, so from their point of view the blink never happened.
When a user joins a Room, Alteruna runs a
force sync: every user already in the Room sends the new user the current state of all their Synchronizables.
To take part in that, the blink needs to be
state the component holds, rather than an event it sends.
Read more about how force sync works in
Developer Guide Part 03.
AttributesSync does have a
ForceSyncMethods setting (off by default) that repeats the last call to each RPC after a force sync.
It is a quick fix, but a limited one: only the user with the lowest user ID repeats its calls, only calls that user made itself are repeated, and they run again for everyone in the Room, not only for the new user.
A
Synchronizable is still recommended, for full control over what is sent and better performance.
Update the PlayerController
We will change
PlayerController to inherit from
Synchronizable instead of
AttributesSync.
A
Synchronizable holds its own state and writes it out whenever it is asked to, both for regular updates and for force sync.
We will make five changes to
PlayerController:
- Change the base class from
AttributesSync to Synchronizable. - Replace the
BlinkRpc method with an _isBlinking field that holds the current state. - In
Update(), when the Jump axis changes, update _isBlinking and call Commit() to tell Alteruna the state should be sent. Then call SyncUpdate() so the committed state actually goes out. - Override
AssembleData to write _isBlinking, and DisassembleData to read it back and update the color. - Set
Reliability to Reliable in Awake(). A Synchronizable is unreliable by default, which suits data that changes every frame, but the blink state only changes when Space is pressed or released, so no update can be lost.
Here is the full, updated script:
using UnityEngine;
using Alteruna.Multiplayer.Core;
using Alteruna.Multiplayer.Core.MethodArguments;
using Alteruna.Multiplayer.Core.PacketProcessing;
using Alteruna.Multiplayer.Unity;
public class PlayerController : Synchronizable
{
public float Speed = 10.0f;
public float RotationSpeed = 180.0f;
private SpriteRenderer _renderer;
private bool _isBlinking = false;
void Awake()
{
_renderer = GetComponent<SpriteRenderer>();
// The blink state only changes when Space is pressed or released, so every update must arrive.
Reliability = Reliability.Reliable;
}
public override void Possessed(bool isMe, User user)
{
enabled = isMe;
UpdateColor();
}
// Only runs when possessed by local user (isMe == true)
void Update()
{
// Get the horizontal and vertical axis.
float _translation = Input.GetAxis("Vertical") * Speed;
float _rotation = -Input.GetAxis("Horizontal") * RotationSpeed;
_translation *= Time.deltaTime;
_rotation *= Time.deltaTime;
transform.Translate(0, _translation, 0, Space.Self);
transform.Rotate(0, 0, _rotation);
// Blink based on the state of Jump axis (Spacebar button)
bool blink = Input.GetAxisRaw("Jump") >= 0.5f;
if (blink != _isBlinking)
{
_isBlinking = blink;
UpdateColor();
// Tell Alteruna that our state has changed and should be sent to the other users.
Commit();
}
// Send the committed state. Only the local player changes it, so this only needs to run here.
SyncUpdate();
}
// Called when our state is sent, both for regular updates and when a new user joins (force sync).
public override void AssembleData(Writer writer, SerializeInfo info)
{
writer.Write(_isBlinking);
}
// Called on the other users when our state arrives, including a user who just joined the Room.
public override void DisassembleData(Reader reader, UnserializeInfo info)
{
_isBlinking = reader.ReadBool();
UpdateColor();
}
private void UpdateColor()
{
if (_isBlinking)
{
// Set color to red.
_renderer.color = Color.red;
}
else
{
// Green for the local user, white for everyone else.
// (enabled is set to isMe in Possessed.)
_renderer.color = enabled ? Color.green : Color.white;
}
}
}
DisassembleData does not care whether the data came from a regular update or a force sync.
Either way it gets the current blink state and sets the color to match.
Test the game!
Build and run the game again. Hold Space on the first instance, then join the Room with a second one.
This time, the second instance sees the first player in red as soon as it joins, and the color keeps following Space presses as before.
Events or state?
Both parts of this guide have their place:
- Use an RPC, like in Part 2, for things that happen once and are over, like firing a weapon or playing a sound. A player who joins later has no reason to see them.
- Use a Synchronizable for state that lasts, like a color, health, or a door being open. A player who joins later needs to know it.
A Synchronizable also gives you full control over force sync. For example, you can leave an object out of the force sync entirely.
See
Developer Guide Part 03 - Force Sync for how, and read more about Synchronizables
here.
← Back |
Next →