BMAD-METHOD/src/modules/bmgd/gametest/knowledge/input-testing.md

8.9 KiB

Input Testing Guide

Overview

Input testing validates that all supported input devices work correctly across platforms. Poor input handling frustrates players instantly—responsive, accurate input is foundational to game feel.

Input Categories

Device Types

Device Platforms Key Concerns
Keyboard + Mouse PC Key conflicts, DPI sensitivity
Gamepad (Xbox/PS) PC, Console Deadzone, vibration, button prompts
Touch Mobile, Switch Multi-touch, gesture recognition
Motion Controls Switch, VR Calibration, drift, fatigue
Specialty Various Flight sticks, wheels, fight sticks

Input Characteristics

Characteristic Description Test Focus
Responsiveness Input-to-action delay Should feel instant (< 100ms)
Accuracy Input maps to correct action No ghost inputs or missed inputs
Consistency Same input = same result Deterministic behavior
Accessibility Alternative input support Remapping, assist options

Test Scenarios

Keyboard and Mouse

SCENARIO: All Keybinds Functional
  GIVEN default keyboard bindings
  WHEN each bound key is pressed
  THEN corresponding action triggers
  AND no key conflicts exist

SCENARIO: Key Remapping
  GIVEN player remaps "Jump" from Space to F
  WHEN F is pressed
  THEN jump action triggers
  AND Space no longer triggers jump
  AND remapping persists after restart

SCENARIO: Mouse Sensitivity
  GIVEN sensitivity set to 5 (mid-range)
  WHEN mouse moves 10cm
  THEN camera rotation matches expected degrees
  AND movement feels consistent at different frame rates

SCENARIO: Mouse Button Support
  GIVEN mouse with 5+ buttons
  WHEN side buttons are pressed
  THEN they can be bound to actions
  AND they function correctly in gameplay

Gamepad

SCENARIO: Analog Stick Deadzone
  GIVEN controller with slight stick drift
  WHEN stick is in neutral position
  THEN no movement occurs (deadzone filters drift)
  AND intentional small movements still register

SCENARIO: Trigger Pressure
  GIVEN analog triggers
  WHEN trigger is partially pressed
  THEN partial values are read (e.g., 0.5 for half-press)
  AND full press reaches 1.0

SCENARIO: Controller Hot-Swap
  GIVEN game running with keyboard
  WHEN gamepad is connected
  THEN input prompts switch to gamepad icons
  AND gamepad input works immediately
  AND keyboard still works if used

SCENARIO: Vibration Feedback
  GIVEN rumble-enabled controller
  WHEN damage is taken
  THEN controller vibrates appropriately
  AND vibration intensity matches damage severity

Touch Input

SCENARIO: Multi-Touch Accuracy
  GIVEN virtual joystick and buttons
  WHEN left thumb on joystick AND right thumb on button
  THEN both inputs register simultaneously
  AND no interference between touch points

SCENARIO: Gesture Recognition
  GIVEN swipe-to-attack mechanic
  WHEN player swipes right
  THEN attack direction matches swipe
  AND swipe is distinguished from tap

SCENARIO: Touch Target Size
  GIVEN minimum touch target of 44x44 points
  WHEN buttons are placed
  THEN all interactive elements meet minimum size
  AND elements have adequate spacing

Platform-Specific Testing

PC

  • Multiple keyboard layouts (QWERTY, AZERTY, QWERTZ)
  • Different mouse DPI settings (400-3200+)
  • Multiple monitors (cursor confinement)
  • Background application conflicts
  • Steam Input API integration

Console

Platform Specific Tests
PlayStation Touchpad, adaptive triggers, haptics
Xbox Impulse triggers, Elite controller paddles
Switch Joy-Con detachment, gyro, HD rumble

Mobile

  • Different screen sizes and aspect ratios
  • Notch/cutout avoidance
  • External controller support
  • Apple MFi / Android gamepad compatibility

Automated Test Examples

Unity

using UnityEngine.InputSystem;

[UnityTest]
public IEnumerator Movement_WithGamepad_RespondsToStick()
{
    var gamepad = InputSystem.AddDevice<Gamepad>();

    yield return null;

    // Simulate stick input
    Set(gamepad.leftStick, new Vector2(1, 0));
    yield return new WaitForSeconds(0.1f);

    Assert.Greater(player.transform.position.x, 0f,
        "Player should move right");

    InputSystem.RemoveDevice(gamepad);
}

[UnityTest]
public IEnumerator InputLatency_UnderLoad_StaysAcceptable()
{
    float inputTime = Time.realtimeSinceStartup;
    bool actionTriggered = false;

    player.OnJump += () => {
        float latency = (Time.realtimeSinceStartup - inputTime) * 1000;
        Assert.Less(latency, 100f, "Input latency should be under 100ms");
        actionTriggered = true;
    };

    var keyboard = InputSystem.AddDevice<Keyboard>();
    Press(keyboard.spaceKey);

    yield return new WaitForSeconds(0.2f);

    Assert.IsTrue(actionTriggered, "Jump should have triggered");
}

[Test]
public void Deadzone_FiltersSmallInputs()
{
    var settings = new InputSettings { stickDeadzone = 0.2f };

    // Input below deadzone
    var filtered = InputProcessor.ApplyDeadzone(new Vector2(0.1f, 0.1f), settings);
    Assert.AreEqual(Vector2.zero, filtered);

    // Input above deadzone
    filtered = InputProcessor.ApplyDeadzone(new Vector2(0.5f, 0.5f), settings);
    Assert.AreNotEqual(Vector2.zero, filtered);
}

Unreal

bool FInputTest::RunTest(const FString& Parameters)
{
    // Test gamepad input mapping
    APlayerController* PC = GetWorld()->GetFirstPlayerController();

    // Simulate gamepad stick input
    FInputKeyParams Params;
    Params.Key = EKeys::Gamepad_LeftX;
    Params.Delta = FVector(1.0f, 0, 0);
    PC->InputKey(Params);

    // Verify movement
    APawn* Pawn = PC->GetPawn();
    FVector Velocity = Pawn->GetVelocity();

    TestTrue("Pawn should be moving", Velocity.SizeSquared() > 0);

    return true;
}

Godot

func test_input_action_mapping():
    # Verify action exists
    assert_true(InputMap.has_action("jump"))

    # Simulate input
    var event = InputEventKey.new()
    event.keycode = KEY_SPACE
    event.pressed = true

    Input.parse_input_event(event)
    await get_tree().process_frame

    assert_true(Input.is_action_just_pressed("jump"))

func test_gamepad_deadzone():
    var input = Vector2(0.15, 0.1)
    var deadzone = 0.2

    var processed = input_processor.apply_deadzone(input, deadzone)

    assert_eq(processed, Vector2.ZERO, "Small input should be filtered")

func test_controller_hotswap():
    # Simulate controller connect
    Input.joy_connection_changed(0, true)
    await get_tree().process_frame

    var prompt_icon = ui.get_action_prompt("jump")

    assert_true(prompt_icon.texture.resource_path.contains("gamepad"),
        "Should show gamepad prompts after controller connect")

Accessibility Testing

Requirements Checklist

  • Full keyboard navigation (no mouse required)
  • Remappable controls for all actions
  • Button hold alternatives to rapid press
  • Toggle options for hold actions
  • One-handed control schemes
  • Colorblind-friendly UI indicators
  • Screen reader support for menus

Accessibility Test Scenarios

SCENARIO: Keyboard-Only Navigation
  GIVEN mouse is disconnected
  WHEN navigating through all menus
  THEN all menu items are reachable via keyboard
  AND focus indicators are clearly visible

SCENARIO: Button Hold Toggle
  GIVEN "sprint requires hold" is toggled OFF
  WHEN sprint button is tapped once
  THEN sprint activates
  AND sprint stays active until tapped again

SCENARIO: Reduced Button Mashing
  GIVEN QTE assist mode enabled
  WHEN QTE sequence appears
  THEN single press advances sequence
  AND no rapid input required

Performance Metrics

Metric Target Maximum Acceptable
Input-to-render latency < 50ms 100ms
Polling rate match 1:1 with device No input loss
Deadzone processing < 1ms 5ms
Rebind save/load < 100ms 500ms

Best Practices

DO

  • Test with actual hardware, not just simulated input
  • Support simultaneous keyboard + gamepad
  • Provide sensible default deadzones
  • Show device-appropriate button prompts
  • Allow complete control remapping
  • Test at different frame rates

DON'T

  • Assume controller layout (Xbox vs PlayStation)
  • Hard-code input mappings
  • Ignore analog input precision
  • Skip accessibility considerations
  • Forget about input during loading/cutscenes
  • Neglect testing with worn/drifting controllers