using joysticks

what’s the deal with joystick support?
the panda3d manual says to use PyGame but on top there is a note saying

it seems logical that somewhere there must be some basic joystick support but I can’t find it. Is there joystick support in the CVS?

I think there’s support for it through VRPN, this might help you further:
panda3d.org/apiref.php?page=AnalogNode
Unfortunately, few know how to use it.

Hi cheburshka!

I use pygame and a class called joypad. I can’t remember who wrote it and where I got it from. It works really well for me. I even use it at the same time as a wiimote.

Check this out:
https://discourse.panda3d.org/viewtopic.php?t=2305&highlight=joypad

there are working projects already up and running that make use of joystick - here it is one
I recon it is quite difficult to snatch the code from there though, therefore I post you here a class I made for my stuff that maybe is a little less confusing and useful for you, beside it still WIP and not 100% commented in English but there is a couple of usage snippets that should make you clear enough how to use it:

'''
by: Fabius Astelix @ 2009-06
summary: input helpers - actually WIP but it works nice

'''
__all__ = ['easyinput']

import direct.directbase.DirectStart

from direct.showbase.DirectObject import DirectObject
from direct.gui.OnscreenText import OnscreenText
from direct.task.Task import Task
import sys
import p3dutils as p3dut

#================================================================
#
try:
  import pygame as PYG
  PYG.init()
except ImportError:
  PYG=None

class easyinput(DirectObject):
  '''
  -si definisce un testo di config tipo quake in cui il comando bind precede l'evento-device-di-input e la key che fa da ponte agli handler amissibili (ovviamente non c'e' relazione diretta con l'handler x motivi di sicurezza)
  -si crea l'handler
  -si aggiunge nel dizionario l'handler con la chiave relativa associata all'evento
  '''
  debug=True
  #------------------------------------------------------
  #
  DEFAULT_CONFIG='''
//JOY ------
bind joy0-button1 "action"
//+- y axis movement : by default will fire forward/back events
bind joy0-axis1 "thrust"
bind joy0-button2 "jump"
//+- x axis movement : by default will fire moveleft/moveright events
bind joy0-axis0 "heading"
bind joy0-button11 "quit"
bind joy0-button6 "run"
//KB ------
bind enter "action"
bind arrow_down "back"
bind arrow_up "forward"
bind rcontrol "jump"
bind arrow_left "moveleft"
bind arrow_right "moveright"
bind page_up "headup"
bind page_down "headdown"
bind escape "quit"
bind rshift "run"
bind \ "alwaysrun"
// MOUSE ------
//to unbind some event specify an empty string as handler
bind mouse2 ""
bind mouse1 ""
bind mouse3 ""
bind mouse-x "heading"
//+- z axis movement : by default will fire headup/headdown events
bind mouse-y "pitch"
'''
  def __init__(self, config='', bridge={}):
    '''
    config=\n sepatated string of 3 element lines:
    [0]=bind [1]=<event name> [2]=<bridge key>
    bridge=dictionary of '<event name>':<handler> elements
    config and bridge will be used to eventually override the defaults
    '''
    DirectObject.__init__(self)
    #
    self.JOY=0

    # self.config={'<command>':{'<event>': <handler>}}
    self.config={}
    self.add_config(self.DEFAULT_CONFIG)
    self.add_config(config)

    #bridge with predefined events connected to handlers to be overrided
    _bridge={
      'action': self.action,
      'back': self.back,
      'forward': self.forward,
      'jump': self.jump,
      'moveleft': self.moveleft,
      'moveright': self.moveright,
      'thrust': self.thrust,
      'pitch': self.pitch,
      'heading': self.heading,
      'headup': self.headup,
      'headdown': self.headdown,
      'quit': self.quitme,
      'run': self.run,
      'alwaysrun': self.alwaysrunToggle,
    }
    # melt default bridge with user defined
    for k,v in bridge.iteritems(): _bridge[k]=v

    # returns the number of joysticks found
    self.JOY=self.pyga_joysetup()

    # bind operations happens here
    readmouse={}
    for evt, hnd in self.config['bind'].iteritems():
      if hnd in _bridge:
        hname=_bridge[hnd]
        #bind joystick
        if evt.startswith('joy'):
          if self.JOY:
            ###print ">>>JOaccept:%s->%s" % (evt, hname)
            self.accept(evt, _bridge[hnd])
        #bind mouse
        elif evt.startswith('mouse'):
          # if there is a bind for mouse movement will be created relative listeners to hear mouse-x or mouse-y events fired by a mouse polling task eventually created below
          if evt in ["mouse-x", "mouse-y"]:
            readmouse[evt]=_bridge[hnd]
          else:
            #here should bind button listeners
            ###print ">>>MOaccept:%s->%s" % (evt, hname)
            self.accept(evt, _bridge[hnd], [1])
            self.accept(evt+"-up", _bridge[hnd], [0])
        # qui dovrebbe arrivare solo kbkey
        else:
          ###print ">>>KBaccept:%s->%s" % (evt, hname)
          self.accept(evt+'-up', _bridge[hnd], [0])
          self.accept(evt, _bridge[hnd], [1])

    # spawn the mouse movement tracking task - see above the mouse bind part for info
    if readmouse:
      taskName = "_esnptmo-%s" % id(self)
      #assign and eventually fix handle holes
      self.readmouse_binds={}
      for xy in ['mouse-x', 'mouse-y']:
        self.readmouse_binds[xy]=readmouse.get(xy, self._foo)
      self.mouseTask = taskMgr.add(self._read_mouse, taskName, sort=30)

    ###taskMgr.popupControls()
    ###print messenger
  #------------------------------------------------------
  #
  _pre_mox=0
  _pre_moy=0
  mouse_speed_factor=5.
  _mouse_stdby=True
  def _read_mouse(self, task):
    '''read the mouse position and then route mouse position to the relative binded handler
    '''
    if base.mouseWatcherNode.hasMouse():
      x = base.win.getPointer(0).getX()
      y = base.win.getPointer(0).getY()
      deltax=(x-self._pre_mox)
      deltay=(y-self._pre_moy)
      if deltax or deltay:
        self.readmouse_binds["mouse-x"](deltax/self.mouse_speed_factor)
        self.readmouse_binds["mouse-y"](deltay/self.mouse_speed_factor)
        if self._mouse_stdby: self._mouse_stdby=False
      else:
        if not self._mouse_stdby:
          self._mouse_stdby=True
          self.readmouse_binds["mouse-x"](0)
          self.readmouse_binds["mouse-y"](0)
          self._pre_mox=self._pre_moy=0.0
      # stick the mouse pointer in a fixed position to calculate the relative motion from there
      if x <> 100 or y <> 100:
        base.win.movePointer(0, 100, 100)
        x=y=100

      if self._pre_mox <> x: self._pre_mox=x
      if self._pre_moy <> y: self._pre_moy=y
    else:
      if not self._mouse_stdby:
        self._mouse_stdby=True
        self.readmouse_binds["mouse-x"](0)
        self.readmouse_binds["mouse-y"](0)
        self._pre_mox=self._pre_moy=0.0
        #print "nomouse"

    return Task.cont
  #------------------------------------------------------
  #
  def _foo(self, foo): pass
  #------------------------------------------------------
  #
  def add_config(self, config):
    '''merge a text file config in the main config data collector where the latter override the former
    '''
    clean=lambda n: n.strip().strip('"')
    for line in config.split('\n'):
      items=line.strip().split()
      if items and len(items) >= 3:
        cmd, evt, hnd=items[:3]
        # x ora e' previsto solo il comando 'bind'
        cmd=clean(cmd).lower()
        if cmd in ['bind']:
          if not cmd in self.config: self.config[cmd]={}
          self.config[cmd].update([[clean(evt).lower(), clean(hnd)]])
  #------------------------------------------------------
  #
  def pyga_joysetup(self):
    '''joystick(s) startup - returns the number of joysticks found
    '''
    jcount=0
    if PYG:
      print "pygame starts"
      jcount=PYG.joystick.get_count()
      if jcount > 0:
        for x in range(jcount):
          j = PYG.joystick.Joystick(x)
          j.init()
          print ">>>Enabled joystick: %s" % j.get_name()
        taskMgr.add(self.pyga_joytask, 'tsk_pygajoy')
      else:
        print "No Joysticks to Initialize!"

    return jcount
  #------------------------------------------------------
  #
  def pyga_joytask(self, task):
    '''if there is a joy event it will be sent a proper string message and event value to the messenger queue
    '''
    for e in PYG.event.get():
      # joystick buttons up and down events routing
      # will send a string like, i.e.: "joy0-button1" for the button 1 of the joy 0 and 0 or 1 as a parameter for button up or down status respectively
      if e.type in [PYG.JOYBUTTONDOWN, PYG.JOYBUTTONUP]:
        s="joy%d-button%d" % (e.joy, e.button)
        messenger.send(s, [1 if e.type == PYG.JOYBUTTONDOWN else 0])
      # joistick axis (analog and digital)
      # will send a string like, i.e.: "joy0-axis1" for the axis 1 of the joy 0 and a number between 0 and 1 or 0 and -1 as the stick or hat status (the digital stick returns 0 OR +-1 but analog sticks floating values from 0.0 and +-1.0)
      elif e.type == PYG.JOYAXISMOTION:
        s="joy%d-axis%d" % (e.joy, e.axis)
        ###print "Jax-%r(%r)" % (e.axis, e.value)
        #\note maybe it's just my joystick but the values returned by vertical axis are upsidedown so I fix'em here
        if e.axis in [1,2]:
          messenger.send(s, [-e.value])
        else:
          messenger.send(s, [e.value])
    return Task.cont
  #------------------------------------------------------
  #
  def printdebug(self, txt):
    if self.debug: print ">>>%s"%txt
  #------------------------------------------------------
  # PREDEFINED INPUT EVENT HANDLERS (to override)
  '''
  \note ci sono eventi triangolari dove cioe' 2 eventi fanno capo ad uno solo che raggruppa i segni opposti - x es: thrust riceve in input evt tra -1 e 1 i quali possono arrivare da singoli eventi forward e back
  Di norma quindi l'evento attivo e' quello singolo che raggruppa e gli altri 2 invece convergono in esso.
  '''
  #------------------------------------------------------
  #
  def action(self, evt=None):
    '''evt=0 or 1'''
    self.printdebug("action(%r)"%evt)
  #------------------------------------------------------
  #
  def alwaysrunToggle(self, evt=None):
    '''evt=0 or 1'''
    self.printdebug("alwaysrun(%r)"%evt)
  #------------------------------------------------------
  #
  def back(self, evt=None):
    '''evt=0 to 1'''
    self.thrust(-evt)
  #------------------------------------------------------
  #
  def forward(self, evt=None):
    '''evt=0 to 1'''
    self.thrust(evt)
  #------------------------------------------------------
  #
  def jump(self, evt=None):
    '''evt=0 or 1'''
    self.printdebug("jump(%r)"%evt)
  #------------------------------------------------------
  #
  def heading(self, evt=None):
    '''evt=-1. to 1. - for x movements
    to use for steering
    '''
    self.printdebug("heading(%r)"%evt)
  #------------------------------------------------------
  #
  def headup(self, evt=None):
    '''evt=0 to 1'''
    self.pitch(evt)
  #------------------------------------------------------
  #
  def headdown(self, evt=None):
    '''evt=0 to 1'''
    self.pitch(-evt)
  #------------------------------------------------------
  #
  def moveleft(self, evt=None):
    '''evt=0 to 1'''
    self.printdebug("moveleft(%r)"%evt)
  #------------------------------------------------------
  #
  def moveright(self, evt=None):
    '''evt=0 to 1'''
    self.printdebug("moveright(%r)"%evt)
  #------------------------------------------------------
  #
  def pitch(self, evt=None):
    '''evt=-1. to 1. - for z movements
    by default the evt collect headup and headdown events
    override or bind this in your subclass
    '''
    self.printdebug("pitch(%r)"%evt)
  #------------------------------------------------------
  #
  def quitme(self, evt=None):
    '''evt=0 or 1'''
    if evt: self.printdebug("quitting...")
  #------------------------------------------------------
  #
  def run(self, evt=None):
    '''evt=0 or 1'''
    self.printdebug("run(%r)"%evt)
  #------------------------------------------------------
  #
  def thrust(self, evt=None):
    '''evt=-1. to 1. - for y movements
    by default the evt collect forward and back events
    override or bind this in your subclass
    '''
    self.printdebug("thrust(%r)"%evt)
#================================================================
#
if __name__ == '__main__':

  ###
  class instanced(DirectObject):
    '''sample use of easyinput class
    in brief you define a text very similar to quake .cfg files strings where to issue a bind command followed by a input event name and a keyword that define a bridge between the input event and a handler that will be called when that event will be fired
    '''
    #------------------------------------------------------
    #
    def __init__(self):
      DirectObject.__init__(self)
      #
      self.accept('escape-up', self.quitme)
      # crea ost di debug
      self.display={}
      l=0
      for x in ['joy', 'mouse', 'kb']:
        l+=1
        self.display[x]=p3dut.text2d(line=l, text=x.upper()+":")
      ###
      # bind eventi device di input
      #file.cfg tipo quake3
      cfg='''//JOY ------
  bind joy0-button11 "quit" // a comment
  bind joy0-button1 "action"
  bind joy0-axis1 "axis1"
  //KB ------
  bind arrow_up "forward"
  bind escape "quit"
  bind enter "action"
  // MOUSE ------
  bind mouse1 "action"
  '''
      #questi sono gli handlers accettabili - aggiungere qui eventuali altri nuovi bind-abili da cfg
      hndbridge={
        'axis1': self.axis1test,
        'quit': self.quitme,
      }
      #
      self.xinput=easyinput(cfg, hndbridge)
    #------------------------------------------------------
    #
    def quitme(self, evt=None):
      '''qui ci si aspetta un evt click (evt=true)'''
      if evt:
        print ">>>bye!"
        sys.exit()
    #------------------------------------------------------
    #
    def axis1test(self, evt=None):
      self.display['joy'].setText("JOY:%r"%evt)
  ###
  ###
  class derived(easyinput):
    '''sample usage of easyinput class as derived clas
    '''
    #------------------------------------------------------
    #
    def quitme(self, evt=None):
      '''evt=0 or 1'''
      if evt:
        print "bye!"
        sys.exit()

  ####
  print '''
  Sample choice
  =============
  1) Instanced easyInput class
  2) Derived class from easyInput
  RETURN) exit
  '''
  x=raw_input('?> ')

  ####
  if x == '1':
    app = instanced()
  else:
    app = derived()
  ###
  run()

let me know if helped you out

I see it’s unanimous, that pygame should be used for joystick support. But…
I installed pygame and it didn’t work. About 15 minutes later, I realized that it was installed for the main copy of python, not the panda one. I tried running the installer again but the only two options were repair and uninstall, so I uninstalled and reinstalled for the panda copy. Now I get the following error when I run a Panda-Python program that imports pygame:

Windows Message Box:

Console:

Did you install the version of pygame that was compiled against Python 2.5, not 2.6?

good one, it works flawlessly now! I really wish the installer was a bit smarter though.
Were should I call the quit methods? I think I read somewhere that Run() doesn’t return.

taskMgr.add(dummy_func, "deinits pygame when program exits", uponDeath=DeinitPygame)

doesn’t work

Also if I use pandapack, will it include pygame with the rest of the code?

And also, astelix, thanks for posting your easyinput class but i can’t figure out what exactly it does

it is an input class which lock hardware input events to python methods
I’m sorry but actually I can’t post something more clear.
but I’ll do soon - this weekend I’ll probably post a much more helpful complete minigame that make use of this class