The manner in which I keep track of the position of the race drivers is by calculating the distance each driver has driven. In order to do this we need to setup our track in a certain way.

TLDR

Our race track needs to be populated with waypoints. With these waypoints we create sectors. Using the sectors we can calculate the distance that the drivers have traveled. Now you only need to sort the race drivers on the distance traveled to know their current position.

Structure of a waypoint

The waypoints consist of 2 edge nodes which define the left (green cube) and right (red cube) side of the track. Some nodes for defining racing lines for the AI drivers. I’m using a left (green sphere), right (red sphere) and ideal (yellow sphere) racing line. In the center of the waypoint is a direction node (yellow pyramid shaped node) that defines the direction the waypoint is pointing.

Creating Sectors

At runtime I create sectors, a sector is the area between 2 waypoints. When a sector is created the length of the sector is calculated. This is done by calculating the length of the vector from the start of the sector (the position of the direction node of the start waypoint) to the end of the vector (the position of the direction node of the end waypoint.)

Track Manager

Every Level has a TrackManager which is responsible for registering the competitors of the race. The Track Manager holds a reference to all the sectors of the track.

The Car Control Component

The CarControlComponent provides data to the TrackManager. The CarControlComponent is aware of the current sector the car is in.
Every frame the CarControlComponent’s updateTrackPosition method is called. This method keeps track of the sector the car is driving in and calls the updateDistance method of the track manager.

The updateDistance method is responsible for updating 2 properties of the Competitor object, 1 is de distance traveled within the current sector, another is the accumulated distance of all the sectors before the current one. The competitor class has a computed property to calculate the total distance.

On every frame the track manager sort the list of competitors. The list is simply sorted on the total distance traveled.

How is distance calculated?

The distance is calculated in reference to an imaginary line that runs through the centers of all waypoints (position of direction nodes.)

In order to calculate the distance on the center line of the car at “B” we need to project the vector from A -> B onto the vector A -> F. The result of the projection is the vector A -> C. The length of the result vector is the distance traveled on the center line.

formula for vector projection
explanation about vector projection
Some Swift Source Code

Here are some partial classes I use in my game. You shouldn’t treat them as source code but more as an example, some of the classes need some dependencies that I haven’t included. It’s more to give you an idea of a possible implementation.

Sector class

This class is responsible for calculating the distance the car has driven within the sector, projected on to the center line of the sector.


class Sector
{
    private(set) var start: Waypoint!

    private(set) var end: Waypoint!

    var direction: SCNVector3

    var length: Float!

    private var sectorVector: SCNVector3!

    public init(start: Waypoint, end: Waypoint)
    {
        self.start = start
        self.end   = end

        let endPosition   = end.node.presentation.position
        let startPosition = start.node.presentation.position

        let sectorVector  = (endPosition - startPosition)
        self.sectorVector = sectorVector

        self.direction   = sectorVector.normalized()
        self.length      = sectorVector.length()
    }

    public func lengthInSector(for targetPosition: SCNVector3) -> Float
    {
        let vectorToProject = targetPosition - start.node.presentation.position

        return SCNVector3Project(vectorToProject, onto: sectorVector).length()
    }
}
Vector projection helper function

This is the actual calculation to project a vector onto another.


func SCNVector3Project(_ a: SCNVector3, onto b: SCNVector3) -> SCNVector3
{
    return b * (SCNVector3DotProduct(a, b) / SCNVector3DotProduct(b, b))
}
Track manager class

This class keeps track of the competitors.


import Foundation
import SceneKit
import SpriteKit
import GLKit

class TrackManager
{ 
    fileprivate(set) var competitors: [Competitor] = []

    func updateDistance(for raceCompetitorId: String, currentSector: Int, position: SCNVector3)
    {
        if let competitor = competitor(byRaceCompetitorId: raceCompetitorId) {
            if competitor.currentSector != currentSector {
                if currentSector > competitor.currentSector {
                    // Going forwards through sectors
                    let previousSector = sectors[competitor.currentSector]
                    competitor.accumulatedSectorDistance += previousSector.length
                } else {
                    // Going backwards through sectors
                    let previousSector = sectors[currentSector]
                    competitor.accumulatedSectorDistance -= previousSector.length
                }

                competitor.currentSector = currentSector
            }

            competitor.currentSectorDistance = sectors[currentSector].lengthInSector(for: position)
        }
    }

    func updateCompetitorPositions() -> [Competitor]
    {
        let sorted = competitors.sorted(by: { $0.totalDistance > $1.totalDistance })

        for (index, competitor) in sorted.enumerated() {
            competitor.currentPosition = index + 1
        }

        return sorted
    }
}
Competitor class

Here you see what kind of data is stored in the competitor objects.


import Foundation

public class Competitor
{
    public var isPlayer = false    

    public var currentLapTime: TimeInterval = 0.0

    public var currentLap = 0
    public var currentSector = 0
    public var currentPosition = 0
    public var finalPosition = 0

    public var accumulatedSectorDistance: Float = 0.0
    public var currentSectorDistance: Float = 0.0

    public var totalDistance: Float {
        return accumulatedSectorDistance + currentSectorDistance
    }

    public var displayPosition: Int {
        return max(1, currentPosition)
    }

    public fileprivate(set) var raceCompetitorId: String = ""
}
About the author
Leave Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

clear formSubmit