Advanced Customization (Code)
This guide is for developers who want to customize behavior, build custom UIs, or implement their own race rules.
1) Prefer Core for race logic
- Core (
Assets/RaceTiming/Core/) is pure C# and testable outside Unity. - Adapter/UI are Unity-specific. Keep them thin.
If you’re adding: - race rules, ranking logic, finish rules → Core - MonoBehaviour glue, transforms, colliders → Adapter - display-only code → UI
2) Subscribe to events (C#)
You can subscribe directly to Core events via the LapTimer:
using UnityEngine;
using RaceTiming.Adapter;
using RaceTiming.Core;
using RaceTiming.Core.Events;
public class RaceAnnouncer : MonoBehaviour
{
private LapTimer _timer;
void Start()
{
_timer = RaceTimingManager.Instance.LapTimer;
_timer.LapCompleted += OnLapCompleted;
_timer.NewSessionBestLap += OnNewSessionBestLap;
}
void OnDestroy()
{
if (_timer == null) return;
_timer.LapCompleted -= OnLapCompleted;
_timer.NewSessionBestLap -= OnNewSessionBestLap;
}
private void OnLapCompleted(LapCompletedEventArgs e)
=> Debug.Log($"Car {e.CompetitorId} lap {e.LapData.LapNumber} in {e.LapData.Duration:F3}s");
private void OnNewSessionBestLap(SessionBestLapEventArgs e)
=> Debug.Log($"SESSION BEST: {e.NewSessionBestLap.Duration:F3}s by {e.CompetitorId}");
}
UnityEvents alternative
If you prefer Inspector wiring, RaceTimingManager exposes UnityEvents for these same events.
3) Build your own UI
A common “hybrid” approach: - use events for discrete updates (lap complete, position changed) - poll session data for continuously changing values (live gaps)
Get the current session snapshot
var session = RaceTimingManager.Instance.LapTimer.GetSessionData();
On-track interval ordering
var intervals = RaceTimingManager.Instance.LapTimer.QueryTrackPositionIntervals(referenceDriverId);
4) Custom ranking strategies
Ranking is pluggable via IRaceRankingStrategy (Core):
public interface IRaceRankingStrategy
{
void UpdateStandings(Session session);
}
Built-in strategies:
- StandardRaceRanking (started first, then laps desc, then distance desc)
- BestLapRanking (best lap asc)
To add a new strategy:
1. Implement IRaceRankingStrategy in Core.
2. Set it on the RaceDirector (Core).
Note:
RaceDirectoris responsible for standings updates and also triggers lapped detection + gap calculation.
5) Custom finish conditions
Finish conditions are pluggable via ISessionFinishCondition (Core):
public interface ISessionFinishCondition
{
bool IsComplete(Session session, int competitorId);
IReadOnlyList<int> GetFinalOrder(Session session);
SessionProgress GetProgress(Session session);
}
Built-in:
- LapCountFinishCondition
Important implementation note
RaceTimingManager currently sets the finish condition using reflection to reach LapTimer’s internal session.
If you plan to extend finish logic heavily, consider adding a proper public API in Core (e.g., LapTimer.SetFinishCondition(...)) to avoid reflection.
6) Custom sector definitions
Sectors are defined by ratios around the lap.
- The default list is generated by
SectorDefinition.GenerateDefault(sectorCount). - You can manually edit sectors on the track asset (
TrackMarkerScriptableObject.sectors) in the Inspector, or by dragging the yellow sector handles in the Scene View.
Sector timing is triggered by the adapter calling:
- LapTimer.TriggerSector(competitorId, sectorId)
7) Testing recommendation
Core is designed to be testable via dotnet test (see tests/RaceTiming.Core.Tests/).
If you add race logic, prefer adding unit tests there.