Files
aibrashandClaude Sonnet 5.5 2d347d132b Геймпад Xbox, меню паузы с настройками, новый хаб с музыкой
- Геймпад: раскладка под паркур и гонки, обзор правым стиком, навигация по меню (A/B/крестовина)
- Меню паузы (Esc/Start): выход в хаб, настройки (мышь, стик, FOV, громкость, инверсия), справка по управлению
- Хаб 48x32x10 м: колонны, окна с городом, голограмма, лавки; музыка «Local Forecast - Elevator» (Kevin MacLeod, CC-BY)
- Тесты: pause_test, pad_menu_test, hub_tour; README обновлён

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 18:36:50 +05:00

139 lines
5.6 KiB
GDScript

extends Node
# Проверка меню паузы, настроек и геймпада без окна:
# godot --headless --path . res://tools/test/pause_test.tscn -- --client --server-url=ws://127.0.0.1:1
# Печатает строки OK/FAIL и «ИТОГО». Хаб загружается как сцена-потомок; последний шаг проверяет «Выйти в хаб».
var level
var p
var fails := 0
func check(name: String, cond: bool) -> void:
print(("OK " if cond else "FAIL ") + name)
if not cond:
fails += 1
func act(action: String, pressed := true) -> void:
var e := InputEventAction.new()
e.action = action
e.pressed = pressed
Input.parse_input_event(e)
func frames(n: int) -> void:
for i in n:
await get_tree().process_frame
func _ready() -> void:
Net.nick_asked = true
level = (load("res://maps/hub/hub.tscn") as PackedScene).instantiate()
add_child(level)
p = level.get_node("Player")
await frames(5)
await _run()
print("ИТОГО: %s" % ("всё хорошо" if fails == 0 else "провалов %d" % fails))
get_tree().quit(fails)
func _run() -> void:
# --- раскладка геймпада в InputMap ---
var want := {"jump": 2, "sprint": 2, "crouch": 1, "dash": 2, "zoom": 2, "ready": 1, "leaderboard": 1, "giveup": 1, "pause": 2,
"move_forward": 2, "look_left": 1, "look_down": 1}
for a in want:
var pads := 0
for e in InputMap.action_get_events(a):
if e is InputEventJoypadButton or e is InputEventJoypadMotion:
pads += 1
check("геймпад: %s привязан (%d)" % [a, pads], pads >= 1 and (a != "jump" and a != "dash" or pads == 2))
# --- пауза ---
check("меню закрыто в начале", not PauseMenu.open)
act("pause")
await frames(3)
act("pause", false)
check("Esc/Start открывает меню", PauseMenu.open)
check("игрок заблокирован в меню", Net.input_blocked)
check("в хабе кнопки «Выйти в хаб» нет", not PauseMenu._hub_btn.visible)
act("pause")
await frames(3)
act("pause", false)
check("повторный Esc закрывает меню", not PauseMenu.open)
check("после закрытия игрок свободен", not Net.input_blocked)
# --- настройки: мышь, инверсия, FOV ---
Settings.mouse_sens = 1.0
Settings.invert_y = false
p.rotation.y = 0.0
p.head.rotation.x = 0.0
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
var m := Vector2(100, 50) # мышь в headless не «захвачена», поэтому зовём расчёт обзора напрямую
p._mouse_look(m)
var yaw1: float = absf(p.rotation.y)
var pitch1: float = p.head.rotation.x
Settings.mouse_sens = 2.0
p.rotation.y = 0.0
p.head.rotation.x = 0.0
p._mouse_look(m)
check("чувствительность ×2 удваивает поворот (%.3f -> %.3f)" % [yaw1, absf(p.rotation.y)], absf(absf(p.rotation.y) / yaw1 - 2.0) < 0.05)
Settings.invert_y = true
p.head.rotation.x = 0.0
p._mouse_look(m)
check("инверсия меняет знак наклона (%.3f и %.3f)" % [pitch1, p.head.rotation.x], pitch1 != 0.0 and signf(p.head.rotation.x) == -signf(pitch1))
Settings.invert_y = false
Settings.mouse_sens = 1.0
Settings.fov = 90.0
await frames(3)
check("FOV из настроек применяется (%.0f)" % p.base_fov, is_equal_approx(p.base_fov, 90.0))
Settings.fov = Settings.DEFAULT_FOV
# --- обзор правым стиком ---
p.rotation.y = 0.0
Input.action_press("look_right", 1.0)
await frames(10)
Input.action_release("look_right")
check("правый стик поворачивает обзор (%.2f рад)" % p.rotation.y, p.rotation.y < -0.1)
var before: float = p.rotation.y
Net.input_blocked = true
Input.action_press("look_right", 1.0)
await frames(5)
Input.action_release("look_right")
Net.input_blocked = false
check("в меню стик обзор не крутит", is_equal_approx(p.rotation.y, before))
# --- кнопки геймпада доходят до действий ---
var b := InputEventJoypadButton.new()
b.button_index = JOY_BUTTON_A
b.pressed = true
Input.parse_input_event(b)
await frames(2)
check("кнопка A = прыжок", Input.is_action_pressed("jump"))
b.pressed = false
Input.parse_input_event(b)
var t := InputEventJoypadMotion.new()
t.axis = JOY_AXIS_TRIGGER_RIGHT
t.axis_value = 1.0
Input.parse_input_event(t)
await frames(2)
check("правый курок = спринт", Input.is_action_pressed("sprint"))
t.axis_value = 0.0
Input.parse_input_event(t)
# --- сохранение настроек ---
Settings.mouse_sens = 1.7
Settings.save()
Settings.mouse_sens = 1.0
Settings.load_settings()
check("настройки читаются из файла", is_equal_approx(Settings.mouse_sens, 1.7))
Settings.reset_defaults()
Settings.save()
# --- выход в хаб с другой карты ---
level.reparent(get_tree().root) # хаб — текущая сцена, чтобы смена сцены не убила сам тест
get_tree().current_scene = level
Net.current_map = "race"
act("pause")
await frames(3)
act("pause", false)
check("не в хабе кнопка «Выйти в хаб» видна", PauseMenu._hub_btn.visible)
PauseMenu._go_hub()
await frames(10)
check("«Выйти в хаб» закрыло меню", not PauseMenu.open and not Net.input_blocked)
check("«Выйти в хаб» открыло хаб (%s)" % get_tree().current_scene, get_tree().current_scene != null and get_tree().current_scene.name == "Hub")
check("карта сменилась на hub", Net.current_map == "hub")