stick-the-quick/autoload/Cursor3D/Cursor3D.gd

201 lines
5.3 KiB
GDScript3
Raw Normal View History

extends Node3D
signal selected(target: Node3D)
signal deselected(target: Node3D)
signal clicked(target: Node3D)
const RUNNER2CAMERA_LERP: float = 0.125
@export var spin_speed: float = 1.0
@export var hover_amplitude: float = 1.0
@export var hover_frequency: float = 0.5
@export var scale_timeout: float = 0.5
@export var click_scale_timeout: float = 0.25
@export var click_spin_boost: float = 4.0
@export var select_sound: AudioStream
@export var deselect_sound: AudioStream
@export var click_sound: AudioStream
@onready var _audio_player := $AudioStreamPlayer as AudioStreamPlayer
@onready var _material := (
($Cube as MeshInstance3D).get_surface_override_material(0)
) as ShaderMaterial
@onready var _test_camera := $SceneTestCamera as SceneTestCamera
@onready var _lock := NonReentrantCoroutineMutex.new()
var _target: Node3D = null
var _target_offset := Vector3.ZERO
var _hover_phase: float = 0.0
var _camera_distance_on_interact: float = FX.DEFAULT_CAMERA_DISTANCE
var _saved_camera_distance: float = FX.DEFAULT_CAMERA_DISTANCE
var target: Node3D:
get: return _target
set(whom): await select(whom)
func _ready() -> void:
if not _test_camera.test_mode:
hide()
func _process(delta: float) -> void:
if visible:
if _target:
global_position = _target.global_position + _target_offset
global_position += delta*Vector3.UP*hover_amplitude*sin(_hover_phase)
_hover_phase = fposmod(
_hover_phase + delta*TAU*hover_frequency,
TAU
)
rotate(Vector3.UP, spin_speed*delta)
func select(
whom: Node3D,
color: Color = Color.WHITE,
offset: Vector3 = Vector3.ZERO,
camera_distance_on_interact: float = FX.DEFAULT_CAMERA_DISTANCE
) -> void:
if whom == _target:
pass
elif not whom:
await deselect()
else:
await Wait.until(PlayerControl.enabled)
if _target:
await deselect()
await _lock.acquire()
await _do_select_anim(
whom, color, offset, camera_distance_on_interact
)
_target = whom
selected.emit(_target)
_lock.relinquish()
func deselect(whom: Node3D = null) -> void:
await _lock.acquire()
if _target:
if not whom:
whom = _target
if _target == whom:
_target = null
await _do_deselect_anim()
deselected.emit(whom)
_lock.relinquish()
func targeting(whom: Node3D = self) -> bool:
if not whom:
return not _target
elif whom == self:
return not not _target
else:
return _target == whom
func click() -> void:
await _lock.acquire()
if _target:
PlayerControl.borrow()
await _do_click_anim_first_part()
PlayerControl.restore()
FX.set_camera_target_override(self)
_saved_camera_distance = FX.get_camera_distance()
FX.set_camera_distance(_camera_distance_on_interact)
clicked.emit(_target)
await Wait.tick()
await Wait.until(PlayerControl.enabled)
FX.clear_camera_target_override()
FX.set_camera_distance(_saved_camera_distance)
await _do_click_anim_second_part()
_lock.relinquish()
func _do_select_anim(
new_target: Node3D,
color: Color,
offset: Vector3,
camera_distance_on_interact: float
) -> void:
_target_offset = offset
_camera_distance_on_interact = camera_distance_on_interact
color = Color(color, 0.75)
_material.set_shader_parameter(&'color', color)
_material.set_shader_parameter(&'glow', color/2.0)
_audio_player.stream = select_sound
_audio_player.play()
show()
scale = Vector3.ZERO
var timeout: float = scale_timeout
var new_position := new_target.global_position + _target_offset
var old_position := new_position
var runner := PlayerControl.get_runner()
if runner:
old_position = runner.global_position.lerp(
PlayerControl.get_camera().global_position,
RUNNER2CAMERA_LERP
)
while timeout > 0.0:
var delta: float = await Wait.tick()
scale += Vector3.ONE*delta/scale_timeout
global_position = old_position.lerp(
new_position,
1.0 - timeout/scale_timeout
)
timeout -= delta
scale = Vector3.ONE
func _do_deselect_anim() -> void:
_audio_player.stream = deselect_sound
_audio_player.play()
scale = Vector3.ONE
var timeout: float = scale_timeout/2.0
var old_position := global_position
var new_position := old_position
var runner := PlayerControl.get_runner()
while timeout > 0.0:
if runner:
new_position = runner.global_position.lerp(
PlayerControl.get_camera().global_position,
RUNNER2CAMERA_LERP
)
var delta: float = await Wait.tick()
scale -= Vector3.ONE*4.0*delta/scale_timeout
global_position = old_position.lerp(
new_position,
1.0 - timeout/scale_timeout
)
timeout -= delta
scale = Vector3.ZERO
hide()
func _do_click_anim_first_part() -> void:
_audio_player.stream = click_sound
_audio_player.play()
scale = Vector3.ONE
var runner := PlayerControl.get_runner()
if runner:
runner.turn_toward(global_position)
var timeout: float = click_scale_timeout
while timeout > 0.0:
var delta: float = await Wait.tick()
scale -= Vector3.ONE*delta/click_scale_timeout
rotate(Vector3.UP, click_spin_boost*delta)
timeout -= delta
scale = Vector3.ZERO
hide()
func _do_click_anim_second_part() -> void:
show()
var timeout: float = click_scale_timeout
while timeout > 0.0:
var delta: float = await Wait.tick()
scale += Vector3.ONE*delta/click_scale_timeout
rotate(Vector3.UP, click_spin_boost*delta)
timeout -= delta
scale = Vector3.ONE
func _input(event: InputEvent) -> void:
if (
(not not _target) and
PlayerControl.enabled() and
event.is_action_released(&'interact')
):
get_viewport().set_input_as_handled()
click()