201 lines
5.3 KiB
GDScript3
201 lines
5.3 KiB
GDScript3
|
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()
|