- Геймпад: раскладка под паркур и гонки, обзор правым стиком, навигация по меню (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>
139 lines
5.6 KiB
GDScript
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")
|