player.gd 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. extends XROrigin3D
  2. # Settings to control the character
  3. @export var rotation_speed : float = 1.0
  4. @export var movement_speed : float = 5.0
  5. @export var movement_acceleration : float = 5.0
  6. # Get the gravity from the project settings to be synced with RigidBody nodes.
  7. var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
  8. # Helper variables to keep our code readable
  9. @onready var character_body : CharacterBody3D = $CharacterBody3D
  10. @onready var camera_node : XRCamera3D = $XRCamera3D
  11. @onready var neck_position_node : Node3D = $XRCamera3D/Neck
  12. @onready var black_out : Node3D = $XRCamera3D/BlackOut
  13. # `recenter` is called when the user has requested their view to be recentered.
  14. # The code here assumes the player has walked into an area they shouldn't be
  15. # and we return the player back to the character body.
  16. # But other strategies can be applied here as well such as returning the player
  17. # to a starting position or a checkpoint.
  18. func recenter():
  19. # Calculate where our camera should be, we start with our global transform
  20. var new_camera_transform : Transform3D = character_body.global_transform
  21. # Set to the height of our neck joint
  22. new_camera_transform.origin.y = neck_position_node.global_position.y
  23. # Apply transform our our next position to get our desired camera transform
  24. new_camera_transform = new_camera_transform * neck_position_node.transform.inverse()
  25. # Remove tilt from camera transform
  26. var camera_transform : Transform3D = camera_node.transform
  27. var forward_dir : Vector3 = camera_transform.basis.z
  28. forward_dir.y = 0.0
  29. camera_transform = camera_transform.looking_at(camera_transform.origin + forward_dir.normalized(), Vector3.UP, true)
  30. # Update our XR location
  31. global_transform = new_camera_transform * camera_transform.inverse()
  32. # Recenter character body
  33. character_body.transform = Transform3D()
  34. # `_get_movement_input` returns our move input by querying the move action on each controller
  35. func _get_movement_input() -> Vector2:
  36. var movement : Vector2 = Vector2()
  37. # If move is not bound to one of our controllers,
  38. # that controller will return a Vector2(0.0, 0.0)
  39. movement += $LeftHand.get_vector2("move")
  40. movement += $RightHand.get_vector2("move")
  41. return movement
  42. # `_process_on_physical_movement` handles the physical movement of the player
  43. # adjusting our character body position to "catch up to" the player.
  44. # If the character body encounters an obstruction our view will black out
  45. # and we will stop further character movement until the player physically
  46. # moves back.
  47. func _process_on_physical_movement(delta) -> bool:
  48. # Remember our current velocity, we'll apply that later
  49. var current_velocity = character_body.velocity
  50. # Remember where our player body currently is
  51. var org_player_body: Vector3 = character_body.global_transform.origin
  52. # Determine where our player body should be
  53. var player_body_location: Vector3 = camera_node.transform * neck_position_node.transform.origin
  54. player_body_location.y = 0.0
  55. player_body_location = global_transform * player_body_location
  56. # Attempt to move our character
  57. character_body.velocity = (player_body_location - org_player_body) / delta
  58. character_body.move_and_slide()
  59. # Set back to our current value
  60. character_body.velocity = current_velocity
  61. # Check if we managed to move all the way, ignoring height change
  62. var movement_left = player_body_location - character_body.global_transform.origin
  63. movement_left.y = 0.0
  64. # Check if we managed to move where we wanted to
  65. var location_offset = movement_left.length()
  66. if location_offset > 0.1:
  67. # We couldn't go where we wanted to, black out our screen
  68. black_out.fade = clamp((location_offset - 0.1) / 0.1, 0.0, 1.0)
  69. return true
  70. else:
  71. black_out.fade = 0.0
  72. return false
  73. func _copy_player_rotation_to_character_body():
  74. # We only copy our forward direction to our character body, we ignore tilt
  75. var camera_forward: Vector3 = -camera_node.global_transform.basis.z
  76. var body_forward: Vector3 = Vector3(camera_forward.x, 0.0, camera_forward.z)
  77. character_body.global_transform.basis = Basis.looking_at(body_forward, Vector3.UP)
  78. # `_process_movement_on_input` handles movement through controller input.
  79. # We first handle rotating the player and then apply movement.
  80. # We also apply the effects of gravity at this point.
  81. func _process_movement_on_input(is_colliding, delta):
  82. # Remember where our player body currently is
  83. var org_player_body: Vector3 = character_body.global_transform.origin
  84. if !is_colliding:
  85. # Only handle input if we've not physically moved somewhere we shouldn't.
  86. var movement_input = _get_movement_input()
  87. # First handle rotation, to keep this example simple we are implementing
  88. # "smooth" rotation here. This can lead to motion sickness.
  89. # Adding a comfort option with "stepped" rotation is good practice but
  90. # falls outside of the scope of this demonstration.
  91. var t1 := Transform3D()
  92. var t2 := Transform3D()
  93. var rot := Transform3D()
  94. # We are going to rotate the origin around the player
  95. var player_position = character_body.global_transform.origin - global_transform.origin
  96. t1.origin = -player_position
  97. t2.origin = player_position
  98. rot = rot.rotated(Vector3(0.0, 1.0, 0.0), -movement_input.x * delta * rotation_speed)
  99. global_transform = (global_transform * t2 * rot * t1).orthonormalized()
  100. # Now ensure our player body is facing the correct way as well
  101. _copy_player_rotation_to_character_body()
  102. # Now handle forward/backwards movement.
  103. # Straffing can be added by using the movement_input.x input
  104. # and using a different input for rotational control.
  105. # Straffing is more prone to motion sickness.
  106. var direction: Vector3 = (character_body.global_transform.basis * Vector3(0.0, 0.0, -movement_input.y)) * movement_speed
  107. if direction:
  108. character_body.velocity.x = move_toward(character_body.velocity.x, direction.x, delta * movement_acceleration)
  109. character_body.velocity.z = move_toward(character_body.velocity.z, direction.z, delta * movement_acceleration)
  110. else:
  111. character_body.velocity.x = move_toward(character_body.velocity.x, 0, delta * movement_acceleration)
  112. character_body.velocity.z = move_toward(character_body.velocity.z, 0, delta * movement_acceleration)
  113. # Always handle gravity
  114. character_body.velocity.y -= gravity * delta
  115. # Attempt to move our player
  116. character_body.move_and_slide()
  117. # And now apply the actual movement to our origin
  118. global_transform.origin += character_body.global_transform.origin - org_player_body
  119. # _physics_process handles our player movement.
  120. func _physics_process(delta):
  121. var is_colliding = _process_on_physical_movement(delta)
  122. _process_movement_on_input(is_colliding, delta)