The Problem:
Generate a method to score the players accuracy in navigating any loaded race course, on uneven terrain.
The Data:
Courses are recorded by users/players and stored as an array of positions that define the central "line" of the course.
The player controller maintains a "ground normalized position" which is the "point" result of a raycast it performs periodically to determine its current grounded status.
The Solution:
Generate a "get line distance" method using the ground normalized position of the player, an increment only index counter to move down the line, and a few square magnitude calculations to produce the closest square magnitude distance to/from the "line".
Use periodic samples from the "get line distance" method to calculate the overall average of the players navigation accuracy.
The Code:
coursePositions is a Vector3[] of the course positions, they are about 5m apart in the game world.
courseFinishPosIndex = coursePositions.Length-1..
currentPositionIndex starts @ zero and only increments..
private float GetLineDistance()
{
//retrieve the player's current raycast to ground position.
groundPosition = sbc.groundPosition;
if (groundPosition == Vector3.zero)
{
//in case the player's raycast failed for some reason
groundPosition = Player.transform.position;
}
if (currentPositionIndex + 1 <= courseFinishPosIndex)
{
//Calculate the square magnitude to the current index position and the next index position
csm = (groundPosition - coursePositions[currentPositionIndex]).sqrMagnitude;
nsm = (groundPosition - coursePositions[currentPositionIndex + 1]).sqrMagnitude;
//Compare them
while (csm > nsm && currentPositionIndex+1 < courseFinishPosIndex)
{
//Push the currentPositionIndex to the closest point on the line (to the player ground position).
currentPositionIndex++;
csm = (groundPosition - coursePositions[currentPositionIndex]).sqrMagnitude;
nsm = (groundPosition - coursePositions[currentPositionIndex + 1]).sqrMagnitude;
}
while (csm < 1000 && currentPositionIndex +1 < courseFinishPosIndex)
{
//Push the currentPositionIndex to ~1000sqMag AHEAD of the player.
currentPositionIndex++;
csm = (groundPosition - coursePositions[currentPositionIndex]).sqrMagnitude;
}
}
//return the current sqare magnitude.
return csm;
}
Debug Implementation:
In loop/at frequency of choice:
courseDistanceAccum += GetLineDistance();
courseSamples++;
Debug.Log("AvSqm: (" + courseDistanceAccum.ToString() + " / " + courseSamples.ToString() + ") " + (courseDistanceAccum/courseSamples).ToString());
Results:
So far, excellent. Gives a wide range of values(if you stray off course) that are easy to categorize and it will work for any line based course. Now I just need to implement some logic based on the result.
1000-1200 = Perfect score
2000+ = Just foolin around.
3000+ = Trying not to try.
4000+ = Disqualification zone.
CLARITY NOTE: These aren't the players "Final Score" this represents the average distance the player was from the central line of the course. Which I'll be translating into a Precision Percentage 0-100%(4000 - 1000) that will further modify the players overall standings.
The point score I'm currently deriving from these numbers is calculated as follows:
(in loop)
courseDistanceAccum += GetLineDistance();
courseSamples++;
if (courseDistanceAccum/courseSamples > 4000)
{
PlayerDisqualified();
}
if (racePointTimer.ElapsedMilliseconds > 500)
{
//Sample the current average every .5 seconds.
float curOffset = (4000 - (courseDistanceAccum / courseSamples)) / 10f; //Build point modifier. 300-0
if (curOffset < 100) curOffset *= -1; //Subtract points if player is too far off course.
playerScore += curOffset; //Modify score.
racePointTimer.Restart();
}
Essentially, the player earns close to 300pts every .5 seconds they are reasonably within the course boundaries.
If they go out of bounds, even for a short period, they experience a much lower score.
It also takes some intention to get disqualified.
I'm not that familiar with skiing / snowboarding, but as a novice I'd be expecting to have score lowered only if I went out of the gated route, i.e. measure the distance from the edges formed by adjacent flags to the player, if he is outside the route.
For any map you might be able to just precalculate on a grid the closest edge, then do a point - line segment distance calculation. You may have to take account of players cutting corners (e.g. missing out a whole section of course).
Another possibility is simply to have the gates change colour as the player goes through them. If they miss a gate they have to go back and get it to change colour, so there is a time penalty for missing a gate.
Also I read for slalom there seems to be single red and blue gates, and you have to go through the appropriate right or left side I think. This is another idea that might be good for some courses.