keebie/scripts/game_key.gd

327 lines
7.7 KiB
GDScript

class_name GameKey extends Node3D
#region variables
static var _scene := preload("res://scenes/game_key.tscn")
@export_group("Node references")
@export var _skeleton_primary: Skeleton3D
@export var _skeleton_secondary: Skeleton3D
@export var _upper_left_label: Label3D
@export var _upper_right_label: Label3D
@export var _lower_left_label: Label3D
@export var _lower_right_label: Label3D
@export var _center_label: Label3D
@export var _press_light: OmniLight3D
@export var _sfx_player: AudioStreamPlayer3D
@export_group("Bones")
@export var _bone_top_left: String = "TopLeft"
@export var _bone_top_right: String = "TopRight"
@export var _bone_bottom_left: String = "BottomLeft"
@export var _bone_bottom_right: String = "BottomRight"
@export_group("Light")
@export_group("Animation")
@export var _pos_sod_fzr: Vector3 = Vector3(5, 0.15, 0)
@export var _light_fade_time: float = 0.25
@export_subgroup("Idle")
@export var _idle_amplitude: float = 0.4
@export var _idle_frequency := Vector2(0.25, -0.25)
@export var _press_offset: float = 0.4
@export_group("SFX")
@export var _press_sfx: AudioStream
@export var _release_sfx: AudioStream
var props: KeyProps
var keyboard: GameKeyboard
var _is_pressed: bool
var _light_timer: float
var _default_position: Vector3
var _light_energy: float
var _pos_sod: SecondOrderDynamics
@onready
var _polyphonic := _sfx_player.get_stream_playback() as AudioStreamPlaybackPolyphonic
#endregion
#region static methods
static func instantiate_with_props(
_props: KeyProps, default_position: Vector3, _keyboard: GameKeyboard
) -> GameKey:
var node := _scene.instantiate() as GameKey
node.keyboard = _keyboard
if _props.physical_keycode != KEY_SPECIAL:
node.name = node.name + " " + OS.get_keycode_string(_props.physical_keycode)
if _props.location != KEY_LOCATION_UNSPECIFIED:
node.name += (
" " + ("Left" if _props.location == KEY_LOCATION_LEFT else "Right")
)
node.load_props(_props, default_position)
return node
#endregion
#region builtins
func _ready() -> void:
_set_labels()
_reset_animations()
func _process(delta: float) -> void:
_animate(delta)
_animate_light(delta)
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("reset_animations"):
_reset_animations()
return
if event is not InputEventKey:
return
var event_key := event as InputEventKey
if (
event_key.physical_keycode != props.physical_keycode
or event_key.location != props.location
):
return
_set_pressing_from_event(event_key)
if event_key.echo:
return
if LayoutConfig.is_configuring and event_key.is_pressed():
var new_char_type: KeyProps.Char
if not keyboard.alt_visual_layout:
if not event_key.shift_pressed:
new_char_type = KeyProps.Char.MAIN
else:
new_char_type = KeyProps.Char.SHIFT
else:
if not event_key.shift_pressed:
new_char_type = KeyProps.Char.ALT
else:
new_char_type = KeyProps.Char.ALT_SHIFT
var chars_dict: Dictionary[KeyProps.Char, String] = {
new_char_type: char(event_key.unicode).to_upper()
}
_set_labels(chars_dict)
func _exit_tree() -> void:
_set_keyboard_pressed_position(false)
#endregion
#region public
func load_props(_props: KeyProps, default_position: Vector3) -> void:
_set_keyboard_pressed_position(false)
_default_position = default_position
if _is_pressed:
_set_keyboard_pressed_position(true)
if props:
_props.chars_from_dict(props.chars_to_dict())
props = _props
_skeleton_primary.visible = true
_skeleton_secondary.visible = props.secondary_rect
_set_bones()
#endregion
#region pressing
func _set_pressing_from_event(event: InputEventKey) -> void:
_set_pressing(event.is_pressed())
keyboard.emit_key_press(self, event)
func _set_pressing(is_pressed: bool) -> void:
if _is_pressed == is_pressed:
return
_is_pressed = is_pressed
_set_keyboard_pressed_position(_is_pressed)
if _is_pressed:
_play_sfx(_press_sfx)
else:
_play_sfx(_release_sfx)
func _set_keyboard_pressed_position(pressed: bool) -> void:
if pressed:
keyboard.pressed_positions[_default_position] = pressed
else:
keyboard.pressed_positions.erase(_default_position)
#endregion
#region labels
func _set_labels(chars_dict: Dictionary[KeyProps.Char, String] = {}) -> void:
_upper_left_label.text = ""
_upper_right_label.text = ""
_lower_left_label.text = ""
_lower_right_label.text = ""
_center_label.text = ""
_set_labels_text(chars_dict)
_upper_left_label.visible = _upper_left_label.text != ""
_upper_right_label.visible = _upper_right_label.text != ""
_lower_left_label.visible = _lower_left_label.text != ""
_lower_right_label.visible = _lower_right_label.text != ""
_center_label.visible = _center_label.text != ""
func _set_labels_text(chars_dict: Dictionary[KeyProps.Char, String]) -> void:
if props.physical_keycode == KEY_SPECIAL:
return
if not props.is_char():
_center_label.text = OS.get_keycode_string(props.physical_keycode)
return
if chars_dict:
props.chars_from_dict(chars_dict, false)
if props.main_char and not props.shift_char:
_upper_left_label.text = props.main_char
if props.main_char and props.shift_char:
_upper_left_label.text = props.shift_char
_lower_left_label.text = props.main_char
if not props.main_char and props.shift_char:
_upper_left_label.text = props.shift_char
if props.alt_char:
_lower_right_label.text = props.alt_char
if props.alt_shift_char:
_upper_right_label.text = props.alt_shift_char
#endregion
#region bones
func _set_bones() -> void:
var rect := Rect2(
-(keyboard.key_size * props.width) / 2,
-keyboard.key_size / 2,
keyboard.key_size * props.width,
keyboard.key_size * props.height
)
_set_bones_from_rect(rect, _skeleton_primary)
if props.secondary_rect:
var rect_secondary := Rect2(
rect.position.x + props.x2 * keyboard.key_size,
rect.position.y + props.y2 * keyboard.key_size,
keyboard.key_size * props.width2,
keyboard.key_size * props.height2
)
_set_bones_from_rect(rect_secondary, _skeleton_secondary)
func _set_bones_from_rect(rect: Rect2, skeleton: Skeleton3D) -> void:
var top_left_idx := skeleton.find_bone(_bone_top_left)
skeleton.set_bone_pose_position(
top_left_idx, Vector3(rect.position.x, 0, rect.position.y)
)
var top_right_idx := skeleton.find_bone(_bone_top_right)
skeleton.set_bone_pose_position(
top_right_idx, Vector3(rect.end.x, 0, rect.position.y)
)
var bottom_left_idx := skeleton.find_bone(_bone_bottom_left)
skeleton.set_bone_pose_position(
bottom_left_idx, Vector3(rect.position.x, 0, rect.end.y)
)
var bottom_right_idx := skeleton.find_bone(_bone_bottom_right)
skeleton.set_bone_pose_position(
bottom_right_idx, Vector3(rect.end.x, 0, rect.end.y)
)
#endregion
#region animation
func _reset_animations() -> void:
_pos_sod = SecondOrderDynamics.new(_pos_sod_fzr, _default_position)
_light_energy = _press_light.light_energy
func _animate(delta: float) -> void:
position = _pos_sod.process(delta, _animate_position())
func _animate_position() -> Vector3:
var new_position := _default_position
new_position.y += (
sin(
(
keyboard.anim_time
+ (_default_position.x / keyboard.key_size) * _idle_frequency.x
+ (_default_position.z / keyboard.key_size) * _idle_frequency.y
)
)
* _idle_amplitude
* keyboard.key_size
)
new_position.y -= (_press_offset * keyboard.key_size) if _is_pressed else 0.0
return new_position
func _animate_light(delta: float) -> void:
if _is_pressed:
_light_timer = _light_fade_time
if _light_timer <= 0:
_press_light.visible = false
return
_press_light.visible = true
_press_light.light_energy = (_light_timer / _light_fade_time) * _light_energy
_light_timer -= delta
#endregion
#region sounds
func _play_sfx(stream: AudioStream) -> void:
_polyphonic.play_stream(stream)
#endregion