Pavlovia + phaser3: tips for writing data?

Hi!

I’ve previously hosted experiments on Pavlovia written using the jsPsych library, which has worked very nicely.

My next task is written in js/html using phaser 3 (https://phaser.io/phaser3). At the current stage of testing, participant responses are being written client-side into a JSON structure.

Is there an easy way to ensure that this data can be written to the Pavlovia server? One option I’m thinking of is just using the jsPsych data write function ( jsPsych.data.get().addToLast({key: value, …}); ), but it seems like there might be a more straightforward way to do this that doesn’t require importing additional libraries?

Any tips would be much appreciated.

Best,

Agnes

NB I haven’t uploaded the task repository to Pavlovia yet as I wanted to try and work out where best to host this study first, but am happy to do so if this would be helpful to answer the question or be useful for other users

1 Like

Hey @agnesnorbury, what an interesting question, apologies for the late reply. If you are happy using PsychoJS, ExperimentHandler.save() would be the way to go I believe. Should you be willing to share if only a short sample of what your experiment looks like, I might be able to provide you with more details. Here to help, thanks, s.

Thanks so much for getting back to me @sotiri , I’m sure it’s a busy time in the world of online studies…

I uploaded a test version of the game code at https://gitlab.pavlovia.org/agnes.norbury/study-game-1 (see https://run.pavlovia.org/agnes.norbury/study-game-1/ for a demo).

All the game functionality seems to work when I run it from the Pavlovia dashboard in pilot mode. I can also see the trial data logging in the console (as set up for debugging purposes), which is great.

Currently, trial data generated in the different game scenes is being stored in the global phaser data registry using this.registry.set() (see e.g. js/scenes/platformerSceneA.js, line 384).

Should it then be as straightforward as loading the psychoJS library in index.html, importing it into the relevant scene modules (e.g. js/scenes/platformerSceneA.js), and calling something like ExperimentHandler.save(this.registry.getAll()) on each update?
I have not actually used PsychoJS to code experiments before so am not sure if it’s possible to just cherry pick it’s functions like this, without using the PsychoJS experiment structure.

I’ll have a go at implementing the above workaround this afternoon, and update this post if successful in case it’s useful to anyone else.

Best

Agnes

OK this is one super cool experiment you have in the works! What you propose makes a lot of sense, please allow me some time to run a few tests, I should be able to have a definite answer for you fairly soon. Happy coding! s.

Hey Agnes, how is your project coming along? As it happens getting the data to save on demand like you describe requires first adding script tags for a few of the runtime dependencies for PsychoJS, like you do with phaser in your index.html, then importing the library in your code and calling the appropriate methods. You can see how in this fork -note the IIFE for initialising the psychoJS controller inside the platformerSceneA module because it’s an asynchronous call :blush:

You can test that below with data up to the second trial saved when you close the browser tab:

I suspect your use case is slightly more involved, but I’m hoping that should help get you started for now. On standby in case you need more details, x

Thanks so much for taking the time to do this, this is incredibly helpful! I’ll have a go at implementing this method (hopefully this week) and will let you know then if everything seems to be working OK.

:blush:

Hi, I am running into a few issues when trying to adapt this solution for use across modules (I suspect this might be due to my lack of js knowledge rather than anything specific to pavlovia…)

  1. I assume that I need to create a single PyschoJS object for the entire experiment by calling const psychoJS = new PsychoJS in the first scene/module (here, js/scenes/platformerSceneA.js). Within the scene, this object is updated on after trial using a function that calls to psychoJS.experiment.addData(key, value).

  2. So, when the next module (scene/stage of the experiment) starts (here, js/scenes/platformerSceneB.js), what I want to do is continue adding to the same psychoJS object as before.
    I think I have achieved this by using export/import across modules, i.e.:
    in platformerSceneA.js:
    export const psychoJS = new PsychoJS({ debug: true })
    in platformerSceneB.js:
    import { psychoJS } from "./platformerSceneA.js"

This seems to work, up until the end of the task (last module, js/scenes/taskEndScene.js, when I am getting a fatal javascript error if I either try to log the psychoJS object to the console (to check that everything is getting stored), or call psychoJS.experiment.save(); (see below):


FATAL unknown | {} [log4javascript.min.js:1:40074](https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js)

append https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1

doAppend https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1

callAppenders https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1

log https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1

fatal https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1

dialog https://run.pavlovia.org/agnes.norbury/study-game-1/lib/core-2020.2.js:1151

onerror https://run.pavlovia.org/agnes.norbury/study-game-1/lib/core-2020.2.js:1823

I am also not generating any csv datafiles on gitlab.

I am wondering if either I am doing something wrong in with import/export above, or if I am missing something simple in my script to communicate with the core library to say that I’m ending my experiment? (I am also getting a couple of uncaught type errors, TypeError: this._setRequiredKeys is undefined [at the start of the experiment, I think related to downloading resources, and assume I can ignore], and Uncaught TypeError: dialogElement is null [which I think might relate to the fatal error at the end, may something to do with not setting psychoJS.Status correctly]).

I don’t know if this makes a difference, but I am currently testing in pilot mode (so as not to consume credits), might this be adding an extra layer of complication?

Sorry for the very long message, I will keep trying to look into this this afternoon but any help would be much appreciated.

Best

Agnes

OK, alright, yes, I think I follow, please allow me some time to look into how to best make those adjustments, thanks, s.

1 Like

Hey Agnes,

I was able to get the study to save by assigning attaching the psychoJS instance to the window object, which in turn makes it globally available. There are other ways of achieving the same result in JS (e.g., using a Singleton), but none less complicated. I also added a ‘util’ module to host saveTrialData() for sharing across scenes.

The one resources related error at the very beginning is a PsychoJS bug I’m hoping to have fixed in the next few days.

Commenting out the taskEndScene.js#L100 psychoJS.quit() call seems to have made the rest of errors go away. That allowed for the data to be saved as expected I believe, but I could be missing something :blush:

x

2 Likes

That is amazing! Thank you so much for taking the time to work on this. I implemented the changes you suggested in my branch and can confirm that data from across all the different task scenes is saving as expected in the output .csv file.

Hopefully this will be useful for anyone in the future who wants to use Phaser for their experiments.

Thanks again,

Agnes

2 Likes

Thank you Agnes :blush:

Hey Sotiri,

I have been following Agnes’ example and your post to implement a similar data saving in my task (project). However, I have been getting this error “log4javascript is not defined” in the console.

I did script load in index.html, and borrowed Agnes’ code from enterID.js. I assume this is a problem with JS integration/loading module? Any thoughts or feedback will be appreciated!

best,
QN

Hi @XXXiV-QN, sounds like you are missing the log4javascript.js dependency. Could you give me developer access so I can look at your project? Or try adding a script tag for it to your index.html? Thanks, s.

1 Like

Hi Sotiri, just added you as the developer! I will take a look at the script tag you mentioned later. Thank you so much for all the help. I am very new to JavaScript and Pavlovia, so everything I have now are mostly taken from tutorials/examples.

Best,
QN

1 Like

Hi @XXXiV-QN, cool project :+1: As it happens, PsychoJS is wired to intercept and display every JS error across all page scripts. I believe it’s meant as a convenience for easier debugging, but in this case it seems to be getting in the way.

In particular, if you look at the browser console once the diver hits the sea bed, you should be able to confirm the source of the problem is how you are calling sprite.anims.play('swim', true); inside of Player.update_float() and Player.update_sink(). The sprite is missing that ‘swim’ animation somehow?

I don’t know phaser well enough to have this sorted out for you on the spot :blush: But it looks like you would need to tweak that anims.create(); call in your constructor to then let this.sprite know?

Please feel free to follow up if you need more support, x

1 Like

Dear Sotiri,

Thank you so much for the suggestion. It was indeed a logical mistake (trying to play animation on a sprite that’s already destroyed). I changed things around, and added the same PsychoJS code like yesterday, the error is gone! I think I will work from there to continue implementing data saving then.

Cheers! :partying_face:
QN

No problem QN, happy coding :sun_with_face:

Hi @sotiri!

Do you know if anything was changed in lib/core-2020.2.js recently? Today I’m getting a fatal error when I attempt to run the task, which seems to trace back to there:

FATAL unknown | {} log4javascript.min.js:1:40074
append https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1
doAppend https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1
callAppenders https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1
log https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1
fatal https://cdnjs.cloudflare.com/ajax/libs/log4javascript/1.4.9/log4javascript.min.js:1
dialog https://run.pavlovia.org/agnes.norbury/study-game-1/lib/core-2020.2.js:1157
onerror https://run.pavlovia.org/agnes.norbury/study-game-1/lib/core-2020.2.js:1847

In the previously working setup, we were using

import * as data from "../../lib/data-2020.2.js";

to grab some relevant data-saving functions from the pavlovia lib, if something has changed in the source code is there a way I can edit the above so to make it work again?

Thank you!

Agnes

If there have been changes to lib/data-2020.2.js, maybe the best solution would for me to host a local version of the older code as part of my experiment rather than relying on the lib files (which might be subject to change) - is this available anywhere?