Ники, общий рейтинг (топ-10 по картам), таблица Tab, сдача в гонке, таймер Parkour
- Ники: профиль user://profile.cfg (client_id + ник), панель при первом запуске и по N; Net.nick_of(id); ники над призраками, в HUD/итогах гонки и таблицах - Рейтинг на сервере: лучшее время каждого игрока по картам race/parkour/metro, топ-10, user://leaderboard.json (атомарная запись, лимиты, проверка карты/комнаты/времени) - Таблица лидеров по Tab, доски топ-3 над порталами в хабе, итоги гонки с общим рейтингом - Гонка: сдача (G, удержание 1,5 с), RACE_GIVEUP; Parkour: таймер, финиш на вершине, R — перезапуск - Новые RPC (submit_profile, recv_nick, submit_record, recv_record_result, request_leaderboard, recv_leaderboard): клиент и сервер выкладываются вместе - Тесты: nicks_lb_test, race_giveup_test, parkour_run_test, nick_ui_test, ui_tour; README обновлён Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WxZo3ekR5m1af437Qzqwc1
This commit is contained in:
+220
@@ -0,0 +1,220 @@
|
||||
extends Node
|
||||
# Автолоад «Overlay»: общие панели поверх любой карты — ввод ника (клавиша N, первый запуск в хабе)
|
||||
# и таблица лидеров (Tab). Пока открыта панель ника, Net.input_blocked = true: игрок (player.gd) стоит и не
|
||||
# обрабатывает игровые клавиши. Это отдельный флаг, НЕ player.input_frozen (его ведёт гонка на отсчёте).
|
||||
|
||||
const TEXT := Color(0.92, 0.88, 0.8)
|
||||
const DIM := Color(0.62, 0.6, 0.56)
|
||||
const MINE := Color(0.96, 0.76, 0.44)
|
||||
|
||||
var nick_open := false
|
||||
var lb_open := false
|
||||
|
||||
var _first_run := false
|
||||
var _nick_layer: CanvasLayer
|
||||
var _nick_title: Label
|
||||
var _edit: LineEdit
|
||||
var _lb_layer: CanvasLayer
|
||||
var _lb_title: Label
|
||||
var _lb_tabs: HBoxContainer
|
||||
var _lb_holder: Control
|
||||
var _lb_hint: Label
|
||||
var _lb_view: LbView
|
||||
var _lb_maps: Array = []
|
||||
var _lb_index := 0
|
||||
var _lb_for_map := ""
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_build_nick_panel()
|
||||
_build_lb_panel()
|
||||
|
||||
# ---------- ввод ника ----------
|
||||
|
||||
func _build_nick_panel() -> void:
|
||||
_nick_layer = CanvasLayer.new()
|
||||
_nick_layer.layer = 20
|
||||
_nick_layer.visible = false
|
||||
add_child(_nick_layer)
|
||||
var root := Control.new()
|
||||
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
root.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_nick_layer.add_child(root)
|
||||
var panel := PanelContainer.new()
|
||||
panel.add_theme_stylebox_override("panel", LbView.panel_style(26))
|
||||
root.add_child(panel)
|
||||
panel.set_anchors_and_offsets_preset(Control.PRESET_CENTER)
|
||||
panel.grow_horizontal = Control.GROW_DIRECTION_BOTH
|
||||
panel.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||
var box := VBoxContainer.new()
|
||||
box.add_theme_constant_override("separation", 14)
|
||||
panel.add_child(box)
|
||||
_nick_title = LbView.make_label("Введите ник", 28, TEXT)
|
||||
_nick_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(_nick_title)
|
||||
_edit = LineEdit.new()
|
||||
_edit.max_length = Net.NICK_MAX
|
||||
_edit.custom_minimum_size = Vector2(320, 40)
|
||||
_edit.add_theme_font_size_override("font_size", 22)
|
||||
_edit.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_edit.placeholder_text = "до %d символов" % Net.NICK_MAX
|
||||
_edit.context_menu_enabled = false
|
||||
var sb := StyleBoxFlat.new()
|
||||
sb.bg_color = Color(0.015, 0.018, 0.02, 0.95)
|
||||
sb.border_color = Color(0.45, 0.30, 0.16, 0.9)
|
||||
sb.set_border_width_all(1)
|
||||
sb.set_content_margin_all(8)
|
||||
_edit.add_theme_stylebox_override("normal", sb)
|
||||
_edit.add_theme_stylebox_override("focus", sb)
|
||||
_edit.add_theme_color_override("font_color", TEXT)
|
||||
box.add_child(_edit)
|
||||
_edit.focus_next = _edit.get_path() # Tab не уводит фокус
|
||||
_edit.focus_previous = _edit.get_path()
|
||||
_edit.text_submitted.connect(_on_nick_submitted)
|
||||
_edit.gui_input.connect(_on_edit_gui_input)
|
||||
var hint := LbView.make_label("Enter — подтвердить · Esc — пропустить", 16, DIM)
|
||||
hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(hint)
|
||||
|
||||
func open_nick(first_run := false) -> void:
|
||||
if nick_open:
|
||||
return
|
||||
nick_open = true
|
||||
_first_run = first_run
|
||||
Net.input_blocked = true
|
||||
if lb_open:
|
||||
close_lb()
|
||||
_nick_title.text = "Введите ник" if (first_run or Net.my_nick == "") else "Смена ника"
|
||||
_edit.text = Net.my_nick
|
||||
_edit.caret_column = _edit.text.length()
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_nick_layer.visible = true
|
||||
_edit.grab_focus.call_deferred() # открывающая клавиша уже обработана и в поле не попадает
|
||||
|
||||
func _close_nick(apply: bool) -> void:
|
||||
if not nick_open:
|
||||
return
|
||||
if apply:
|
||||
Net.set_nick(_edit.text)
|
||||
elif _first_run:
|
||||
Net.skip_nick()
|
||||
nick_open = false
|
||||
Net.input_blocked = false
|
||||
_nick_layer.visible = false
|
||||
_edit.release_focus()
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
func _on_nick_submitted(_text: String) -> void:
|
||||
_close_nick(true)
|
||||
|
||||
func _on_edit_gui_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
_edit.accept_event()
|
||||
_close_nick(false)
|
||||
|
||||
# ---------- таблица лидеров ----------
|
||||
|
||||
func _build_lb_panel() -> void:
|
||||
_lb_layer = CanvasLayer.new()
|
||||
_lb_layer.layer = 14
|
||||
_lb_layer.visible = false
|
||||
add_child(_lb_layer)
|
||||
var root := Control.new()
|
||||
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
root.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_lb_layer.add_child(root)
|
||||
var panel := PanelContainer.new()
|
||||
panel.add_theme_stylebox_override("panel", LbView.panel_style(18))
|
||||
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
root.add_child(panel)
|
||||
# справа по центру: сверху остаётся таймер гонки, в центре — отсчёт, слева — место и ворота
|
||||
panel.anchor_left = 1.0
|
||||
panel.anchor_right = 1.0
|
||||
panel.anchor_top = 0.5
|
||||
panel.anchor_bottom = 0.5
|
||||
panel.offset_right = -48.0
|
||||
panel.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||||
panel.grow_vertical = Control.GROW_DIRECTION_BOTH
|
||||
var box := VBoxContainer.new()
|
||||
box.add_theme_constant_override("separation", 8)
|
||||
panel.add_child(box)
|
||||
_lb_title = LbView.make_label("", 24, TEXT)
|
||||
box.add_child(_lb_title)
|
||||
_lb_tabs = HBoxContainer.new()
|
||||
_lb_tabs.add_theme_constant_override("separation", 14)
|
||||
box.add_child(_lb_tabs)
|
||||
var hold := VBoxContainer.new()
|
||||
hold.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
box.add_child(hold)
|
||||
_lb_holder = hold
|
||||
_lb_hint = LbView.make_label("", 14, DIM)
|
||||
box.add_child(_lb_hint)
|
||||
|
||||
func _lb_map_list() -> Array:
|
||||
if Net.TIMED_MAPS.has(Net.current_map):
|
||||
return [Net.current_map]
|
||||
return Net.TIMED_MAPS.keys() # хаб и карты без рейтинга: вкладки по всем картам
|
||||
|
||||
func open_lb() -> void:
|
||||
if lb_open or nick_open:
|
||||
return
|
||||
lb_open = true
|
||||
_lb_for_map = Net.current_map
|
||||
_lb_maps = _lb_map_list()
|
||||
_lb_index = 0
|
||||
_lb_view = LbView.create(_lb_maps[0], 18)
|
||||
_lb_holder.add_child(_lb_view)
|
||||
_lb_layer.visible = true
|
||||
_lb_refresh()
|
||||
|
||||
func close_lb() -> void:
|
||||
if not lb_open:
|
||||
return
|
||||
lb_open = false
|
||||
_lb_layer.visible = false
|
||||
if _lb_view != null:
|
||||
_lb_holder.remove_child(_lb_view)
|
||||
_lb_view.queue_free()
|
||||
_lb_view = null
|
||||
|
||||
func _lb_refresh() -> void:
|
||||
var m: String = _lb_maps[_lb_index]
|
||||
_lb_title.text = "ЛУЧШИЕ ВРЕМЕНА · " + Net.MAP_TITLES.get(m, m).to_upper() if _lb_maps.size() == 1 else "ЛУЧШИЕ ВРЕМЕНА"
|
||||
for c in _lb_tabs.get_children():
|
||||
c.queue_free()
|
||||
_lb_tabs.visible = _lb_maps.size() > 1
|
||||
for i in _lb_maps.size():
|
||||
_lb_tabs.add_child(LbView.make_label(Net.MAP_TITLES.get(_lb_maps[i], _lb_maps[i]), 18, MINE if i == _lb_index else DIM))
|
||||
_lb_hint.text = ("A / D — карта · " if _lb_maps.size() > 1 else "") + "Tab / Esc — закрыть"
|
||||
_lb_view.set_map(m)
|
||||
Net.request_board(m)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if lb_open and _lb_for_map != Net.current_map:
|
||||
close_lb() # перешли на другую карту через портал
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if nick_open:
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
_close_nick(false)
|
||||
return
|
||||
if event.is_echo():
|
||||
return
|
||||
if event.is_action_pressed("nick"):
|
||||
open_nick(false)
|
||||
elif event.is_action_pressed("leaderboard"):
|
||||
if lb_open:
|
||||
close_lb()
|
||||
else:
|
||||
open_lb()
|
||||
elif lb_open and event.is_action_pressed("ui_cancel"):
|
||||
close_lb()
|
||||
elif lb_open and _lb_maps.size() > 1:
|
||||
var d := 0
|
||||
if event.is_action_pressed("move_left"):
|
||||
d = -1
|
||||
elif event.is_action_pressed("move_right"):
|
||||
d = 1
|
||||
if d != 0:
|
||||
_lb_index = posmod(_lb_index + d, _lb_maps.size())
|
||||
_lb_refresh()
|
||||
Reference in New Issue
Block a user