123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- extends RigidBody3D
- class_name VehicleController
- signal query_driver(vehicle: VehicleController)
- @export var max_force: float = 20000
- @export var boost_factor: float = 1.7
- @export var rev_min: float = 1
- @export var rev_multiplier: float = 5 # Higher values result in higher revs.
- @export var rev_normalized_max: float = 1
- @export var shift_time_ms: int = 333 # in milliseconds
- @export var vmax_wheel_spin: float = 6
- @export var force_curve: Curve
- @export var omega_curve: Curve
- @export var omega_max: float = 0.6
- @export var omega_max_drift: float = 1.7
- @export var spring_distance_max_in: float = 0.07
- @export var spring_distance_max_out: float = 0.14
- # Hard spring.
- @export var spring_constant: float = 42214
- @export var spring_damping: float = 7904
- @export var taillights: MeshInstance3D
- @export var taillights_material_index: int = 0
- @export var taillights_material_energy: float = 0.9
- @onready var steering_controller: Node = get_node("SteeringController")
- @onready var drift_controller = get_node("DriftController")
- @onready var omega_controller: Node = get_node("OmegaController")
- @onready var anti_roll_controller: Node = get_node("AntiRollController")
- @onready var gearbox: Node = get_node("GearBox")
- @onready var driver: Node = get_node("Driver")
- @onready var motor: Node = get_node("Motor")
- @onready var fl: WheelController = get_node("FL")
- @onready var fr: WheelController = get_node("FR")
- @onready var rl: WheelController = get_node("RL")
- @onready var rr: WheelController = get_node("RR")
- @onready var sound_emitter: SoundEmitter = get_node("SoundEmitter")
- var vehicle_state = VehicleState.new()
- var wheel_state = WheelState.new()
- var controlled_by_player: bool = false
- var brake_value: float = 20000
- var on_ground: bool = false
- var has_grip: bool = false
- var grip_force: float
- var driving_force_position: Vector3 # The force to move the car is applied at this position (local to the car).
- var offset_drive: Vector3
- var omega_reference: float
- var wheelbase: float
- var turn_radius: float
- var front_rolling_measurement: float
- var anti_roll_force: float
- var velocity_measurement: float
- var acceleration_measurement: float
- var acceleration_force: float
- var is_cornering: bool
- var rev_normalized: float
- var rev: float
- var apply_boost: bool
- var current_force: float
- var taillights_material: BaseMaterial3D
- func _ready():
- var weight: float = mass * ProjectSettings.get_setting("physics/3d/default_gravity")
- wheelbase = rl.transform.origin.z - fl.transform.origin.z
- driving_force_position = Vector3(0, -rl.wheel_radius, wheelbase * 0.5) # At the rear axis and at the contact point of the wheel.
- fl.init_suspension(weight / 4, spring_distance_max_in, spring_distance_max_out, spring_constant, spring_damping)
- fr.init_suspension(weight / 4, spring_distance_max_in, spring_distance_max_out, spring_constant, spring_damping)
- rl.init_suspension(weight / 4, spring_distance_max_in, spring_distance_max_out, spring_constant, spring_damping)
- rr.init_suspension(weight / 4, spring_distance_max_in, spring_distance_max_out, spring_constant, spring_damping)
- gearbox.gear_shift_time = shift_time_ms
- gearbox.set_force_limits(max_force)
- motor.rev_normalized_max = rev_normalized_max
- if taillights:
- taillights_material = taillights.get_active_material(taillights_material_index)
- if taillights_material:
- taillights_material.emission_enabled = true
- taillights_material.emission = Color(1, 0, 0)
- func _physics_process(delta: float):
- var torque: float
- var torque_vector: Vector3
- var vehicle_velocity_magnitude: float = linear_velocity.length()
- var vehicle_rotation = Quaternion(transform.basis)
- vehicle_state.update(vehicle_rotation, linear_velocity)
- var steering: float = vehicle_state.drift_angle_measurement
- apply_boost = false
- current_force = 0
- on_ground = update_suspension(delta, vehicle_rotation)
- rev_normalized = motor.update_state(self)
- rev = rev_normalized * rev_multiplier
- sound_emitter.update_motor_sound(self)
- if controlled_by_player:
- emit_signal("query_driver", self)
- if taillights_material:
- if driver.did_brake:
- taillights_material.emission_energy_multiplier = taillights_material_energy
- else:
- taillights_material.emission_energy_multiplier = 0
- if !on_ground:
- has_grip = false
- grip_force = steering_controller.reset()
- drift_controller.reset()
- omega_controller.reset()
- return
- if driver.did_declutch:
- return
- offset_drive = to_global(driving_force_position) - transform.origin
- acceleration_measurement = (vehicle_velocity_magnitude - velocity_measurement) / delta
- velocity_measurement = vehicle_velocity_magnitude
- gearbox.select_gear(vehicle_velocity_magnitude, driver.did_accelerate)
- turn_radius = get_turn_radius(vehicle_velocity_magnitude)
- if driver.did_accelerate:
- if driver.did_steer_left || driver.did_steer_right:
- if is_cornering:
- has_grip = false
- else:
- if is_drift_agle_less_than(deg_to_rad(1)):
- has_grip = true
- is_cornering = false
- else:
- if driver.did_steer_left || driver.did_steer_right:
- is_cornering = true
- else:
- is_cornering = false
- if is_drift_agle_less_than(deg_to_rad(9)):
- has_grip = true
- if has_grip:
- drift_controller.reset()
- steering = asin(wheelbase / turn_radius)
- control_omega(delta, vehicle_velocity_magnitude, omega_max, 2)
- apply_steering_force(vehicle_rotation)
- else:
- steering_controller.reset()
- if driver.did_accelerate:
- adjust_cornering(delta)
- apply_drift_force(vehicle_rotation)
- else:
- drift_controller.reset()
- grip_force = 0
- steering = asin(wheelbase / turn_radius)
- control_omega(delta, vehicle_velocity_magnitude, omega_max_drift, 5)
- apply_steering_force(vehicle_rotation)
- if driver.did_accelerate:
- accelerate()
- if vehicle_state.velocity_rear_axis < vmax_wheel_spin:
- vehicle_state.velocity_rear_axis = vmax_wheel_spin
- else: if driver.did_reverse:
- reverse()
- else:
- acceleration_force = 0
- if driver.did_brake:
- brake(vehicle_velocity_magnitude)
- torque = omega_controller.adjust(omega_reference, angular_velocity.y)
- torque_vector = vehicle_rotation * Vector3.UP * torque
- apply_torque(torque_vector)
- wheel_state.update(delta, vehicle_state.velocity_front_axis, vehicle_state.velocity_rear_axis)
- update_wheel_rotation(delta, steering)
- func is_drift_agle_less_than(angle: float):
- if vehicle_state.drift_angle_measurement > -angle && vehicle_state.drift_angle_measurement < angle:
- return true
- return false
- func control_omega(delta: float, velocity: float, omega_wanted: float, time_factor: float):
- var value: float = omega_curve.sample(velocity)
- if driver.did_steer_left:
- omega_reference = lerp(omega_reference, omega_wanted * value, time_factor * delta)
- else: if driver.did_steer_right:
- omega_reference = lerp(omega_reference, -omega_wanted * value, time_factor * delta)
- else:
- omega_reference = lerp(omega_reference, 0.0, 9 * delta)
- func apply_steering_force(vehicle_rotation: Quaternion):
- if !vehicle_state.vehicle_moving_forward:
- grip_force = steering_controller.reset()
- return
- grip_force = steering_controller.adjust(0, vehicle_state.velocity_sideways)
- var direction = vehicle_rotation * Vector3.LEFT
- var grip_force_vector: Vector3 = direction * grip_force
- apply_force(grip_force_vector, offset_drive)
- func adjust_cornering(delta: float):
- if driver.did_steer_left:
- if omega_reference < 0: # countersteer
- apply_boost = true
- omega_reference = lerp(omega_reference, omega_max_drift, 0.9 * delta)
- else:
- omega_reference = lerp(omega_reference, omega_max_drift, 2 * delta)
- else: if driver.did_steer_right:
- if omega_reference > 0: # countersteer
- apply_boost = true
- omega_reference = lerp(omega_reference, -omega_max_drift, 0.9 * delta)
- else:
- omega_reference = lerp(omega_reference, -omega_max_drift, 2 * delta)
- else:
- if is_cornering:
- omega_reference = lerp(omega_reference, 0.0, 0.1 * delta)
- else:
- omega_reference = 0
- func apply_drift_force(vehicle_rotation: Quaternion):
- if !vehicle_state.vehicle_moving_forward:
- drift_controller.reset()
- grip_force = 0
- else:
- grip_force = drift_controller.adjust(0, vehicle_state.velocity_sideways)
- var direction: Vector3 = vehicle_rotation * Vector3.LEFT
- var grip_force_vector: Vector3 = direction * grip_force
- apply_force(grip_force_vector, offset_drive)
- func update_wheel_rotation(delta: float, steering: float):
- fl.rotate_wheel(delta, wheel_state.total_movement_front, steering)
- fr.rotate_wheel(delta, wheel_state.total_movement_front, steering)
- rl.rotate_wheel(delta, wheel_state.total_movement_rear, 0)
- rr.rotate_wheel(delta, wheel_state.total_movement_rear, 0)
- func accelerate():
- if gearbox.gear_changing:
- return
- var vmax: float = gearbox.get_vmax()
- acceleration_force = gearbox.force_max_value[gearbox.gear - 1] * force_curve.sample(velocity_measurement / vmax)
- if apply_boost:
- acceleration_force *= boost_factor
- current_force = acceleration_force
- var force_vector: Vector3 = vehicle_state.vehicle_direction * acceleration_force
- apply_force(force_vector, offset_drive)
- func reverse():
- var velocity_max_reverse: float = 7
- if velocity_measurement < velocity_max_reverse:
- acceleration_force = gearbox.force_max_value[1]
- var force_vector: Vector3 = vehicle_state.vehicle_direction * acceleration_force
- apply_force(-force_vector, offset_drive)
- func brake(vehicle_velocity_magnitude: float):
- var brake_force: Vector3
- vehicle_state.brake()
- omega_reference = 0
- if vehicle_velocity_magnitude > 0.5:
- brake_force = brake_value * vehicle_state.velocity_direction
- else:
- brake_force = brake_value * linear_velocity
- apply_force(-brake_force, offset_drive)
- func get_turn_radius(vehicle_velocity_magnitude: float) -> float:
- var radius: float
- var radius_min: float = 1.1 * wheelbase
- radius = 999
- if vehicle_velocity_magnitude > 0.05:
- if angular_velocity.y > 0:
- radius = min(999, vehicle_velocity_magnitude / angular_velocity.y)
- if radius < radius_min:
- radius = radius_min
- else: if angular_velocity.y < 0:
- radius = max(-999, vehicle_velocity_magnitude / angular_velocity.y)
- if radius > -radius_min:
- radius = -radius_min
- if radius > -radius_min && radius < radius_min:
- print_debug("turn radius?")
- return radius
- func update_suspension(delta: float, vehicle_rotation: Quaternion) -> bool:
- var contact_front: bool
- var contact_rear: bool
- front_rolling_measurement = fl.spring_distance - fr.spring_distance
- anti_roll_force = anti_roll_controller.adjust(0, front_rolling_measurement)
- contact_front = fl.add_spring_force(delta, self, -anti_roll_force, vehicle_rotation)
- contact_front = fr.add_spring_force(delta, self, anti_roll_force, vehicle_rotation) && contact_front
- contact_rear = rl.add_spring_force(delta, self, 0, vehicle_rotation)
- contact_rear = rr.add_spring_force(delta, self, 0, vehicle_rotation) && contact_rear
- return contact_front && contact_rear
- class VehicleState:
- var velocity_front_axis: float
- var velocity_rear_axis: float
- var velocity_sideways: float
- var vehicle_direction: Vector3
- var velocity_direction: Vector3
- var vehicle_moving_forward: bool
- var drift_angle_measurement: float
- func update(vehicle_rotation: Quaternion, vehicle_velocity: Vector3):
- var vehicle_direction_sideways: Vector3 = vehicle_rotation * Vector3.LEFT
- vehicle_direction = vehicle_rotation * Vector3.FORWARD
- velocity_front_axis = vehicle_velocity.length()
- velocity_rear_axis = vehicle_velocity.dot(vehicle_direction)
- velocity_sideways = vehicle_velocity.dot(vehicle_direction_sideways)
- if velocity_front_axis > 0.1:
- velocity_direction = vehicle_velocity.normalized()
- else:
- velocity_direction = vehicle_direction
- vehicle_moving_forward = vehicle_direction.dot(velocity_direction) > 0
- var cross_product: Vector3
- if vehicle_moving_forward:
- cross_product = vehicle_direction.cross(velocity_direction)
- else:
- cross_product = velocity_direction.cross(vehicle_direction)
- if velocity_front_axis > 0.1:
- drift_angle_measurement = asin(cross_product.y)
- if !vehicle_moving_forward:
- velocity_front_axis = -velocity_front_axis
-
- func brake():
- velocity_front_axis = 0
- velocity_rear_axis = 0
- class WheelState:
- var total_movement_front: float
- var total_movement_rear: float
- func update(delta: float, velocity_front_axis: float, velocity_rear_axis: float):
- total_movement_front += delta * velocity_front_axis
- total_movement_rear += delta * velocity_rear_axis
|