Unity C# countdown to persist through scenes - c#

Hi I have a C# script written which I can then call inside another script to keep track of the amount of time remaining between different scenes, and then load a scene when the timer reaches 0. I beleive that both scripts are written correctly, but there is no action when the timer reaches 0, I think the issue is that I'm not placing the code in the correct location but I'm not really sure, I've tried adding it to both my start function and my update function.
Here is the script for the static class;
public static class GlobalCountDown
{
static DateTime TimeStarted;
static TimeSpan TotalTime;
public static void StartCountDown(TimeSpan totalTime)
{
TimeStarted = DateTime.UtcNow;
TotalTime = totalTime;
}
public static TimeSpan TimeLeft
{
get
{
var result = DateTime.UtcNow - TimeStarted;
if (result.TotalSeconds <= 0)
SceneManager.LoadScene("Lose");
return TimeSpan.Zero;
//return result;
}
}
}
And here is the call I make in each game scene to check the time remaining;
GlobalCountDown.StartCountDown (TimeSpan.FromSeconds (5));
I get a warning when compiling the script 'The private field `GlobalCountDown.TotalTime' is assigned but its value is never used' but am not sure how to fix it.
Any help or advice would be appreciated.

Change this
var result = DateTime.UtcNow - TimeStarted;
To this
var result = TotalTime - (DateTime.UtcNow - TimeStarted);
In your behaviour do this
void Start()
{
GlobalCountDown.StartCountDown (TimeSpan.FromSeconds (5));
}
void Update()
{
if (GlobalCountDown.TimeLeft == TimeSpan.Zero)
SceneManager.LoadScene("Lose");
}
and remove the SceneManager code from your GlobalCountDown class

Related

How I can make "Time" to be update constantly

I am beginner game dev in unity and i start new little project "alone", and in my game i have a little problem.
My problem is that the time, i.e. the clock in the game is not synchronized and does not synchronize and I have some suspicions.
I know maybe I'm doing another wrong step, maybe I put a wrong equation in the whole function and nothing works anymore, but to tell you more briefly, I want to make a game in which the game interface is a desktop and the clock on I coded it below, it doesn't work to be updated with time, I waited and tried many solutions that I applied and they didn't work, so I'll leave the code below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
public class DateTime : MonoBehaviour
{
public Text timeLockScreenText;
public Text dateLockScreenText;
public Text timeTaskBarText;
string time = System.DateTime.UtcNow.ToLocalTime().ToString("HH:mm");
string date = System.DateTime.UtcNow.ToLocalTime().ToString("MM-dd-yyyy");
// Update is called once per frame
void Update()
{
InvokeRepeating("DateTimeV",0f,1f);
}
public void DateTimeV()
{
timeLockScreenText.text = time;
dateLockScreenText.text = date;
timeTaskBarText.text = timeLockScreenText.text;
}
}
Now I have some theories:
one is that in the game, just like in real life, it's like with any personal computer user when we open it there is a lockscreen with a clock and we press enter to enter all the time to access the internet and other files on the desktop, so I have the theory that the lockscreen being a GameObject which is like visibility opposite to the desktop, i.e. if the lockscreen is active the desktop is not visible and vice versa, the clock does not update because let's say if we are on the desktop the lockscreen is disabled and that's why the script doesn't really work and you have to to make two separate ones, one for the lockscreen and one for the desktop, actually for the clock on the desktop
or I don't know how to code well
I tried to put in Update() function, in InvokeRepeting, StartCourutine with IEnumerator function, and i dont have the solutions
I don't know anything about Unity, but it seems that you're never updating the values of time and date after they're initially assigned. Perhaps something like this would do the trick, where you just assign the correct values in the method:
public void DateTimeV()
{
DateTime utcNow = System.DateTime.UtcNow.ToLocalTime();
timeLockScreenText.text = utcNow.ToString("HH:mm");
dateLockScreenText.text = utcNow.ToString("MM-dd-yyyy");
timeTaskBarText.text = timeLockScreenText.text;
}
Use fixed update, and the code from Rufus L:
void FixedUpdate() => DateTimeV();
public void DateTimeV()
{
DateTime utcNow = System.DateTime.UtcNow.ToLocalTime();
timeLockScreenText.text = utcNow.ToString("HH:mm");
dateLockScreenText.text = utcNow.ToString("MM-dd-yyyy");
timeTaskBarText.text = timeLockScreenText.text;
}
What Rufus L said + you would use InvokeRepeating only once e.g. in
private void Start()
{
InvokeRepeating(nameof(DateTimeV), 0f, 1f);
}
or use a simple counter
private float timer;
private void Update ()
{
timer += Time.deltaTime;
if(timer > 1f)
{
timer -= 1f;
DateTimeV();
}
}
or a coroutine
private IEnumerator Start()
{
while(true)
{
DateTimeV();
yield return new WaitForSeconds(1f);
}
}

How to save in Unity the time obtained in the game timer?

I have created a timer for my game and I need to save in a variable or a function, the time elapsed until the moment of finishing, and then show it on the screen as "Record".
I want to display on screen the shortest time achieved, when a shorter time is achieved, replace, (overwrite) the time that was until that moment.
So far I have managed to show the clock time and a text to show "Record" that only shows "00:00" since I don't know how to put my code together.
The stopwatch works and restarts every time a new game starts, but I don't know how to save the time achieved at the end of the game
How do I get the shortest time achieved to display?
My experience is sparse in C#, I have started with a simple template to experiment and make changes.
I have built the code of the clock, following an example.
On the other hand, looking for information, I see that with the PlayerPrefs method you can save and delete data.
I found another example and studied the logic to use that method on my watch, but I don't quite understand the logic and I can't get it to work.
How can I get to save the time to display it as text on the screen?
How to make it always rewrite with the shortest time obtained at the end of the game?
I show the code of my Reloj.cs which works and is displayed on the screen.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Reloj : MonoBehaviour
{
[Tooltip("Tiempo inicial")]
public int tiempoInicial;
[Tooltip("Escala de tiempo del relog")]
[Range(-10.0f, 10.0f)]
public float escalaDeTiempo = 1;
private Text myText;
private float tiempoDelFrameConTimeScale = 0f;
private float tiempoAMostrarEnSegundos = 0f;
private float escalaDeTiempoAlPausar, escalaDeTiempoInicial;
private bool estaPausado = false;
void Start()
{
// set the timeline
escalaDeTiempoInicial = escalaDeTiempo;
myText = GetComponent<Text>();
//we start the variable
tiempoAMostrarEnSegundos = tiempoInicial;
ActualizarRelog(tiempoInicial);
}
// Update is called once per frame
void Update()
{
if (!estaPausado)
{
// the following represents the time of each frame considered the time scale
tiempoDelFrameConTimeScale = Time.deltaTime * escalaDeTiempo;
// the following variable accumulates the elapsed time to show it in the Relog
tiempoAMostrarEnSegundos += tiempoDelFrameConTimeScale;
ActualizarRelog(tiempoAMostrarEnSegundos);
}
}
public void ActualizarRelog(float tiempoEnSegundos)
{
int minutos = 0;
int segundos = 0;
string textoDelReloj;
// ensure that the time is not negative
if (tiempoEnSegundos <= 0) tiempoEnSegundos = 0;
// calculate seconds and minutes
minutos = (int)tiempoEnSegundos / 60;
tiempoEnSegundos = (int)tiempoEnSegundos % 60;
// create the string of digital characters that form the relog
textoDelReloj = minutos.ToString("00") + ':' + tiempoEnSegundos.ToString("00");
// update UI text element with character string
myText.text = textoDelReloj;
}
public void Pausar()
{
if (!estaPausado)
{
estaPausado = true;
escalaDeTiempoAlPausar = escalaDeTiempo;
escalaDeTiempo = 0;
}
}
}
On the other hand, I have been doing tests to try to implement the following example in my Clock, but I can't. Before asking the question here, I have looked for a solution and I have tried to learn how to do it, I have seen videos, examples, and I do not get it.
The following example, I create it by watching a video, and I get it to save the data on the screen, and delete it from a button, but I don't know how to implement it in my Clock. I imagine it would be very simple, but I can't understand the logic.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LogicaPuntuaje : MonoBehaviour
{
public Text textoPuntaje;
public int numPuntaje;
// The start is called before the first table update
public Text textoRecord;
void Start()
{
numPuntaje = 0;
textoRecord.text = PlayerPrefs.GetInt("PuntajeRecord", 0).ToString();
}
// The update is called once per frame
void Update()
{
}
// button to create points by doing that I don't need
public void PuntajeAlAzaro()
{
numPuntaje = Random.Range(0, 11);
textoPuntaje.text = numPuntaje.ToString();
if (numPuntaje > PlayerPrefs.GetInt("PuntajeRecord", 0))
{
PlayerPrefs.SetInt("PuntajeRecord", numPuntaje);
textoRecord.text = numPuntaje.ToString();
}
}
// function to delete the record data
public void BorrarDatos()
{
PlayerPrefs.DeleteKey("PuntajeRecord");
textoRecord.text = "00:00";
}
}
// here is the solution:
float currentTime; // your current timer (without logic)
float highscore = 99999; // set it to a really high value by default
void start()
{
highscore = PlayerPrefs.GetFloat("best"); // get your previous highscore
}
//on level completed:
{
if(currentTime <= highscore //your best time
{
highscore = currentTime;
PlayerPrefs.SetFloat("best", highscore); // save your score
}
}

How to keep time reference during mobile window switch? (Unity) [duplicate]

This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 2 years ago.
I have a game where I have a queue matchup system.
I would like to show the player how long they been in the current queue. It works well, until the player presses the menu/app overview button on their phone, which basically freezes the timer, and it only continues counting when the player switches back to full screen mode on their phone.
I tried looking for an app lifecycle method (somewhat like onApplicationPause, but it didn't work for me)
I also tried syncing the time by saving it in the db and then loading from the database actually, but Firebase puts on some delay, so it won't be exact.
How could I solve this, so it will keep counting when the user presses their app overview/menu button on their phone?
For now, I have this code which counts the user's queue time:
private void Update() {
if(startedCounting) {
timer += Time.deltaTime;
int seconds = Mathf.FloorToInt(timer % 60);
int minutes = Mathf.FloorToInt(timer / 60);
queueStatusText.text = "You are in the queue\n"
+ string.Format("{0:00}:{1:00}", minutes, seconds);
}
}
There are different aproaches, some using static class or singleton pattern. It is better to not update this time variable each time on Update() as it takes the computation time each update (if you don't need this time for anything else). Also user doesn't need to have exact time by frames so you can avoid things like adding Time.deltaTime.
I'll show you example with static class, it can hold this information. Also note that this script is only added as C# file, but you don't attach it to any GameObject :
public static class QueueTimerInformation //It is not inheriting from MonoBehavior!
{
private static DateTime dt;
private static bool isRunning = false;
//Save current DateTime when user did the action
public static void Start()
{
if(!isRunning)
{
dt = DateTime.Now;
isRunning = true;
}
}
public static void Reset()
{
isRunning = false;
}
// This gets the actual time in String value
// Usually it is better to return just `elapsedTime` and format it later
public static string GetTimeElapsed()
{
if(!isRunning) return "00:00"; //Not running, return some default
var elapsedTime = (DateTime.Now - dt);
return $"{elapsedTime:mm\\:ss}";
}
}
Usage
//On 1st time enter lobby
QueueTimerInformation.Start();
//In update method
var result = QueueTimerInformation.GetTimeElapsed();

Right way to manage time event in unity

I have to do some time based task(simulation) like,
As the game started:
After 2 mintues start TrainA.
After 6 mintues star Train B
after 12 minutes start Train C and so on
But remember If i want to quickly view the simulation, the timer should be speed able.
Now i am considering two approaches (other approaches are also welcome which is the purpose of the question)
Each Object (Train) script manage its time by WaitForSeconds:
void Start()
{
StartCoroutine("MyEvent");
}
private IEnumerator MyEvent()
{
yield return new WaitForSeconds(120f); // wait two minutes
//Launch Train
}
This kind of script attach to every object that requires action after a certain time:
Problem:
How do I speed up its time?
Maybe Performance intensive. As each script managing its own co-routine(Maybe I am wrong)
One Global script for Timer:
function Update ()
{
Timer += Time.deltaTime; //Time.deltaTime will increase the value with 1 every second.
if (timer>= 120){
//launch train, an so one conditions
//Or get timer variable in other script and compare time on update
}
}
Now using the above script I can get the Timer variable in another script and can execute my task based on time in the update method.
The question is how do I manage it? Either first way or second way or third way (by you)?
Because I also want to speed up the time too which seems impossible within co-routine once it registers.
Need your help folks!!
You use either of the two ways and simply change Time.timescale if you want to view it faster/slower etc in the example by pressing Space:
public class Example : MonoBehaviour
{
// Toggles the time scale between 1 (normal) and 0.5 (twice as fast)
// whenever the user hits the Space key.
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (Time.timeScale == 1.0f)
{
Time.timeScale = 0.5f;
}
else
{
Time.timeScale = 1.0f;
}
// Also adjust fixed delta time according to timescale
// The fixed delta time will now be 0.02 frames per real-time second
Time.fixedDeltaTime = 0.02f * Time.timeScale;
}
}
}
For the Coroutine this works without you having to change anything because WaitForSeconds is affected by the Time.timescale:
The actual time suspended is equal to the given time multiplied by Time.timeScale.
Time.deltaTime afaik is not affacted by the Time.timescale so in order to allow faster replay you would have to do
private void Update ()
{
Timer += Time.deltaTime * (1 / Time.timescale);
if (timer >= 120)
{
//launch train, an so one conditions
//Or get timer variable in other script and compare time on update
}
}
Whether one Update or multiple Coroutines are more performant depends a lot on the specific usecase. Afaik it isn't really noticable until you have like maybe 10.000 Coroutines running (don't nail me on numbers here ;) ).
In your case for only raising one or multiple events it might be better to stick with the one Update() method instead and invoke an event or something like that.
But - why not simply have one single Coroutine instead of the Update at all:
public class GlobalTimer : MonoBehaviour
{
public float Delay = 2.0f;
public UnityEvent onTimerDone;
// Unity allows to use Start as IEnumerator instead of a void
private IEnumerator Start()
{
yield return new WaitforSeconds(delay);
onTimerDone.Invoke();
}
}
than you have only one invoked method and can add callbacks to that timer like
globalTimerReference.onTimerDone.AddListener(() => {
Debug.LogFormat("Timer of {0} seconds is done now.", globalTimerReference.Delay);
});
or via the inspector (like in a UI.Button.onClick).
For multiple events I just came up with a quick and dirty solution so you can simply define multiple events via the inspector and add various callbacks and stuff:
public class GlobalTimer : MonoBehaviour
{
public List<UnityEvent> events;
public List<float> delays;
private void Start()
{
var validPairs = Mathf.Min(events.Count, delays.Count);
for (int i = 0; i < validPairs; i++)
{
StartCoroutine(InvokeDelayed(events[i], delays[i]));
}
}
private IEnumerator InvokeDelayed(UnityEvent unityEvent, float delay)
{
yield return new WaitForSeconds(delay);
unityEvent.Invoke();
}
}
Just make sure that for every event there is a delay in the list. In the future you might want to write a proper CustomEditor in order to edit this more beautiful in the inspector.
Update
or you can take mine :D
public class ExampleScript : MonoBehaviour
{
[SerializeField] private List<EventDelayPair> EventDelayPairs;
private void Start()
{
foreach (var eventDelayPair in EventDelayPairs)
{
StartCoroutine(InvokeDelayed(eventDelayPair.unityEvent, eventDelayPair.Delay));
}
}
private IEnumerator InvokeDelayed(UnityEvent unityEvent, float delay)
{
yield return new WaitForSeconds(delay);
unityEvent.Invoke();
}
[Serializable]
private class EventDelayPair
{
public UnityEvent unityEvent;
public float Delay;
}
}
[CustomEditor(typeof(ExampleScript))]
public class ExampleInspector : Editor
{
private SerializedProperty EventDelayPairs;
private ReorderableList list;
private ExampleScript _exampleScript;
private void OnEnable()
{
_exampleScript = (ExampleScript)target;
EventDelayPairs = serializedObject.FindProperty("EventDelayPairs");
list = new ReorderableList(serializedObject, EventDelayPairs)
{
draggable = true,
displayAdd = true,
displayRemove = true,
drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "DelayedEvents");
},
drawElementCallback = (rect, index, sel, act) =>
{
var element = EventDelayPairs.GetArrayElementAtIndex(index);
var unityEvent = element.FindPropertyRelative("unityEvent");
var delay = element.FindPropertyRelative("Delay");
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), delay);
rect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUI.GetPropertyHeight(unityEvent)), unityEvent);
},
elementHeightCallback = index =>
{
var element = EventDelayPairs.GetArrayElementAtIndex(index);
var unityEvent = element.FindPropertyRelative("unityEvent");
var height = EditorGUI.GetPropertyHeight(unityEvent) + EditorGUIUtility.singleLineHeight;
return height;
}
};
}
public override void OnInspectorGUI()
{
DrawScriptField();
serializedObject.Update();
list.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
private void DrawScriptField()
{
// Disable editing
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour(_exampleScript), typeof(ExampleScript), false);
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
}
}
Example
or with a preview of the delays for debugging
public class ExampleScript : MonoBehaviour
{
public List<EventDelayPair> EventDelayPairs;
private void Start()
{
foreach (var eventDelayPair in EventDelayPairs)
{
StartCoroutine(InvokeDelayed(eventDelayPair));
}
}
private IEnumerator InvokeDelayed(EventDelayPair pair)
{
var timer = pair.Delay;
do
{
timer -= Time.deltaTime * (1 / Time.timeScale);
pair.Delay = timer;
yield return null;
} while (timer > 0);
pair.Delay = 0;
pair.unityEvent.Invoke();
}
[Serializable]
public class EventDelayPair
{
public UnityEvent unityEvent;
public float Delay;
}
}
Btw your comment
//Time.deltaTime will increase the value with 1 every second.
is not formulated correctly. Instead it should be:
Time.deltaTime will increase the value every frame about the time passed in seconds since the last frame was rendered.
EDIT - Reduce delays afterwards
I understood from the question that you wanted to speed up the entire playback.
From the comments I learned now that instead you wanted to rather reduce the delay afterwards. So you can't use Time.timescale.
For this you can use the second example a bit altered:
[Serializable]
public class EventDelayPair
{
public UnityEvent unityEvent;
public float Delay;
// add a time multiplicator for each delay with default 1
public float TimeMultiplicator = 1.0f;
}
Note: You'll have to also add it to the EditorScript if you use it - I leave this as your homework ;)
private IEnumerator InvokeDelayed(EventDelayPair pair)
{
var timer = pair.Delay;
do
{
timer -= Time.deltaTime * pair.TimeMultiplicator;
pair.Delay = timer;
yield return null;
} while (timer > 0);
pair.Delay = 0;
pair.unityEvent.Invoke();
}
so you can in the Inspector or also by script
exampleScriptReference.EventDelayPairs[0].TimeMultiplicator = 2;
reduce the Delay faster.
You might also want to add an overall multiplicator like
// Again you have to add it to the inspector if you use it
public float overallMultiplicator = 1.0f;
//...
timer -= Time.deltaTime * pair.TimeMultiplicator * overallMultiplicator;

In Unity, limit reaction to an action only once every 1 second?

In a Scene in Unity3D, how can I make code react to an action only once every one second in a MonoBehaviour in the runloop?
If you're in a Unity3D environment, stop reading and look at Joe Blow's answer. Otherwise, continue reading.
You could use a Stopwatch to time your events. Create one Stopwatch as a private field/property and initialize it from your constructor:
public YourClass()
{
ScoreStopwatch = new Stopwatch();
ScoreStopwatch.Start();
// Other initialization...
}
private Stopwatch ScoreStopwatch { get; set; }
Then you can use the Elapsed property to get the time since your last score increase, like this:
if(Input.GetMouseButtonUp(0) && ScoreStopwatch.Elapsed.TotalSeconds > 1)
{
score++;
ScoreStopwatch.Reset();
ScoreStopwatch.Start();
}

Categories

Resources