mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2026-01-04 15:00:09 +01:00
This leads to code that is easier to understand and runs faster thanks to GDScript's typed instructions. The untyped declaration warning is now enabled on all projects where type hints were added. All projects currently run without any untyped declration warnings. Dodge the Creeps and Squash the Creeps demos intentionally don't use type hints to match the documentation, where type hints haven't been adopted yet (given its beginner focus).
138 lines
5.8 KiB
GDScript
138 lines
5.8 KiB
GDScript
extends CharacterBody3D
|
|
|
|
# Settings to control the character.
|
|
@export var rotation_speed := 1.0
|
|
@export var movement_speed := 5.0
|
|
@export var movement_acceleration := 5.0
|
|
|
|
# Get the gravity from the project settings to be synced with RigidBody nodes.
|
|
var gravity := float(ProjectSettings.get_setting("physics/3d/default_gravity"))
|
|
|
|
# Helper variables to keep our code readable.
|
|
@onready var origin_node: XROrigin3D = $XROrigin3D
|
|
@onready var camera_node: XRCamera3D = $XROrigin3D/XRCamera3D
|
|
@onready var neck_position_node: Node3D = $XROrigin3D/XRCamera3D/Neck
|
|
@onready var black_out: Node3D = $XROrigin3D/XRCamera3D/BlackOut
|
|
|
|
## Called when the user has requested their view to be recentered.
|
|
func recenter() -> void:
|
|
# The code here assumes the player has walked into an area they shouldn't be
|
|
# and we return the player back to the character body.
|
|
# But other strategies can be applied here as well such as returning the player
|
|
# to a starting position or a checkpoint.
|
|
|
|
# Calculate where our camera should be, we start with our global transform.
|
|
var new_camera_transform : Transform3D = global_transform
|
|
|
|
# Set to the height of our neck joint.
|
|
new_camera_transform.origin.y = neck_position_node.global_position.y
|
|
|
|
# Apply transform our our next position to get our desired camera transform.
|
|
new_camera_transform = new_camera_transform * neck_position_node.transform.inverse()
|
|
|
|
# Remove tilt from camera transform.
|
|
var camera_transform : Transform3D = camera_node.transform
|
|
var forward_dir : Vector3 = camera_transform.basis.z
|
|
forward_dir.y = 0.0
|
|
camera_transform = camera_transform.looking_at(camera_transform.origin + forward_dir.normalized(), Vector3.UP, true)
|
|
|
|
# Update our XR location.
|
|
origin_node.global_transform = new_camera_transform * camera_transform.inverse()
|
|
|
|
|
|
# Returns our move input by querying the move action on each controller.
|
|
func _get_movement_input() -> Vector2:
|
|
var movement := Vector2()
|
|
|
|
# If move is not bound to one of our controllers,
|
|
# that controller will return `Vector2.ZERO`.
|
|
movement += $XROrigin3D/LeftHand.get_vector2("move")
|
|
movement += $XROrigin3D/RightHand.get_vector2("move")
|
|
|
|
return movement
|
|
|
|
# `_process_on_physical_movement()` handles the physical movement of the player
|
|
# adjusting our character body position to "catch up to" the player.
|
|
# If the character body encounters an obstruction our view will black out
|
|
# and we will stop further character movement until the player physically
|
|
# moves back.
|
|
func _process_on_physical_movement(delta: float) -> bool:
|
|
# Remember our current velocity, as we'll apply that later.
|
|
var current_velocity := velocity
|
|
|
|
# Start by rotating the player to face the same way our real player is.
|
|
var camera_basis: Basis = origin_node.transform.basis * camera_node.transform.basis
|
|
var forward: Vector2 = Vector2(camera_basis.z.x, camera_basis.z.z)
|
|
var angle: float = forward.angle_to(Vector2(0.0, 1.0))
|
|
|
|
# Rotate our character body.
|
|
transform.basis = transform.basis.rotated(Vector3.UP, angle)
|
|
|
|
# Reverse this rotation our origin node.
|
|
origin_node.transform = Transform3D().rotated(Vector3.UP, -angle) * origin_node.transform
|
|
|
|
# Now apply movement, first move our player body to the right location.
|
|
var org_player_body: Vector3 = global_transform.origin
|
|
var player_body_location: Vector3 = origin_node.transform * camera_node.transform * neck_position_node.transform.origin
|
|
player_body_location.y = 0.0
|
|
player_body_location = global_transform * player_body_location
|
|
|
|
velocity = (player_body_location - org_player_body) / delta
|
|
move_and_slide()
|
|
|
|
# Now move our XROrigin back.
|
|
var delta_movement := global_transform.origin - org_player_body
|
|
origin_node.global_transform.origin -= delta_movement
|
|
|
|
# Negate any height change in local space due to player hitting ramps, etc.
|
|
origin_node.transform.origin.y = 0.0
|
|
|
|
# Return our value.
|
|
velocity = current_velocity
|
|
|
|
# Check if we managed to move where we wanted to.
|
|
var location_offset := (player_body_location - global_transform.origin).length()
|
|
if location_offset > 0.1:
|
|
# We couldn't go where we wanted to, black out our screen.
|
|
black_out.fade = clampf((location_offset - 0.1) / 0.1, 0.0, 1.0)
|
|
return true
|
|
else:
|
|
black_out.fade = 0.0
|
|
return false
|
|
|
|
# `_process_movement_on_input()` handles movement through controller input.
|
|
# We first handle rotating the player and then apply movement.
|
|
# We also apply the effects of gravity at this point.
|
|
func _process_movement_on_input(is_colliding: bool, delta: float) -> void:
|
|
if not is_colliding:
|
|
# Only handle input if we've not physically moved somewhere we shouldn't.
|
|
var movement_input := _get_movement_input()
|
|
|
|
# First handle rotation, to keep this example simple we are implementing
|
|
# "smooth" rotation here. This can lead to motion sickness.
|
|
# Adding a comfort option with "stepped" rotation is good practice but
|
|
# falls outside of the scope of this demonstration.
|
|
rotation.y += -movement_input.x * delta * rotation_speed
|
|
|
|
# Now handle forward/backwards movement.
|
|
# Straffing can be added by using the movement_input.x input
|
|
# and using a different input for rotational control.
|
|
# Straffing is more prone to motion sickness.
|
|
var direction := global_transform.basis * Vector3(0.0, 0.0, -movement_input.y) * movement_speed
|
|
if direction:
|
|
velocity.x = move_toward(velocity.x, direction.x, delta * movement_acceleration)
|
|
velocity.z = move_toward(velocity.z, direction.z, delta * movement_acceleration)
|
|
else:
|
|
velocity.x = move_toward(velocity.x, 0, delta * movement_acceleration)
|
|
velocity.z = move_toward(velocity.z, 0, delta * movement_acceleration)
|
|
|
|
# Always handle gravity
|
|
velocity.y -= gravity * delta
|
|
|
|
move_and_slide()
|
|
|
|
# `_physics_process()` handles our player movement.
|
|
func _physics_process(delta: float) -> void:
|
|
var is_colliding := _process_on_physical_movement(delta)
|
|
_process_movement_on_input(is_colliding, delta)
|