145 lines
4.9 KiB
GDScript3
145 lines
4.9 KiB
GDScript3
|
|
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://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
|