Saturday 26 January 2019

Deterministic Unity RTS Dev

Deterministic Unity RTS Dev
I started creating a deterministic RTS in Unity in November 2017. I thought it would be fun & interesting project to recreate a ClashRoyale style game in a sci-fi setting with some additional features.

I knew from the start I wanted the game to be based around great multiplayer. I saw this as what I enjoy most in these sort of games both for the gameplay and the technical challenges involved in the dev process.

Here's a progress vlog for the first six months.



I was very impressed with how easy it was to get going with Unity. I’ve been developing games for over a decade but that doesn’t always mean it’s seamless to learn a new engine.

Getting started - determinism & multiplayer
I picked Photon TrueSync to help me get started with multiplayer. It’s been extremely useful even if I’ve not really used it in the intended way.

TrueSync is designed around a lock-step deterministic model where only the inputs are sent over the network. This was very appealing, however I didn’t want to be too hamstrung by their API. What I needed was something simpler where I could just use their tech purely for lockstep and networking and keep my simulation code separate.

I eventually ended up having a single TrueSyncBehaviour called TSPlayerInput. This allowed me to keep my own sim code fairly clean and not dependent on TrueSync.

public override void OnSyncedInput ()
{
    //
    // Write player inputs

    m_writeInputs.Clear ();
    m_simulationController.PollInput (ref m_writeInputs);

    if (m_writeInputs.m_spawns.Count > 0)
    {
        WriteCommands (m_writeInputs.m_spawns, SpawnCommandId);
    }

    if (m_writeInputs.m_emotes.Count > 0)
    {
        WriteCommands (m_writeInputs.m_emotes, EmoteCommandId);
    }

    TrueSyncInput.SetInt (CheckSumId, JDX.Checksum.Instance ().GetChecksumValue ());

}

public override void OnSyncedUpdate ()
{
    //
    // Read inputs for each player and update the sim if it's the last player

    int inputPlayerId = owner.Id;


    // Each player has it's own input lists separate to other players so it is necessary

    // to process each players inputs in OnSyncedUpdate.
    {
        ReadCommands (ref m_readInputs.m_spawns, SpawnCommandId);
        ReadCommands (ref m_readInputs.m_emotes, EmoteCommandId);
     
        m_simulationController.ReadInputs (inputPlayerId, m_readInputs);

        m_readInputs.Clear ();


        int playerFrameChecksum = TrueSyncInput.GetInt (CheckSumId);

        m_simulationController.SetPlayerChecksum (inputPlayerId, playerFrameChecksum);
    }
 

    // OnSyncedUpdate is called for every player on each device. We only want the simulation

    // to be run a single time on each device though. So only for the last player after ProcessInputs()
    // has been called for each player do we tick the sim controller.
    {
        int lastPlayerIndex = TrueSyncManager.Players.Count - 1;
        int lastPlayerId = TrueSyncManager.Players [lastPlayerIndex].Id;
        if ((inputPlayerId == lastPlayerId) || (inputPlayerId == OfflinePlayerId))
        {
            m_simulationController.FixedUpdateNetwork ();
        }
    }
}

No comments:

Post a Comment