Code Practice(Let`s see how to setup timers in networking games)

Let`s discuss how to setup timers in network based games and what are the challenges. Lastly we will inspect my ChangeTurn method together!

 

Title: Mastering Network-Based Timers in Unity: A Deep Dive into High-Performance Solutions

Introduction: Timers are a fundamental part of game development, enabling developers to schedule events, control gameplay mechanics, and manage various game systems. However, when it comes to network-based timers in Unity, additional challenges arise. In this blog post, we’ll explore the intricacies of network-based timers and discuss professional concepts and code examples to overcome these challenges effectively.

Understanding Network-Based Timers: Network-based timers are essential for synchronizing game events across multiple networked players or clients. They ensure that specific actions occur simultaneously for all players, preserving the integrity of the game state.

Challenge 1: Network Latency: The first challenge in network-based timers stems from network latency, which can introduce inconsistencies in the timing of events between clients.

we start the timer on the server and notify all clients using the [ClientRpc] attribute. Each client starts their local timer using StartCoroutine, but network latency can cause variations in the countdown duration, resulting in inconsistent timer finishes.

Fix 1: Server Authority and Synchronization: To overcome network latency challenges, it’s crucial to maintain server authority over timers.

Challenge 2: Timer Drift: Another challenge with network-based timers is timer drift, which occurs when the timer on one client falls out of sync with other clients due to variations in frame rates, network hiccups, or processing delays.

Fix 2: Time Synchronization: To address timer drift, we needto synchronize time across all clients. Here’s an example a code that includes time synchronization:

 

But let`s see if we wanted to do a simpler turn change what could it look like:

Explanation of the method:

First of all we determine if the change of turn happens by the timer or we are forcing turn change in case we want to add penalty to the want who misses his turns or stays idle for the timer to pass.

The same approach has been implemented in GodsUnchanged Timer code in which making no moves will add some penalty to you and your timer will decrease faster.

Since the character skills which are some async methods take some time we wait for those animations to finish and the animaitons will fire an event on their completion.

Trust me on this:

Firing events of animations utilising the signal invokation of timelines in unity is much simpler approach for even complex skill setup of characters.

We have many pools that could affect the gameplay like being stunned(isses the turn) or bleeding or being poisoned,… which take time so we should process pool effects before changing the turns and nothing is much sweet setup that utilising Reflection  for our abstract skill  pool types.

As you can see we go through each pool for the characters that could be in.

THen we decide to forgive the player`s penalty by rewarding them for their appropriate behaviour!

So we reduce their timer speed and then change the turn and toggle the events and do some UI stuff.

Remember all we discussed before about netowrking parts can be integrated easily to this method.

I hope you enjoyed this post.

Feel free to ask me if anything occludes your minds.

Happy Coding!

 

				
					using System;
using UnityEngine;
using UnityEngine.Networking;

public class NetworkedTimer : NetworkBehaviour
{
    [SerializeField]
    private float timerDuration = 5.0f;

    private float timerCountdown;
    private float serverStartTime;

    private event Action TimerFinished;

    private void Start()
    {
        if (isServer)
        {
            StartServerTimer();
        }
        else
        {
            CmdStartTimer(timerDuration);
        }
    }

    [Command]
    private void CmdStartTimer(float duration)
    {
        RpcStartTimer(duration);
    }

    [ClientRpc]
    private void RpcStartTimer(float duration)
    {
        if (isLocalPlayer)
        {
            StartClientTimer(duration);
        }
    }

    private void StartServerTimer()
    {
        serverStartTime = Time.time;
        RpcStartTimer(timerDuration);
    }

    private void StartClientTimer(float duration)
    {
        timerCountdown = duration - (Time.time - serverStartTime);
        StartCoroutine(CountdownTimer());
    }

    private System.Collections.IEnumerator CountdownTimer()
    {
        while (timerCountdown > 0.0f)
        {
            yield return null;
            timerCountdown -= Time.deltaTime;
        }

        if (isServer)
        {
            RpcTimerFinished();
        }
    }

    [ClientRpc]
    private void RpcTimerFinished()
    {
        HandleTimerFinishedEvent();
    }

    private void HandleTimerFinishedEvent()
    {
        TimerFinished?.Invoke();
        Debug.Log("Timer finished!");
    }

    public void RegisterTimerFinishedCallback(Action callback)
    {
        TimerFinished += callback;
    }

    public void UnregisterTimerFinishedCallback(Action callback)
    {
        TimerFinished -= callback;
    }
}

				
			
				
					 public async  Task ChangeTurn( bool  byTimer)
    {
        List<Task> tasks = new List<Task>();
        if (IsMyTurn())
        {
            for (int i = 0; i < Grid.Instance.myAgents.Length; i++)
            {
                if (!Grid.Instance.myAgents[i].isDead)
                {
                    for (int j = 0; j < Grid.Instance.myAgents[i].effectedPoolsDictionary.Keys.Count; j++)
                    {
                        if (Grid.Instance.myAgents[i].effectedPoolsDictionary[(AbilityEnumPool)j])
                        {
                            await Grid.Instance.myAgents[i].abstractPoolTypesDict[(AbilityEnumPool)j]
                                .ProcessPoolEffect();
                        }
                    }
                }
            }
        }
        else
        {
            for (int i = 0; i < Grid.Instance.enemyAgents.Length; i++)
            {
                if (!Grid.Instance.enemyAgents[i].isDead)
                {
                    for (int j = 0; j < Grid.Instance.enemyAgents[i].effectedPoolsDictionary.Keys.Count; j++)
                    {
                        if (Grid.Instance.enemyAgents[i].effectedPoolsDictionary[(AbilityEnumPool)j])
                        {
                            await Grid.Instance.enemyAgents[i].abstractPoolTypesDict[(AbilityEnumPool)j]
                                .ProcessPoolEffect();
                        }
                    }
                }
            }
        }

        if (!byTimer)
        {
            ForgivePlayerPartially();

        }
      
        
        ResetTimerForOther();
        gridRef.ToggleTurn();
        ToggleTurnPrefabs();
        ResetSliderValue();
        SetTimerSprite();
        
        gridRef.OnTurnChanged?.Invoke();
        // await Task.CompletedTask;

    }
				
			

Leave a Reply

Your email address will not be published. Required fields are marked *