Lap timer script - need help perfecting "Best lap"

I’ve been working on a Lap timer and I’m nearly there but I’ve got one minor fault in the code that I’d like help with.
I’ve got a trigger on the start finish line and it’s all working as it should be expect for the fact that the “Best Lap” time is being updated to match the “Last lap” time if the car is taking longer than the recorded “Best”.
To try and illustrate:
Lap 1 time = 1:00
Lap 2 time = 0.50
upto this point it’s working well (I think)
Lap 3 time = 0.70
Lap 4 > 0.70
when the current lap time is greater than the best recorded so far it changes the best to equal the last lap time.

I’m really quite new to coding and so any help would be much appreciated.

private var startTimer;
private var Timer : int;
private var roundedTimer : int;
private var Current;
private var displaySeconds : int;
private var displayMinutes : int;
private var Last;
private var LastMinutes : int;
private var LastSeconds : int;
private var Best;
private var BestMinutes : int;
private var BestSeconds : int;
private var Lastlap;

var font : Font;


function OnTriggerExit(car : Collider)  
	{
    startTimer = Time.time;
	}

function OnTriggerEnter(car : Collider)
	{
	LastMinutes = displayMinutes;
	LastSeconds = displaySeconds;
	Lastlap = ((LastMinutes*60) + LastSeconds);
	}
	
function OnGUI () 
{

//make sure that your time is based on when this script was first called
    //instead of when your game started
var currentTime = Time.time - startTimer;

    Timer = currentTime;

	//display the timer
    roundedTimer = Mathf.CeilToInt(Timer);
    displaySeconds = roundedTimer % 60;
    displayMinutes = roundedTimer / 60; 

Current = String.Format ("{0:00}:{1:00}", displayMinutes, displaySeconds);

	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 0, 150, 50), "Current Lap:" +(Current));
	GUI.EndGroup ();
	
Last = String.Format("{0:00}:{1:00}", LastMinutes, LastSeconds);
	
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 20, 150, 50), "Last Lap:" +(Last));
	GUI.EndGroup ();
	
	if (Lastlap < roundedTimer)
	{
	BestMinutes = LastMinutes;
	BestSeconds = LastSeconds;
	}
	else if (Lastlap > roundedTimer)
	{
	BestMinutes = BestMinutes;
	BestSeconds = BestSeconds;
	}
	
Best = String.Format("{0:00}:{1:00}", BestMinutes, BestSeconds);
	
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 40, 150, 50), "Best Lap:" +(Best));
	GUI.EndGroup ();
}

You’ll notice I’ve got a bit saying else if (X > Y) B = B; I’m trying to say leave B as it is i.e. do nothing but when I changed the code to use what I say someone else was using as s ‘do nothing’ command i.e. return; it seemed to do nothing but actually scrapped it. As I said, I learning as I go along here and so please excuse any noob mistakes.

Thanks in advance.

else if (Lastlap > roundedTimer)
{
	BestMinutes = BestMinutes;
	BestSeconds = BestSeconds;
}

I’m wondering what this is for?
Why set something equal to itself?

well… first this bit:

else if (Lastlap > roundedTimer)
{
BestMinutes = BestMinutes;
BestSeconds = BestSeconds;
}
is unnecessary, you don’t need to re-assign the values to themselves, just remove the lines or comment them out.

Second, lastlap is calculated as int seconds, where as roundedTimer is a rounded int. So, when the lap time is .7, roundedTImer should be 1, and lastLap should be .7, but if the actual time is 1.7, then roundedTImer will be 2 and lastLap will be 60.7. So, until the lap takes more than 1 minute, lastLap will always be less than roundedTimer.

[edit] I keep re-reading your code, trying to figure out why you’re handling timer this way. Display seconds should be Mathf.CeilToInt(currentTime) I believe. Current time is already expressed in seconds, with the numbers after decimal being partial seconds. In any case, there in lies your issue. The variables exist during the scene, so really you want to calc the best time in the OnCollisionEnter() not the OnGUI().

Hope this helps,

Galen

Ok, try this:

function OnTriggerEnter(car : Collider) {
	Lastlap = Time.time - startTimer;
        // for simplicity's sake, let's just calc the value:
        var bestTimeInSeconds : int = (BestMinutes * 60) + BestSeconds;
        if (Lastlap < bestTimeInSeconds)
	{
	BestMinutes = Mathf.CeilToInt(bestTimeInSeconds / 60);
	BestSeconds = bestTimeInSeconds % 60;
	}
}

then remove (or comment out) all the lastlap calc code in OnGUI()

Let me know if this works.

Cheers,

Galen

private var startTimer;
private var Current=0.0;
private var Last=Mathf.Infinity;
private var Best=Mathf.Infinity;


function OnTriggerEnter(car : Collider) {
	Last = Time.time - Current;
        if(Last<Best) Best=last;
        Current=Time.time;
}

function secondsToTime(t){
        var h=Mathf.Floor(t/360);
        var m=Mathf.Floor((t-h * 360)/60);
        var s=t - h * 360 - m* 60;
        return h.ToString("00") + ":" + m.ToString("00") + ":" + s.ToString("00");
}

In your OnGUI, simply call secondsToTime(Best) to get the best time in hours, mins and seconds…

Thanks for the replies.

To be honest the bit about the timer and roundedTimer came from another script that I pulled out with fully understanding how they work. All I know is that it worked in that it created a clock that works. So I was trying to adapt it to make it work as a lap timer. I’ll look into it further and try to understand what it is doing exactly. I’ve been meaning to do it because I need to adapt the timer to make it (mins:seconds:subseconds 00:00:000) but wanted to resolve the other problem first being adding changing it.

I did try to explain my reasoning behind

else if (Lastlap > roundedTimer)
{
	BestMinutes = BestMinutes;
	BestSeconds = BestSeconds;
}

but obviously not very well. I’m trying to complete the solution and didn’t have it in there at one point but because I’ve got the bit saying:

if (Lastlap < roundedTimer)

I thought to limit the possible errors I was having I tried to tell it what to do if the opposite was the case i.e. Lastlap > roundedTimer but in this scenario I just want to say if this is true do nothing - leave the values as they were. Hence best = best. If I remove this part completely will it just do nothing if Lastlap > roundedTimer I presume?

I’ll take a look and let you know how I get on.

Ok, I’m starting to lose the will to live with this one. I have tried to make it simpler and have now got sub-seconds included also. I still can’t get the Best lap feature to work, what am I doing wrong? I’ve tried moving various bits of the code around to see if the order that things happen make a difference and sometimes it does but still no solution that works.

var startTimer;
var currentMinutes;
var currentSeconds;
var currentlap;
var LastMinutes;
var LastSeconds;
var Lastlap;
var BestMinutes;
var BestSeconds;
var Bestlap;
var LastTime : int = ((LastMinutes*60)+LastSeconds);
var BestTime : int = ((BestMinutes*60)+BestSeconds);

var font : Font;

function Awake ()
	{
	currentlap = 0;
	Lastlap = 0;
	BestMinutes = 99;
	BestSeconds = 99;
	}

function OnTriggerExit(car : Collider)  
	{
    startTimer = Time.time;

	Bestlap = String.Format("{0:00}.", BestMinutes)+(BestSeconds);
	Lastlap = String.Format("{0:00}.", LastMinutes)+(LastSeconds);
	}
	
function OnTriggerEnter(car : Collider)
	{

	LastMinutes = currentMinutes;
	LastSeconds = currentSeconds;

	//if (((LastMinutes*60)+LastSeconds) < ((BestMinutes*60)+BestSeconds))
	if (LastTime<BestTime)
	{
	BestMinutes = currentMinutes;
	BestSeconds = currentSeconds;
	}
	

	}
		
function OnGUI () 
{
	currentTime = (Time.time - startTimer);
	currentMinutes = Mathf.FloorToInt((currentTime)/60);
	currentSeconds = ((currentTime)%60).ToString("F3");
	currentlap = String.Format ("{0:00}.", currentMinutes)+(currentSeconds);

	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 0, 150, 50), "Current Lap:" +(currentlap));
	GUI.EndGroup ();
	
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 20, 150, 50), "Last Lap:" +(Lastlap));
	GUI.EndGroup ();
	
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 40, 150, 50), "Best Lap:" +(Bestlap));
	GUI.EndGroup ();
	
}

Thanks in advance.

Ok, BigMisterB’s approach was cleaner, so I started with a combo of yours and his, give this a try:

var startTimer : float;
//var currentMinutes;
var currentSeconds : float;
var currentlap : String;
//var LastMinutes;
var LastSeconds : float;
//var Lastlap;
//var BestMinutes;
var BestSeconds : float;
//var Bestlap;
//var LastTime : int = ((LastMinutes*60)+LastSeconds);
//var BestTime : int = ((BestMinutes*60)+BestSeconds);

var font : Font;

function Awake ()
	{
	currentlap = 0;
	Lastlap = 0;
	BestMinutes = 99;
	BestSeconds = 99;
	}


// assuming that enter is the "end of a lap"
function OnTriggerEnter(car : Collider) {
	var lapTimeInSec : float = Time.time - startTimer;
    if(lapTimeInSec<BestSeconds) 
    	BestSeconds=lapTimeInSec;
    LastSeconds=lapTimeInSec;
}

// this function just converts time in seconds/milliseconds to minutes:seconds:subseconds as 00:00:000
function secondsToTime(t : float) : String {
        //var h=Mathf.Floor(t/360); // no need for hours (unless this is one HUGE lap!)
        var m : int = Mathf.Floor(t/60);// minutes 
        var s : int = Mathf.Floor(t- m*60); // seconds 
        var ss : int = t - (m*60) - s; // leaves subseconds (milliseconds)
        return(m.ToString("00") + ":" + s.ToString("00") + ":" + ss.ToString("000"));
}

// here we start the race by leaving the finish line trigger
function OnTriggerExit(car : Collider)  
	{
    startTimer = Time.time;

	//Bestlap = String.Format("{0:00}.", BestMinutes)+(BestSeconds);
	//Lastlap = String.Format("{0:00}.", LastMinutes)+(LastSeconds);
	}
	
//function OnTriggerEnter(car : Collider)
//	{
//
//	LastMinutes = currentMinutes;
//	LastSeconds = currentSeconds;
//
//	//if (((LastMinutes*60)+LastSeconds) < ((BestMinutes*60)+BestSeconds))
//	if (LastTime<BestTime)
//	{
//	BestMinutes = currentMinutes;
//	BestSeconds = currentSeconds;
//	}
//	
//
//	}
		
function OnGUI () 
{
	var currentTime : float = (Time.time - startTimer);
	//currentMinutes = Mathf.FloorToInt((currentTime)/60);
	//currentSeconds = ((currentTime)%60).ToString("F3");
	//currentlap = String.Format ("{0:00}.", currentMinutes)+(currentSeconds);

	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 0, 150, 50), "Current Lap: " +secondsToTime(currentTime);//(currentlap));
	GUI.EndGroup ();
	
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 20, 150, 50), "Last Lap:" +secondsToTime(LastSeconds);//(Lastlap));
	GUI.EndGroup ();
	
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 40, 150, 50), "Best Lap:" +secondsToTime(BestSeconds);//(Bestlap));
	GUI.EndGroup ();
	
}

Hope this helps,

Galen

Hi Galen,
I really appreciate your help on this one but the code doesn’t work. Apart from a couple of ) missing, I had to change the ‘current lap’ from a string to int (otherwise it complained about not being able to convert int to string) not sure if this was the right thing to do or not but it compiled. The milliseconds (subseconds) don’t work and I tried a similar approach at one point to what you’ve done (-s) but it ended up giving me negative value, where as yours just displays 000. The Best lap doesn’t update either.

Even though this one is taking me a long time to solve I am learning so much about the coding so I do really appreciate all your help. I’ve got put this to the back of the job lists for a few days now but I will come back to it. Any more updates are much appreciated.

Cheers,
Andy

I still can’t seem to make the Best/fastest lap function work. I’ve got 1 trigger object that is on the start line and I’m using the enter and exit trigger functions.
I’ve tried many different configurations of the code, with no success. I’m convinced it’s all in the order that the variables are updated but I can’t seem to get the correct order.

The code below is what I think should work but it doesn’t.
I need to save the previous laps time (LastTime) so I can compare it to the current lap time.

var startTimer;
var currentMinutes;
var currentSeconds;
var currentlap;
var currentTime;
var LastMinutes;
var LastSeconds;
var Lastlap;
var LastTime;
var BestMinutes;
var BestSeconds;
var Bestlap;
var BestTime;
public var Lap : int = -2;


var font : Font;

function Awake ()
	{
	currentlap = 0;
	Lastlap = 0;
	Bestlap = 0;
	Lap = -2;
	}
	
function OnTriggerEnter(car : Collider)  
	{
	Lap += 1;
	LastMinutes = currentMinutes;
	LastSeconds = currentSeconds;
	Lastlap = String.Format("{0:00}.", LastMinutes)+(LastSeconds);
	
	//LastTime = (LastMinutes*60 + LastSeconds);
	BestTime = (currentMinutes*60 + currentSeconds);

	if (LastTime < BestTime)
	{
	BestMinutes = LastMinutes;
	BestSeconds = LastSeconds;
	Bestlap = String.Format("{0:00}.", BestMinutes)+(BestSeconds);
	}
	}
	
function OnTriggerExit(car : Collider)
	{
    startTimer = Time.time;
	LastTime = (LastMinutes*60 + LastSeconds);
	}
		
function OnGUI () 
{
	currentTime = (Time.time - startTimer);
	currentMinutes = Mathf.FloorToInt((currentTime)/60);
	currentSeconds = ((currentTime)%60).ToString("F3");
	currentlap = String.Format ("{0:00}.", currentMinutes)+(currentSeconds);

	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 0, 150, 50), "Current Lap:" +(currentlap));
	GUI.EndGroup ();
	
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 20, 150, 50), "Last Lap:" +(Lastlap));
	GUI.EndGroup ();
		
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 40, 150, 50), "Best Lap:" +(Bestlap));
	GUI.EndGroup ();

	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 80, 150, 50), "Lap:" +(Lap));
	GUI.EndGroup ();
}

This was another way I thought of doing it. I realise I am only comparing the current lap with the one before and not all the laps gone before but this doesn’t work either. I’m sure there is a way to store each lap time better than having to create a variable for every lap. What if I need the number of laps to be hundreds?? If someone could help me do this also that would be great.

var startTimer;
var currentMinutes;
var currentSeconds;
var currentlap;
var currentTime;
var LastMinutes;
var LastSeconds;
var Lastlap;
var LastTime;
var BestMinutes;
var BestSeconds;
var Bestlap;
var BestTime;
public var Lap : int = -2;


var font : Font;

function Awake ()
	{
	currentlap = 0;
	Lastlap = 0;
	Bestlap = 0;
	Lap = -2;
	}
	
function OnTriggerEnter(car : Collider)  
	{
	Lap += 1;
	LastMinutes = currentMinutes;
	LastSeconds = currentSeconds;
	Lastlap = String.Format("{0:00}.", LastMinutes)+(LastSeconds);
	
	LastTime = (LastMinutes*60 + LastSeconds);

var Lap0 = 99999;
var Lap1;
var Lap2;
var Lap3;
var Lap4;
var Lap5;
	
	if (Lap == 1)
	{
	Lap1 = LastTime;
	}
	if (Lap == 2)
	{
	Lap2 = LastTime;
	}
	if (Lap == 3)
	{
	Lap3 = LastTime;
	}
	if (Lap == 4)
	{
	Lap4 = LastTime;
	}
	if (Lap == 5)
	{
	Lap5 = LastTime;
	}

	if ((Lap == 1)  (Lap1 < Lap0))
	{
	BestMinutes = LastMinutes;
	BestSeconds = LastSeconds;
	}
	if ((Lap == 2)  (Lap2 < Lap1))
	{
	BestMinutes = LastMinutes;
	BestSeconds = LastSeconds;
	}
	if ((Lap == 3)  (Lap3 < Lap2))
	{
	BestMinutes = LastMinutes;
	BestSeconds = LastSeconds;
	}
	if ((Lap == 4)  (Lap4 < Lap3))
	{
	BestMinutes = LastMinutes;
	BestSeconds = LastSeconds;
	}
	if ((Lap == 5)  (Lap5 < Lap4))
	{
	BestMinutes = LastMinutes;
	BestSeconds = LastSeconds;
	}
	}
	
function OnTriggerExit(car : Collider)
	{
    startTimer = Time.time;
	Bestlap = String.Format("{0:00}.", BestMinutes)+(BestSeconds);
	}
		
function OnGUI () 
{
	currentTime = (Time.time - startTimer);
	currentMinutes = Mathf.FloorToInt((currentTime)/60);
	currentSeconds = ((currentTime)%60).ToString("F3");
	currentlap = String.Format ("{0:00}.", currentMinutes)+(currentSeconds);

	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 0, 150, 50), "Current Lap:" +(currentlap));
	GUI.EndGroup ();
	
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 20, 150, 50), "Last Lap:" +(Lastlap));
	GUI.EndGroup ();
		
	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 40, 150, 50), "Best Lap:" +(Bestlap));
	GUI.EndGroup ();

	GUI.BeginGroup (Rect (540,10,200,200));
		GUI.skin.font = font;
		GUI.contentColor = Color.black;
		GUI.Label (Rect (0, 80, 150, 50), "Lap:" +(Lap));
	GUI.EndGroup ();
}

After I’ve cracked the storing of all lap times driven (don’t have to be displayed for now) locally, I may want the user to be able to upload their best lap time to a server at a later date. I’ve been working on this for hours and I could really do with some help in getting this working. If you can spot what I am doing wrong, please enlighten me because I’m stumped on this one.

I’m struggling with this right now myself. Any updates?

You can do something like this… (Sorry its in C#, but it’s trivial to convert)

     void OnTriggerEnter(Collider collider)
     {

        if(collider.gameObject.name == "StartFinish")
          AddLap();
     }
    
    void AddLap()
    {
        _localPlayerLap++;

        if (_bestLap.TotalMilliseconds == 0)        
            _bestLap = _lapTimer;                    
        else if (_lapTimer < _bestLap)            {
                _bestLap = _lapTimer;        

       UpdateBestLapUI(); // Update your UI

        // Reset lap time
        _lapTimer = _lapTimer.Subtract(_lapTimer);
    }

Note : The above code will work in your vehicle class, but in my case the OnTriggerEnter method is on my vehicle class, and the AddLap is on my GameManager instance. You’ll need to do it this way if you want to track not only the player’s best lap, but AI or other multiplayer client best laps, so the lap timer/best lap logic should be decoupled from the vehicle logic.

@Meltdown - thank you very much for the help. I now have a working lap system. You cant really cheat the lap count, you have to hit checkpoints on the track before it increments. You can reset the lap timer by driving over the finish line and backing up…I’ll figure that out. Again thank you for the support. You can test out the build at www.fatboxsoftware.com

With the above code… of course that will happen.
Let me give you some advice on how to approach two other problems you’ll face.
(I faced them in my Monster Truck game)

Problem 1 : Resetting the lap timer by driving over the finish line and backing up.
Solution : Create 2 or 3 other triggers around various parts of the track. Trigger1, Trigger2, Trigger3.
When starting a race, Trigger 1 must be entered, then Trigger 2, then Trigger 3. In that order (to prevent player going wrong way).
When the player crosses the finish line, check that all 3 triggers were activated to register a lap.
Then reset all the triggers. (Just use a boolean for each trigger register), then add the lap. If he backs up now, trigger1, 2 and 3 haven’t been triggered yet, so a new lap won’t be registered.

Problem 2 : Determining race position at all times
Solution : There are several ways to approach this, I’ll be interested to hear the solution you take.

Problem 2 theoretical solution:

Divide track in parts, put all parts in an array, cast a ray down from the car, get track piece identifier from raycast hit.

Far from efficient, but low difficulty.