Input Triggers

Asteroid Impact can be configured to synchronize advancing steps with pulses from an external system. The external system should either send a byte over serial to the computer running Asteroid Impact, appear as a keyboard and press a key, or change the logic level of at least one parallel status pin. Each of these events is seen as a incoming trigger pulse, and steps in Asteroid Impact advance after a configurable number of pulses.

You can visualize trigger pulses with an icon that flashes on screen by using the -–trigger-blink true command-line option.

Input Triggers

The input triggers will automatically advance to the next step after the game receives the configured number of trigger pulses. The trigger pulse count for each step is configured with the trigger_count attribute.

The input triggers can be configured for one of the the three inputs:

  1. When receiving a particular byte, such as an ascii ‘5’ over a serial port.
  2. When a keyboard key, like the 5 number key is pressed down.
  3. When the input on configured parallel port changes the status byte value from the configured common value to the configured trigger value.

JSON Configuration Sample

Below is a sample script JSON with input triggers configured.

{
  "trigger_settings": {
    "mode": "keyboard",

    "serial_options": {
      "port": "COM5",
      "baudrate": 19200,
      "trigger_byte_value": 53
    },

    "keyboard_options": {
      "trigger_key": "K_5"
    },

    "parallel_options": {
      "port_address_hex": "BF00",
      "common_status_value_hex": "0x00",
      "trigger_status_value_hex": "0x08"
    }
  },
  "output_trigger_settings": {
    "mode": "none",

    "serial_trigger_strings_by_event": {
      "step_begin": "1",
      "game_level_begin": "2",
      "game_level_complete": "3",
      "game_death": "4",
      "game_shield_activate": "5",
      "game_slow_activate": "6",
      "game_crystal_collected": "7",
      "adaptive_difficulty_increase": "8",
      "adaptive_difficulty_decrease": "9"
    },
    "serial_options": {
      "port": "COM6",
      "baudrate": 19200
    },

    "parallel_trigger_hex_values_by_event": {
      "step_begin": "0x01",
      "game_level_begin": "0x02",
      "game_level_complete": "0x04",
      "game_death": "0x08",
      "game_shield_activate": "0x10",
      "game_slow_activate": "0x20",
      "game_crystal_collected": "0x40",
      "adaptive_difficulty_increase": "0x80",
      "adaptive_difficulty_decrease": "0xFF"
    },
    "parallel_options": {
      "port_address_hex": "BF00",
      "common_data_value_hex": "0x10",
      "trigger_frames": 3
    }
  },
  "steps": [
    {
      "action": "instructions",
      "trigger_count": 10
    },
    {
      "action": "game",
      "levels": "levels/standardlevels.json",
      "trigger_count": 10,
      "reaction_prompts": [
        {
          "diameter": 80,
          "position_list": [
            [ 200, 200 ],
            [ 200, 300 ],
            [ 200, 400 ],
            [ 200, 500 ],
            [ 200, 600 ]
          ],
          "image": "triangle.png",
          "sound": "tone440.wav",
          "showtimes_millis": [ 1000, 3000, 5000, 7000, 9000, 11000, 13000, 15000, 17000, 19000, 21000, 23000, 25000, 27000, 29000, 31000, 33000, 35000, 37000, 39000, 41000, 43000, 45000, 47000, 49000, 51000, 53000, 55000, 57000, 59000, 61000, 63000, 65000, 67000, 69000, 71000, 73000, 75000, 77000, 79000, 81000, 83000, 85000, 87000, 89000, 91000, 93000, 95000, 97000, 99000, 101000, 103000, 105000, 107000, 109000, 111000, 113000, 115000, 117000, 119000, 121000, 123000, 125000, 127000, 129000, 131000, 133000, 135000, 137000, 139000, 141000, 143000, 145000, 147000, 149000, 151000, 153000, 155000, 157000, 159000, 161000, 163000, 165000, 167000, 169000, 171000, 173000, 175000, 177000, 179000, 181000, 183000, 185000, 187000, 189000, 191000, 193000, 195000, 197000, 199000 ],
          "showtimes_trigger_counts": [],
          "input_key": "K_1",
          "timeout_millis": 1500,
          "stay_visible": false
        },
        {
          "diameter": 80,
          "position_list": [
            [ 300, 200 ]
          ],
          "image": "circle.png",
          "sound": "tone659.wav",
          "showtimes_millis": [ 1500, 3500, 5500, 7500, 9500, 11500, 13500, 15500, 17500, 19500, 21500, 23500, 25500, 27500, 29500, 31500, 33500, 35500, 37500, 39500, 41500, 43500, 45500, 47500, 49500, 51500, 53500, 55500, 57500, 59500, 61500, 63500, 65500, 67500, 69500, 71500, 73500, 75500, 77500, 79500, 81500, 83500, 85500, 87500, 89500, 91500, 93500, 95500, 97500, 99500, 101500, 103500, 105500, 107500, 109500, 111500, 113500, 115500, 117500, 119500, 121500, 123500, 125500, 127500, 129500, 131500, 133500, 135500, 137500, 139500, 141500, 143500, 145500, 147500, 149500, 151500, 153500, 155500, 157500, 159500, 161500, 163500, 165500, 167500, 169500, 171500, 173500, 175500, 177500, 179500, 181500, 183500, 185500, 187500, 189500, 191500, 193500, 195500, 197500, 199500 ],
          "showtimes_trigger_counts": [],
          "input_key": "K_2",
          "timeout_millis": 1500,
          "stay_visible": false
        },
        {
          "diameter": 80,
          "position_list": [
            [ 400, 200 ]
          ],
          "image": "square.png",
          "sound": "tone146.wav",
          "showtimes_millis": [],
          "showtimes_trigger_counts": [ 1, 2, 3, 4, 5, 6 ],
          "input_key": "K_MOUSE1",
          "timeout_millis": "never",
          "stay_visible": false
        }
      ]
    },
    {
      "action": "text",
      "text": "Custom instructions can appear here. They can be split into paragraphs by escaping newlines.\n\nThis is a second paragraph.\n\nThe next step after this one is a 5 second black screen.",
      "title": "Additional Instructions",
      "trigger_count": 10
    },
    {
      "action": "blackscreen",
      "trigger_count": 5
    },
    {
      "action": "survey",
      "prompt": "Bacon ipsum dolor amet tail ribeye cow prosciutto flank. Short ribs sausage leberkas boudin biltong jerky swine spare ribs flank salami kevin short loin pork chop. Meatloaf drumstick spare ribs ball tip venison meatball. Picanha biltong t-bone fatback flank ribeye. Pork shoulder meatloaf beef, bresaola meatball ground round filet mignon. Tri-tip swine pork belly turkey, prosciutto filet mignon pork loin bresaola kielbasa pig biltong pork frankfurter. Tri-tip ham boudin biltong pig meatloaf pork belly pork tail shank t-bone shoulder pastrami.",
      "options": [ "one", "two", "three", "four", "five" ],
      "trigger_count": 25
    },
    {
      "action": "game",
      "levels": "levels/hardlevels.json",
      "trigger_count": 20
    },
    {
      "trigger_count": 10,
      "action": "game-adaptive",
      "start_level": 0.5,
      "level_completion_increment": 0.3,
      "level_death_decrement": 0.4,
      "continuous_asteroids_on_same_level": false,
      "adaptive_asteroid_size_locked_to_initial": false,
      "show_advance_countdown": false,
      "level_templates": [
        {
          "asteroid_count": 1,
          "asteroid_speeds": "slow",
          "powerup_count": 0,
          "target_count": 3
        },
        {
          "asteroid_count": 3,
          "asteroid_sizes": "varied",
          "asteroid_speeds": "medium",
          "powerup_count": 10,
          "powerup_delay": 0.5,
          "powerup_types": [
            "slow"
          ],
          "target_count": 3
        },
        {
          "asteroid_count": 8,
          "asteroid_sizes": "varied",
          "asteroid_speeds": "medium",
          "powerup_count": 10,
          "powerup_delay": 2.0,
          "powerup_types": [
            "slow",
            "shield"
          ],
          "target_count": 3
        },
        {
          "asteroid_count": 5,
          "asteroid_speeds": "extreme",
          "powerup_count": 10,
          "powerup_delay": 0.5,
          "powerup_types": [
            "shield"
          ],
          "target_count": 3
        }
      ]
    },
    {
      "action": "blackscreen",
      "trigger_count": 10
    }
  ]
}

Serial Input Configuration

When configured for serial input, the game connects to the serial port with the specified options and reads all input. When the game receives a byte that matches trigger_byte_value configuration it treats that as a trigger input. Other bytes are read but ignored.

Below is a sample script JSON with only serial input triggers configured and two text steps.

{
  "trigger_settings": {
    "mode": "keyboard",

    "serial_options": {
      "port": "COM5",
      "baudrate": 19200,
      "trigger_byte_value": 53
    }
  },
  "steps": [
    {
      "action": "text",
      "text": "Text step 1"
      "title": "",
      "trigger_count": 10
    },
    {
      "action": "text",
      "text": "Text step 2"
      "title": "",
      "trigger_count": 10
    }
  ]
}

serial_options options:

trigger_byte_value
The decimal representation of the byte you want to trigger on. In the sample above, the trigger_byte_value of 53 is the ‘5’ character in ASCII. See http://www.asciitable.com the ‘Dec’ colum shows the decimal number of the character.
port
This should be the device name of your serial port. In Windows it will likely be "COM1" or similar. Check Device Manager to find your serial port. In Linux it will likely be something like "/dev/ttyUSB0", and in OSX something like "/dev/tty.usbmodem1234"
baudrate
The baudrate in symbols/second to connect to the serial port. For hardware serial devices connecting at the wrong baudrate will result in gibberish characters being read.
bytesize
Number of bits per byte. Defaults to 8, but it can sometimes be 7, 6 or 5.
stopbits
Number of stop bits. Defaults to 1, can be 2.
parity
The parity must be one of the following: "even", "mark", "names", "none", "odd", or "space".

Keyboard Input

Below is a sample script JSON with only keyboard input triggers configured and two text steps.

{
  "trigger_settings": {
    "mode": "keyboard",

    "keyboard_options": {
      "trigger_key": "K_5"
    },
  },
  "steps": [
    {
      "action": "text",
      "text": "Text step 1"
      "title": "",
      "trigger_count": 10
    },
    {
      "action": "text",
      "text": "Text step 2"
      "title": "",
      "trigger_count": 10
    }
  ]
}

The trigger pulse is when you press down the configured trigger_key. "K_5" corresponds to the 5 key on your keyboard.

There is not currently an option to trigger on joystick buttons, or mouse buttons. Only keyboard right now.

Multi-key trigger sequences are not supported. The availble options for trigger_key1 are the following:

K_0 through K_9
K_AMPERSAND
K_ASTERISK
K_AT
K_BACKQUOTE
K_BACKSLASH
K_BACKSPACE
K_BREAK
K_CAPSLOCK
K_CARET
K_CLEAR
K_COLON
K_COMMA
K_DELETE
K_DOLLAR
K_DOWN
K_END
K_EQUALS
K_ESCAPE
K_EURO
K_EXCLAIM
K_F1 through K_F15
K_FIRST
K_GREATER
K_HASH
K_HELP
K_HOME
K_INSERT
K_KP0 through K_KP9
K_KP_DIVIDE
K_KP_ENTER
K_KP_EQUALS
K_KP_MINUS
K_KP_MULTIPLY
K_KP_PERIOD
K_KP_PLUS
K_LALT
K_LAST
K_LCTRL
K_LEFT
K_LEFTBRACKET
K_LEFTPAREN
K_LESS
K_LMETA
K_LSHIFT
K_LSUPER
K_MENU
K_MINUS
K_MODE
K_NUMLOCK
K_PAGEDOWN
K_PAGEUP
K_PAUSE
K_PERIOD
K_PLUS
K_POWER
K_PRINT
K_QUESTION
K_QUOTE
K_QUOTEDBL
K_RALT
K_RCTRL
K_RETURN
K_RIGHT
K_RIGHTBRACKET
K_RIGHTPAREN
K_RMETA
K_RSHIFT
K_RSUPER
K_SCROLLOCK
K_SEMICOLON
K_SLASH
K_SPACE
K_SYSREQ
K_TAB
K_UNDERSCORE
K_UP
K_a through K_z

Parallel Input

Note: Parallel input only works on Windows computers, and they require the inpout32 driver to be installed.

Below is a sample script JSON with only serial input triggers configured and two text steps.

{
  "trigger_settings": {
    "mode": "parallel",

    "parallel_options": {
      "port_address_hex": "BF00",
      "common_status_value_hex": "0x00",
      "trigger_status_value_hex": "0x08"
    }
  },
  "steps": [
    {
      "action": "text",
      "text": "Text step 1"
      "title": "",
      "trigger_count": 10
    },
    {
      "action": "text",
      "text": "Text step 2"
      "title": "",
      "trigger_count": 10
    }
  ]
}

The parallel trigger mode will connect to a parallel port at the data address specified, and when the value in the status byte changes from the common to the trigger value will increment the current trigger count. See See Parallel Port Connections for how to use the parallel port test feature to find the values and test. The values configured are in hexadecimal.

parallel_options fields:

port_address_hex
The IO port address for the parallel port. You can see this in Device Manager, go to properties for the parallel port, and on the Resources tab the first listed IO range address in hex is the one you should enter here.
common_status_value_hex
The “off” value to wait for for the status register to return to before the next trigger.
trigger_status_value_hex
The “active” value to wait for on the status register that indicates a trigger pulse.

The parallel port triggers have common/trigger values configured independently so that you can configure both active high and active low, or status pins that are inverted (11/busy).

Trigger Latency

Depending on the computer being run, the trigger and other input sources has a bit of latency that will add some delay between when a trigger pulse is received and the game updates on screen. This was measured at under 0.1 seconds between sending a trigger pulse and the screen updating. This latency does not delay the scanner or device sending trigger pulses, so the overall timing should be similar, especially if the same hardware is used.

The latency has several factors: * The game runs at 60hz so at best the average latency would be about 1/60s. * Serial input is typically buffered on a computer for faster read rates. * Graphic drawing is typically pipelined so there is enough work to do at once, and the drawing is completed before output to the screen (double buffering). * Video scaling hardware in the LCD or projector will wait for receiving a full frame before scaling the image to the actual display element (LCD or mirrors in a projector)

Latency was measured as follows: * Configure Arduino Leonardo as game trigger (sketch below) and to turn on LED when trigger pulse is sent. The basic stamp board based emulator also blinks an LED when it sends trigger pulses. * Run game with --trigger-blink true command-line option * Record 120FPS video using iPhone framed to show both LED on Arduino and trigger blink in lower right of game on screen * Count frames between LED turning on (frame 0) and game showing blink (typically 9-13 frames at 120FPS). be careful that the video you’re counting frames in is actually 120FPS and not the slow down/speed up effect the iPhone adds at the start/end.

Keyboard input latency was typically 2 frames shorter in the 120fps video, and Windows seemed to have a lower latency than OSX.

Serial Latency test sketch (Nearly any Arduino will work, tested with Leonardo, Uno)

// Arduino Leonardo sketch to test latency of input trigger in serial
// Blinks LED each time trigger pulse is sent over serial
// Record video of LED with display of trigger pulse to measure latency
#define DELAY_MILLIS 1000
#define BLINK_MILLIS 100

void setup() {
  // initialize serial communication at 19200 bits per second:
  Serial.begin(19200);
  pinMode(13, OUTPUT);
}

void loop() {
  delay(BLINK_MILLIS);
  digitalWrite(13, LOW);
  delay(DELAY_MILLIS-BLINK_MILLIS);
  Serial.print("5");
  Serial.flush();
  digitalWrite(13, HIGH);
}

Keyboard Latency test sketch for Arduino Leonardo

// Arduino Leonardo sketch to test latency of input trigger in keyboard mode
// Blinks LED each time trigger pulse is sent over keyboard (5 number key is pressed)
// Record video of LED with display of trigger pulse to measure latency
#define DELAY_MILLIS 1000
#define BLINK_MILLIS 100

#include "Keyboard.h"

void setup() {
  Keyboard.begin();
  pinMode(13, OUTPUT);
}

void loop() {
  // wait a few seconds before starting
  delay(10000);

  while (1) {
    Keyboard.press('5');
    digitalWrite(13, HIGH);

    delay(BLINK_MILLIS);

    Keyboard.releaseAll();
    digitalWrite(13, LOW);

    delay(DELAY_MILLIS - BLINK_MILLIS);
  }
}