walker.gd 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. class_name Walker
  2. extends CharacterBody3D
  3. signal focus_required(me: Node3D)
  4. signal got_in
  5. signal got_out
  6. signal can_get_in(possible: bool)
  7. ## How fast the player moves in meters per second.
  8. @export var speed = 14
  9. ## Convert an axis from [-1, -1] to rad
  10. @export var turn_to_rad: float = 0.020
  11. ## The downward acceleration when in the air, in meters per second squared.
  12. @export var fall_acceleration = 75
  13. ## Vertical impulse applied to the character upon jumping in meters per second.
  14. @export var jump_impulse = 20
  15. @export_group("Seat", "seat_")
  16. @export var seat_min_dist: float = 0.0005
  17. @export var seat_access_speed: float = 2.0
  18. @export var seat_rotation_duration: float = 1.0
  19. var target_velocity := Vector3.ZERO
  20. var target_character_direction := Vector3.ZERO # From command
  21. var _vehicle: Node3D = null
  22. var _seat: Node3D = null
  23. var _can_get_in: bool = false
  24. @onready var _vehicle_range: RayCast3D = $VehicleRange
  25. func _ready() -> void:
  26. focus_required.emit(self)
  27. func trigger_jump() -> void:
  28. if is_on_floor():
  29. target_velocity.y = jump_impulse
  30. func trigger_direction(dir: Vector2) -> void:
  31. target_character_direction = Vector3(dir.x, 0.0, -dir.y)
  32. ## Return true if inside a vehicle
  33. func is_onboard() -> bool:
  34. return _vehicle != null
  35. ## Return true if the player can get in a vehicle
  36. func can_get_in_vehicle() -> bool:
  37. return !is_onboard() and _get_closest_vehicle() != null
  38. ## Give this walker a chocolate bar
  39. func give_chocolate():
  40. print("Yum, chocolate !")
  41. func _physics_process(delta: float) -> void:
  42. if is_onboard():
  43. _get_on_driver_seat()
  44. else:
  45. _move_with_feet(delta)
  46. _signal_when_can_get_in()
  47. func _move_with_feet(delta: float):
  48. # Walk according to the camera angle
  49. var camera_basis: Basis = get_viewport().get_camera_3d().get_camera_transform().basis
  50. var target_world_direction: Vector3 = camera_basis * target_character_direction
  51. target_world_direction.y = 0.0
  52. var target_walk_velocity = target_world_direction.normalized() * speed * target_character_direction.length()
  53. target_velocity.x = target_walk_velocity.x
  54. target_velocity.z = target_walk_velocity.z
  55. # Face forward
  56. _look_forward(target_velocity)
  57. # Gravity
  58. if not is_on_floor():
  59. target_velocity.y = target_velocity.y - (fall_acceleration * delta)
  60. # Moving the Character
  61. velocity = target_velocity
  62. move_and_slide()
  63. ## Trigger only signal when ability is changed
  64. func _signal_when_can_get_in() -> void:
  65. var can_get_in_now : bool = can_get_in_vehicle()
  66. if _can_get_in != can_get_in_now:
  67. can_get_in.emit(can_get_in_now)
  68. _can_get_in = can_get_in_now
  69. func _get_on_driver_seat() -> void:
  70. if _seat == null :
  71. return
  72. if position.distance_squared_to(_seat.position) < seat_min_dist:
  73. return
  74. var rotation_tween: Tween = create_tween()
  75. rotation_tween.tween_property(self, "quaternion", _seat.quaternion, seat_rotation_duration)
  76. var local_velocity = (_seat.position - position).normalized() * seat_access_speed
  77. velocity = get_parent().get_transform().basis * local_velocity
  78. move_and_slide()
  79. func _on_dir_changed(dir: Vector2) -> void:
  80. if is_onboard():
  81. return
  82. trigger_direction(dir)
  83. func _on_main_action(pressed: bool) -> void:
  84. if is_onboard():
  85. return
  86. if pressed:
  87. trigger_jump()
  88. func _on_get_in_action(commander: LocalInput) -> void:
  89. if is_onboard():
  90. _get_out_vehicle()
  91. else:
  92. _get_in_vehicle(commander)
  93. ## Seek the first vehicle in front of the player
  94. ## and get in to it
  95. ## and take the wheel !
  96. func _get_in_vehicle(commander: LocalInput) -> void:
  97. var closest_vehicle: Node3D = _get_closest_vehicle()
  98. if closest_vehicle == null:
  99. return # No vehicle to get inside
  100. _vehicle = closest_vehicle
  101. reparent(_vehicle)
  102. add_collision_exception_with(_vehicle)
  103. _vehicle.drive_with(commander)
  104. _seat = _find_seat_on(_vehicle)
  105. got_in.emit()
  106. ## Get out of the current vehicle
  107. func _get_out_vehicle() -> void:
  108. _vehicle.get_out()
  109. remove_collision_exception_with(_vehicle)
  110. reparent(_vehicle.get_parent())
  111. _vehicle = null
  112. _seat = null
  113. _get_head_up()
  114. got_out.emit()
  115. ## Make the player stand up
  116. func _get_head_up():
  117. set_rotation(Vector3(0.0, rotation.y, 0.0))
  118. ## Make the player look forward
  119. func _look_forward(forward: Vector3) -> void:
  120. var horizontal_forward: Vector3 = forward.slide(Vector3.UP)
  121. if horizontal_forward.is_zero_approx():
  122. return
  123. else:
  124. look_at(position + horizontal_forward)
  125. ## Return closest vehicle within reach
  126. ## or null if there are none
  127. func _get_closest_vehicle() -> Node3D:
  128. _vehicle_range.force_raycast_update()
  129. var object_within_range: Object = _vehicle_range.get_collider()
  130. if object_within_range is Node3D:
  131. var node_within_range: Node3D = object_within_range as Node3D
  132. if node_within_range.is_in_group("vehicle"):
  133. return node_within_range
  134. return null
  135. ## Have a seat on the driven vehicle if a seat
  136. ## is available
  137. func _find_seat_on(vehicule: Node3D) -> Node3D:
  138. if vehicule.has_method("get_free_seat") :
  139. return vehicule.get_free_seat()
  140. return null