Win, lose screen. Game version info. Matador a lot more aggressive/wins easier (tweaks needed). No more score/combo/waves
This commit is contained in:
@@ -7,11 +7,6 @@ const _BG := Color(0.06, 0.04, 0.02, 0.78)
|
||||
const _BADGE := Color(0.16, 0.10, 0.03, 0.90)
|
||||
const _BORDER := Color(0.65, 0.48, 0.15, 0.55)
|
||||
|
||||
# Scoring: each kill scores _KILL_BASE × the live combo count; the combo grows with
|
||||
# every kill inside _COMBO_WINDOW seconds of the last and resets when that lapses.
|
||||
const _KILL_BASE: int = 100
|
||||
const _COMBO_WINDOW: float = 3.0
|
||||
|
||||
const _ABILITY_ICONS: Array[String] = [
|
||||
"res://HUD/HUD_0000_ability_JALG.png",
|
||||
"res://HUD/HUD_0001_ability_SLAM.png",
|
||||
@@ -30,20 +25,19 @@ var _toggle_btn: Button
|
||||
var _tab_hint: Label
|
||||
var _spawner: Node
|
||||
var _player: Node = null
|
||||
var _died_connected: bool = false
|
||||
var _cd_overlays: Array[ColorRect] = []
|
||||
var _cd_labels: Array[Label] = []
|
||||
var _health_segments: Array[ColorRect] = []
|
||||
var _health_connected: bool = false
|
||||
|
||||
var _score: int = 0
|
||||
var _combo: int = 0
|
||||
var _combo_timer: float = 0.0
|
||||
var _score_label: Label
|
||||
var _wave_label: Label
|
||||
var _combo_label: Label
|
||||
var _combo_track: ColorRect
|
||||
var _combo_fill: ColorRect
|
||||
var _banner_label: Label
|
||||
# Win/lose screen. _game_over latches so the result is shown once; _result_win
|
||||
# records which way it went (read by the gameplay test).
|
||||
const _WIN_VIDEO := "res://videos/bull_win.ogv" # the bull triumphs
|
||||
const _LOSS_VIDEO := "res://videos/matador_win.ogv" # the matador triumphs
|
||||
|
||||
var _game_over: bool = false
|
||||
var _result_win: bool = false
|
||||
var _over_root: Control
|
||||
var _over_video: VideoStreamPlayer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -52,22 +46,35 @@ func _ready() -> void:
|
||||
_build_controls_panel()
|
||||
_build_tab_hint()
|
||||
_build_ability_bar()
|
||||
_build_health_bar()
|
||||
_build_scoreboard()
|
||||
_build_version_label()
|
||||
_build_game_over()
|
||||
_find_spawner.call_deferred()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_update_combo(delta)
|
||||
# Unobtrusive early-build tag in the bottom-right corner.
|
||||
func _build_version_label() -> void:
|
||||
var lbl := Label.new()
|
||||
lbl.text = GameVersion.full()
|
||||
lbl.add_theme_color_override("font_color", _MUTED)
|
||||
lbl.add_theme_font_size_override("font_size", 12)
|
||||
lbl.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
lbl.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||||
lbl.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||
lbl.offset_right = -14.0
|
||||
lbl.offset_bottom = -10.0
|
||||
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(lbl)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _player == null:
|
||||
var players := get_tree().get_nodes_in_group(&"player")
|
||||
if not players.is_empty():
|
||||
_player = players[0]
|
||||
return
|
||||
if not _health_connected:
|
||||
_health_connected = true
|
||||
_player.health_changed.connect(_on_health_changed)
|
||||
_on_health_changed(_player.health)
|
||||
if not _died_connected:
|
||||
_died_connected = true
|
||||
_player.died.connect(_on_player_died)
|
||||
for i: int in _cd_overlays.size():
|
||||
var fraction: float = _player.call(&"ability_cooldown_fraction", i)
|
||||
var ov := _cd_overlays[i]
|
||||
@@ -87,14 +94,14 @@ func _find_spawner() -> void:
|
||||
if spawners.is_empty():
|
||||
return
|
||||
_spawner = spawners[0]
|
||||
_spawner.matador_killed.connect(_on_matador_killed)
|
||||
_spawner.wave_changed.connect(_on_wave_changed)
|
||||
_on_wave_changed(_spawner.current_wave())
|
||||
_spawner.all_defeated.connect(_on_all_defeated)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
if event.physical_keycode == KEY_TAB:
|
||||
if _game_over and event.physical_keycode in [KEY_ENTER, KEY_KP_ENTER]:
|
||||
_restart()
|
||||
elif event.physical_keycode == KEY_TAB and not _game_over:
|
||||
_toggle_controls()
|
||||
elif event.physical_keycode == KEY_ESCAPE:
|
||||
_return_to_menu()
|
||||
@@ -106,6 +113,12 @@ func _return_to_menu() -> void:
|
||||
get_tree().change_scene_to_file("res://MainMenu.tscn")
|
||||
|
||||
|
||||
func _restart() -> void:
|
||||
get_tree().paused = false
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
get_tree().reload_current_scene()
|
||||
|
||||
|
||||
func _toggle_controls() -> void:
|
||||
_controls_visible = not _controls_visible
|
||||
_controls_panel.visible = _controls_visible
|
||||
@@ -113,14 +126,79 @@ func _toggle_controls() -> void:
|
||||
_toggle_btn.text = "Hide controls" if _controls_visible else "Show controls"
|
||||
|
||||
|
||||
func _on_reset_pressed() -> void:
|
||||
if get_tree().paused:
|
||||
get_tree().paused = false
|
||||
if _spawner:
|
||||
_spawner.reset_spawn()
|
||||
_score = 0
|
||||
_score_label.text = "SCORE 0"
|
||||
_reset_combo()
|
||||
# ── Win / lose ──────────────────────────────────────────────────────────────
|
||||
# The bull takes one clean blade and it's over; clear the last matador and it's a
|
||||
# victory. Both routes end here — the arena freezes and the result is proclaimed.
|
||||
|
||||
func _on_player_died(cause: String = "") -> void:
|
||||
_show_game_over(false, cause)
|
||||
|
||||
|
||||
func _on_all_defeated() -> void:
|
||||
_show_game_over(true)
|
||||
|
||||
|
||||
func _show_game_over(win: bool, _cause: String = "") -> void:
|
||||
if _game_over:
|
||||
return
|
||||
_game_over = true
|
||||
_result_win = win
|
||||
|
||||
if _controls_panel:
|
||||
_controls_panel.visible = false
|
||||
if _tab_hint:
|
||||
_tab_hint.visible = false
|
||||
|
||||
# Swap in the matching outcome clip and loop it behind the buttons.
|
||||
var path := _WIN_VIDEO if win else _LOSS_VIDEO
|
||||
if ResourceLoader.exists(path):
|
||||
_over_video.stream = load(path)
|
||||
_over_video.play()
|
||||
|
||||
_over_root.visible = true
|
||||
_over_root.modulate.a = 0.0
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
get_tree().paused = true
|
||||
|
||||
create_tween().tween_property(_over_root, "modulate:a", 1.0, 0.5)
|
||||
|
||||
|
||||
func _build_game_over() -> void:
|
||||
_over_root = Control.new()
|
||||
_over_root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_over_root.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_over_root.visible = false
|
||||
add_child(_over_root)
|
||||
|
||||
# Full-screen outcome clip (bull win / matador win), looping.
|
||||
_over_video = VideoStreamPlayer.new()
|
||||
_over_video.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_over_video.expand = true
|
||||
_over_video.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_over_video.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_over_video.finished.connect(_over_video.play) # loop the outcome clip
|
||||
_over_root.add_child(_over_video)
|
||||
|
||||
# Buttons overlaid at the bottom of the clip.
|
||||
var row := HBoxContainer.new()
|
||||
row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
row.add_theme_constant_override("separation", 16)
|
||||
row.anchor_left = 0.5
|
||||
row.anchor_right = 0.5
|
||||
row.anchor_top = 1.0
|
||||
row.anchor_bottom = 1.0
|
||||
row.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
row.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||
row.offset_bottom = -48.0
|
||||
_over_root.add_child(row)
|
||||
|
||||
var again := _make_button("Go Again (Enter)")
|
||||
again.pressed.connect(_restart)
|
||||
row.add_child(again)
|
||||
|
||||
var menu := _make_button("Main Menu")
|
||||
menu.pressed.connect(_return_to_menu)
|
||||
row.add_child(menu)
|
||||
|
||||
|
||||
# A bordered, rounded, gold-trimmed panel background — the HUD's one repeated look.
|
||||
@@ -140,174 +218,6 @@ func _panel_style(bg: Color, corner: int, h_margin: float, v_margin: float,
|
||||
return s
|
||||
|
||||
|
||||
func _build_health_bar() -> void:
|
||||
var bar := HBoxContainer.new()
|
||||
bar.add_theme_constant_override("separation", 4)
|
||||
bar.anchor_left = 0.5
|
||||
bar.anchor_right = 0.5
|
||||
bar.anchor_top = 0.0
|
||||
bar.anchor_bottom = 0.0
|
||||
bar.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
bar.offset_left = -90.0
|
||||
bar.offset_top = 16.0
|
||||
bar.offset_bottom = 16.0
|
||||
add_child(bar)
|
||||
|
||||
var panel := PanelContainer.new()
|
||||
panel.add_theme_stylebox_override("panel", _panel_style(_BG, 4, 6.0, 6.0))
|
||||
bar.add_child(panel)
|
||||
|
||||
var inner := HBoxContainer.new()
|
||||
inner.add_theme_constant_override("separation", 3)
|
||||
panel.add_child(inner)
|
||||
|
||||
for i: int in 10:
|
||||
var seg := ColorRect.new()
|
||||
seg.custom_minimum_size = Vector2(14.0, 14.0)
|
||||
seg.color = Color(0.72, 0.08, 0.08)
|
||||
inner.add_child(seg)
|
||||
_health_segments.append(seg)
|
||||
|
||||
|
||||
func _on_health_changed(new_health: int) -> void:
|
||||
for i: int in _health_segments.size():
|
||||
_health_segments[i].color = Color(0.72, 0.08, 0.08) if i < new_health \
|
||||
else Color(0.20, 0.10, 0.10)
|
||||
|
||||
|
||||
# ── Scoreboard: score · wave · kill-combo ──────────────────────────────────────
|
||||
|
||||
func _build_scoreboard() -> void:
|
||||
var panel := PanelContainer.new()
|
||||
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
panel.anchor_left = 1.0
|
||||
panel.anchor_right = 1.0
|
||||
panel.anchor_top = 0.0
|
||||
panel.anchor_bottom = 0.0
|
||||
panel.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||||
panel.offset_left = -172.0
|
||||
panel.offset_right = -14.0
|
||||
panel.offset_top = 14.0
|
||||
panel.add_theme_stylebox_override("panel", _panel_style(_BG, 6, 12.0, 8.0))
|
||||
add_child(panel)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
vbox.add_theme_constant_override("separation", 2)
|
||||
panel.add_child(vbox)
|
||||
|
||||
_score_label = _score_line(vbox, "SCORE 0", _GOLD, 20)
|
||||
_wave_label = _score_line(vbox, "WAVE 1", _CREAM, 14)
|
||||
_combo_label = _score_line(vbox, "", _CREAM, 16)
|
||||
|
||||
_combo_track = ColorRect.new()
|
||||
_combo_track.color = Color(0.0, 0.0, 0.0, 0.4)
|
||||
_combo_track.custom_minimum_size = Vector2(0.0, 4.0)
|
||||
_combo_track.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_combo_track.visible = false
|
||||
vbox.add_child(_combo_track)
|
||||
|
||||
_combo_fill = ColorRect.new()
|
||||
_combo_fill.color = _GOLD
|
||||
_combo_fill.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_combo_fill.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_combo_track.add_child(_combo_fill)
|
||||
|
||||
# Centre banner that flashes on each new wave.
|
||||
_banner_label = Label.new()
|
||||
_banner_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_banner_label.anchor_left = 0.5
|
||||
_banner_label.anchor_right = 0.5
|
||||
_banner_label.anchor_top = 0.26
|
||||
_banner_label.anchor_bottom = 0.26
|
||||
_banner_label.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
_banner_label.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||
_banner_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_banner_label.add_theme_color_override("font_color", _GOLD)
|
||||
_banner_label.add_theme_font_size_override("font_size", 44)
|
||||
_banner_label.modulate.a = 0.0
|
||||
add_child(_banner_label)
|
||||
|
||||
|
||||
func _score_line(parent: VBoxContainer, text: String, color: Color, size: int) -> Label:
|
||||
var lbl := Label.new()
|
||||
lbl.text = text
|
||||
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
lbl.add_theme_color_override("font_color", color)
|
||||
lbl.add_theme_font_size_override("font_size", size)
|
||||
parent.add_child(lbl)
|
||||
return lbl
|
||||
|
||||
|
||||
# Every kill lengthens the combo; scoring is base × combo so a fast chain is worth
|
||||
# far more than the same kills spread out — rewarding aggressive, uninterrupted play.
|
||||
func _on_matador_killed() -> void:
|
||||
_combo += 1
|
||||
_combo_timer = _COMBO_WINDOW
|
||||
_score += _KILL_BASE * _combo
|
||||
_score_label.text = "SCORE %d" % _score
|
||||
_pulse(_score_label, 1.15)
|
||||
if _combo >= 2:
|
||||
_combo_label.text = "%d× COMBO" % _combo
|
||||
_combo_label.add_theme_color_override("font_color", _combo_color())
|
||||
_combo_fill.color = _combo_color()
|
||||
_combo_track.visible = true
|
||||
_pulse(_combo_label, 1.25)
|
||||
else:
|
||||
_combo_label.text = ""
|
||||
_combo_track.visible = false
|
||||
|
||||
|
||||
func _on_wave_changed(wave: int) -> void:
|
||||
_wave_label.text = "WAVE %d" % wave
|
||||
if wave > 1:
|
||||
_flash_banner("WAVE %d" % wave)
|
||||
|
||||
|
||||
func _update_combo(delta: float) -> void:
|
||||
if _combo <= 0:
|
||||
return
|
||||
_combo_timer -= delta
|
||||
if _combo_timer <= 0.0:
|
||||
_reset_combo()
|
||||
elif _combo_track.visible:
|
||||
_combo_fill.anchor_right = clampf(_combo_timer / _COMBO_WINDOW, 0.0, 1.0)
|
||||
|
||||
|
||||
func _reset_combo() -> void:
|
||||
_combo = 0
|
||||
_combo_timer = 0.0
|
||||
_combo_label.text = ""
|
||||
_combo_track.visible = false
|
||||
|
||||
|
||||
func _combo_color() -> Color:
|
||||
if _combo >= 8:
|
||||
return Color(1.0, 0.25, 0.10)
|
||||
if _combo >= 5:
|
||||
return Color(1.0, 0.50, 0.10)
|
||||
if _combo >= 3:
|
||||
return Color(1.0, 0.85, 0.20)
|
||||
return _CREAM
|
||||
|
||||
|
||||
func _pulse(ctrl: Control, amount: float) -> void:
|
||||
ctrl.pivot_offset = ctrl.size * 0.5
|
||||
ctrl.scale = Vector2.ONE * amount
|
||||
create_tween().tween_property(ctrl, "scale", Vector2.ONE, 0.25) \
|
||||
.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
||||
|
||||
|
||||
func _flash_banner(text: String) -> void:
|
||||
_banner_label.text = text
|
||||
_banner_label.modulate.a = 0.0
|
||||
var tw := create_tween()
|
||||
tw.tween_property(_banner_label, "modulate:a", 1.0, 0.2)
|
||||
tw.tween_interval(0.7)
|
||||
tw.tween_property(_banner_label, "modulate:a", 0.0, 0.6)
|
||||
|
||||
|
||||
func _build_ability_bar() -> void:
|
||||
var bar := HBoxContainer.new()
|
||||
bar.add_theme_constant_override("separation", 10)
|
||||
@@ -430,9 +340,8 @@ func _build_controls_panel() -> void:
|
||||
_row(vbox, Controls.get_key_label(&"ability_kick"), "Kick", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_slam"), "Slam", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_dash"), "Dash", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_roll"), "Roll (bank off walls!)", _CREAM)
|
||||
_row(vbox, Controls.get_key_label(&"ability_roll"), "Roll (Powerball: ram the matador!)", _CREAM)
|
||||
_row(vbox, "Scroll", "Zoom", _CREAM)
|
||||
_row(vbox, "R", "Respawn matadors (cooldown)", _MUTED)
|
||||
_row(vbox, "Tab", "Toggle controls", _MUTED)
|
||||
_row(vbox, "Esc", "Back to menu", _MUTED)
|
||||
|
||||
@@ -444,17 +353,9 @@ func _build_controls_panel() -> void:
|
||||
sep.add_theme_stylebox_override("separator", sep_style)
|
||||
vbox.add_child(sep)
|
||||
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_theme_constant_override("separation", 8)
|
||||
vbox.add_child(hbox)
|
||||
|
||||
_toggle_btn = _make_button("Show controls")
|
||||
_toggle_btn.pressed.connect(_toggle_controls)
|
||||
hbox.add_child(_toggle_btn)
|
||||
|
||||
var reset_btn := _make_button("Reset")
|
||||
reset_btn.pressed.connect(_on_reset_pressed)
|
||||
hbox.add_child(reset_btn)
|
||||
vbox.add_child(_toggle_btn)
|
||||
|
||||
|
||||
func _make_button(label_text: String) -> Button:
|
||||
@@ -462,15 +363,17 @@ func _make_button(label_text: String) -> Button:
|
||||
btn.text = label_text
|
||||
btn.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
|
||||
var normal := _panel_style(Color(0.18, 0.12, 0.04, 0.90), 5, 12.0, 6.0)
|
||||
var hover := _panel_style(Color(0.30, 0.20, 0.06, 0.95), 5, 12.0, 6.0, _GOLD)
|
||||
var normal := _panel_style(Color(0.18, 0.12, 0.04, 0.90), 5, 16.0, 8.0)
|
||||
var hover := _panel_style(Color(0.30, 0.20, 0.06, 0.95), 5, 16.0, 8.0, _GOLD)
|
||||
|
||||
btn.add_theme_stylebox_override("normal", normal)
|
||||
btn.add_theme_stylebox_override("hover", hover)
|
||||
btn.add_theme_stylebox_override("pressed", hover)
|
||||
btn.add_theme_stylebox_override("focus", hover)
|
||||
btn.add_theme_font_override("font", UiFonts.body())
|
||||
btn.add_theme_color_override("font_color", _GOLD)
|
||||
btn.add_theme_color_override("font_hover_color", _GOLD)
|
||||
btn.add_theme_font_size_override("font_size", 12)
|
||||
btn.add_theme_color_override("font_hover_color", _CREAM)
|
||||
btn.add_theme_font_size_override("font_size", 18)
|
||||
|
||||
return btn
|
||||
|
||||
|
||||
Reference in New Issue
Block a user