- Home /
How to get a precise time measurement for a stop watch function (race game)?
Hello everyone,
I am working at my first Unity project which is a simple race game. Now I encountered that my time measurement is not very exact and varies from run to run. Even though only within the milliseconds, but if I want to create a highscore list, that can make a relevant difference.
My test track is the following:
A short straight track
"car" accelerates on its own, so no user interaction which might cause a delay
the time measurement starts after a 3 seconds count down (after the car starts the acceleration).
the time is stopped after hitting (collision) the finishing line (which is an obstacle in my case).
The observed time span with this setting is between 2:50 sec and 2:80 sec, so quite huge.
With my little knowledge about Unity I assume that it could have to do something with how and especially when unity calls the different methodes, in my case the time span between 2 update() calls. Am I on the right track?
Anyhow, how can I get a precise time measurement?
Here is my stopwatch script:
public class Stopwatch : MonoBehaviour
{
float timer;
float msec;
float sec;
float min;
[SerializeField] Text txt_stopWatch;
bool doCalculation;
// Start is called before the first frame update
void Start()
{
doCalculation = false;
timer = 0;
}
// Update is called once per frame
void Update()
{
if (doCalculation)
{
StopWatchCalculation();
}
}
void StopWatchCalculation()
{
timer += Time.deltaTime;
//example: timer = 1.3375 | int(timer) = 1 | (int)((timer - (int)timer) * 1000) = 337
msec = (int)((timer - (int)timer) * 1000);
sec = (int)(timer % 60);
min = (int)(timer / 60 % 60);
txt_stopWatch.text = string.Format("{0:00}:{1:00}:{2:00}", min, sec, msec);
}
public static string GimmeTimeFormat(float time)
{
float tmpMsec = (int)((time - (int)time) * 1000);
float tmpSec = (int)(time % 60);
float tmpMin = (int)(time / 60 % 60);
return string.Format("{0:00}:{1:00}:{2:00}", tmpMin, tmpSec, tmpMsec);
}
public void StartStopwatch()
{
doCalculation = true;
}
public void StopStopwatch()
{
doCalculation = false;
}
Answer by rh_galaxy · Nov 01, 2021 at 01:48 PM
Something like this? :
public class Player : MonoBehaviour
{
bool bTimeCounting = false;
internal float fTotalTime = 0.0f;
//I use FixedUpdate() since I think it is most fair to players
// but may be done in Update also using Time.deltaTime
void FixedUpdate()
{
if (bTimeCounting) fTotalTime += Time.fixedDeltaTime;
}
void Update()
{
if(...) bTimeCounting = true; //set bTimeCounting to true only after all init/loading is
// done, so any hickups won't be part of the time. Perhaps have a start-line needed to
// be crossed to begin time counting
//finished?
/*if (iCurLap == iTotalLaps)
{
bTimeCounting = false;
}*/
}
public static string GimmeTimeFormat(float time)
{
//...
}
}
Thanks for your reply.
Unfortunately, the change from Update to FixedUpdate did not really change anything.
Your hint about waiting until all loading etc is fully completed is good. Is there any explicit indicator of Unity which I can use to know when everything is done? At the moment I have my 3 seconds countdown before the start which probably should be enough, but without any guarantee.
Let me try again
The reason it's not precise is probably that you do physics in Update() instead of FixedUpdate(), or have low/unstable framerate. In my VR-game I have a fixed 90 Hz Update and set FixedUpdate to 100 Hz instead of the default 50, it has very exact ti$$anonymous$$g down to 10ms.
The problem is not the calculation of the timers that you can either put in Update() or FixedUpdate(), but a varying framerate that causes Physics to be different different runs.
Is it a mobile or a PC?
My target system is mobile, however so far I only use the PC for testing.
I have been reading through several pages to better understand the problem you stated.
.
However, at this unity doc page: https://docs.unity3d.com/ScriptReference/Application-targetFrameRate.html it says that it is not easy to play around with the targetFrameRate, since you might end up with a worse result if you don't do it right.
At the moment I am setting up a test setting and then I will play around with the targetFrameRate which is probably what you used for your fixed Hz frequence, right?
The fixed 90 Hz framerate is because of the VR-headset, all VR headsets work at a fixed frequency, but not always the same, it varies from 60-144 Hz. It's up to the game to meet this pre-set framerate. If frames are missed it is noticeable by the user so you better produce frames at that rate. On PC the gfx-card either runs free of what's called V-sync and produce frames as fast as possible, or it can be fixed at 60Hz commonly, but sometimes other like 120Hz. It is not as noticeable if frames are missed on a mobile or PC screen as in VR.
Answer by XoetziX · Nov 11, 2021 at 08:47 AM
Anyone else any idea how to get a precise time measurement as possible?
Answer by Pindwin · Nov 11, 2021 at 12:16 PM
Look into System.Diagnostics.Stopwatch
class. Bear in mind reading will be different between the two different object Update
calls (in the same frame), so you probably want to read it once every frame anyway? It doesn't get more precise that though.
Thanks for your reply.
I haven't used System.Diagnostics.Stopwatch yet. Is it "only" a utility class which makes it easier or is it really more precise than my manual way of using Time.deltaTime?
Which "two different object update call" do you mean? I do not get your point here.
As I said, it's as precise as it gets: normally, you'd use it for stuff like profiling how fast your code runs, but it's still de facto standard way of measuring time spans in C# - unless you actually do want the imprecise Unity measurement, and I believe you do!
Now, it might be that your issue is with the way Unity works in general: there is a game loop, and for each frame in a game loop, Update
will be called on each MonoBehaviour
derived class that implements it. So, in the same frame, UnityEngine.Time.time
and UnityEngine.Time.deltaTime
will be exactly the same on Update in every object with a script attached (even though - from physics point of view - a different timespan has passed). However, if your mesurement takes place in Updates on different frames (which is what I meant when talking about two different object update calls), time will differ.
And, probably most importantly - your test is not very accurate as well. I wouldn't expect the run time to be deter$$anonymous$$istic. Sure, it should be similar, but framerate differences will add up. So, the browser running in the background could affect your results. For anything physics related, you probably want to use FixedUpdate
, which makes an attempt to be as predictable as possible. I see someone else already suggested it to you and it didn't work, but, well, it should work.
Also, the way you are doing the float-to-displayed-time calculation is weird. If you use the Stopwatch
class, you'll have access to this. I didn't run the code, but with the amount of int
casts, you could be losing precision during conversion maybe? Though nothing like that I'd see with a naked eye.
Thanks a lot for your detailed explanation which helped to understand these things!
.
I will definitely give the Stopwatch a chance, because it seems to be a helpful class anyway and I am curious about comparing both approaches.
Your answer
Follow this Question
Related Questions
more resource efficient TIMER 2 Answers
how to make a varied countdown timer 1 Answer
Timer script is turning minutes into seconds 2 Answers