🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Scoring Navigation Precision on User Created Courses

posted in Septopus for project Unsettled World
Published December 31, 2018
C#
Advertisement

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. :D

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.

2 likes 0 comments

Comments

lawnjelly

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. :) 

December 31, 2018 08:38 AM
Septopus

I've updated the live build v0.3.4, it's now calculating points for races based on the above(nothing happens with these points yet though..).  It's also performing disqualifications. ;)

13 minutes ago, lawnjelly said:

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.

That's essentially what the above is doing, it provides for a range of space around a single line down the middle of the course that is considered in-bounds and your presence within that area increases your total point score.  This range of space stays right where the player should be at all times. ;)

13 minutes ago, lawnjelly said:

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. :) 

Unfortunately, these courses are all generated by the players themselves and I have no ability to precalculate anything.  I'm thinking about adding extra course features though that will be procedurally generated, some of those may be flags and gates with triggering rules attached to them.  I like that idea a lot. ;)

Yeah, this isn't a normal snowboarding game and normal scoring/maneuvering doesn't really happen at these speeds. 

I've hit almost 300km/h!  :D

So I'm pretty much making things up as I go as far as what to score on and how.

Fortunately, this method takes care of cutting corners and scoring positively unless the player goes significantly outside the marked course.  The player can go outside of the boundaries or cut corners, but their final point score will be diminished significantly and they risk disqualification.

December 31, 2018 09:12 AM
Septopus

@lawnjelly, I think I understand where you were coming from now.  1200 being better than 4000, yeah that doesn't make much sense for a score does it. 

I wrote that pretty late in the evening/early in the morning. 

I've added a new section explaining how I actually generate a Point Score based on this method to the end of the blog.  It should make a little more sense. :D

December 31, 2018 06:26 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement