Files
zombie-fps/scripts/start_screen.gd
T

199 lines
7.9 KiB
GDScript

extends CanvasLayer
## Schermata iniziale: nome del giocatore, pulsante di avvio e classifica globale
## (tutte le partite). Il pulsante di avvio e' anche il gesto che sblocca l'audio
## nel browser, quindi la partita parte solo da qui.
signal started(player_name: String)
const Profile = preload("res://scripts/player_profile.gd")
const AMBER := Color(1.0, 0.66, 0.16)
const DIM := Color(0.85, 0.78, 0.6)
const ROWS_DESKTOP := 10
const ROWS_TOUCH := 5
var player: Node
var leaderboard: Node
var profile := {}
var name_field: LineEdit
var rows_box: VBoxContainer
var status_label: Label
var me_label: Label
var rows_shown := ROWS_DESKTOP
## Vero quando il nome e' gia' stato scelto: niente campo di testo, solo il saluto.
var name_locked := false
func _ready() -> void:
layer = 4
name_locked = Profile.is_name_locked(profile)
rows_shown = ROWS_TOUCH if (player != null and player.touch_mode) else ROWS_DESKTOP
_build_ui()
if leaderboard != null:
leaderboard.fetch()
## Nome con cui si giochera': quello memorizzato se e' bloccato, altrimenti il campo.
func _stored_name() -> String:
return Profile.sanitize_name(str(profile.get("name", Profile.DEFAULT_NAME)))
func _build_ui() -> void:
var touch_mode: bool = player != null and player.touch_mode
var viewport := get_viewport().get_visible_rect().size
var width := minf(620.0, viewport.x * 0.92)
var backdrop := ColorRect.new()
backdrop.color = Color(0.01, 0.01, 0.02, 0.92)
backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(backdrop)
var center := CenterContainer.new()
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(center)
var column := VBoxContainer.new()
column.custom_minimum_size = Vector2(width, 0)
column.add_theme_constant_override("separation", 10 if touch_mode else 14)
center.add_child(column)
var title := _label("Work-Death Balance", 34 if touch_mode else 44, AMBER, HORIZONTAL_ALIGNMENT_CENTER)
column.add_child(title)
column.add_child(_label("MatGames — classifica globale", 15, DIM, HORIZONTAL_ALIGNMENT_CENTER))
if name_locked:
# Nome gia' scelto: non si richiede piu' (e nessun campo da toccare per sbaglio).
column.add_child(_label("giocatore: %s" % _stored_name(), 22, AMBER, HORIZONTAL_ALIGNMENT_CENTER))
else:
column.add_child(_build_name_row())
var play := Button.new()
play.text = "GIOCA"
play.add_theme_font_size_override("font_size", 30)
play.custom_minimum_size = Vector2(0, 56)
play.pressed.connect(_start)
column.add_child(play)
var panel := PanelContainer.new()
var style := StyleBoxFlat.new()
style.bg_color = Color(0.03, 0.025, 0.01, 0.9)
style.border_color = Color(1.0, 0.66, 0.16, 0.4)
style.set_border_width_all(1)
style.set_corner_radius_all(4)
style.set_content_margin_all(10)
panel.add_theme_stylebox_override("panel", style)
column.add_child(panel)
var board_column := VBoxContainer.new()
board_column.add_theme_constant_override("separation", 4)
panel.add_child(board_column)
var header := HBoxContainer.new()
board_column.add_child(header)
header.add_child(_label("CLASSIFICA", 17, AMBER, HORIZONTAL_ALIGNMENT_LEFT))
var spacer := Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header.add_child(spacer)
var refresh := Button.new()
refresh.text = "aggiorna"
refresh.add_theme_font_size_override("font_size", 15)
refresh.pressed.connect(func(): _set_status("aggiornamento...") ; leaderboard.fetch())
header.add_child(refresh)
rows_box = VBoxContainer.new()
rows_box.add_theme_constant_override("separation", 2)
board_column.add_child(rows_box)
me_label = _label("", 15, DIM, HORIZONTAL_ALIGNMENT_LEFT)
board_column.add_child(me_label)
status_label = _label("caricamento classifica...", 14, DIM, HORIZONTAL_ALIGNMENT_LEFT)
board_column.add_child(status_label)
_show_rows([])
## Riga del nome, mostrata solo finche' il nome non e' stato scelto.
func _build_name_row() -> HBoxContainer:
var name_row := HBoxContainer.new()
name_row.add_theme_constant_override("separation", 8)
name_field = LineEdit.new()
name_field.text = str(profile.get("name", Profile.DEFAULT_NAME))
name_field.max_length = Profile.NAME_MAX
name_field.placeholder_text = "il tuo nome"
name_field.virtual_keyboard_enabled = true # necessario su mobile Web
name_field.size_flags_horizontal = Control.SIZE_EXPAND_FILL
name_field.add_theme_font_size_override("font_size", 22)
# Invio / tasto "Vai" della tastiera del telefono: conferma il nome, NON avvia
# la partita (prima lo faceva e su mobile partiva la partita per sbaglio).
name_field.text_submitted.connect(func(text): name_field.text = Profile.sanitize_name(text))
name_row.add_child(name_field)
var edit := Button.new()
edit.text = "nome…"
edit.tooltip_text = "tastiera del telefono / finestra del browser"
edit.add_theme_font_size_override("font_size", 18)
edit.pressed.connect(_ask_name)
name_row.add_child(edit)
return name_row
func _label(text: String, size: int, color: Color, align: int) -> Label:
var label := Label.new()
label.text = text
label.horizontal_alignment = align
label.add_theme_color_override("font_color", color)
label.add_theme_color_override("font_shadow_color", Color.BLACK)
label.add_theme_font_size_override("font_size", size)
return label
func _set_status(text: String) -> void:
if status_label != null:
status_label.text = text
## Righe della classifica: tre colonne, per restare leggibile senza font monospaziato.
func _show_rows(rows: Array) -> void:
for child in rows_box.get_children():
child.queue_free()
if rows.is_empty():
rows_box.add_child(_label("nessun punteggio registrato: gioca la prima partita", 15, DIM, HORIZONTAL_ALIGNMENT_LEFT))
return
var mine := str(profile.get("player_id", ""))
for i in mini(rows_shown, rows.size()):
var row: Dictionary = rows[i]
var line := HBoxContainer.new()
line.add_theme_constant_override("separation", 6)
var rank := _label("%d." % int(row.get("rank", i + 1)), 17, AMBER, HORIZONTAL_ALIGNMENT_RIGHT)
rank.custom_minimum_size = Vector2(38, 0)
line.add_child(rank)
var is_mine := str(row.get("player_id", "")) == mine and mine != ""
var who := _label(str(row.get("name", "?")), 17, AMBER if is_mine else Color.WHITE,
HORIZONTAL_ALIGNMENT_LEFT)
who.size_flags_horizontal = Control.SIZE_EXPAND_FILL
who.clip_text = true
line.add_child(who)
var score_label := _label(str(int(row.get("score", 0))), 17, AMBER, HORIZONTAL_ALIGNMENT_RIGHT)
score_label.custom_minimum_size = Vector2(80, 0)
line.add_child(score_label)
rows_box.add_child(line)
func show_leaderboard(rows: Array, total: int, me: Dictionary) -> void:
if rows_box == null:
return
_show_rows(rows)
_set_status("%d partite registrate" % total if total > 0 else "nessuna partita registrata")
if me.is_empty():
me_label.text = ""
else:
me_label.text = "il tuo miglior punteggio: %d (%d° posto, %d partite)" % [
int(me.get("best", 0)), int(me.get("rank", 0)), int(me.get("games", 0))]
func show_leaderboard_error(reason: String) -> void:
_set_status("classifica non disponibile (%s)" % reason)
## Su Web la tastiera virtuale di Godot e' sperimentale: in aggiunta al campo di testo
## si offre il prompt nativo del browser, che ha sempre la tastiera del telefono.
func _ask_name() -> void:
if name_locked or name_field == null:
return
var current := Profile.js_escape(name_field.text)
if OS.has_feature("web") and ClassDB.class_exists("JavaScriptBridge"):
var answer := str(JavaScriptBridge.eval(
"window.prompt('Il tuo nome (max 16 caratteri):', '%s') || ''" % current, true))
if answer.strip_edges() != "":
name_field.text = Profile.sanitize_name(answer)
return
name_field.grab_focus()
## La partita parte SOLO da qui, cioe' dal pulsante GIOCA: un click o un tocco
## altrove (sfondo, campo del nome, tasto Invio della tastiera del telefono) non deve
## avviare nulla.
func _start() -> void:
if name_locked or name_field == null:
started.emit(_stored_name())
return
started.emit(Profile.sanitize_name(name_field.text))