How do you measure milliseconds at runtime?

I didn’t fully read all the replies yet, but seeing this you would be able to get the exact time if you keep track of the goal line position, previous frame position, previous frame time, current position, current time and speed.

I am not sure on the exact math, but there definitely is a formula to calculate at what time the car would have crossed.

2 Likes

I am talking about car but I am making a Star Wars Podracing game so my current car is that.

I only want to calculate the time for the capsule and not for the engines. I will continue with the sweep test and if I don’t come up with something good enough, I will try another method.

Thank you for your constructive feedback.

Couldn’t you also just take the distance past the checkpoint and the speed of the vehicle and work backwards from there?

That could also be a solution. I think I will lose a bit of precision, but it will be easier.

It works but the resulting time should be between 0 and 0.02s but I can get higher values. This is due to the “myCollider” which is the collider of the chekpoint and the GetClosestPoint. The OnTriggerEnter doesn’t always trigger with the best accuracy, so the position is a bit off, causing the rest to be inaccurate.

I will stick with this for now and see if I can find a better solution later.

void OnTriggerEnter(Collider other)
{
    if (other.gameObject.layer != mask) return;

    Vector3 hitPosition = myCollider.ClosestPoint(other.transform.position);

    Vector3 direction = other.GetComponent<Rigidbody>().velocity;
    Vector3 previousPosition = other.transform.position - direction * Time.fixedDeltaTime;

    float speed = direction.magnitude;
    float distance = Vector3.Distance(hitPosition, previousPosition);
    float time = distance / speed;

    Debug.Log(speed + "\t |" + distance + "\t |" + time);
}

In a pursuit of better accuracy it seems one might benefit from the greatest degree of timekeeping precision by going in the direction of private double timeSinceStartup = Time.realtimeSinceStartupAsDouble; and align all the timekeeping variables to that.

void OnCollisionEnter(Collision collision) shall be called as many times as there are collisions in a frame. As you are apparently set to catch only one such event (i.e. crossing the line) just record the measurement, flag it recorded and you are done. Also don’t be misled, any one of your objects, especially a complex one, can (and probably) should have multiple colliders on it, smaller and well fitting. It does not have to be just one big ‘sphere.’ It could be more smaller ones fitting the shape of a racer’s capsule. You may consider putting something of a following script on your finishing line game object. It’s a “one racing pod” script but could be easily made for an array of racing pods.

using UnityEngine;

public class FinishingLineCollision : MonoBehaviour
{
    double timeStart, timeFinish, timeRace;
    bool isTimeCollected;   

    public void RaceStarted()
    {
        isTimeCollected = false;
        timeStart = Time.realtimeSinceStartupAsDouble;
    }

    //Detect collisions between other games objects with the lot of colliders attached to this game object, i.e. the finishing line
    void OnCollisionEnter(Collision collision)
    {
        //Check for a match with the specified name on any game object that collides with the finishing line
        if (!isTimeCollected && collision.gameObject.name == "MyRacingPod")
        {
            timeFinish = Time.realtimeSinceStartupAsDouble;
            timeRace = timeFinish - timeStart;
            isTimeCollected = true;
        }
    }
}

Nevertheless, since you might need it for this or related stuff, you may want to explore coroutines (i.e. make stuff run in parallel to your main script code, e.g. same thread but intertwined) and threading (make code run in parallel threads).

On the side, as one can hardly overlook the nature of the mentioned game, is it bound to be a multiplayer one? Considering aimed timekeeping precision, are you planning to implement state prediction/verification algorithms because of network latency?

Most racing involves tracking a point at the forward end of the car (bumper?) or the nose of the runner or whatever: the first racer’s bumper to cross the finish line “plane” is the winner.

Let’s call that special forepart the “nose.” If you have the position of that “nose” at T1 and T2 (previous and current frames) then you can use the linear positions of the nose vs the finish line plane to work out the fraction of time for each player at the moment they crossed the line.

(from your image above)

T1 = orange solid box
T2 = orange dotted box

Use the normal of the finish line plane and Vector3.Dot() to get Car1@T1, Car1@T2, Car2@T1 and Car2@T2 linear positions, and use those to Lerp between T1 and T2 for each car.

1 Like

Here is my implementation of the above algorithm. Wiggle the cars around, rotate stuff to any arbitrary rotation or orientation, whatever.

You can either drag cars in or the code will create them for you.

View the console window for output of the two times.

1 Like

I noticed something stange.

I tried using the C# Stopwatch' and the Time.realtimeSinceStartupAsDouble’, the first one I start and the second one, I store the value inside a float in the Start method.

void Start()
{
    stopwatch.Start();
    time = Time.realtimeSinceStartupAsDouble;
}

When I press forward with no movement, just in a straight line to the first checkpoint, I always get a different time. I don’t know if it’s because of the editor, and I’m not going to build it. Or if it is just because Unity is not made for this or something else.

Any values you read from any API in code called from Unity is going to be quantized directly to whenever Unity gets around to calling you.

Here is some timing diagram help:

Otherwise, the code I posted above lets you find relative “aheadness” or “behindness” in time when an agent crosses a finish line.

Take a look through Unity’s documentation on how it handles timing. If you’re simulating your game logic/physics in FixedUpdate then measuring time using real time is not going to give you correct or consistent results. You need to compute the time by multiplying the number of fixed updates that were run from the start of the race until the when finish line is crossed by the fixed update timeslice. A more accurate time can be estimated using methods that have been mentioned in the thread (such as what Kurt and Adrian detailed): by analytically determining where during the final update the entity crossed the finish line.

You can also get more precise timing if you increase the physics update rate. I believe TrackMania’s physics updates at 100 times per second. That may seem like a lot but even Quake updated its physics at 77 times per second and that was 25 years ago.

1 Like

You cannot have a millisecond accuracy no matter what you do. Even when you are using a timer that reports in milliseconds that is actually fake, it is an estimation due to OS constraints.

At best, any kind of timer in C# will have an accuracy of 10ms. Anything lower than that is an estimation, as it is being constrained on how the OS handles the execution of different threads. For more details see: https://learn.microsoft.com/en-us/archive/blogs/mediasdkstuff/why-are-the-multimedia-timer-apis-timesetevent-not-as-accurate-as-i-would-expect

If greater accuracy than 10ms is needed, then this can be achieved by using native interop and call the Windows multimedia timer which has about one millisecond precision. You have to call timeBeginPeriod to inform windows that we need high accuracy and then call timeSetEvent. After that, timeKillEvent will stop the timer and timeEndPeriod will inform windows that high precision isn’t needed anymore.

This has many drawbacks:

  1. Is very performance intensive, not something you want to have running in an action game.
  2. Is Windows only, or rather OS specific, so you have to write different code for different operating systems.
  3. Is hardware constrained, its accuracy depends on the target hatdware and if it can dedicate a whole physical thread to just measure time.
  4. Unity used to have some problems when trying to run code that used the winmm.dll, don’t know how it is right now and if it works on different builds.

Generally speaking you should “fake it”, as most games do. Nobody can actually measure with ms accuracy this kind of thing. Even the lag between the signal of you GPU and when it is displayed on screen is typically more than 1ms and different for each display type, the same is true for the lag between the controller and you pc, and so on for the communication between the different hardware parts.

Millisecond accuracy is reserved for calculations in the CPU, there is not a way to have that accuracy when other parts of the hardware are involved like the different input methods, a display for showing the results, communication between CPU/GPU, plus any OS overrides and thread handling.

1 Like

Upon even more reflection, is accuracy important, or is determinism and fairness more important? Because I feel like you’d actually want to record time in such a way that is fair and deterministic, rather than “accurate” to certain decimal points.

Case in point, even though old games such as Goldeneye had very weird ways of handling time, the in-game timer is still used in the speed running community as its the most fair way to compare times across runs. This is the case for a lot of speed running in old games.

So perhaps most fair is what Zulo suggested:

Or do something similar with your own timer, since that would be a ‘fair’ way to determine the time that should be the same for all players, as everyone will be subject to the same level of accuracy within the engine anyway.

Thank you for all your feedback!

In the best case scenario, I’d love to have everything, of course, but I don’t think that’s possible. If you have ever played Trackmania 2020, I am looking for something similar with 0.001 accuracy.

I think the most important thing is the determinism. With the same inputs, it is very important to have the same result and the same time at the checkpoint.

I tried the stopwatch and this was not the case, so I guess I should add up all the Time.fixedDeltaTime and then do my own calculation to determine the elapsed milliseconds.

I was interested in the OnCollision function, but it stops my Podracer when it passes the checkpoint on the right. Is it possible to detect the collision without being stopped?

Use a trigger instead?

Even though Unity’s normal 3d physics is not necessarily 100% deterministic across different machines due to slight floating point differences across different implementations, the physics themselfs is already highly deterministic. Some time ago I actually did some physics simulations in Unity by having something like 30 rigidbody objects in a closed box (6 enclosing box colliders) and apply “random” initial positions and velocities to all of them. Of course with a set seed value, so when I re-run the simulation it starts woth the same initial condition. I disabled gravity, and let the objects bounce all over the place inside the box for 30 seconds (actually a fix number of physics frames, 1500) and recorded the position, rotation, velocity and angular velocity of every rigidbody in that system. The results of 3 separate runs through this simulation were 100% identical.

The only factors which make things non-deterministic do include user input or other things which may be frame rate dependent. For example the usual input value ramp up / ramp down usually is frame rate dependent. It’s “fixed” with deltaTime, but it’s still independent of the FixedUpdate.

As for the precision of certain in-game events like hitting a checkpoint or the finish line, as it was pointed out, this can be simply calculated by doing a frame to frame analysis.

If you need / want super precise checkpoint / finish line detection, trigger and colliders probably are not your friends here. Slight differences in the rotation of the collider would change the AABB and may trigger one physics frame earlier or later. It also makes it more difficult to determine the intermediate point when you actually “crossed” the line. In order to have a very consistent result, you probably should check a single point of your car and it counts when this point crosses the finish line. So the finish line can be a mathematical Plane / or mesh and you can use raycasts to determine when it actually crosses this plane. Of course when you cross such a finish or checkpoint line, there’s always one frame before and one frame after the the line. When you cast a ray from the point before to the point after, you get an intersection point or distance from the first point to the hit point. When you divide this distance value by the distance between the before and after point, you get a fractional “lerp” value between those two points. Each of those points have a time value associated to them. They probably run on the fixedTime as this would give you the most deterministic results. You can simply lerp between those two times using that fractional value you determined. This will give you an ultra precice time value, probably much more precise than 1ms as the only limit is floating point accuracy. So unless you create an extremely huge world where the world space positions gets really large (so you loose the fine granularity of the position) you can get really precise with the time.

Don’t get me wrong. If this is a super important property like in Trackmania, you may want to pick an even more reliable physics engine, especially if consistency across platforms is important. Though solving the timing of events essentially works the same way.

3 Likes

You can use modifiable contacts.

And keep in mind that the OnTriggerEnter and OnCollisionEnter events don’t occur until the second frame of intersection and so this could be throwing off your calculation if you’re assuming that the intersection occurred between the time of the previous frame and the current frame.

3 Likes

Uhhh, nice. When was this event introduced? I was hoping for such an event for years.

Ahh it seems it was introduced in 2021.2. There’s quite a bit you can do with the ModifiableContactPair.

Because there’s so few examples in the documentation I think people are struggling to understand how to use it and so it rarely gets mentioned.

It’s great for making fast moving objects that can pass through other objects and yet still trigger a collision. Whereas if you tried to do the same with regular triggers then they’ll be limited to discrete collision detection which isn’t reliable for detecting fast moving objects.

2 Likes