339 lines
10 KiB
GDScript
339 lines
10 KiB
GDScript
extends Node2D
|
|
|
|
@onready var rhythm_conductor: Node = $RhythmConductor
|
|
@onready var rhythm_track: Control = $RhythmFeedback/RhythmTrack
|
|
@onready var rhythm_feedback_label: Label = $RhythmFeedback/JudgementLabel
|
|
@onready var player: Node = $Player
|
|
@onready var center_base: TextureRect = $RhythmFeedback/RhythmTrack/CenterBase
|
|
@onready var center_flash: TextureRect = $RhythmFeedback/RhythmTrack/CenterFlash
|
|
@onready var left_mover: TextureRect = $RhythmFeedback/RhythmTrack/LeftMover
|
|
@onready var right_mover: TextureRect = $RhythmFeedback/RhythmTrack/RightMover
|
|
@onready var combo_skill_label: Label = $RhythmFeedback/ComboSkillLabel
|
|
@onready var health_bar: ProgressBar = $RhythmFeedback/StatusBars/HealthBar
|
|
@onready var charge_bar: ProgressBar = $RhythmFeedback/StatusBars/ChargeBar
|
|
@onready var energy_segments: Array[Panel] = [
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment0,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment1,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment2,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment3,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment4,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment5,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment6,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment7,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment8,
|
|
$RhythmFeedback/StatusBars/EnergyBar/Segment9,
|
|
]
|
|
@onready var combo_slot_panels: Array[PanelContainer] = [
|
|
$RhythmFeedback/ComboWindow/Slot0,
|
|
$RhythmFeedback/ComboWindow/Slot1,
|
|
$RhythmFeedback/ComboWindow/Slot2,
|
|
$RhythmFeedback/ComboWindow/Slot3,
|
|
]
|
|
@onready var combo_key_labels: Array[Label] = [
|
|
$RhythmFeedback/ComboWindow/Slot0/Key,
|
|
$RhythmFeedback/ComboWindow/Slot1/Key,
|
|
$RhythmFeedback/ComboWindow/Slot2/Key,
|
|
$RhythmFeedback/ComboWindow/Slot3/Key,
|
|
]
|
|
|
|
var combo_clear_tween: Tween
|
|
var combo_clear_flash := 0.0
|
|
var charge_bar_ready := false
|
|
var charge_flash := 0.0
|
|
|
|
var track_center := Vector2.ZERO
|
|
var left_mover_start := Vector2.ZERO
|
|
var right_mover_start := Vector2.ZERO
|
|
var mover_size := Vector2.ZERO
|
|
var center_flash_size := Vector2.ZERO
|
|
var feedback_flash := 0.0
|
|
var beat_flash := 0.0
|
|
|
|
|
|
func _ready() -> void:
|
|
_cache_rhythm_track_layout()
|
|
rhythm_conductor.action_judged.connect(_on_rhythm_action_judged)
|
|
rhythm_conductor.beat.connect(_on_rhythm_beat)
|
|
if player.has_signal("combo_window_changed"):
|
|
player.connect("combo_window_changed", _on_combo_window_changed)
|
|
if player.has_signal("combo_window_cleared"):
|
|
player.connect("combo_window_cleared", _on_combo_window_cleared)
|
|
if player.has_signal("skill_requested"):
|
|
player.connect("skill_requested", _on_skill_requested)
|
|
if player.has_signal("energy_changed"):
|
|
player.connect("energy_changed", _on_energy_changed)
|
|
if player.has_signal("health_changed"):
|
|
player.connect("health_changed", _on_health_changed)
|
|
if player.has_signal("charge_changed"):
|
|
player.connect("charge_changed", _on_charge_changed)
|
|
rhythm_feedback_label.text = "READY"
|
|
_on_combo_window_changed([])
|
|
if player.has_method("get_energy") and player.has_method("get_max_energy"):
|
|
_on_energy_changed(player.call("get_energy"), player.call("get_max_energy"))
|
|
if player.has_method("get_health") and player.has_method("get_max_health"):
|
|
_on_health_changed(player.call("get_health"), player.call("get_max_health"))
|
|
if player.has_method("get_charge") and player.has_method("get_max_charge") and player.has_method("is_charge_ready") and player.has_method("is_charge_active"):
|
|
_on_charge_changed(player.call("get_charge"), player.call("get_max_charge"), player.call("is_charge_ready"), player.call("is_charge_active"))
|
|
_update_rhythm_track(0.0)
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
_update_rhythm_track(delta)
|
|
_update_combo_clear_animation(delta)
|
|
_update_charge_bar_flash(delta)
|
|
if feedback_flash > 0.0:
|
|
feedback_flash = maxf(0.0, feedback_flash - delta * 4.0)
|
|
rhythm_feedback_label.scale = Vector2.ONE * (1.0 + feedback_flash * 0.18)
|
|
|
|
|
|
func _on_rhythm_action_judged(action_name: String, rating: Dictionary) -> void:
|
|
var rating_name: String = str(rating.get("label", "miss"))
|
|
var color: Color = rating.get("color", Color("ff0055")) as Color
|
|
var diff: float = float(rating.get("diff", INF))
|
|
|
|
rhythm_feedback_label.text = "%s %s %s" % [
|
|
_format_action_name(action_name),
|
|
rating_name.to_upper(),
|
|
_format_signed_ms(diff),
|
|
]
|
|
rhythm_feedback_label.modulate = color
|
|
feedback_flash = 1.0
|
|
|
|
|
|
func _on_rhythm_beat(_position: int) -> void:
|
|
beat_flash = 1.0
|
|
|
|
|
|
func _on_combo_window_changed(slots: Array) -> void:
|
|
for index: int in range(combo_key_labels.size()):
|
|
var filled := index < slots.size()
|
|
var label := combo_key_labels[index]
|
|
var panel := combo_slot_panels[index]
|
|
label.text = str(slots[index]) if filled else "·"
|
|
label.modulate = Color(1.0, 1.0, 1.0, 1.0 if filled else 0.32)
|
|
panel.modulate = Color(1.0, 1.0, 1.0, 1.0 if filled else 0.48)
|
|
if filled:
|
|
_pulse_combo_slot(panel)
|
|
|
|
|
|
func _on_combo_window_cleared(_reason: String) -> void:
|
|
_play_combo_clear_animation()
|
|
|
|
|
|
func _on_skill_requested(skill_id: String) -> void:
|
|
combo_skill_label.text = _format_skill_name(skill_id)
|
|
|
|
|
|
func _on_energy_changed(current: int, maximum: int) -> void:
|
|
var filled_segments := clampi(current, 0, min(maximum, energy_segments.size()))
|
|
for index: int in range(energy_segments.size()):
|
|
var filled := index < filled_segments
|
|
var panel := energy_segments[index]
|
|
panel.modulate = Color(1.0, 1.0, 1.0, 1.0 if filled else 0.38)
|
|
|
|
|
|
func _on_health_changed(current: int, maximum: int) -> void:
|
|
health_bar.max_value = max(1, maximum)
|
|
health_bar.value = clampi(current, 0, maximum)
|
|
|
|
|
|
func _on_charge_changed(current: float, maximum: float, ready: bool, active: bool) -> void:
|
|
charge_bar.max_value = maxf(0.01, maximum)
|
|
charge_bar.value = clampf(current, 0.0, maximum)
|
|
charge_bar_ready = ready and active
|
|
if charge_bar_ready:
|
|
return
|
|
charge_bar.modulate = Color(1.0, 1.0, 1.0, 1.0 if active or current > 0.0 else 0.45)
|
|
|
|
|
|
func _update_charge_bar_flash(delta: float) -> void:
|
|
if not charge_bar_ready:
|
|
charge_flash = 0.0
|
|
return
|
|
charge_flash = fmod(charge_flash + delta * 7.0, TAU)
|
|
var alpha := 0.62 + 0.38 * absf(sin(charge_flash))
|
|
charge_bar.modulate = Color(1.0, 1.0, 1.0, alpha)
|
|
|
|
|
|
func _play_combo_clear_animation() -> void:
|
|
if combo_clear_tween != null and combo_clear_tween.is_valid():
|
|
combo_clear_tween.kill()
|
|
combo_clear_flash = 1.0
|
|
for panel: PanelContainer in combo_slot_panels:
|
|
panel.scale = Vector2(1.16, 1.16)
|
|
panel.modulate = Color(1.0, 1.0, 1.0, 1.0)
|
|
|
|
|
|
func _update_combo_clear_animation(delta: float) -> void:
|
|
if combo_clear_flash <= 0.0:
|
|
return
|
|
combo_clear_flash = maxf(0.0, combo_clear_flash - delta * 5.0)
|
|
var eased := combo_clear_flash * combo_clear_flash
|
|
for panel: PanelContainer in combo_slot_panels:
|
|
panel.scale = Vector2.ONE * (1.0 + 0.16 * eased)
|
|
panel.modulate = Color(1.0, 1.0, 1.0, 0.48 + 0.52 * eased)
|
|
if combo_clear_flash <= 0.0:
|
|
_restore_empty_combo_slots()
|
|
|
|
|
|
func _pulse_combo_slot(panel: PanelContainer) -> void:
|
|
var tween := create_tween()
|
|
panel.scale = Vector2(1.08, 1.08)
|
|
tween.tween_property(panel, "scale", Vector2.ONE, 0.09)
|
|
|
|
|
|
func _restore_empty_combo_slots() -> void:
|
|
for index: int in range(combo_slot_panels.size()):
|
|
combo_slot_panels[index].modulate = Color(1.0, 1.0, 1.0, 0.48)
|
|
combo_slot_panels[index].scale = Vector2.ONE
|
|
combo_key_labels[index].text = "·"
|
|
combo_key_labels[index].modulate = Color(1.0, 1.0, 1.0, 0.32)
|
|
|
|
|
|
func _update_rhythm_track(delta: float) -> void:
|
|
beat_flash = maxf(0.0, beat_flash - delta * 8.0)
|
|
var progress := 0.0
|
|
if rhythm_conductor.has_method("get_current_beat_progress"):
|
|
progress = float(rhythm_conductor.call("get_current_beat_progress"))
|
|
if beat_flash > 0.15:
|
|
progress = 1.0
|
|
|
|
_set_control_center(left_mover, left_mover_start.lerp(track_center, progress), mover_size)
|
|
_set_control_center(right_mover, right_mover_start.lerp(track_center, progress), mover_size)
|
|
_set_control_center(center_flash, track_center, center_flash_size)
|
|
center_flash.modulate = Color(1.0, 1.0, 1.0, beat_flash)
|
|
|
|
|
|
func _cache_rhythm_track_layout() -> void:
|
|
track_center = _control_center(center_base)
|
|
left_mover_start = _control_center(left_mover)
|
|
right_mover_start = _control_center(right_mover)
|
|
mover_size = left_mover.size
|
|
center_flash_size = center_flash.size
|
|
|
|
|
|
func _control_center(control: Control) -> Vector2:
|
|
return Vector2(
|
|
(control.offset_left + control.offset_right) * 0.5,
|
|
(control.offset_top + control.offset_bottom) * 0.5
|
|
)
|
|
|
|
|
|
func _set_control_center(control: Control, center: Vector2, size: Vector2) -> void:
|
|
control.offset_left = center.x - size.x * 0.5
|
|
control.offset_top = center.y - size.y * 0.5
|
|
control.offset_right = center.x + size.x * 0.5
|
|
control.offset_bottom = center.y + size.y * 0.5
|
|
|
|
|
|
func _format_action_name(action_name: String) -> String:
|
|
match action_name:
|
|
"w":
|
|
return "W"
|
|
"a":
|
|
return "A"
|
|
"d":
|
|
return "D"
|
|
"s":
|
|
return "S"
|
|
"space":
|
|
return "SP"
|
|
"skill_w":
|
|
return "W"
|
|
"skill_wa":
|
|
return "W+A"
|
|
"skill_wd":
|
|
return "W+D"
|
|
"skill_s":
|
|
return "S"
|
|
"skill_a":
|
|
return "A"
|
|
"skill_d":
|
|
return "D"
|
|
"skill_aa":
|
|
return "A+A"
|
|
"skill_dd":
|
|
return "D+D"
|
|
"skill_aaa":
|
|
return "A+A+A"
|
|
"skill_ddd":
|
|
return "D+D+D"
|
|
"skill_a_space":
|
|
return "A+SP"
|
|
"skill_d_space":
|
|
return "D+SP"
|
|
"skill_a_space_space":
|
|
return "A+SP+SP"
|
|
"skill_d_space_space":
|
|
return "D+SP+SP"
|
|
"skill_aa_space":
|
|
return "A+A+SP"
|
|
"skill_ad_space":
|
|
return "A+D+SP"
|
|
"skill_da_space":
|
|
return "D+A+SP"
|
|
"skill_dd_space":
|
|
return "D+D+SP"
|
|
"skill_s_projectile_1":
|
|
return "S+SP"
|
|
"skill_s_projectile_2":
|
|
return "S+SP+SP"
|
|
"skill_s_projectile_3":
|
|
return "S+SP+SP+SP"
|
|
_:
|
|
return action_name.to_upper()
|
|
|
|
|
|
func _format_skill_name(skill_id: String) -> String:
|
|
match skill_id:
|
|
"skill_w":
|
|
return "W"
|
|
"skill_wa":
|
|
return "W+A"
|
|
"skill_wd":
|
|
return "W+D"
|
|
"skill_s":
|
|
return "S"
|
|
"skill_a":
|
|
return "A"
|
|
"skill_d":
|
|
return "D"
|
|
"skill_aa":
|
|
return "A+A"
|
|
"skill_dd":
|
|
return "D+D"
|
|
"skill_aaa":
|
|
return "A+A+A"
|
|
"skill_ddd":
|
|
return "D+D+D"
|
|
"skill_a_space":
|
|
return "A+SP"
|
|
"skill_d_space":
|
|
return "D+SP"
|
|
"skill_a_space_space":
|
|
return "A+SP+SP"
|
|
"skill_d_space_space":
|
|
return "D+SP+SP"
|
|
"skill_aa_space":
|
|
return "A+A+SP"
|
|
"skill_ad_space":
|
|
return "A+D+SP"
|
|
"skill_da_space":
|
|
return "D+A+SP"
|
|
"skill_dd_space":
|
|
return "D+D+SP"
|
|
"skill_s_projectile_1":
|
|
return "S+SP"
|
|
"skill_s_projectile_2":
|
|
return "S+SP+SP"
|
|
"skill_s_projectile_3":
|
|
return "S+SP+SP+SP"
|
|
_:
|
|
return skill_id.to_upper()
|
|
|
|
|
|
func _format_signed_ms(seconds: float) -> String:
|
|
if is_inf(seconds):
|
|
return "-- ms"
|
|
return "%+.0f ms" % (seconds * 1000.0)
|