307 lines
8.2 KiB
GDScript
307 lines
8.2 KiB
GDScript
class_name GameKeyboard extends Node3D
|
|
|
|
signal key_press_changed(game_key: GameKey, event: InputEventKey)
|
|
|
|
#region variables
|
|
@export_group("Node references")
|
|
@export var _keys_holder: Node3D
|
|
@export var _sfx_player: AudioStreamPlayer3D
|
|
|
|
@export_group("Key params")
|
|
@export var key_size: float = 0.25
|
|
@export var key_gap: float = 0.025
|
|
|
|
@export_group("Animation")
|
|
@export var _rot_sod_fzr: Vector3 = Vector3(3, 0.1, 2)
|
|
@export var _time_scale: float = 0.5
|
|
|
|
@export_subgroup("Pressing")
|
|
@export var _pressing_lean_pitch: float = 0.05
|
|
@export var _pressing_lean_roll: float = 0.01
|
|
|
|
@export_group("SFX")
|
|
@export var _layout_swap_sfx: AudioStream
|
|
|
|
var alt_layout: bool
|
|
var pressed_positions: Dictionary[Vector3, bool]
|
|
var anim_time: float
|
|
|
|
@onready var _gap_to_size_ratio: float = key_gap / key_size
|
|
|
|
@onready var _default_position: Vector3 = position
|
|
@onready var _rot_sod := SecondOrderDynamics.new(_rot_sod_fzr, Vector3.ZERO)
|
|
@onready
|
|
var _polyphonic := _sfx_player.get_stream_playback() as AudioStreamPlaybackPolyphonic
|
|
|
|
#endregion
|
|
|
|
|
|
#region builtins
|
|
func _ready() -> void:
|
|
_generate_keys(LayoutConfig.layout_rows)
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
_animate(delta)
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if event.is_action_pressed("reset_animations"):
|
|
_rot_sod = SecondOrderDynamics.new(_rot_sod_fzr, _default_position)
|
|
return
|
|
|
|
if event is not InputEventKey:
|
|
return
|
|
|
|
var event_key := event as InputEventKey
|
|
|
|
if event_key.echo:
|
|
return
|
|
|
|
if (
|
|
event_key.is_pressed()
|
|
and LayoutConfig.is_configuring
|
|
and event_key.physical_keycode >= KEY_KP_0
|
|
and event_key.physical_keycode <= KEY_KP_9
|
|
):
|
|
_swap_layout(event_key.physical_keycode)
|
|
return
|
|
|
|
var keycode := event_key.get_physical_keycode_with_modifiers()
|
|
|
|
if (
|
|
event_key.is_pressed()
|
|
and (keycode == KEY_SHIFT | KEY_MASK_ALT or keycode == KEY_ALT | KEY_MASK_SHIFT)
|
|
):
|
|
alt_layout = not alt_layout
|
|
print("change layout! " + str(alt_layout))
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region public
|
|
func set_key_press(game_key: GameKey, event: InputEventKey) -> void:
|
|
key_press_changed.emit(game_key, event)
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region key generation
|
|
func _generate_keys(layout_rows: Array[Array]) -> void:
|
|
print("generating keys... ", LayoutConfig.current_layout.name())
|
|
|
|
for row: Array[KeyProps] in layout_rows:
|
|
_set_row_key_scales_with_gaps(row)
|
|
|
|
var rows_amount := layout_rows.size()
|
|
var row_width := _get_row_width(layout_rows)
|
|
var row_offset_z: float = 0
|
|
|
|
for i in range(rows_amount):
|
|
var row := layout_rows[i] as Array[KeyProps]
|
|
row_offset_z = _generate_row(row, i, row_width, rows_amount, row_offset_z)
|
|
|
|
|
|
func _generate_row(
|
|
row: Array[KeyProps],
|
|
row_index: int,
|
|
row_width: float,
|
|
rows_amount: int,
|
|
row_offset_z: float
|
|
) -> float:
|
|
var key_pos := _get_row_key_starting_pos(
|
|
row_index, rows_amount, row_width, row_offset_z
|
|
)
|
|
var key_pos_init := key_pos
|
|
|
|
for key_props in row:
|
|
var key_default_pos := _get_row_key_pos_with_offset(key_pos, key_props)
|
|
var game_key := GameKey.instantiate_with_props(key_props, key_default_pos, self)
|
|
_keys_holder.add_child(game_key, true)
|
|
|
|
key_pos += _get_row_key_step(key_props)
|
|
|
|
return row_offset_z + key_pos_init.z - key_pos.z
|
|
|
|
|
|
func _set_row_key_scales_with_gaps(row: Array[KeyProps]) -> void:
|
|
for key_props in row:
|
|
key_props.width = _get_scale_with_gaps(key_props.width)
|
|
key_props.height = _get_scale_with_gaps(key_props.height)
|
|
key_props.x = _get_scale_with_gaps(key_props.x) + _gap_to_size_ratio
|
|
key_props.y = _get_scale_with_gaps(key_props.y) + _gap_to_size_ratio
|
|
key_props.width2 = _get_scale_with_gaps(key_props.width2)
|
|
key_props.height2 = _get_scale_with_gaps(key_props.height2)
|
|
key_props.x2 = _get_scale_with_gaps(key_props.x2) + _gap_to_size_ratio
|
|
key_props.y2 = _get_scale_with_gaps(key_props.y2) + _gap_to_size_ratio
|
|
|
|
|
|
func _get_scale_with_gaps(key_scale: float) -> float:
|
|
return key_scale + _gap_to_size_ratio * (key_scale - 1)
|
|
|
|
|
|
func _get_row_width(layout_rows: Array[Array]) -> float:
|
|
var max_width: float = 0
|
|
for row: Array[KeyProps] in layout_rows:
|
|
var width: float = 0
|
|
for key_props in row:
|
|
width += key_size * key_props.width
|
|
var width_with_gaps := width + (row.size() - 1) * key_gap
|
|
max_width = maxf(max_width, width_with_gaps)
|
|
return max_width
|
|
|
|
|
|
func _get_row_key_starting_pos(
|
|
row_index: int, rows_amount: int, row_width: float, row_offset_z: float
|
|
) -> Vector3:
|
|
var offset_z: float = (
|
|
((rows_amount - 1) * key_size + (rows_amount - 1) * key_gap) / 2 + row_offset_z
|
|
)
|
|
var key_pos_x: float = -row_width / 2 + key_size / 2
|
|
var key_pos_z: float = row_index * key_size + row_index * key_gap - offset_z
|
|
return Vector3(key_pos_x, 0, key_pos_z)
|
|
|
|
|
|
func _get_row_key_pos_with_offset(key_pos: Vector3, key_props: KeyProps) -> Vector3:
|
|
key_pos.x += (key_size * key_props.width - key_size) / 2 + key_size * key_props.x
|
|
key_pos.z += key_size * key_props.y
|
|
return key_pos
|
|
|
|
|
|
func _get_row_key_step(key_props: KeyProps) -> Vector3:
|
|
var step_x := key_size * key_props.width + key_gap + key_size * key_props.x
|
|
var step_z := key_size * key_props.y
|
|
return Vector3(step_x, 0, step_z)
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region layout swapping
|
|
func _swap_layout(kp_key: Key) -> void:
|
|
if kp_key == KEY_KP_0:
|
|
return
|
|
|
|
var index := kp_key - KEY_KP_1
|
|
if index < 0 or index >= LayoutConfig.layouts.size():
|
|
return
|
|
|
|
var new_layout := (
|
|
(LayoutConfig.layouts.values() as Array[AbstractLayout])[index].name()
|
|
)
|
|
|
|
if new_layout == LayoutConfig.current_layout.name():
|
|
return
|
|
|
|
LayoutConfig.swap_layout(new_layout)
|
|
_regenerate_keys(LayoutConfig.layout_rows)
|
|
_play_sfx(_layout_swap_sfx)
|
|
|
|
|
|
func _regenerate_keys(layout_rows: Array[Array]) -> void:
|
|
print("REgenerating keys... ", LayoutConfig.current_layout.name())
|
|
|
|
var config := LayoutConfig.get_config_file()
|
|
var current_keys: Dictionary[Vector2i, Array]
|
|
|
|
for node in _keys_holder.get_children():
|
|
if node is not GameKey:
|
|
continue
|
|
var game_key := node as GameKey
|
|
var key_props := game_key.props
|
|
var dict_key := Vector2i(key_props.physical_keycode, key_props.location)
|
|
if current_keys.has(dict_key):
|
|
current_keys[dict_key].append(game_key)
|
|
else:
|
|
current_keys[dict_key] = [game_key] as Array[GameKey]
|
|
|
|
for row: Array[KeyProps] in layout_rows:
|
|
_set_row_key_scales_with_gaps(row)
|
|
|
|
var rows_amount := layout_rows.size()
|
|
var row_width := _get_row_width(layout_rows)
|
|
var row_offset_z: float = 0
|
|
|
|
for i in range(rows_amount):
|
|
var row := layout_rows[i] as Array[KeyProps]
|
|
row_offset_z = _regenerate_row(
|
|
row, i, row_width, rows_amount, row_offset_z, current_keys, config
|
|
)
|
|
|
|
for game_keys: Array[GameKey] in current_keys.values():
|
|
for game_key in game_keys:
|
|
game_key.queue_free()
|
|
|
|
|
|
func _regenerate_row(
|
|
row: Array[KeyProps],
|
|
row_index: int,
|
|
row_width: float,
|
|
rows_amount: int,
|
|
row_offset_z: float,
|
|
current_keys: Dictionary[Vector2i, Array],
|
|
config: ConfigFile,
|
|
) -> float:
|
|
var key_pos := _get_row_key_starting_pos(
|
|
row_index, rows_amount, row_width, row_offset_z
|
|
)
|
|
var key_pos_init := key_pos
|
|
|
|
for key_props in row:
|
|
var key_default_pos := _get_row_key_pos_with_offset(key_pos, key_props)
|
|
var dict_key := Vector2i(key_props.physical_keycode, key_props.location)
|
|
|
|
if current_keys.has(dict_key):
|
|
var game_key := current_keys[dict_key].pop_front() as GameKey
|
|
game_key.load_props(key_props, key_default_pos)
|
|
if current_keys[dict_key].size() == 0:
|
|
current_keys.erase(dict_key)
|
|
else:
|
|
if config:
|
|
var props_dict := LayoutConfig.get_key_config_dict(
|
|
key_props.physical_keycode, config
|
|
)
|
|
if props_dict:
|
|
key_props.chars_from_dict(props_dict)
|
|
|
|
var game_key := GameKey.instantiate_with_props(
|
|
key_props, key_default_pos, self
|
|
)
|
|
_keys_holder.add_child(game_key, true)
|
|
|
|
key_pos += _get_row_key_step(key_props)
|
|
|
|
return row_offset_z + key_pos_init.z - key_pos.z
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region animation
|
|
func _animate(delta: float) -> void:
|
|
anim_time += delta * _time_scale
|
|
rotation = _rot_sod.process(delta, _animate_rotation())
|
|
|
|
|
|
func _animate_rotation() -> Vector3:
|
|
var new_rotation := Vector3.ZERO
|
|
for pos: Vector3 in pressed_positions.keys():
|
|
var pos_pressed := pressed_positions[pos]
|
|
if not pos_pressed:
|
|
continue
|
|
new_rotation.z -= pos.x * _pressing_lean_roll
|
|
new_rotation.x += pos.z * _pressing_lean_pitch
|
|
|
|
return new_rotation
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region sounds
|
|
func _play_sfx(stream: AudioStream) -> void:
|
|
_polyphonic.play_stream(stream)
|
|
#endregion
|