← Back to projects

Neon Racer [On Development]

Futuristic time trial racing game with a focus on flow and speed.

Role Gameplay & Game Design
Team 2 Developers/Game Designer
Duration 10 weeks
Stack Unity / C#

The Context

Neon Racer is being developed as part of a school project based on designing a controller with good game feel. The project is based on a prototype I made between the end of our previous project and the launch of this one.

In this game, you play a ship that is attracted to the ground regardless of its orientation in a futuristic setting. Our goal is to create a dynamic game with a gameplay loop based on completing a level 100% and competing for the high score.

My Contributions

  • 01 Controller Implemented a fully functional controller
  • 02 Chrono Implemented a fully functional chronometer system with checkpoints

Learning & Implementation

The Hover Physics

The biggest challenge with hover vehicles is making them feel grounded. A simple force often results in endless bouncing. I implemented a Spring-Damper system (Hooke's Law) on each of the 4 thrusters independently. This allows the ship to tilt and react to uneven terrain naturally, just like a car suspension.

HoverController.cs HOOKE'S LAW APPLICATION

// Physics logic applied to each thruster (corner of the ship)

// 1. Calculate Compression (Spring)
// How far are we compressed compared to the target height?
float compression = _currentHoverTarget - hit.distance;

// 2. Calculate Damping (Shock Absorber)
// We check the vertical velocity at this specific thruster point.
// This prevents the ship from oscillating/bouncing endlessly.
float downwardSpeed = Vector3.Dot(rb.GetPointVelocity(thruster.position), transform.up);

// 3. Apply Hooke's Law Formula (F = -kx - bv)
// Spring Strength pushes up, Damper resists the movement.
float force = (compression * settings.springStrength) - (downwardSpeed * settings.springDamper);

// Apply the calculated force at the exact position of the thruster logic
rb.AddForceAtPosition(transform.up * force, thruster.position);

Sticking to the Track (Spline Projection)

In a game with loops and vertical walls, standard gravity doesn't work. Instead of complex raycast averaging, I used the track's Spline Data. By projecting the ship's position onto the spline, I retrieve the exact surface normal and smoothly align the ship's "Up" vector to it. This ensures the ship never falls off, even upside down.

HoverController.cs SPLINE PROJECTION

// Aligning the ship to the track surface (Spline)

if (currentSpline != null)
{
    // Project our position onto the spline to find the closest point
    SplineSample sample = new SplineSample();
    currentSpline.Project(transform.position, ref sample);

    // Smoothly blend the ship's "Up" vector towards the track's normal.
    // We use Slerp to keep it fluid, avoiding jerky rotations on steep loops.
    averageNormal = Vector3.Slerp(averageNormal, sample.up, 0.8f);
}

// Calculate the new forward direction relative to this surface normal
Vector3 targetForward = Vector3.ProjectOnPlane(transform.forward, averageNormal).normalized;
Quaternion targetRotation = Quaternion.LookRotation(targetForward, averageNormal);

// Apply rotation using physics
rb.MoveRotation(Quaternion.Slerp(rb.rotation, targetRotation, Time.fixedDeltaTime * settings.alignSpeed));