191 lines
6.2 KiB
GDScript
191 lines
6.2 KiB
GDScript
extends RigidBody3D
|
|
# Initially I used a CharacterBody3D, however, I wanted the player to bounce off
|
|
# other objects in the environment and that would have required manual handling
|
|
# of collisions. So that's why we're using a RigidBody3D instead.
|
|
|
|
const MOVE_SPEED := 8.0
|
|
const ACCELLERATION := 30.0
|
|
const DECELLERATION := 40.0
|
|
const JUMP_SPEED := 4.0
|
|
const MAX_NUMBER_OF_JUMPS := 2
|
|
|
|
const MIN_FOV := 10
|
|
const MAX_FOV := 180
|
|
const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1
|
|
var mouse_sensitivity := 0.005
|
|
var rotation_x := 0.0
|
|
var rotation_y := 0.0
|
|
var cameraMoveMode := false
|
|
var current_number_of_jumps := 0
|
|
var _pending_mouse_delta := Vector2.ZERO
|
|
var _last_move_forward := Vector3(0, 0, 1)
|
|
var _last_move_right := Vector3(1, 0, 0)
|
|
var _camera_offset_local := Vector3.ZERO
|
|
var _camera_yaw := 0.0
|
|
var _camera_pitch := 0.0
|
|
var _in_vehicle := false
|
|
var _vehicle_collision_layer := 0
|
|
var _vehicle_collision_mask := 0
|
|
|
|
@export var camera_follow_speed := 10.0
|
|
|
|
var jump_sound = preload("res://assets/audio/jump.ogg")
|
|
var audio_player = AudioStreamPlayer.new()
|
|
|
|
@export var camera_path: NodePath
|
|
@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
|
|
@export var phone_path: NodePath
|
|
@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null
|
|
var phone_visible := false
|
|
|
|
func _ready() -> void:
|
|
axis_lock_angular_x = true
|
|
axis_lock_angular_z = true
|
|
angular_damp = 6.0
|
|
contact_monitor = true
|
|
max_contacts_reported = 4
|
|
add_child(audio_player)
|
|
audio_player.stream = jump_sound
|
|
audio_player.volume_db = -20
|
|
if cam:
|
|
_camera_offset_local = cam.transform.origin
|
|
_camera_pitch = cam.rotation.x
|
|
_camera_yaw = global_transform.basis.get_euler().y
|
|
cam.set_as_top_level(true)
|
|
cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local)
|
|
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
|
|
var move_basis := cam.global_transform.basis if cam else global_transform.basis
|
|
var forward := move_basis.z
|
|
var right := move_basis.x
|
|
forward.y = 0.0
|
|
right.y = 0.0
|
|
if forward.length() > 0.0001:
|
|
_last_move_forward = forward.normalized()
|
|
if right.length() > 0.0001:
|
|
_last_move_right = right.normalized()
|
|
_vehicle_collision_layer = collision_layer
|
|
_vehicle_collision_mask = collision_mask
|
|
|
|
func _integrate_forces(state):
|
|
if _in_vehicle:
|
|
linear_velocity = Vector3.ZERO
|
|
return
|
|
if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO:
|
|
rotation_x -= _pending_mouse_delta.y * mouse_sensitivity
|
|
rotation_y -= _pending_mouse_delta.x * mouse_sensitivity
|
|
rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90)) # Prevent flipping
|
|
_camera_pitch = rotation_x
|
|
rotation.y = rotation_y
|
|
_pending_mouse_delta = Vector2.ZERO
|
|
|
|
# Input as 2D vector
|
|
var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
|
|
|
if Input.is_action_just_pressed("player_phone"):
|
|
phone_visible = !phone_visible
|
|
if phone:
|
|
phone.visible = phone_visible
|
|
|
|
# Camera based movement
|
|
var forward := Vector3.FORWARD * -1.0
|
|
var right := Vector3.RIGHT
|
|
if cam:
|
|
forward = cam.global_transform.basis.z
|
|
right = cam.global_transform.basis.x
|
|
# Project onto ground plane so looking up/down doesn't kill movement.
|
|
forward.y = 0.0
|
|
right.y = 0.0
|
|
if forward.length() > 0.0001:
|
|
forward = forward.normalized()
|
|
_last_move_forward = forward
|
|
else:
|
|
forward = _last_move_forward
|
|
if right.length() > 0.0001:
|
|
right = right.normalized()
|
|
_last_move_right = right
|
|
else:
|
|
right = _last_move_right
|
|
|
|
var dir := (right * input2v.x + forward * input2v.y).normalized()
|
|
var target_v := dir * MOVE_SPEED
|
|
|
|
var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION
|
|
linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step)
|
|
linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step)
|
|
|
|
# Jump Logic
|
|
var on_floor = false
|
|
for i in state.get_contact_count():
|
|
var normal = state.get_contact_local_normal(i)
|
|
if normal.y > 0.5:
|
|
on_floor = true
|
|
break
|
|
|
|
if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1):
|
|
current_number_of_jumps = (current_number_of_jumps + 1) % 2
|
|
linear_velocity.y = JUMP_SPEED
|
|
audio_player.play()
|
|
|
|
if cam:
|
|
var target_yaw := global_transform.basis.get_euler().y
|
|
_camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step)
|
|
var target_basis := Basis(Vector3.UP, _camera_yaw)
|
|
var target_pos := global_position + (target_basis * _camera_offset_local)
|
|
cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step)
|
|
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
|
|
|
|
func _input(event):
|
|
if _in_vehicle:
|
|
return
|
|
if event is InputEventMouseButton:
|
|
if event.button_index == MOUSE_BUTTON_MIDDLE:
|
|
if event.pressed:
|
|
cameraMoveMode = true
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
|
else:
|
|
cameraMoveMode = false
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
|
|
if event is InputEventMouseMotion and cameraMoveMode:
|
|
_pending_mouse_delta += event.relative
|
|
|
|
if event is InputEventMouseButton and event.pressed:
|
|
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
|
zoom_camera(1.0 / ZOOM_FACTOR) # Zoom in
|
|
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
|
zoom_camera(ZOOM_FACTOR) # Zoom out
|
|
|
|
if event.is_action_pressed("player_light"):
|
|
$SpotLight3D.visible = !$SpotLight3D.visible
|
|
|
|
func zoom_camera(factor):
|
|
var new_fov = cam.fov * factor
|
|
cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV)
|
|
|
|
func enter_vehicle(_vehicle: Node, seat: Node3D, vehicle_camera: Camera3D) -> void:
|
|
_in_vehicle = true
|
|
freeze = true
|
|
sleeping = true
|
|
collision_layer = 0
|
|
collision_mask = 0
|
|
if seat:
|
|
global_transform = seat.global_transform
|
|
if cam:
|
|
cam.current = false
|
|
if vehicle_camera:
|
|
vehicle_camera.current = true
|
|
|
|
func exit_vehicle(exit_point: Node3D, vehicle_camera: Camera3D) -> void:
|
|
_in_vehicle = false
|
|
freeze = false
|
|
sleeping = false
|
|
collision_layer = _vehicle_collision_layer
|
|
collision_mask = _vehicle_collision_mask
|
|
if exit_point:
|
|
global_transform = exit_point.global_transform
|
|
if vehicle_camera:
|
|
vehicle_camera.current = false
|
|
if cam:
|
|
cam.current = true
|
|
|