
Godot Genre Racing
- 135 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-genre-racing for development tasks
About
godot-genre-racing: A skill for development. This provides functionality for development workflows.
- godot-genre-racing
Godot Genre Racing by the numbers
- 135 all-time installs (skills.sh)
- +8 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,658 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-genre-racingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 135 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-genre-racing for development tasks
Files
Genre: Racing
Expert blueprint for racing games balancing physics, competition, and sense of speed.
NEVER Do (Expert Anti-Patterns)
Physics & Handling
- NEVER use a rigid camera attachment; strictly use a Smooth Follow pattern with
lerp()to prevent motion sickness. - NEVER prioritize realism over fun; strictly increase Gravity Scale (2x-3x) and keep friction high for responsive arcade feel.
- NEVER use
VehicleBody3Ddefault settings for karts; strictly rewrite suspension using Raycasts or custom spring/damper models. - NEVER apply steering torque directly to mass; strictly use a steering curve factored by lateral velocity.
- NEVER calculate suspension without a damper model; strictly include damping to prevent eternal oscillation (bouncing).
- NEVER ignore the Center of Mass property; strictly offset it downward to ensure stability during high-speed turns.
- NEVER multiply engine force by
delta; it is an integrated force in the physics solver. - NEVER rely on
is_action_pressed()for manual gear shifting; strictly useis_action_just_pressed()for single-tap accuracy.
AI & Competition
- NEVER use static AI speeds; strictly use Rubber-Banding to keep races competitive based on player distance.
- NEVER run AI pathfinding across the entire track every frame; strictly use a "Look-Ahead" point on a spline/path.
- NEVER ignore racing Checkpoints; strictly enforce sequential
Area3Dvalidation to prevent track shortcuts. - NEVER use standard
Area3Dfor slipstreaming without a Dot Product check to ensure the player is directly behind.
Visuals & Audio
- NEVER skip "Sense of Speed" effects; strictly implement dynamic FOV scaling, motion blur, and high-speed camera shake.
- NEVER update minimap transforms for static elements in
_process(); strictly update dynamic racers only. - NEVER serialize ghost cars as mass transform lists; strictly store positions/quaternions at fixed intervals.
- NEVER use constant pitch for engine sounds; strictly map RPM or engine load to
pitch_scale. - NEVER spawn particles for skid marks every frame; strictly use Trail3D or procedural strips for low-cost persistence.
- NEVER use standard Strings for surface detection; strictly use
StringName(e.g.,&"asphalt").
---
🛠 Expert Components (scripts/)
Original Expert Patterns
- arcade_vehicle_physics.gd - High-performance arcade handling with custom gravity, air control, and friction-slip drifting.
- spline_ai_controller.gd - Professional racing AI using Path3D predictive steering and rubber-banding logic.
Modular Components
- arcade_vehicle_controller.gd - Alternative tight, raycast-based vehicle movement model for non-physics karts.
- slipstream_handler.gd - Drafting zones with relative dot-product checks for speed boosts.
- lap_tracker.gd - High-precision lap management with sequential checkpoint logic.
- ghost_recorder.gd - Binary transform serialization for lightweight ghost car playback.
- engine_audio_controller.gd - RPM-to-pitch audio synthesis for engine revving and gear shifts.
- skid_mark_emitter.gd - Conditional tire-slip trail system for persistent visual feedback.
- minimap_icon_projector.gd - 3D-to-2D bridge for projecting racers onto a localized UI.
- force_feedback_router.gd - Haptic and rumble management based on terrain and collisions.
- raycast_suspension.gd - Spring/damper model for raycast wheels with configurable stiffness.
- racing_checkpoint.gd - Indexed trigger gate for modular track-based lap progression.
---
Core Loop
1. Race: Player controls a vehicle on a track. 2. Compete: Player overtakes opponents or beats the clock. 3. Upgrade: Player earns currency/points to buy parts/cars. 4. Tune: Player adjusts vehicle stats (grip, acceleration). 5. Master: Player learns track layouts and optimal lines.
Skill Chain
| Phase | Skills | Purpose |
|---|---|---|
| 1. Physics | physics-bodies, vehicle-wheel-3d | Car movement, suspension, collisions |
| 2. AI | navigation, steering-behaviors | Opponent pathfinding, rubber-banding |
| 3. Input | input-mapping | Analog steering, acceleration, braking |
| 4. UI | progress-bars, labels | Speedometer, lap timer, minimap |
| 5. Feel | camera-shake, godot-particles | Speed perception, tire smoke, sparks |
Architecture Overview
1. Vehicle Controller
Handling the physics of movement.
# car_controller.gd
extends VehicleBody3D
@export var max_torque: float = 300.0
@export var max_steering: float = 0.4
func _physics_process(delta: float) -> void:
steering = lerp(steering, Input.get_axis("right", "left") * max_steering, 5 * delta)
engine_force = Input.get_axis("back", "forward") * max_torque2. Checkpoint System
Essential for tracking progress and preventing cheating.
# checkpoint_manager.gd
extends Node
var checkpoints: Array[Area3D] = []
var current_checkpoint_index: int = 0
signal lap_completed
func _on_checkpoint_entered(body: Node3D, index: int) -> void:
if index == current_checkpoint_index + 1:
current_checkpoint_index = index
elif index == 0 and current_checkpoint_index == checkpoints.size() - 1:
complete_lap()3. Race Manager
high-level state machine.
# race_manager.gd
enum State { COUNTDOWN, RACING, FINISHED }
var current_state: State = State.COUNTDOWN
func start_race() -> void:
# 3.. 2.. 1.. GO!
await countdown()
current_state = State.RACING
start_timer()Key Mechanics Implementation
Drifting
Arcade drifting usually involves faking physics. Reduce friction or apply a sideways force.
func apply_drift_mechanic() -> void:
if is_drifting:
# Reduce sideways traction
wheel_friction_slip = 1.0
# Add slight forward boost on exit
else:
wheel_friction_slip = 3.0 # High gripRubber Banding AI
Keep the race competitive by adjusting AI speed based on player distance.
func update_ai_speed(ai_car: VehicleBody3D, player: VehicleBody3D) -> void:
var dist = ai_car.global_position.distance_to(player.global_position)
if ai_car_is_ahead_of_player(ai_car, player):
ai_car.max_speed = base_speed * 0.9 # Slow down
else:
ai_car.max_speed = base_speed * 1.1 # Speed upGodot-Specific Tips
- VehicleBody3D: Godot's built-in node for vehicle physics. It's decent for arcade, but for sims, you might want a custom RayCast suspension.
- Path3D / PathFollow3D: Excellent for simple AI traffic or fixed-path racers (on-rails).
- AudioBus: Use the
Dopplereffect on the AudioListener for realistic passing sounds. - SubViewport: Use for the rear-view mirror or minimap texture.
Common Pitfalls
1. Floaty Physics: Cars feel like they are on ice. Fix: Increase gravity scale (2x-3x) and adjust wheel friction. Realism < Fun. 2. Bad Camera: Camera is rigidly attached to the car. Fix: Use a Marker3D with a lerp script to follow the car smoothly with a slight delay. 3. Tunnel Vision: No sense of speed. Fix: Increase FOV as speed increases, add camera shake, wind lines, and motion blur.
Advanced Racing Meta-Systems
Elite implementation of competitive integrity, aerodynamics, and auditory realism.
1. Drift-Boost (Mini-Turbo)
Implement a drift-boost mechanic by accumulating a charge variable during the _physics_process() callback while the player is in a drift state. Upon release, apply a burst of speed using apply_central_impulse() on the RigidBody3D (or VehicleBody3D), creating the classic arcade "Mini-Turbo" effect.
class_name DriftBoostSystem extends Node
var drift_charge: float = 0.0
const BOOST_MULTIPLIER = 1000.0
func _physics_process(delta: float) -> void:
if owner.is_drifting:
drift_charge += delta
elif drift_charge > 0:
execute_boost()
func execute_boost() -> void:
var boost_force := owner.global_transform.basis.z * (drift_charge * BOOST_MULTIPLIER)
owner.apply_central_impulse(boost_force)
drift_charge = 0.02. Tire-Smoke Particles
Attach a GPUParticles3D node to each wheel and toggle the emitting property based on the wheel's traction state. This provides immediate visual feedback for drifting, burnouts, and high-speed braking.
class_name TireSmokeManager extends Node3D
@export var smoke_particles: GPUParticles3D
@export var wheel: VehicleWheel3D
func _process(_delta: float) -> void:
# Emit smoke if the wheel is slipping significantly
smoke_particles.emitting = wheel.get_skidinfo() < 0.53. Replay-Ghost Binary Storage
For high-performance ghost car replays, convert positional and rotational data into a PackedVector2Array or use Godot's binary .res format via ResourceSaver. This is significantly faster and more compact than text-based formats for storing frame-by-frame data.
class_name GhostRecorder extends Node
var frame_data: PackedVector3Array = []
func record_frame(pos: Vector3) -> void:
frame_data.append(pos)
func save_ghost_binary(path: String) -> void:
var file := FileAccess.open(path, FileAccess.WRITE)
if file:
file.store_var(frame_data) # Binary Variant serialization
file.close()Architectural Tip: When implementing Drift-Boost, use a Tween to briefly increase the Camera FOV during the boost to enhance the sense of sudden acceleration.
Reference
- Master Skill: godot-master
Reference
- Master Skill: godot-master
# arcade_vehicle_controller.gd
extends CharacterBody3D
class_name ArcadeVehicleController
# Raycast-Based Arcade Vehicle Controller
# Predictable, tight steering and suspension without the complexity of VehicleBody3D.
@export var engine_force := 40.0
@export var steering_limit := 0.4
@export var suspension_rest_dist := 0.5
@export var suspension_stiffness := 30.0
@export var suspension_damping := 2.0
func _physics_process(delta: float) -> void:
var input_dir = Input.get_vector(&"move_left", &"move_right", &"move_forward", &"move_back")
# Steering
rotation.y -= input_dir.x * steering_limit * delta * (velocity.length() * 0.1)
# Simple Engine Acceleration
var forward_dir = -global_transform.basis.z
velocity += forward_dir * input_dir.y * engine_force * delta
# Friction/Drag
velocity *= 0.98
move_and_slide()
# skills/genre-racing/scripts/arcade_vehicle_physics.gd
extends VehicleBody3D
## Arcade Vehicle Physics (Expert Pattern)
## Custom tweaks for VehicleBody3D to make it feel "fun" rather than realistic.
## Increases gravity, modifies friction for drifting, and handles air control.
class_name ArcadeVehiclePhysics
@export var gravity_scale: float = 3.0 # Arcade cars need to stick to the ground
@export var air_control: float = 0.5
@export var drift_friction_slip: float = 1.0
@export var normal_friction_slip: float = 3.0
var is_drifting: bool = false
func _ready() -> void:
# Setup wheels
for child in get_children():
if child is VehicleWheel3D:
child.wheel_friction_slip = normal_friction_slip
func _physics_process(delta: float) -> void:
# 1. Custom Gravity
if not is_on_floor():
apply_central_force(Vector3.DOWN * 9.8 * gravity_scale * mass)
# 2. Air Control (Tilt)
var pitch = Input.get_axis("forward", "back") * air_control
var roll = Input.get_axis("left", "right") * air_control
apply_torque(transform.basis.x * pitch + transform.basis.z * roll)
# 3. Drifting Logic
if Input.is_action_pressed("drift"):
is_drifting = true
for child in get_children():
if child is VehicleWheel3D and not child.use_as_steering: # Rear wheels usually
child.wheel_friction_slip = drift_friction_slip
else:
is_drifting = false
for child in get_children():
if child is VehicleWheel3D:
child.wheel_friction_slip = normal_friction_slip
func is_on_floor() -> bool:
# Simple raycast check or wheel contact check
for child in get_children():
if child is VehicleWheel3D and child.is_colliding():
return true
return false
## EXPERT USAGE:
## Attach to VehicleBody3D. Map 'drift', 'forward', 'back', 'left', 'right' actions.
# engine_audio_controller.gd
extends AudioStreamPlayer3D
class_name EngineAudioController
# Engine Audio Simulation (Pitch-Based RPM)
# Maps vehicle speed/RPM to audio pitch for realistic revving.
@export var min_pitch := 0.5
@export var max_pitch := 2.5
@export var speed_scale := 0.05
func update_engine_sound(current_speed: float) -> void:
# Pattern: Calculate pitch based on speed/RPM curve.
var target_pitch = min_pitch + (current_speed * speed_scale)
pitch_scale = lerp(pitch_scale, clamp(target_pitch, min_pitch, max_pitch), 0.1)
# force_feedback_router.gd
extends Node
class_name ForceFeedbackRouter
# Force-Feedback and Rumble Coordination
# Manages controller vibration based on collisions and track surface.
func trigger_impact(intensity: float, duration: float) -> void:
# Pattern: Use Input.start_joy_vibration for localized haptics.
var device_id = 0 # Assume first controller
Input.start_joy_vibration(device_id, intensity * 0.5, intensity, duration)
func shake_on_surface(surface_type: StringName) -> void:
match surface_type:
&"grass":
Input.start_joy_vibration(0, 0.1, 0.1, 0.1)
&"rough":
Input.start_joy_vibration(0, 0.3, 0.3, 0.1)
# ghost_recorder.gd
extends Node
class_name GhostRecorder
# Ghost Car Recording (Compact Serialization)
# Caches position and rotation at intervals for low-memory replay loops.
var recording: Array[Dictionary] = []
var record_interval := 0.1
var time_since_last_record := 0.0
func _physics_process(delta: float) -> void:
time_since_last_record += delta
if time_since_last_record >= record_interval:
time_since_last_record = 0.0
_record_snapshot()
func _record_snapshot() -> void:
var parent = get_parent() as Node3D
if not parent: return
recording.append({
"p": parent.global_position,
"r": parent.global_quaternion
})
func save_ghost(path: String) -> void:
var file = FileAccess.open(path, FileAccess.WRITE)
# Pattern: Use binary store_var for speed and compactness.
file.store_var(recording)
file.close()
extends Node
class_name LapCheckpointManager
## Expert Lap Manager (Godot 4.6).
## Sequence-validated checkpoints to prevent cheating.
signal lap_completed(total_laps: int)
var next_expected_id: int = 1
var current_lap: int = 0
@export var total_checkpoints: int = 4
func _on_checkpoint_passed(id: int) -> void:
if id == next_expected_id:
if id == total_checkpoints:
next_expected_id = 1
current_lap += 1
lap_completed.emit(current_lap)
else:
next_expected_id += 1
else:
print("Cheated or skipped! Expected %d, got %d" % [next_expected_id, id])
## [SKILL NOTICE]: Use 'Sequence-ID' validation (1 -> 2 -> 3) rather
## than just distance to prevent players from bypassing the track.
# lap_tracker.gd
extends Node
class_name LapTracker
# Lap/Time Tracking with Validation
# Sequential checkpoint checks to prevent "cheating" by reversing through the start.
signal lap_completed(total_time: float)
var current_lap := 1
var next_checkpoint_idx := 0
var lap_start_time := 0.0
var checkpoints: Array[Node3D] = []
func _ready() -> void:
lap_start_time = Time.get_ticks_msec() / 1000.0
func on_checkpoint_passed(checkpoint: Node3D) -> void:
var idx = checkpoints.find(checkpoint)
# Pattern: Validate sequence to stop lap-shortcuts.
if idx == next_checkpoint_idx:
next_checkpoint_idx += 1
if next_checkpoint_idx >= checkpoints.size():
_complete_lap()
func _complete_lap() -> void:
var end_time = Time.get_ticks_msec() / 1000.0
var lap_time = end_time - lap_start_time
lap_completed.emit(lap_time)
current_lap += 1
next_checkpoint_idx = 0
lap_start_time = end_time
# minimap_icon_projector.gd
extends Control
class_name MinimapIconProjector
# Minimap Projection (Camera3D Unprojection)
# Maps 3D global positions to a 2D UI minimap rectangle.
@export var map_rect: Rect2
@export var track_bounds: Rect2
@export var target_node: Node3D
func _process(_delta: float) -> void:
if not target_node: return
var pos_3d = target_node.global_position
# Pattern: Normalize world position within track bounds then scale to UI rect.
var nx = inv_lerp(track_bounds.position.x, track_bounds.end.x, pos_3d.x)
var ny = inv_lerp(track_bounds.position.y, track_bounds.end.y, pos_3d.z)
position.x = map_rect.position.x + (nx * map_rect.size.x)
position.y = map_rect.position.y + (ny * map_rect.size.y)
# racing_checkpoint.gd
extends Area3D
class_name RacingCheckpoint
# Sequential Checkpoint Validation Gate
# Signal-based tracker for lap progression.
@export var checkpoint_index := 0
signal crossed(node: Node3D, idx: int)
func _on_body_entered(body: Node3D) -> void:
if body.is_in_group(&"player"):
crossed.emit(body, checkpoint_index)
# raycast_suspension.gd
extends RayCast3D
class_name RaycastSuspension
# Physics-Based Suspension (Raycast Spring)
# Calculates upward force based on compression to simulate vehicle bounce.
@export var stiffness := 40.0
@export var damping := 3.0
var last_compression := 0.0
func get_spring_force(delta: float) -> float:
if is_colliding():
var contact_dist = (get_collision_point() - global_position).length()
var compression = clamp(target_position.length() - contact_dist, 0.0, 1.0)
# Spring force
var force = compression * stiffness
# Damping (velocity of compression)
var vel = (compression - last_compression) / delta
force += vel * damping
last_compression = compression
return force
last_compression = 0.0
return 0.0
extends RigidBody3D
class_name RaycastVehicleController
## Expert Raycast Vehicle (Godot 4.6).
## Manual suspension and friction for crisp arcade feel.
@export var spring_stiffness: float = 30.0
@export var spring_damping: float = 2.0
@export var spring_rest_length: float = 0.5
@export var tire_grip: float = 2.0
@onready var wheels: Array[RayCast3D] = [$W1, $W2, $W3, $W4]
func _physics_process(delta: float) -> void:
for wheel in wheels:
if wheel.is_colliding():
var depth = spring_rest_length - wheel.get_collision_point().distance_to(wheel.global_position)
var spring_force = (depth * spring_stiffness)
# Apply upward force at wheel position
apply_force(Vector3.UP * spring_force, wheel.position)
# Lateral Friction (Grip)
var lateral_vel = global_transform.basis.x.dot(get_velocity_at_local_point(wheel.position))
apply_force(-global_transform.basis.x * lateral_vel * tire_grip, wheel.position)
## [SKILL NOTICE]: Avoid 'VehicleBody3D' for arcade feel. Manually
## calculate suspension via RayCast3D and apply forces to a RigidBody3D.
# skid_mark_emitter.gd
extends Node3D
class_name SkidMarkEmitter
# Tire Smoke and Skid Marks
# Triggers visual effects based on side-slip or drift state.
@export var smoke_particles: GPUParticles3D
@export var threshold := 2.0
func _physics_process(_delta: float) -> void:
var car = get_parent() as CharacterBody3D
if not car: return
# Calculate lateral velocity (drifting)
var side_vel = car.global_transform.basis.x.dot(car.velocity)
# Pattern: Only emit when exceeding a slip threshold to save draw calls.
if abs(side_vel) > threshold:
if smoke_particles: smoke_particles.emitting = true
_place_skid_mark()
else:
if smoke_particles: smoke_particles.emitting = false
func _place_skid_mark() -> void:
# Implementation for spawning Mesh/Trail nodes here.
pass
# slipstream_handler.gd
extends Area3D
class_name SlipstreamHandler
# Slipstream/Drafting Mechanics
# Provides a velocity boost when following a lead car within a specific cone.
@export var boost_multiplier := 1.2
@export var max_angle_deg := 15.0
func _on_area_entered(area: Area3D) -> void:
var parent = get_parent()
if area.get_parent() is ArcadeVehicleController:
var lead_car = area.get_parent()
var to_lead = (lead_car.global_position - parent.global_position).normalized()
var dot = to_lead.dot(-lead_car.global_transform.basis.z)
# Pattern: Verify dot product to ensure we are actually behind the car.
if dot > cos(deg_to_rad(max_angle_deg)):
_apply_draft_boost(parent)
func _apply_draft_boost(car: Node3D) -> void:
if "velocity" in car:
car.velocity *= boost_multiplier
# skills/genre-racing/scripts/spline_ai_controller.gd
extends VehicleBody3D
## Spline AI Controller (Expert Pattern)
## Racing AI that follows a Path3D with look-ahead steering and rubber-banding.
class_name SplineAIController
@export var path: Path3D
@export var max_speed: float = 50.0 # m/s
@export var rubber_band_strength: float = 0.1
@export var target_node: Node3D # The player (for rubber banding)
var current_offset: float = 0.0
var look_ahead: float = 20.0
func _physics_process(delta: float) -> void:
if not path: return
# 1. Find Closest Point on Curve
var curve = path.curve
# Approximating offset update based on speed (better than searching nearest every frame)
current_offset += linear_velocity.length() * delta
if current_offset >= curve.get_baked_length():
current_offset -= curve.get_baked_length() # Loop
# 2. Calculate Steering Target (Look Ahead)
var target_offset = current_offset + look_ahead
if target_offset >= curve.get_baked_length():
target_offset -= curve.get_baked_length()
var target_pos = path.to_global(curve.sample_baked(target_offset))
var local_target = to_local(target_pos)
# 3. Apply Steering
# Simple P-controller: steer towards x component of local target
steering = clamp(local_target.x * 0.1, -0.4, 0.4)
# 4. Apply Throttle / Rubber Banding
var desired_speed = max_speed
if target_node:
var dist = global_position.distance_to(target_node.global_position)
# If behind, speed up
# We need to know track position to know who is ahead, simple distance check is flawed on loops
# Assuming linear track progress for simple rubber band example:
if current_offset < _get_node_track_offset(target_node):
desired_speed *= (1.0 + rubber_band_strength)
else:
desired_speed *= (1.0 - rubber_band_strength)
if linear_velocity.length() < desired_speed:
engine_force = 1000.0
brake = 0.0
else:
engine_force = 0.0
brake = 10.0
func _get_node_track_offset(node: Node3D) -> float:
# Helper to find approx offset of player
return path.curve.get_closest_offset(path.to_local(node.global_position))
## EXPERT USAGE:
## Assign a Path3D (the racing line). Adjust look_ahead for smoother turns.
extends Path3D
class_name SplineTrackSpawner
## Expert Track Spawner (Godot 4.6).
## Places obstacles/points perfectly along a Curve3D.
@export var obstacle_scene: PackedScene
@export var spacing: float = 10.0
func generate_obstacles() -> void:
var curve: Curve3D = self.curve
var length = curve.get_baked_length()
var current_offset = 0.0
while current_offset < length:
var pos = curve.sample_baked(current_offset)
var up = curve.sample_baked_up_vector(current_offset)
var obj = obstacle_scene.instantiate()
add_child(obj)
obj.global_position = global_position + pos
obj.look_at(obj.global_position + curve.sample_baked(current_offset + 0.1), up)
current_offset += spacing
## [SKILL NOTICE]: Use 'sample_baked_up_vector()' when spawning items
## on splines to ensure they respect the track's banking and slopes.