Make the slot machine its own sidebar entry; rework its visuals

Moves Slot Machine into the sidebar's account section (alongside RZ
Shop/Settings/Log out) instead of being reachable only from the shop -
removes the shop's promo banner and the screen's "Back to Shop" link
accordingly.

Also closes a chunk of the visual gap against the mockup: a proper
cabinet-style panel (arched top corners, glowing border), a light
"screen" panel holding a real 3x3 reel grid instead of 3 single icons,
a decorative lever, an "RZ" marquee, and the cost/spin/auto-spin
controls each in their own bordered box. Added two new vendored icons
(gem, flask-conical) for reel variety and reward-list icons.
This commit is contained in:
hz
2026-07-30 01:20:24 -05:00
parent 1860e6ede6
commit bcb411bed8
9 changed files with 294 additions and 118 deletions
+174 -51
View File
@@ -5,11 +5,13 @@ const STAR_ICON := preload("res://assets/icons/lucide/star.svg")
const SHIELD_ICON := preload("res://assets/icons/lucide/shield.svg")
const CLOCK_ICON := preload("res://assets/icons/lucide/clock-3.svg")
const RESET_ICON := preload("res://assets/icons/lucide/rotate-ccw.svg")
const REEL_SYMBOLS := [STAR_ICON, SHIELD_ICON, CLOCK_ICON, RESET_ICON]
const GEM_ICON := preload("res://assets/icons/lucide/gem.svg")
const FLASK_ICON := preload("res://assets/icons/lucide/flask-conical.svg")
const REEL_SYMBOLS := [STAR_ICON, SHIELD_ICON, CLOCK_ICON, RESET_ICON, GEM_ICON, FLASK_ICON]
const ICON_BY_REWARD_KEY := {
"jackpot": "star",
"epic": "star",
"great": "star",
"epic": "gem",
"great": "shield",
"exam_token": "clock-3",
"qbank_reset": "rotate-ccw",
"nice": "star",
@@ -19,11 +21,13 @@ const ICON_BY_REWARD_KEY := {
var main: Control
var subtitle_label: Label
var back_button: Button
var machine_panel: PanelContainer
var reel_icons: Array = []
var reel_columns: Array = []
var win_up_to_card: PanelContainer
var win_up_to_label: Label
var cost_card: PanelContainer
var cost_value_label: Label
var auto_card: PanelContainer
var spin_button: Button
var auto_spin_toggle: CheckButton
var status_label: Label
@@ -74,14 +78,6 @@ func on_show(_payload: Variant = null) -> void:
func _build_header() -> void:
back_button = Button.new()
back_button.text = "← Back to Shop"
back_button.add_theme_font_override("font", Brand.font)
back_button.flat = true
back_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
back_button.pressed.connect(func(): main.show_screen("rz_store"))
add_child(back_button)
var title := Label.new()
title.text = "RZ Coin Slot Machine"
title.add_theme_font_override("font", Brand.font)
@@ -99,55 +95,93 @@ func _build_machine(parent: Control) -> void:
var dark := UIThemeService.is_dark_mode()
machine_panel = PanelContainer.new()
machine_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
machine_panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, true, Brand.ROYAL_BLUE))
machine_panel.add_theme_stylebox_override("panel", _cabinet_box())
parent.add_child(machine_panel)
var margin := MarginContainer.new()
margin.add_theme_constant_override("margin_left", 20)
margin.add_theme_constant_override("margin_right", 20)
margin.add_theme_constant_override("margin_top", 18)
margin.add_theme_constant_override("margin_bottom", 18)
margin.add_theme_constant_override("margin_left", 24)
margin.add_theme_constant_override("margin_right", 24)
margin.add_theme_constant_override("margin_top", 22)
margin.add_theme_constant_override("margin_bottom", 22)
machine_panel.add_child(margin)
var column := VBoxContainer.new()
column.add_theme_constant_override("separation", 14)
column.add_theme_constant_override("separation", 16)
margin.add_child(column)
var reels_row := HBoxContainer.new()
reels_row.add_theme_constant_override("separation", 12)
reels_row.alignment = BoxContainer.ALIGNMENT_CENTER
column.add_child(reels_row)
_build_marquee(column)
reel_icons.clear()
for _i in range(3):
var reel_panel := PanelContainer.new()
reel_panel.custom_minimum_size = Vector2(84, 84)
reel_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.ROYAL_BLUE, 12))
var icon := TextureRect.new()
icon.texture = STAR_ICON
icon.modulate = Brand.AMBER
icon.custom_minimum_size = Vector2(40, 40)
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
icon.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
icon.size_flags_vertical = Control.SIZE_SHRINK_CENTER
reel_panel.add_child(icon)
reels_row.add_child(reel_panel)
reel_icons.append(icon)
var reel_row := HBoxContainer.new()
reel_row.add_theme_constant_override("separation", 16)
reel_row.alignment = BoxContainer.ALIGNMENT_CENTER
column.add_child(reel_row)
var reel_screen := PanelContainer.new()
reel_screen.add_theme_stylebox_override("panel", _reel_screen_box())
reel_row.add_child(reel_screen)
var reel_margin := MarginContainer.new()
reel_margin.add_theme_constant_override("margin_left", 16)
reel_margin.add_theme_constant_override("margin_right", 16)
reel_margin.add_theme_constant_override("margin_top", 14)
reel_margin.add_theme_constant_override("margin_bottom", 14)
reel_screen.add_child(reel_margin)
var reels_grid := HBoxContainer.new()
reels_grid.add_theme_constant_override("separation", 12)
reel_margin.add_child(reels_grid)
reel_columns.clear()
for _column_index in range(3):
var reel_col := VBoxContainer.new()
reel_col.add_theme_constant_override("separation", 8)
reels_grid.add_child(reel_col)
var cells: Array = []
for _row_index in range(3):
var icon := TextureRect.new()
icon.texture = STAR_ICON
icon.modulate = Brand.AMBER
icon.custom_minimum_size = Vector2(34, 34)
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
reel_col.add_child(icon)
cells.append(icon)
reel_columns.append(cells)
reel_row.add_child(_build_lever())
win_up_to_card = PanelContainer.new()
win_up_to_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
column.add_child(win_up_to_card)
var win_margin := MarginContainer.new()
win_margin.add_theme_constant_override("margin_left", 14)
win_margin.add_theme_constant_override("margin_right", 14)
win_margin.add_theme_constant_override("margin_top", 10)
win_margin.add_theme_constant_override("margin_bottom", 10)
win_up_to_card.add_child(win_margin)
win_up_to_label = Label.new()
win_up_to_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
win_up_to_label.add_theme_font_override("font", Brand.font)
win_up_to_label.add_theme_font_size_override("font_size", 15)
win_up_to_label.add_theme_color_override("font_color", Brand.text_color(dark))
column.add_child(win_up_to_label)
win_margin.add_child(win_up_to_label)
var controls_row := HBoxContainer.new()
controls_row.add_theme_constant_override("separation", 14)
controls_row.add_theme_constant_override("separation", 12)
column.add_child(controls_row)
cost_card = PanelContainer.new()
cost_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
controls_row.add_child(cost_card)
var cost_margin := MarginContainer.new()
cost_margin.add_theme_constant_override("margin_left", 14)
cost_margin.add_theme_constant_override("margin_right", 14)
cost_margin.add_theme_constant_override("margin_top", 8)
cost_margin.add_theme_constant_override("margin_bottom", 8)
cost_card.add_child(cost_margin)
var cost_box := VBoxContainer.new()
cost_box.add_theme_constant_override("separation", 0)
cost_margin.add_child(cost_box)
var cost_caption := Label.new()
cost_caption.text = "Cost per spin"
cost_caption.add_theme_font_override("font", Brand.font)
@@ -159,19 +193,30 @@ func _build_machine(parent: Control) -> void:
cost_value_label.add_theme_font_size_override("font_size", 15)
cost_value_label.add_theme_color_override("font_color", Brand.text_color(dark))
cost_box.add_child(cost_value_label)
controls_row.add_child(cost_box)
spin_button = Button.new()
spin_button.text = "Spin"
spin_button.custom_minimum_size = Vector2(120, 44)
spin_button.custom_minimum_size = Vector2(140, 58)
spin_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
spin_button.size_flags_vertical = Control.SIZE_SHRINK_CENTER
spin_button.add_theme_font_override("font", Brand.font)
spin_button.add_theme_font_size_override("font_size", 17)
_style_spin_button()
spin_button.pressed.connect(_on_spin_pressed)
controls_row.add_child(spin_button)
auto_card = PanelContainer.new()
auto_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
controls_row.add_child(auto_card)
var auto_margin := MarginContainer.new()
auto_margin.add_theme_constant_override("margin_left", 14)
auto_margin.add_theme_constant_override("margin_right", 14)
auto_margin.add_theme_constant_override("margin_top", 8)
auto_margin.add_theme_constant_override("margin_bottom", 8)
auto_card.add_child(auto_margin)
var auto_box := VBoxContainer.new()
auto_box.add_theme_constant_override("separation", 0)
auto_margin.add_child(auto_box)
auto_spin_toggle = CheckButton.new()
auto_spin_toggle.text = "Auto Spin"
auto_spin_toggle.add_theme_font_override("font", Brand.font)
@@ -182,7 +227,6 @@ func _build_machine(parent: Control) -> void:
auto_caption.add_theme_font_size_override("font_size", 11)
auto_caption.add_theme_color_override("font_color", Brand.muted_color(dark))
auto_box.add_child(auto_caption)
controls_row.add_child(auto_box)
var info_label := Label.new()
info_label.text = "All rewards are instant and sent to your account."
@@ -193,6 +237,80 @@ func _build_machine(parent: Control) -> void:
column.add_child(info_label)
func _build_marquee(column: VBoxContainer) -> void:
var row := HBoxContainer.new()
row.alignment = BoxContainer.ALIGNMENT_CENTER
row.add_theme_constant_override("separation", 12)
column.add_child(row)
row.add_child(_marquee_star())
var mark := Label.new()
mark.text = "RZ"
mark.add_theme_font_override("font", Brand.font)
mark.add_theme_font_size_override("font_size", 34)
mark.add_theme_color_override("font_color", Brand.SKY_BLUE)
row.add_child(mark)
row.add_child(_marquee_star())
func _marquee_star() -> TextureRect:
var star := TextureRect.new()
star.texture = STAR_ICON
star.modulate = Brand.SKY_BLUE
star.custom_minimum_size = Vector2(16, 16)
star.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
star.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
return star
func _build_lever() -> Control:
var wrap := VBoxContainer.new()
wrap.alignment = BoxContainer.ALIGNMENT_BEGIN
wrap.size_flags_vertical = Control.SIZE_EXPAND_FILL
wrap.add_theme_constant_override("separation", 0)
var knob := PanelContainer.new()
knob.custom_minimum_size = Vector2(22, 22)
knob.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
var knob_box := StyleBoxFlat.new()
knob_box.bg_color = Brand.ROYAL_BLUE
knob_box.set_corner_radius_all(11)
knob_box.corner_detail = 20
knob_box.anti_aliasing = false
knob.add_theme_stylebox_override("panel", knob_box)
wrap.add_child(knob)
var rod := ColorRect.new()
rod.color = Color("64748b")
rod.custom_minimum_size = Vector2(5, 90)
rod.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
wrap.add_child(rod)
return wrap
func _cabinet_box() -> StyleBoxFlat:
var box := StyleBoxFlat.new()
box.bg_color = Color("0b1220")
box.border_color = Brand.SKY_BLUE
box.set_border_width_all(3)
box.corner_radius_top_left = 48
box.corner_radius_top_right = 48
box.corner_radius_bottom_left = 16
box.corner_radius_bottom_right = 16
box.shadow_color = Color(Brand.SKY_BLUE, 0.35)
box.shadow_size = 10
return box
func _reel_screen_box() -> StyleBoxFlat:
var box := StyleBoxFlat.new()
box.bg_color = Color("e2e8f0")
box.border_color = Color("94a3b8")
box.set_border_width_all(2)
box.set_corner_radius_all(10)
return box
func _build_rewards_panel(parent: Control) -> void:
var dark := UIThemeService.is_dark_mode()
var panel := PanelContainer.new()
@@ -276,9 +394,11 @@ func _build_footer() -> void:
func _refresh_theme_controls() -> void:
var dark := UIThemeService.is_dark_mode()
subtitle_label.add_theme_color_override("font_color", Brand.muted_color(dark))
machine_panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, true, Brand.ROYAL_BLUE))
win_up_to_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
win_up_to_label.add_theme_color_override("font_color", Brand.text_color(dark))
cost_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
cost_value_label.add_theme_color_override("font_color", Brand.text_color(dark))
auto_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
footer_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false))
footer_icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.ROYAL_BLUE, 18))
footer_heading.add_theme_color_override("font_color", Brand.text_color(dark))
@@ -428,20 +548,23 @@ func _await_spin_result() -> Dictionary:
func _animate_reels() -> void:
for index in range(reel_icons.size()):
_spin_one_reel(reel_icons[index], 0.9 + index * 0.25)
for index in range(reel_columns.size()):
_spin_one_reel(reel_columns[index], 0.9 + index * 0.25)
func _spin_one_reel(icon: TextureRect, duration: float) -> void:
func _spin_one_reel(cells: Array, duration: float) -> void:
var elapsed := 0.0
var interval := 0.06
while elapsed < duration:
icon.texture = REEL_SYMBOLS[randi() % REEL_SYMBOLS.size()]
var symbol: Texture2D = REEL_SYMBOLS[randi() % REEL_SYMBOLS.size()]
for cell in cells:
cell.texture = symbol
await get_tree().create_timer(interval).timeout
elapsed += interval
interval = minf(interval * 1.18, 0.22)
icon.texture = STAR_ICON
icon.modulate = Brand.AMBER
for cell in cells:
cell.texture = STAR_ICON
cell.modulate = Brand.AMBER
func _play_coin_burst(coins_won: int) -> void: