-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcyberpunk_hud.gd
More file actions
217 lines (199 loc) · 8.31 KB
/
Copy pathcyberpunk_hud.gd
File metadata and controls
217 lines (199 loc) · 8.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
extends CanvasLayer
@onready var health_bar: ProgressBar = $ThemeRoot/TopLeft/HealthGroup/HealthContent/HealthBar
@onready var health_val: Label = $ThemeRoot/TopLeft/HealthGroup/HealthContent/HpHeader/HealthVal
@onready var shield_bar: ProgressBar = $ThemeRoot/TopLeft/HealthGroup/HealthContent/ShieldBar
@onready var shield_val: Label = $ThemeRoot/TopLeft/HealthGroup/HealthContent/ShHeader/ShieldVal
@onready var ammo_label: Label = $ThemeRoot/BottomRight/AmmoGroup/AmmoContent/AmmoCount
@onready var pause_overlay: Control = $ThemeRoot/PauseOverlay
@onready var ap: AnimationPlayer = $AP
var cd_labels: Array[Label] = []
var state_labels: Array[Label] = []
var ability_nodes: Array[Control] = []
var log_labels: Array[Label] = []
var _game_state: Node
var _displayed_credits: float = 0.0
var _credit_tick_tween: Tween = null
const _ABILITY_ACCENT_COLORS: Array[Color] = [
Color(0.0, 0.918, 1.0, 1.0), # cyan
Color(1.0, 0.176, 0.584, 1.0), # magenta
Color(1.0, 0.306, 0.306, 1.0), # red
]
const _COOLDOWN_DIM := Color(0.227, 0.251, 0.333, 1.0)
const _CREDIT_SPEND := Color(1.0, 0.42, 0.18, 1.0) # orange flash
const _CREDIT_GAIN := Color(0.22, 1.0, 0.08, 1.0) # green flash
func _ready() -> void:
_game_state = get_node("/root/GameState")
cd_labels = [
$ThemeRoot/BottomLeft/AbilityBar/Ability1/CD1,
$ThemeRoot/BottomLeft/AbilityBar/Ability2/CD2,
$ThemeRoot/BottomLeft/AbilityBar/Ability3/CD3,
]
state_labels = [
$ThemeRoot/BottomLeft/AbilityBar/Ability1/State1,
$ThemeRoot/BottomLeft/AbilityBar/Ability2/State2,
$ThemeRoot/BottomLeft/AbilityBar/Ability3/State3,
]
ability_nodes = [
$ThemeRoot/BottomLeft/AbilityBar/Ability1,
$ThemeRoot/BottomLeft/AbilityBar/Ability2,
$ThemeRoot/BottomLeft/AbilityBar/Ability3,
]
log_labels = [
$ThemeRoot/LogFeed/LogPanel/Messages/Log1,
$ThemeRoot/LogFeed/LogPanel/Messages/Log2,
$ThemeRoot/LogFeed/LogPanel/Messages/Log3,
$ThemeRoot/LogFeed/LogPanel/Messages/Log4,
$ThemeRoot/LogFeed/LogPanel/Messages/Log5,
]
_game_state.health_changed.connect(_on_health_changed)
_game_state.shield_changed.connect(_on_shield_changed)
_game_state.ammo_changed.connect(_on_ammo_changed)
if _game_state.has_signal("credits_changed"):
_game_state.credits_changed.connect(_on_credits_changed)
_game_state.ability_cooldown_changed.connect(_on_cooldown_changed)
_game_state.damage_taken.connect(_on_damage_taken)
if _game_state.has_signal("hp_direct_hit"):
_game_state.hp_direct_hit.connect(_on_hp_direct_hit)
_game_state.log_message.connect(_on_log_message)
health_bar.value = _game_state.health
health_val.text = str(int(_game_state.health))
shield_bar.value = _game_state.shield
shield_val.text = str(int(_game_state.shield))
if "credits" in _game_state:
_displayed_credits = float(_game_state.credits)
ammo_label.text = _format_credits(_game_state.credits)
else:
ammo_label.text = str(_game_state.ammo)
if ap.has_animation("hud_fade_in"):
ap.play("hud_fade_in")
if ap.has_animation("cursor_blink"):
ap.queue("cursor_blink")
_start_hp_breathe()
func _start_hp_breathe() -> void:
# Idle breathe on alpha only — independent of dmg_flash (self_modulate RGB)
# and _on_hp_direct_hit (tweens style bg_color + position).
var breathe := create_tween().set_loops()
breathe.set_trans(Tween.TRANS_SINE)
breathe.tween_property(health_bar, "modulate:a", 0.85, 1.25)
breathe.tween_property(health_bar, "modulate:a", 1.0, 1.25)
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("pause"):
_toggle_pause()
get_viewport().set_input_as_handled()
elif event.is_action_pressed("ability_1"):
_game_state.use_ability(0)
_bounce_ability(0)
get_viewport().set_input_as_handled()
elif event.is_action_pressed("ability_2"):
_game_state.use_ability(1)
_bounce_ability(1)
get_viewport().set_input_as_handled()
elif event.is_action_pressed("ability_3"):
_game_state.use_ability(2)
_bounce_ability(2)
get_viewport().set_input_as_handled()
func _toggle_pause() -> void:
var is_paused := not pause_overlay.visible
pause_overlay.visible = is_paused
get_tree().paused = is_paused
if is_paused and ap.has_animation("pause_slide_in"):
ap.play("pause_slide_in")
func _on_health_changed(value: float, max_value: float) -> void:
health_bar.max_value = max_value
health_bar.value = value
health_val.text = str(int(value))
func _on_shield_changed(value: float, max_value: float) -> void:
shield_bar.max_value = max_value
shield_bar.value = value
shield_val.text = str(int(value))
func _on_ammo_changed(value: int) -> void:
# Legacy path; only fires if nothing else is driving ammo_label.
# When GameState.credits_changed is wired, _on_credits_changed wins.
if not (_game_state != null and "credits" in _game_state):
ammo_label.text = str(value)
func _on_credits_changed(value: int, delta: int) -> void:
# Brief color flash: orange for debit, green for top-up.
var flash := _CREDIT_SPEND if delta < 0 else _CREDIT_GAIN
ammo_label.modulate = flash
var flash_tween := create_tween()
flash_tween.tween_property(ammo_label, "modulate", Color.WHITE, 0.35)
if delta >= 0:
# Gains snap — the ticker animation is reserved for spends so it reads
# as "draining".
if _credit_tick_tween != null and _credit_tick_tween.is_running():
_credit_tick_tween.kill()
_displayed_credits = float(value)
ammo_label.text = _format_credits(value)
return
# Fast-forward counter: tween the displayed number down to the new value,
# rewriting the label each frame.
if _credit_tick_tween != null and _credit_tick_tween.is_running():
_credit_tick_tween.kill()
var duration := clampf(float(absi(delta)) / 700.0, 0.18, 0.45)
_credit_tick_tween = create_tween()
_credit_tick_tween.tween_method(_set_displayed_credits, _displayed_credits, float(value), duration) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
func _set_displayed_credits(v: float) -> void:
_displayed_credits = v
ammo_label.text = _format_credits(int(round(v)))
func _on_cooldown_changed(index: int, remaining: float) -> void:
if index >= 0 and index < cd_labels.size():
if remaining > 0.0:
cd_labels[index].text = "%.1f" % remaining
state_labels[index].text = "COOLDOWN"
state_labels[index].add_theme_color_override("font_color", _COOLDOWN_DIM)
else:
cd_labels[index].text = ["Q", "E", "R"][index]
state_labels[index].text = "ARMED"
state_labels[index].add_theme_color_override("font_color", _ABILITY_ACCENT_COLORS[index])
func _on_hp_direct_hit(_amount: float) -> void:
# Tiny jitter — just enough to register the hit without reading as a shake.
var origin := health_bar.position
var tween := create_tween()
tween.set_parallel(false)
tween.set_trans(Tween.TRANS_SINE)
for i in range(3):
var dx := randi_range(-2, 2)
var dy := randi_range(-1, 1)
tween.tween_property(health_bar, "position", origin + Vector2(dx, dy), 0.025)
tween.tween_property(health_bar, "position", origin, 0.035)
var style := health_bar.get_theme_stylebox("fill").duplicate() as StyleBoxFlat
health_bar.add_theme_stylebox_override("fill", style)
var original_color := style.bg_color
style.bg_color = Color(1.0, 0.1, 0.1, 1.0)
var flash_tween := create_tween()
flash_tween.tween_interval(0.2)
flash_tween.tween_property(style, "bg_color", original_color, 1.2)
func _on_damage_taken(_amount: float) -> void:
pass
func _on_log_message(text: String) -> void:
for i in range(log_labels.size() - 1):
log_labels[i].text = log_labels[i + 1].text
var stamp := Time.get_time_string_from_system()
log_labels[log_labels.size() - 1].text = "[%s] %s" % [stamp, text]
func _bounce_ability(index: int) -> void:
if index < 0 or index >= ability_nodes.size():
return
var node := ability_nodes[index]
if node == null:
return
# Center-pivot so the scale pops radially from the icon, not the top-left.
node.pivot_offset = node.size * 0.5
var tween := create_tween()
tween.tween_property(node, "scale", Vector2(1.18, 1.18), 0.08) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tween.tween_property(node, "scale", Vector2.ONE, 0.42) \
.set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
func _format_credits(value: int) -> String:
# Simple thousands separator (Godot has no built-in locale formatter
# for ints without pulling in the locale system).
var s := str(value)
var out := ""
var count := 0
for i in range(s.length() - 1, -1, -1):
out = s[i] + out
count += 1
if count == 3 and i > 0:
out = "," + out
count = 0
return out