VehicleController.gd 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. extends RigidBody3D
  2. class_name VehicleController
  3. signal query_driver(vehicle: VehicleController)
  4. @export var max_force: float = 20000
  5. @export var boost_factor: float = 1.7
  6. @export var rev_min: float = 1
  7. @export var rev_multiplier: float = 5 # Higher values result in higher revs.
  8. @export var rev_normalized_max: float = 1
  9. @export var shift_time_ms: int = 333 # in milliseconds
  10. @export var vmax_wheel_spin: float = 6
  11. @export var force_curve: Curve
  12. @export var omega_curve: Curve
  13. @export var omega_max: float = 0.6
  14. @export var omega_max_drift: float = 1.7
  15. @export var spring_distance_max_in: float = 0.07
  16. @export var spring_distance_max_out: float = 0.14
  17. # Hard spring.
  18. @export var spring_constant: float = 42214
  19. @export var spring_damping: float = 7904
  20. @export var taillights: MeshInstance3D
  21. @export var taillights_material_index: int = 0
  22. @export var taillights_material_energy: float = 0.9
  23. @onready var steering_controller: Node = get_node("SteeringController")
  24. @onready var drift_controller = get_node("DriftController")
  25. @onready var omega_controller: Node = get_node("OmegaController")
  26. @onready var anti_roll_controller: Node = get_node("AntiRollController")
  27. @onready var gearbox: Node = get_node("GearBox")
  28. @onready var driver: Node = get_node("Driver")
  29. @onready var motor: Node = get_node("Motor")
  30. @onready var fl: WheelController = get_node("FL")
  31. @onready var fr: WheelController = get_node("FR")
  32. @onready var rl: WheelController = get_node("RL")
  33. @onready var rr: WheelController = get_node("RR")
  34. @onready var sound_emitter: SoundEmitter = get_node("SoundEmitter")
  35. var vehicle_state = VehicleState.new()
  36. var wheel_state = WheelState.new()
  37. var controlled_by_player: bool = false
  38. var brake_value: float = 20000
  39. var on_ground: bool = false
  40. var has_grip: bool = false
  41. var grip_force: float
  42. var driving_force_position: Vector3 # The force to move the car is applied at this position (local to the car).
  43. var offset_drive: Vector3
  44. var omega_reference: float
  45. var wheelbase: float
  46. var turn_radius: float
  47. var front_rolling_measurement: float
  48. var anti_roll_force: float
  49. var velocity_measurement: float
  50. var acceleration_measurement: float
  51. var acceleration_force: float
  52. var is_cornering: bool
  53. var rev_normalized: float
  54. var rev: float
  55. var apply_boost: bool
  56. var current_force: float
  57. var taillights_material: BaseMaterial3D
  58. func _ready():
  59. var weight: float = mass * ProjectSettings.get_setting("physics/3d/default_gravity")
  60. wheelbase = rl.transform.origin.z - fl.transform.origin.z
  61. driving_force_position = Vector3(0, -rl.wheel_radius, wheelbase * 0.5) # At the rear axis and at the contact point of the wheel.
  62. fl.init_suspension(weight / 4, spring_distance_max_in, spring_distance_max_out, spring_constant, spring_damping)
  63. fr.init_suspension(weight / 4, spring_distance_max_in, spring_distance_max_out, spring_constant, spring_damping)
  64. rl.init_suspension(weight / 4, spring_distance_max_in, spring_distance_max_out, spring_constant, spring_damping)
  65. rr.init_suspension(weight / 4, spring_distance_max_in, spring_distance_max_out, spring_constant, spring_damping)
  66. gearbox.gear_shift_time = shift_time_ms
  67. gearbox.set_force_limits(max_force)
  68. motor.rev_normalized_max = rev_normalized_max
  69. if taillights:
  70. taillights_material = taillights.get_active_material(taillights_material_index)
  71. if taillights_material:
  72. taillights_material.emission_enabled = true
  73. taillights_material.emission = Color(1, 0, 0)
  74. func _physics_process(delta: float):
  75. var torque: float
  76. var torque_vector: Vector3
  77. var vehicle_velocity_magnitude: float = linear_velocity.length()
  78. var vehicle_rotation = Quaternion(transform.basis)
  79. vehicle_state.update(vehicle_rotation, linear_velocity)
  80. var steering: float = vehicle_state.drift_angle_measurement
  81. apply_boost = false
  82. current_force = 0
  83. on_ground = update_suspension(delta, vehicle_rotation)
  84. rev_normalized = motor.update_state(self)
  85. rev = rev_normalized * rev_multiplier
  86. sound_emitter.update_motor_sound(self)
  87. if controlled_by_player:
  88. emit_signal("query_driver", self)
  89. if taillights_material:
  90. if driver.did_brake:
  91. taillights_material.emission_energy_multiplier = taillights_material_energy
  92. else:
  93. taillights_material.emission_energy_multiplier = 0
  94. if !on_ground:
  95. has_grip = false
  96. grip_force = steering_controller.reset()
  97. drift_controller.reset()
  98. omega_controller.reset()
  99. return
  100. if driver.did_declutch:
  101. return
  102. offset_drive = to_global(driving_force_position) - transform.origin
  103. acceleration_measurement = (vehicle_velocity_magnitude - velocity_measurement) / delta
  104. velocity_measurement = vehicle_velocity_magnitude
  105. gearbox.select_gear(vehicle_velocity_magnitude, driver.did_accelerate)
  106. turn_radius = get_turn_radius(vehicle_velocity_magnitude)
  107. if driver.did_accelerate:
  108. if driver.did_steer_left || driver.did_steer_right:
  109. if is_cornering:
  110. has_grip = false
  111. else:
  112. if is_drift_agle_less_than(deg_to_rad(1)):
  113. has_grip = true
  114. is_cornering = false
  115. else:
  116. if driver.did_steer_left || driver.did_steer_right:
  117. is_cornering = true
  118. else:
  119. is_cornering = false
  120. if is_drift_agle_less_than(deg_to_rad(9)):
  121. has_grip = true
  122. if has_grip:
  123. drift_controller.reset()
  124. steering = asin(wheelbase / turn_radius)
  125. control_omega(delta, vehicle_velocity_magnitude, omega_max, 2)
  126. apply_steering_force(vehicle_rotation)
  127. else:
  128. steering_controller.reset()
  129. if driver.did_accelerate:
  130. adjust_cornering(delta)
  131. apply_drift_force(vehicle_rotation)
  132. else:
  133. drift_controller.reset()
  134. grip_force = 0
  135. steering = asin(wheelbase / turn_radius)
  136. control_omega(delta, vehicle_velocity_magnitude, omega_max_drift, 5)
  137. apply_steering_force(vehicle_rotation)
  138. if driver.did_accelerate:
  139. accelerate()
  140. if vehicle_state.velocity_rear_axis < vmax_wheel_spin:
  141. vehicle_state.velocity_rear_axis = vmax_wheel_spin
  142. else: if driver.did_reverse:
  143. reverse()
  144. else:
  145. acceleration_force = 0
  146. if driver.did_brake:
  147. brake(vehicle_velocity_magnitude)
  148. torque = omega_controller.adjust(omega_reference, angular_velocity.y)
  149. torque_vector = vehicle_rotation * Vector3.UP * torque
  150. apply_torque(torque_vector)
  151. wheel_state.update(delta, vehicle_state.velocity_front_axis, vehicle_state.velocity_rear_axis)
  152. update_wheel_rotation(delta, steering)
  153. func is_drift_agle_less_than(angle: float):
  154. if vehicle_state.drift_angle_measurement > -angle && vehicle_state.drift_angle_measurement < angle:
  155. return true
  156. return false
  157. func control_omega(delta: float, velocity: float, omega_wanted: float, time_factor: float):
  158. var value: float = omega_curve.sample(velocity)
  159. if driver.did_steer_left:
  160. omega_reference = lerp(omega_reference, omega_wanted * value, time_factor * delta)
  161. else: if driver.did_steer_right:
  162. omega_reference = lerp(omega_reference, -omega_wanted * value, time_factor * delta)
  163. else:
  164. omega_reference = lerp(omega_reference, 0.0, 9 * delta)
  165. func apply_steering_force(vehicle_rotation: Quaternion):
  166. if !vehicle_state.vehicle_moving_forward:
  167. grip_force = steering_controller.reset()
  168. return
  169. grip_force = steering_controller.adjust(0, vehicle_state.velocity_sideways)
  170. var direction = vehicle_rotation * Vector3.LEFT
  171. var grip_force_vector: Vector3 = direction * grip_force
  172. apply_force(grip_force_vector, offset_drive)
  173. func adjust_cornering(delta: float):
  174. if driver.did_steer_left:
  175. if omega_reference < 0: # countersteer
  176. apply_boost = true
  177. omega_reference = lerp(omega_reference, omega_max_drift, 0.9 * delta)
  178. else:
  179. omega_reference = lerp(omega_reference, omega_max_drift, 2 * delta)
  180. else: if driver.did_steer_right:
  181. if omega_reference > 0: # countersteer
  182. apply_boost = true
  183. omega_reference = lerp(omega_reference, -omega_max_drift, 0.9 * delta)
  184. else:
  185. omega_reference = lerp(omega_reference, -omega_max_drift, 2 * delta)
  186. else:
  187. if is_cornering:
  188. omega_reference = lerp(omega_reference, 0.0, 0.1 * delta)
  189. else:
  190. omega_reference = 0
  191. func apply_drift_force(vehicle_rotation: Quaternion):
  192. if !vehicle_state.vehicle_moving_forward:
  193. drift_controller.reset()
  194. grip_force = 0
  195. else:
  196. grip_force = drift_controller.adjust(0, vehicle_state.velocity_sideways)
  197. var direction: Vector3 = vehicle_rotation * Vector3.LEFT
  198. var grip_force_vector: Vector3 = direction * grip_force
  199. apply_force(grip_force_vector, offset_drive)
  200. func update_wheel_rotation(delta: float, steering: float):
  201. fl.rotate_wheel(delta, wheel_state.total_movement_front, steering)
  202. fr.rotate_wheel(delta, wheel_state.total_movement_front, steering)
  203. rl.rotate_wheel(delta, wheel_state.total_movement_rear, 0)
  204. rr.rotate_wheel(delta, wheel_state.total_movement_rear, 0)
  205. func accelerate():
  206. if gearbox.gear_changing:
  207. return
  208. var vmax: float = gearbox.get_vmax()
  209. acceleration_force = gearbox.force_max_value[gearbox.gear - 1] * force_curve.sample(velocity_measurement / vmax)
  210. if apply_boost:
  211. acceleration_force *= boost_factor
  212. current_force = acceleration_force
  213. var force_vector: Vector3 = vehicle_state.vehicle_direction * acceleration_force
  214. apply_force(force_vector, offset_drive)
  215. func reverse():
  216. var velocity_max_reverse: float = 7
  217. if velocity_measurement < velocity_max_reverse:
  218. acceleration_force = gearbox.force_max_value[1]
  219. var force_vector: Vector3 = vehicle_state.vehicle_direction * acceleration_force
  220. apply_force(-force_vector, offset_drive)
  221. func brake(vehicle_velocity_magnitude: float):
  222. var brake_force: Vector3
  223. vehicle_state.brake()
  224. omega_reference = 0
  225. if vehicle_velocity_magnitude > 0.5:
  226. brake_force = brake_value * vehicle_state.velocity_direction
  227. else:
  228. brake_force = brake_value * linear_velocity
  229. apply_force(-brake_force, offset_drive)
  230. func get_turn_radius(vehicle_velocity_magnitude: float) -> float:
  231. var radius: float
  232. var radius_min: float = 1.1 * wheelbase
  233. radius = 999
  234. if vehicle_velocity_magnitude > 0.05:
  235. if angular_velocity.y > 0:
  236. radius = min(999, vehicle_velocity_magnitude / angular_velocity.y)
  237. if radius < radius_min:
  238. radius = radius_min
  239. else: if angular_velocity.y < 0:
  240. radius = max(-999, vehicle_velocity_magnitude / angular_velocity.y)
  241. if radius > -radius_min:
  242. radius = -radius_min
  243. if radius > -radius_min && radius < radius_min:
  244. print_debug("turn radius?")
  245. return radius
  246. func update_suspension(delta: float, vehicle_rotation: Quaternion) -> bool:
  247. var contact_front: bool
  248. var contact_rear: bool
  249. front_rolling_measurement = fl.spring_distance - fr.spring_distance
  250. anti_roll_force = anti_roll_controller.adjust(0, front_rolling_measurement)
  251. contact_front = fl.add_spring_force(delta, self, -anti_roll_force, vehicle_rotation)
  252. contact_front = fr.add_spring_force(delta, self, anti_roll_force, vehicle_rotation) && contact_front
  253. contact_rear = rl.add_spring_force(delta, self, 0, vehicle_rotation)
  254. contact_rear = rr.add_spring_force(delta, self, 0, vehicle_rotation) && contact_rear
  255. return contact_front && contact_rear
  256. class VehicleState:
  257. var velocity_front_axis: float
  258. var velocity_rear_axis: float
  259. var velocity_sideways: float
  260. var vehicle_direction: Vector3
  261. var velocity_direction: Vector3
  262. var vehicle_moving_forward: bool
  263. var drift_angle_measurement: float
  264. func update(vehicle_rotation: Quaternion, vehicle_velocity: Vector3):
  265. var vehicle_direction_sideways: Vector3 = vehicle_rotation * Vector3.LEFT
  266. vehicle_direction = vehicle_rotation * Vector3.FORWARD
  267. velocity_front_axis = vehicle_velocity.length()
  268. velocity_rear_axis = vehicle_velocity.dot(vehicle_direction)
  269. velocity_sideways = vehicle_velocity.dot(vehicle_direction_sideways)
  270. if velocity_front_axis > 0.1:
  271. velocity_direction = vehicle_velocity.normalized()
  272. else:
  273. velocity_direction = vehicle_direction
  274. vehicle_moving_forward = vehicle_direction.dot(velocity_direction) > 0
  275. var cross_product: Vector3
  276. if vehicle_moving_forward:
  277. cross_product = vehicle_direction.cross(velocity_direction)
  278. else:
  279. cross_product = velocity_direction.cross(vehicle_direction)
  280. if velocity_front_axis > 0.1:
  281. drift_angle_measurement = asin(cross_product.y)
  282. if !vehicle_moving_forward:
  283. velocity_front_axis = -velocity_front_axis
  284. func brake():
  285. velocity_front_axis = 0
  286. velocity_rear_axis = 0
  287. class WheelState:
  288. var total_movement_front: float
  289. var total_movement_rear: float
  290. func update(delta: float, velocity_front_axis: float, velocity_rear_axis: float):
  291. total_movement_front += delta * velocity_front_axis
  292. total_movement_rear += delta * velocity_rear_axis