daviyisu
daviyisu

Reputation: 11

How can I make an action repeat every x seconds with Timer in C#?

I´m trying to code a feature in a game in Unity that allows the player to go back to him past position 5 seconds ago so I want that every 5 seconds, the player´s coordinates get saved to go back to them if you press a key.

The only thing I don´t know from this is all that is related to the time. I saw guides for using Timer but I don´t get it exactly, someone can help?

Upvotes: 1

Views: 21849

Answers (4)

Kacper
Kacper

Reputation: 598

If you are using Unity then simplest way to code actions related to time are coroutines. You can simply call StartCoroutine and then use yield return new WaitForSecondsRealtime(5f). Using .NET timers is also an option but in most cases I wouldn't recommend it when working on a game in Unity.

For example if you define method like this

IEnumerator MyCoroutine()
{
    yield return new WaitForSeconds(5f);
    //code here will execute after 5 seconds
}

You can later call it like this

StartCoroutine(MyCoroutine)

Another approach is to use Time.deltaTime inside Update method. With this approach your code could look something like this

float timePassed = 0f;
void Update()
{
    timePassed += Time.deltaTime;
    if(timePassed > 5f)
    {
        //do something
        timePassed = 0f;
    } 
}

If you really don't want your code to be Unity specific you will have to choose between System.Threading.Timer and System.Timers.Timer

Upvotes: 8

Study BFL
Study BFL

Reputation: 1

To implement the feature you described in Unity, you can use a combination of coroutines and input detection. Here's a step-by-step guide on how you can achieve this:

  1. Declare the necessary variables:

     private Vector3 savedPosition;
     private bool canSavePosition = true;
     private bool isRewinding = false;
     private float rewindTimer = 5f;
    
  2. Create a coroutine to save the player's position every 5 seconds:

     private IEnumerator SavePositionCoroutine()
     {
         while (true)
         {
             yield return new WaitForSeconds(5f);
    
             if (!isRewinding)
                 savedPosition = transform.position;
         }
     }
    
  3. Start the coroutine when your game starts or when the player becomes active:

     private void Start()
     {
         StartCoroutine(SavePositionCoroutine());
     }
    
  4. Detect the input for the rewind feature and trigger the rewind process:

     private void Update()
     {
         if (Input.GetKeyDown(KeyCode.R) && !isRewinding)
         {
             StartCoroutine(RewindCoroutine());
         }
     }
    
  5. Create a coroutine to rewind the player's position:

     private IEnumerator RewindCoroutine()
     {
         isRewinding = true;
         canSavePosition = false;
    
         // Rewind for 5 seconds
         float elapsedTime = 0f;
         while (elapsedTime < rewindTimer)
         {
             // Calculate the interpolation factor between the saved position and current position
             float t = elapsedTime / rewindTimer;
    
             // Interpolate the position
             transform.position = Vector3.Lerp(transform.position, savedPosition, t);
    
             elapsedTime += Time.deltaTime;
             yield return null;
         }
    
         isRewinding = false;
         canSavePosition = true;
     }
    

In this implementation, the SavePositionCoroutine runs in the background and saves the player's position every 5 seconds as long as the player is not currently rewinding (!isRewinding). The RewindCoroutine is triggered when the player presses the 'R' key and gradually interpolates the player's position between the saved position and the current position over the course of 5 seconds.

Remember to attach this script to your player object in Unity and adjust any additional logic or customization as needed.

If you Just want to copy and paste the script here it is

using UnityEngine;

public class PlayerRewind : MonoBehaviour
{
    private Vector3 savedPosition;
    private bool canSavePosition = true;
    private bool isRewinding = false;
    private float rewindTimer = 5f;

    private void Start()
    {
        StartCoroutine(SavePositionCoroutine());
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.R) && !isRewinding)
        {
            StartCoroutine(RewindCoroutine());
        }
    }

    private IEnumerator SavePositionCoroutine()
    {
        while (true)
        {
            yield return new WaitForSeconds(5f);

            if (!isRewinding)
                savedPosition = transform.position;
        }
    }

    private IEnumerator RewindCoroutine()
    {
        isRewinding = true;
        canSavePosition = false;

        float elapsedTime = 0f;
        while (elapsedTime < rewindTimer)
        {
            float t = elapsedTime / rewindTimer;
            transform.position = Vector3.Lerp(transform.position, savedPosition, t);

            elapsedTime += Time.deltaTime;
            yield return null;
        }

        isRewinding = false;
        canSavePosition = true;
    }
}

Copy the entire script and attach it to your player object in Unity. Make sure you have the necessary input detection (e.g., checking for the 'R' key) and any other components or scripts your player object requires.

Upvotes: 0

Christopher
Christopher

Reputation: 9804

I am not 100% certain for unity, but generally in games you do not use timers for games. What you want sounds like Prince of Persia: The Sands of Times rewind mechanic.

"Realtime" games are just games with a lot of turns, that do not wait for user input. "Tick" is a common term for these turns. Instead of using timers, you count ticks. Instead of seconds, you count ticks - with a known ticks/second figure. Unity hides the ticks a bit to make it easier to programm, but they are there. Ticks are the one constant of game development since pong. The one thing fundamental to programm flow: https://en.wikipedia.org/wiki/Video_game_programming#Game_structure

In this case however, what you need is a history of positions with a maximum. Think of it like the Undo History of any Text Editor. It also goes back "to the last manaual save", or with some sensible limit of history entries.

You need to save the position, every gametick, into a Collection. Once a point in that collecton is more then 5 seconds in the past, it can be dropped to make space for a new one. The specific collection is up for debate. This historygram has similarities to a queue - in that the last added, not decayed thing is what you want out. It has elements of a Linked List as you do not want to shift a filled collection every game tick, as a new entry is added. But you also want to be able to quickly clear it after a rewind (up to before the point you rewinded too), wich requires a clear function or random access.

As a example, I made this old and Primitive FPS counter. It counts the frames, but only over the last second:

using System;
using System.Collections.Generic;

namespace FPS_Counter
{
    class FPS_Counter
    {
        List<DateTime> FrameCounter = new List<DateTime>();

        public void countFrame(){
            FrameCounter.Add(DateTime.Now);
        }

        private void clearOld()
        {
            bool continueLoop;
            DateTime decayLimit = DateTime.Now.AddSeconds(-1);

            do
            {
                continueLoop = false;
                if (FrameCounter.Count > 0 && FrameCounter[0] < decayLimit)
                {
                    FrameCounter.RemoveAt(0);
                    //If you removed one, the one after might be too old too.
                    continueLoop = true;
                }
            }while (continueLoop);
        }

        public int FPS
        {
            get
            {
                clearOld();
                return FrameCounter.Count;
            }
        }
    }
}

Basically it is creating a history of all counts of countFrame(). I used a simple list and cleared the when the get is called. Wich si not efficient, and could easily overflow the collection if you count Frames but do not actually retreive the value.

This was efficient enough for the job, asuming get is called regulary. Your history is going to be a lot bigger, both in number of entries and the size of the entries so you propably need to clear old every time you add something new. And your equivalent of countFrame() has to take the position and gametick as a argument.

Upvotes: 1

dimitar.d
dimitar.d

Reputation: 653

You can use one of the two System Timers - System.Timers.Timer and System.Threading.Timer. Here is an example with the first one:

 System.Timers.Timer timer = new System.Timers.Timer(5000);
 timer.Start();
 timer.Elapsed += SaveDataPeriodically;

where SaveDataPeriodically is a method that takes two arguments:

void SaveDataPeriodically(object sender, ElapsedEventArgs e)
{
    //Save the coordinates
}

You simply subscribe to the Elapsed event which will fire every 5000 millisecond.

Upvotes: 0

Related Questions