Untitled Racing Game

Category

Game

Status

Completed

Duration


1 week

Team Size

5 Members

Role

Gameplay & AI Programmer

Engine

Unity

Languages

C#

This project was for the Advanced Gameplay course at Future Games,

where each team member was assigned specific tasks to complete within

one week development time. My role was to implement the AI for the

opponent racing cars, which includes automatic steering, speed control

and environmental awareness.

    /**
     * Computes steering force to avoid nearby vehicles.
     */
    protected float ComputeSeparationSteer() {
        //Query-based, no OnTriggerEnter etc are needed
        Collider[] hits = Physics.OverlapSphere(transform.position, separationRadius, vehicleLayer, QueryTriggerInteraction.Ignore);

        if (hits.Length == 0)
            return 0f;

        Vector3 separation = Vector3.zero;

        foreach (var hit in hits) {
            if (hit.attachedRigidbody == rb)
                continue;

            Vector3 away = transform.position - hit.transform.position;
            float dist = away.magnitude;
            if (dist < 0.01f)
                continue;

            float weight = 1f - (dist / separationRadius);
            separation += away.normalized * weight;
        }

        Vector3 localSeparation = transform.InverseTransformDirection(separation);
        float speedFactor = 1f - GetSpeedPercentage(); // 1 = slow, 0 = fast
        return Mathf.Clamp(localSeparation.x * speedFactor, -1f, 1f);
    }

    /**
     * Computes throttle based on curve sharpness and steering.
     */
    protected float ComputeThrottle(float steering) {
        float throttle = maxThrottle;

        // Brake for upcoming curve
        float curve = ComputeCurveSharpness();
        throttle *= Mathf.Lerp(1f, minThrottle, curve);

        return Mathf.Clamp(throttle, minThrottle, maxThrottle);
    }

    /**
     * Estimates how sharp the upcoming curve is.
     */
    protected float ComputeCurveSharpness() {
        float speedPercentage = GetSpeedPercentage();
        float currentLookAhead = Mathf.Lerp(minCornerLookAhead, maxCornerLookAhead, speedPercentage);
        float currentSensitivity = Mathf.Lerp(minCornerSensitivity, maxCornerSensitivity, speedPercentage);

        float currentDistance = splineCache.length * splineT;

        float d1 = currentDistance + currentLookAhead;
        float d2 = currentDistance + currentLookAhead * 2f;

        float t1 = DistanceToT(d1);
        float t2 = DistanceToT(d2);

        Vector3 dir1 = spline.transform.TransformDirection((Vector3)spline.Spline.EvaluateTangent(t1)).normalized;
        Vector3 dir2 = spline.transform.TransformDirection((Vector3)spline.Spline.EvaluateTangent(t2)).normalized;

        // 0 = straight, 1 = very sharp
        return Mathf.Clamp01(Vector3.Angle(dir1, dir2) / currentSensitivity

Steering and Speed Control

These functions of the AI system handles the opponent cars’ driving behaviour by combining vehicle avoidance, adaptive speed control, and track awareness. The separation steering logic detects nearby vehicles and adjusts steering to avoid collisions, creating a more realistic racing behaviour. The throttle system dynamically reduces speed when approaching sharp corners by analyzing the upcoming track curvature, allowing the cars to navigate turns more naturally and maintain control at different speeds.

Vehicle Controller:

Spline Navigation

void FixedUpdate() {
    if (!EnsureSpline())
        return;

    UpdateSplineReference();
    SteerAndMove();
}

/**
 * Ensures the spline and its arc-length cache are available.
 */
protected bool EnsureSpline() {
    if (spline == null || spline.Spline == null)
        return false;

    splineCache = SplineCacheManager.Get(spline, arcLengthSamples);
    return splineCache != null;
}

/**
 * Finds the nearest point on the spline to the vehicle
 * and updates splineT accordingly.
 */
void UpdateSplineReference() {
    float3 localPos = spline.transform.InverseTransformPoint(transform.position);

    SplineUtility.GetNearestPoint(
        spline.Spline,
        localPos,
        out float3 nearestLocal,
        out splineT
    );

    nearestSplinePointWorld = spline.transform.TransformPoint(nearestLocal);
}

/**
 * Converts a distance along the spline into a normalized t value.
 */
protected float DistanceToT(float distance) {
    return splineCache.DistanceToT(distance, spline.Spline.Closed);
}

/**
 * Computes steering and throttle based on spline following,
 * lateral error correction, and vehicle separation.
 */
protected void SteerAndMove() {
    float currentDistance = splineCache.length * splineT;
    float targetDistance = currentDistance + lookAheadDistance;
    float targetT = DistanceToT(targetDistance);
    Vector3 targetLocal = spline.Spline.EvaluatePosition(targetT);
    lookAheadPointWorld = spline.transform.TransformPoint(targetLocal);
    lookAheadPointWorld += transform.right * laneOffset;
    Vector3 localTarget = transform.InverseTransformPoint(lookAheadPointWorld);

    if (localTarget.sqrMagnitude < 0.0001f)
        return;

    float lookAheadSteer = localTarget.x / localTarget.magnitude;
    Vector3 toSpline = nearestSplinePointWorld - transform.position;
    float lateralError = Vector3.Dot(transform.right, toSpline);
    float separationSteer = ComputeSeparationSteer();
    float steering = Mathf.Clamp(lookAheadSteer + lateralError * lateralCorrectionStrength +
        separationSteer * separationStrength, -1f, 1f);

    car.Steering = steering;
    car.Throttle = ComputeThrottle(steering

Implementation of an AI-driven vehicle movement system in Unity using spline-based path following. The vehicle determines the nearest point on the spline and uses a predictive look-ahead target further along the path for steering. The system also incorporates driving behaviours such as lane offsetting, lateral path correction and collision avoidance.

© 2026 Sebastian Valck. All rights reserved.