extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_keyboard_input.gd # Desktop/web controls. The phone build is driven entirely by touch, but the # web build is played with a keyboard and mouse, so WASD (and the arrows) # must steer the fleet, Q/E must pivot the formation, and releasing the keys # must stop - the stick has a centred-again signal to hook, the keyboard has # nothing equivalent, so a missed release would leave the fleet flying off # forever. # # Verifies: # 1. W/A/S/D and the arrow keys each produce the right heading, and # diagonals combine and normalise # 2. holding a key actually moves the fleet that way # 3. releasing every key stops it # 4. Q and E turn the formation, in opposite directions # 5. the on-screen stick is not taken away on a touch device var scene: Node var controller: Node var fleet: Node var joystick: Control var stage: int = 0 var elapsed: float = 0.0 var start_centroid: Vector2 = Vector2.ZERO var moving_centroid: Vector2 = Vector2.ZERO func _initialize() -> void: root.get_node("GameState").reset_formation() scene = load("res://world/Main.tscn").instantiate() root.add_child(scene) controller = scene fleet = scene.get_node("Fleet") joystick = scene.get_node("UI/Joystick") func _key(code: int, pressed: bool) -> void: var event: InputEventKey = InputEventKey.new() event.keycode = code event.pressed = pressed controller._handle_key(event) func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) # --- headings ------------------------------------------------- var cases: Array = [ {"keys": [KEY_W], "expect": Vector2.UP}, {"keys": [KEY_S], "expect": Vector2.DOWN}, {"keys": [KEY_A], "expect": Vector2.LEFT}, {"keys": [KEY_D], "expect": Vector2.RIGHT}, {"keys": [KEY_UP], "expect": Vector2.UP}, {"keys": [KEY_RIGHT], "expect": Vector2.RIGHT}, {"keys": [KEY_W, KEY_D], "expect": Vector2(1, -1).normalized()}, {"keys": [KEY_W, KEY_S], "expect": Vector2.ZERO}, ] for case in cases: for k in case.keys: _key(k, true) var got: Vector2 = controller.keyboard_direction() var ok: bool = got.distance_to(case.expect) < 0.001 if not ok: print("keys %s -> %s, expected %s" % [case.keys, got, case.expect]) print("TEST_KEYBOARD_INPUT: FAIL (wrong heading)") quit(1) return true for k in case.keys: _key(k, false) print("all %d key combinations produced the right heading" % cases.size()) # The stick must survive on a touch device. var stick_kept: bool = joystick.visible print("stick still visible after keyboard use: %s (touchscreen=%s)" % [ stick_kept, DisplayServer.is_touchscreen_available() ]) if DisplayServer.is_touchscreen_available() and not stick_kept: print("TEST_KEYBOARD_INPUT: FAIL (took the stick away on a touch device)") quit(1) return true # --- holding a key flies the fleet ---------------------------- start_centroid = fleet.get_centroid() _key(KEY_D, true) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.6: return false moving_centroid = fleet.get_centroid() var travelled: float = moving_centroid.x - start_centroid.x var sideways: float = absf(moving_centroid.y - start_centroid.y) print("held D for 0.6s: moved x=%.1f y-drift=%.1f" % [travelled, sideways]) if travelled < 50.0 or sideways > 30.0: print("TEST_KEYBOARD_INPUT: FAIL (holding a key did not fly the fleet that way)") quit(1) return true # --- releasing stops ------------------------------------------ _key(KEY_D, false) elapsed = 0.0 stage = 2 return false 2: if elapsed < 0.9: return false var coasted: float = fleet.get_centroid().x - moving_centroid.x var still_steering: bool = fleet.steer_direction != Vector2.ZERO print("0.9s after release: drifted a further %.1f, still steering=%s" % [ coasted, still_steering ]) if still_steering: print("TEST_KEYBOARD_INPUT: FAIL (fleet kept flying after the key was released)") quit(1) return true # --- Q/E pivot ------------------------------------------------ var game_state: Node = root.get_node("GameState") var before: float = game_state.formation[1].angle() _key(KEY_E, true) _key(KEY_E, false) var after_cw: float = game_state.formation[1].angle() _key(KEY_Q, true) _key(KEY_Q, false) var after_ccw: float = game_state.formation[1].angle() var cw_turn: float = angle_difference(before, after_cw) var ccw_turn: float = angle_difference(after_cw, after_ccw) print("E turned %.3f rad, Q turned %.3f rad" % [cw_turn, ccw_turn]) if absf(cw_turn) < 0.01 or absf(ccw_turn) < 0.01 or signf(cw_turn) == signf(ccw_turn): print("TEST_KEYBOARD_INPUT: FAIL (Q/E did not pivot in opposite directions)") quit(1) return true print("TEST_KEYBOARD_INPUT: PASS") quit(0) return true return false