Joystick movement help please

Hello, I am trying to add a joystick component that continues to move until the button is no longer pressed. I have been using the builder, but I clearly need a custom code to make this work. Currently when I plug in my USB joystick and press a direction, it moves to that direction to the far side of the screen, and then when you let go, it re-centers. I have changed the joystick.xFactor and joystick.yFactor so it only moves a small amount instead of to the edge of the screen. I have really been struggling for several weeks now to figure out how to get continuous movement out of the joystick. Below is the code I have for just my test program to see if I can get the joystick to work. I have used pygame in the past without using psychopy and have been successful, but have had a ton of issues with pygame in the psychopy builder so I have been sticking to the default pyglet option, which I am less familiar with. Any advice would be greatly appreciated! This would get my dissertation up and running!

#!/usr/bin/env python

-- coding: utf-8 --
“”"
This experiment was created using PsychoPy3 Experiment Builder (v2022.1.3),
on Mon May 2 19:03:28 2022
If you publish work using this script the most relevant publication is:

Peirce J, Gray JR, Simpson S, MacAskill M, Höchenberger R, Sogo H, Kastman E, LindelÞv JK. (2019) 
    PsychoPy2: Experiments in behavior made easy Behav Res 51: 195. 
    https://doi.org/10.3758/s13428-018-01193-y
“”"

from psychopy import locale_setup
from psychopy import prefs
from psychopy import sound, gui, visual, core, data, event, logging, clock, colors, layout
from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED,
STOPPED, FINISHED, PRESSED, RELEASED, FOREVER)

import numpy as np # whole numpy lib is available, prepend ‘np.’
from numpy import (sin, cos, tan, log, log10, pi, average,
sqrt, std, deg2rad, rad2deg, linspace, asarray)
from numpy.random import random, randint, normal, shuffle, choice as randchoice
import os # handy system and path functions
import sys # to get file system encoding

from psychopy.hardware import keyboard

Ensure that relative paths start from the same directory as this script
_thisDir = os.path.dirname(os.path.abspath(file))
os.chdir(_thisDir)

Store info about the experiment session
psychopyVersion = ‘2022.1.3’
expName = ‘triangle’ # from the Builder filename that created this script
expInfo = {‘participant’: ‘’}
dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)
if dlg.OK == False:
core.quit() # user pressed cancel
expInfo[‘date’] = data.getDateStr() # add a simple timestamp
expInfo[‘expName’] = expName
expInfo[‘psychopyVersion’] = psychopyVersion

Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc
filename = thisDir + os.sep + u’data/%s%s_%s’ % (expInfo[‘participant’], expName, expInfo[‘date’])

An ExperimentHandler isn’t essential but helps with data saving
thisExp = data.ExperimentHandler(name=expName, version=’’,
extraInfo=expInfo, runtimeInfo=None,
originPath=’/Users/brookejac55/Desktop/PsychoPy Programs/old/Psychopy_Joystick/triangle.py’,
savePickle=True, saveWideText=True,
dataFileName=filename)

save a log file for detail verbose info
logFile = logging.LogFile(filename+’.log’, level=logging.EXP)
logging.console.setLevel(logging.WARNING) # this outputs to the screen, not a file

endExpNow = False # flag for ‘escape’ or other condition => quit the exp
frameTolerance = 0.001 # how close to onset before ‘same’ frame

Start Code - component code to be run after the window creation
from psychopy.hardware import joystick as joysticklib # joystick/gamepad accsss
from psychopy.experiment.components.joystick import virtualJoystick as virtualjoysticklib

Setup the Window
win = visual.Window(
size=[1440, 900], fullscr=True, screen=0,
winType=‘pyglet’, allowGUI=False, allowStencil=False,
monitor=‘testMonitor’, color=‘white’, colorSpace=‘rgb’,
blendMode=‘avg’, useFBO=True,
units=‘height’)

store frame rate of monitor if we can measure it
expInfo[‘frameRate’] = win.getActualFrameRate()
if expInfo[‘frameRate’] != None:
frameDur = 1.0 / round(expInfo[‘frameRate’])
else:
frameDur = 1.0 / 60.0 # could not measure, so guess

Setup ioHub
ioConfig = {}
ioSession = ioServer = eyetracker = None

create a default keyboard (e.g. to check for escape)
defaultKeyboard = keyboard.Keyboard(backend=‘event’)

Initialize components for Routine “trial”
trialClock = core.Clock()
polygon = visual.ShapeStim(
win=win, name=‘polygon’,
size=(0.2, 0.2), vertices=‘triangle’,
ori=0.0, pos=(0.0, 0.3), anchor=‘center’,
lineWidth=1.0, colorSpace=‘rgb’, lineColor=‘white’, fillColor=‘blue’,
opacity=None, depth=0.0, interpolate=True)
polygon_2 = visual.ShapeStim(
win=win, name=‘polygon_2’,
size=(0.04, 0.04), vertices=‘circle’,
ori=1.0, pos=[0,0], anchor=‘center’,
lineWidth=1.0, colorSpace=‘rgb’, lineColor=‘white’, fillColor=‘red’,
opacity=None, depth=-1.0, interpolate=True)
x, y = [None, None]
joystick = type(’’, (), {})() # Create an object to use as a name space
joystick.device = None
joystick.device_number = 0
joystick.joystickClock = core.Clock()
joystick.xFactor = 1
joystick.yFactor = 1

try:
numJoysticks = joysticklib.getNumJoysticks()
if numJoysticks > 0:
try:
joystickCache
except NameError:
joystickCache={}
if not 0 in joystickCache:
joystickCache[0] = joysticklib.Joystick(0)
joystick.device = joystickCache[0]
if win.units == ‘height’:
joystick.xFactor = 0.5 * win.size[0]/win.size[1]
joystick.yFactor = 0.5
else:
joystick.device = virtualjoysticklib.VirtualJoystick(0)
logging.warning(“joystick_{}: Using keyboard+mouse emulation ‘ctrl’ + ‘Alt’ + digit.”.format(joystick.device_number))
except Exception:
pass

if not joystick.device:
logging.error(‘No joystick/gamepad device found.’)
core.quit()

joystick.status = None
joystick.clock = core.Clock()
joystick.numButtons = joystick.device.getNumButtons()
joystick.getNumButtons = joystick.device.getNumButtons
joystick.getAllButtons = joystick.device.getAllButtons
joystick.getX = lambda: joystick.xFactor * joystick.device.getX()
joystick.getY = lambda: joystick.yFactor * joystick.device.getY()

joystick.xFactor = .1 * joystick.getX()
joystick.yFactor = .1 * joystick.getY()

Create some handy timers
globalClock = core.Clock() # to track the time since experiment started
routineTimer = core.CountdownTimer() # to track time remaining of each (non-slip) routine

------Prepare to start Routine “trial”-------
continueRoutine = True

update component parameters for each repeat
joystick.oldButtonState = joystick.device.getAllButtons()[:]
joystick.activeButtons=[i for i in range(joystick.numButtons)]

setup some python lists for storing info about the joystick
joystick.clicked_name = 
gotValidClick = False # until a click is received
while joystick.status == STARTED:
joystick.getX() * joystick.xFactor
joystick.getY() * joystick.YFactor

keep track of which components have finished
trialComponents = [polygon, polygon_2, joystick]
for thisComponent in trialComponents:
thisComponent.tStart = None
thisComponent.tStop = None
thisComponent.tStartRefresh = None
thisComponent.tStopRefresh = None
if hasattr(thisComponent, ‘status’):
thisComponent.status = NOT_STARTED

reset timers
t = 0
_timeToFirstFrame = win.getFutureFlipTime(clock=“now”)
trialClock.reset(-_timeToFirstFrame) # t0 is time of first possible flip
frameN = -1

-------Run Routine “trial”-------
while continueRoutine:
# get current time
t = trialClock.getTime()
tThisFlip = win.getFutureFlipTime(clock=trialClock)
tThisFlipGlobal = win.getFutureFlipTime(clock=None)
frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
# update/draw components on each frame

# *polygon* updates
if polygon.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
    # keep track of start time/frame for later
    polygon.frameNStart = frameN  # exact frame index
    polygon.tStart = t  # local t and not account for scr refresh
    polygon.tStartRefresh = tThisFlipGlobal  # on global time
    win.timeOnFlip(polygon, 'tStartRefresh')  # time at next scr refresh
    polygon.setAutoDraw(True)

# *polygon_2* updates
if polygon_2.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
    # keep track of start time/frame for later
    polygon_2.frameNStart = frameN  # exact frame index
    polygon_2.tStart = t  # local t and not account for scr refresh
    polygon_2.tStartRefresh = tThisFlipGlobal  # on global time
    win.timeOnFlip(polygon_2, 'tStartRefresh')  # time at next scr refresh
    polygon_2.setAutoDraw(True)
if polygon_2.status == STARTED:  # only update if drawing
    polygon_2.setPos((joystick.getX(), joystick.getY()*-1), log=False)
    polygon_2.setOri(0.0, log=False)
# *joystick* updates
if joystick.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
    # keep track of start time/frame for later
    joystick.frameNStart = frameN  # exact frame index
    joystick.tStart = t  # local t and not account for scr refresh
    joystick.tStartRefresh = tThisFlipGlobal  # on global time
    win.timeOnFlip(joystick, 'tStartRefresh')  # time at next scr refresh
    joystick.status = STARTED
    joystick.joystickClock.reset()
if joystick.status == STARTED:  # only update if started and not finished!
    joystick.newButtonState = joystick.getAllButtons()[:]
    if joystick.newButtonState != joystick.oldButtonState: # New button press
        joystick.pressedButtons = [i for i in range(joystick.numButtons) if joystick.newButtonState[i] and not joystick.oldButtonState[i]]
        joystick.releasedButtons = [i for i in range(joystick.numButtons) if not joystick.newButtonState[i] and joystick.oldButtonState[i]]
        joystick.newPressedButtons = [i for i in joystick.activeButtons if i in joystick.pressedButtons]
        joystick.oldButtonState = joystick.newButtonState
        joystick.buttons = joystick.newPressedButtons
        [logging.data("joystick_{}_button: {}, pos=({:1.4f},{:1.4f})".format(joystick.device_number, i, joystick.getX(), joystick.getY())) for i in joystick.pressedButtons]
        if len(joystick.buttons) > 0:  # state changed to a new click
            # check if the joystick was inside our 'clickable' objects
            gotValidClick = False;
            for obj in [polygon]:
                if obj.contains(joystick.getX(), joystick.getY()):
                    gotValidClick = True
                    joystick.clicked_name.append(obj.name)
            # abort routine on response
            continueRoutine = False

# check for quit (typically the Esc key)
if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
    core.quit()

# check if all components have finished
if not continueRoutine:  # a component has requested a forced-end of Routine
    break
continueRoutine = False  # will revert to True if at least one component still running
for thisComponent in trialComponents:
    if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
        continueRoutine = True
        break  # at least one component has not yet finished

# refresh the screen
if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
    win.flip()
-------Ending Routine “trial”-------
for thisComponent in trialComponents:
if hasattr(thisComponent, “setAutoDraw”):
thisComponent.setAutoDraw(False)
thisExp.addData(‘polygon.started’, polygon.tStartRefresh)
thisExp.addData(‘polygon.stopped’, polygon.tStopRefresh)
thisExp.addData(‘polygon_2.started’, polygon_2.tStartRefresh)
thisExp.addData(‘polygon_2.stopped’, polygon_2.tStopRefresh)

store data for thisExp (ExperimentHandler)
store data for thisExp (ExperimentHandler)
if len(joystick.buttons) > 0:
# check if the joystick was inside our ‘clickable’ objects
gotValidClick = False;
for obj in [polygon]:
if obj.contains(joystick.getX(), joystick.getY()):
gotValidClick = True
joystick.clicked_name.append(obj.name)
x, y = joystick.getX(), joystick.getY()
joystick.newButtonState = joystick.getAllButtons()[:]
joystick.pressedState = [joystick.newButtonState[i] for i in range(joystick.numButtons)]
joystick.time = joystick.joystickClock.getTime()
thisExp.addData(‘joystick.x’, x)
thisExp.addData(‘joystick.y’, y)
[thisExp.addData(‘joystick.button_{0}’.format(i), int(joystick.pressedState[i])) for i in joystick.activeButtons]
thisExp.addData(‘joystick.time’, joystick.time)
if len(joystick.clicked_name):
thisExp.addData(‘joystick.clicked_name’, joystick.clicked_name[0])
thisExp.addData(‘joystick.started’, joystick.tStartRefresh)
thisExp.addData(‘joystick.stopped’, joystick.tStopRefresh)
thisExp.nextEntry()

the Routine “trial” was not non-slip safe, so reset the non-slip timer
routineTimer.reset()

Flip one final time so any remaining win.callOnFlip()
and win.timeOnFlip() tasks get executed before quitting
win.flip()

these shouldn’t be strictly necessary (should auto-save)
thisExp.saveAsWideText(filename+’.csv’, delim=‘auto’)
thisExp.saveAsPickle(filename)
logging.flush()

make sure everything is closed down
if eyetracker:
eyetracker.setConnectionState(False)
thisExp.abort() # or data files will save again on exit
win.close()
core.quit()

@Michael

Hello,

I see the edits you have made to the code. However, this has not solved my problem of getting the joystick to move slowly across the screen until the button is not pressed. Instead, this has only changed it so the joystick only goes up to the stimuli. I am working with non-human primates, so they need to be able to see the cursor/joystick movement. They need to be able to hold the joystick one direct, and see the red cursor move that direction until they let go. When they let go, if the cursor is not in an object, the cursor should stay at the position it was moved to. Is this not possible in psychopy?

Thanks for your help!

I didn’t edit the code at all - I just indented it so that Discourse would format it properly as code and hence make it more easily readable for others.

I don’t have access to a joystick and so can’t offer any useful assistance I’m afraid.

in builder, right? So shouldn’t go back to centre when released, but the position should be updated whenever it’s pulled towards a certain direction.
Guess you can

  1. declare a few variables in advance, say
    joystickposition = [0,0]; to keep track of the position
    joythreshold = 0.2; minimal movement at the joystick required
    joystepsize = 0.01; to set a default pace
    joyspeedfactorY = 1; if you need to increase stepsize relative on x/y
    joyspeedfactorX = 1;

create an image that serves as cursor, update position each frame based on joystickposition
add joystick component, turn off end of trial upon press

  1. (abs values instead of <> ± might’ve made sense here :wink: )
    if (joystick.device.getX() < -joythreshold):
    joystickposition[0] = joystickposition[0] - (joystepsize * joyspeedfactorX);
    if (-joystick.device.getY() < -joythreshold):
    joystickposition[1] = joystickposition[1] - (joystepsize * joyspeedfactorY);
    if (joystick.device.getX() > joythreshold):
    joystickposition[0] = joystickposition[0] + (joystepsize * joyspeedfactorX);
    if (-joystick.device.getY() > joythreshold):
    joystickposition[1] = joystickposition[1] + (joystepsize * joyspeedfactorY);

You also want to make sure the movement can’t go out of bounds / off the screen, e.g.
if (joystickposition[1] > 0.5):
joystickposition[1] = 0.5;
if (joystickposition[1] < - 0.5):
joystickposition[1] = -0.5;
if (joystickposition[0] > 0.85):
joystickposition[0] = 0.85;
if (joystickposition[0] < - 0.85):
joystickposition[0] = -0.85;

  1. Check whether you’re in some rectangle
    if (abs(joystickposition[0] - Region1Position[0]) < Region1Size[0]/2):
    if (abs(joystickposition[1] - Region1Position[1]) < Region1Size[1]/2):
    joystickposition = [0,0];
    yadidadida;

Finally, I don’t know whether the screen refresh rate offers enough debouncing on this, but you can always add a timer or only update the values every n frames.

Not exactly elegant ;), but hope it helps.

Don’t know whether it’s easer or not for nh primates (do they have a prior about pushing left - moving left etcet?), but you can also get rid of the joystick using some cheap big arcade buttons and a board to emulate a keyboard.

2 Likes

Thanks for the help!
We have joysticks and touchscreens for them. Joysticks are better for tasks that need constant movement, like chasing stimuli or going through mazes.