Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-commit gamepad plugin #86

Merged
merged 3 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silent-tigers-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@jspsych-contrib/jspsych-gamepad": major
---

Added gamepad plugin that allows one to use gamepads in a jsPsych experiment
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Plugin/Extension | Contributor | Description
[audio-multi-response](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-audio-multi-response/README.md) | [Adam Richie-Halford](https://github.com/richford) | This plugin collects responses to an audio file using both button clicks and key presses.
[audio-swipe-response](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-audio-swipe-response/README.md) | [Adam Richie-Halford](https://github.com/richford) | This plugin collects responses to an audio file using swipe gestures and keyboard responses.
[corsi-blocks](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-corsi-blocks/README.md) | [Josh de Leeuw](https://github.com/jodeleeuw) | This plugin displays a configurable Corsi blocks task and records a series of click responses.
[gamepad](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-gamepad/README.md) | [Shaobin Jiang](https://github.com/Shaobin-Jiang) | This plugin allows one to use gamepads in a jsPsych experiment.
[html-choice](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-html-choice/README.md) | [Younes Strittmatter](https://github.com/younesStrittmatter) | This plugin displays clickable html elements that can be used to present a choice.
[html-multi-response](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-html-multi-response/README.md) | [Adam Richie-Halford](https://github.com/richford) | This plugin collects responses to an arbitrary HTML string using both button clicks and key presses.
[html-swipe-response](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-html-swipe-response/README.md) | [Adam Richie-Halford](https://github.com/richford) | This plugin collects responses to an arbitrary HTML string using swipe gestures and keyboard responses.
Expand All @@ -38,7 +39,6 @@ Plugin/Extension | Contributor | Description
[rdk](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-rdk/docs/jspsych-rdk.md#jspsych-rdk-plugin) | [Sivananda Rajananda](https://github.com/vrsivananda) | This plugin displays a Random Dot Kinematogram (RDK) and allows the subject to report the primary direction of motion by pressing a key on the keyboard.
[rok](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-rok/docs/jspsych-rok.md#jspsych-rok-plugin) | [Younes Strittmatter](https://github.com/younesStrittmatter) | This plugin displays a Random Object Kinematogram (ROK) and allows the subject to report the primary direction of motion or the primary orientation by pressing a key on the keyboard.
[self-paced-reading](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-self-paced-reading/docs/jspsych-self-paced-reading.md) | [@igmmgi](https://github.com/igmmgi) | Self-paced reading tasks with different display options.
[video-several-keyboard-responses](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-video-several-keyboard-responses/docs/jspsych-video-several-keyboard-responses.md)|[@marianylund](https://github.com/marianylund) | This plugin is based on [video-keyboard-response](https://github.com/jspsych/jsPsych/tree/main/packages/plugin-video-keyboard-response) with possibility of recording multiple responses together with video timestamps.
[vsl-animate-occlusion](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-vsl-animate-occlusion/docs/jspsych-vsl-animate-occlusion.md#jspsych-vsl-animate-occlusion-plugin) | [Josh de Leeuw](https://github.com/jodeleeuw) | The VSL (visual statistical learning) animate occlusion plugin displays an animated sequence of shapes that disappear behind an occluding rectangle while they change from one shape to another.
[vsl-grid-scene](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-vsl-grid-scene/docs/jspsych-vsl-grid-scene.md#jspsych-vsl-grid-scene-plugin) | [Josh de Leeuw](https://github.com/jodeleeuw) | The VSL (visual statistical learning) grid scene plugin displays images arranged in a grid.

Expand Down
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions packages/plugin-gamepad/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# jsPsych Gamepad Plugin

## Overview

This is a plugin that allows one to use gamepads in a jsPsych experiment. Currently, the plugin is only tested with limited models of gamepads (by limited, it means that only xbox 360 controllers have been tested up to now) and certain features are only functional when using these gamepads. Any support or enhancement is appreciated.

## Compatibility

jsPsych >= 7.0

## Parameters

In addition to the [parameters available in all plugins](https://www.jspsych.org/overview/plugins#parameters-available-in-all-plugins), this plugin accepts the following parameters. Parameters with a default value of *undefined* must be specified. Parameters can be left unspecified if the default value is acceptable.

| Parameter | Type | Default Value | Description |
| --------- | ---- | ------------- | ----------- |
| canvas_size | array | `[500, 500]` | Array that defines the size of the canvas element in pixels. First value is height, second value is width. |
| display_minature_gamepad | boolean | false | Whether to display a minature gamepad on the page that reflects gamepad operations. This feature should probably be used for debugging purposes and at the current stage supports only limited models of gamepads (namely, xbox 360 controllers only) |
| end_trial | function | `(context, gamepad, time_stamp, delta) => { return time_stamp > 2000 }` | This function, when returning `true`, would terminate the trial. It is called once every frame, after `on_frame_update`. It receives four arguments, which are the context to paint on, the gamepad connected (**caution: gamepad can be `null`**), the milliseconds that have passed since the start of the first frame, and the milliseconds since the last frame. |
| gamepad_connection_prompt | HTML string | `Awaiting gamepad connection...` | The content to prompt for gamepad connection, displayed beneath the stimulus. |
| on_frame_update | function | `(context, gamepad, time_stamp, delta) => {}` | This function is called once every frame, where new content can be painted (the old content is automatically removed and the user needs not manually do that part). It receives four arguments, which are the context to paint on, the gamepad connected (**caution: gamepad can be `null`**), the milliseconds that have passed since the start of the first frame, and the milliseconds since the last frame. |
| stimulus | function | `(context) => {}` | The unchanging content of each frame. The function is only called once, and the rendered content is thereafter copy-pasted in every frame before `on_frame_update`. |

## Data Generated

In addition to the [default data collected by all plugins](../overview/plugins.md#data-collected-by-all-plugins), this plugin collects the following data for each trial.

| Name | Type | Value |
| --------- | ------- | ---------------------------------------- |
| rt | number | Milliseconds from the start of the first frame to the end of the last frame. |
| input | Array of Gamepad objects | The state of the gamepad in each frame, put together in the form of an array. Note that this can be colossal in size, so mind how you deal with it. |

## Example

See the `examples/` folder. Note that this plugin can only be used via the **https** protocol.

## Trouble Shooting

> I keep on receiving the `npm ERR! node-pre-gyp ERR!` error message when running `npm install` on Windows.

This is actually not about the project itself, but a potential problem one might encounter when installing the dependency [`node-canvas`](https://github.com/Automattic/node-canvas/). If you look more carefully at the full log, you would probably locate this one line:

```
npm ERR! C:\GTK\bin\libpangowin32-1.0-0.dll
```

The solution to this is thus simple. According to the installation guide of `node-canvas` on Windows, you need to also install GTK2 and unzip it to `C:\GTK`. Check out the project's [wiki](https://github.com/Automattic/node-canvas/wiki/Installation:-Windows) for more information.

> `display_minature_gamepad` does not work despite my setting it to `true`

Well, currently I have only tested the feature on Xbox 360 controllers and that is the only model that supports the feature. Theoretically, it should not be too hard to implement the support for other models, but again, I have only this one controller with me, so testing with other gamepads are not possible for me at present. You are, however, more than welcome to contribute to this plugin.
67 changes: 67 additions & 0 deletions packages/plugin-gamepad/docs/jspsych-gamepad.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# jsPsych Gamepad Plugin

## Overview

This is a plugin that allows one to use gamepads in a jsPsych experiment. Currently, the plugin is only tested with limited models of gamepads (by limited, it means that only xbox 360 controllers have been tested up to now) and certain features are only functional when using these gamepads. Any support or enhancement is appreciated.

## Compatibility

jsPsych >= 7.0

## Parameters

In addition to the [parameters available in all plugins](https://www.jspsych.org/overview/plugins#parameters-available-in-all-plugins), this plugin accepts the following parameters. Parameters with a default value of *undefined* must be specified. Parameters can be left unspecified if the default value is acceptable.

| Parameter | Type | Default Value | Description |
| --------- | ---- | ------------- | ----------- |
| canvas_size | array | `[500, 500]` | Array that defines the size of the canvas element in pixels. First value is height, second value is width. |
| display_minature_gamepad | boolean | false | Whether to display a minature gamepad on the page that reflects gamepad operations. This feature should probably be used for debugging purposes and at the current stage supports only limited models of gamepads (namely, xbox 360 controllers only) |
| end_trial | function | `(context, gamepad, time_stamp, delta) => { return time_stamp > 2000 }` | This function, when returning `true`, would terminate the trial. It is called once every frame, after `on_frame_update`. It receives four arguments, which are the context to paint on, the gamepad connected (**caution: gamepad can be `null`**), the milliseconds that have passed since the start of the first frame, and the milliseconds since the last frame. |
| gamepad_connection_prompt | HTML string | `Awaiting gamepad connection...` | The content to prompt for gamepad connection, displayed beneath the stimulus. |
| on_frame_update | function | `(context, gamepad, time_stamp, delta) => {}` | This function is called once every frame, where new content can be painted (the old content is automatically removed and the user needs not manually do that part). It receives four arguments, which are the context to paint on, the gamepad connected (**caution: gamepad can be `null`**), the milliseconds that have passed since the start of the first frame, and the milliseconds since the last frame. |
| stimulus | function | `(context) => {}` | The unchanging content of each frame. The function is only called once, and the rendered content is thereafter copy-pasted in every frame before `on_frame_update`. |

## Data Generated

In addition to the [default data collected by all plugins](../overview/plugins.md#data-collected-by-all-plugins), this plugin collects the following data for each trial.

| Name | Type | Value |
| --------- | ------- | ---------------------------------------- |
| rt | number | Milliseconds from the start of the first frame to the end of the last frame. |
| input | Array of Gamepad objects | The state of the gamepad in each frame, put together in the form of an array. Note that this can be colossal in size, so mind how you deal with it. |

## Examples

### Track gamepad input for 10 s

```javascript
let trial = {
type: jsPsychGamepad,
canvas_size: [400, 400],
display_minature_gamepad: true,
end_trial: (context, gamepad, time_stamp, delta_time) => {
return time_stamp > 10000;
},
gamepad_connection_prompt: 'No controller detected...',
on_frame_update: (context, gamepad, time_stamp, delta) => {
context.save();
context.font = 'normal 16px Arial';
context.fillStyle = 'red';
context.textBaseline = 'top';
context.fillText(`Time: ${Math.round(time_stamp)} ms`, 20, 20);
context.fillText(`Fps: ${Math.round(1000 / delta)}`, 20, 50);
context.restore();
},
stimulus: (context) => {
context.save();
context.fillStyle = 'rgb(200, 200, 200)';
context.fillRect(0, 0, 400, 400);
context.font = 'normal 30px Arial';
context.fillStyle = 'red';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('Trial ends in 10 seconds', 200, 200);
context.restore();
},
};
```
48 changes: 48 additions & 0 deletions packages/plugin-gamepad/examples/basic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>jspsych-gamepad plugin test</title>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/jspsych.css" />
<script src="https://unpkg.com/[email protected]"></script>
<script src="../dist/index.browser.min.js"></script>
</head>
<body>
<script>
const jsPsych = initJsPsych();

let trial = {
type: jsPsychGamepad,
canvas_size: [400, 400],
display_minature_gamepad: true,
end_trial: (context, gamepad, time_stamp, delta_time) => {
return time_stamp > 10000;
},
gamepad_connection_prompt: 'No controller detected...',
on_frame_update: (context, gamepad, time_stamp, delta) => {
context.save();
context.font = 'normal 16px Arial';
context.fillStyle = 'red';
context.textBaseline = 'top';
context.fillText(`Time: ${Math.round(time_stamp)} ms`, 20, 20);
context.fillText(`Fps: ${Math.round(1000 / delta)}`, 20, 50);
context.restore();
},
stimulus: (context) => {
context.save();
context.fillStyle = 'rgb(200, 200, 200)';
context.fillRect(0, 0, 400, 400);
context.font = 'normal 30px Arial';
context.fillStyle = 'red';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('Trial ends in 10 seconds', 200, 200);
context.restore();
},
};

jsPsych.run([trial]);
</script>
</body>
</html>
26 changes: 26 additions & 0 deletions packages/plugin-gamepad/examples/minature-gamepad.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>jspsych-gamepad plugin test</title>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/jspsych.css" />
<script src="https://unpkg.com/[email protected]"></script>
<script src="../dist/index.browser.min.js"></script>
</head>
<body>
<script>
const jsPsych = initJsPsych();

let trial = {
type: jsPsychGamepad,
display_minature_gamepad: true,
end_trial: () => {
return false;
},
};

jsPsych.run([trial]);
</script>
</body>
</html>
68 changes: 68 additions & 0 deletions packages/plugin-gamepad/examples/move-a-ball.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>jspsych-gamepad plugin test</title>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/jspsych.css" />
<script src="https://unpkg.com/[email protected]"></script>
<script src="../dist/index.browser.min.js"></script>
</head>
<body>
<script>
const jsPsych = initJsPsych();

let position = [200, 200];
let radius = 10;
let velocity = 500; // pixels that can be covered within 1 second at full speed

let trial = {
type: jsPsychGamepad,
canvas_size: [400, 400],
display_minature_gamepad: true,
end_trial: (context, gamepad, time_stamp, delta_time) => {
return time_stamp > 10000;
},
gamepad_connection_prompt: 'No controller detected...',
on_frame_update: (context, gamepad, time_stamp, delta) => {
context.save();
context.fillStyle = 'red';

let x = 0;
let y = 0;
if (gamepad !== null) {
x = gamepad.axes[0];
y = gamepad.axes[1];
}

if (!(x === 0 && y === 0)) {
let speed = Math.sqrt((x ** 2 + y ** 2) / 2) * velocity;
let speed_x = x / Math.sqrt(x ** 2 + y ** 2) * speed;
let speed_y = y / Math.sqrt(x ** 2 + y ** 2) * speed;
position[0] += speed_x * delta / 1000;
position[1] += speed_y * delta / 1000;
}

let path = new Path2D();
path.arc(position[0], position[1], radius, 0, Math.PI * 2);
context.fill(path);

context.restore();
},
stimulus: (context) => {
context.save();
context.fillStyle = 'rgb(200, 200, 200)';
context.fillRect(0, 0, 400, 400);
context.font = 'normal 16px Arial';
context.fillStyle = 'red';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('Trial ends in 10 seconds', 200, 370);
context.restore();
},
};

jsPsych.run([trial]);
</script>
</body>
</html>
Loading