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")