Joystick Support

Okay, after finding out that jME doesn't have support for Joysticks I took it upon myself to write support.  Now, I originally planned to go with the same style as Mouse and Keyboard handling in jME, but I went ahead and went (with what I think) is a much better way to go about it and stuck with a similar approach as is done in Swing with the event model.  Please see the code below:


import java.util.*;

import org.lwjgl.*;
import org.lwjgl.input.*;

/**
 * @author Matthew D. Hicks
 */
public class JoystickManager extends Thread {
    private static JoystickManager instance;
   
    private ArrayList listeners;
   
    private JoystickManager() throws LWJGLException {
        Controllers.create();
        listeners = new ArrayList();
    }
   
    public void run() {
        Controllers.clearEvents();
        JoystickButtonEvent buttonEvent;
        JoystickAxisEvent axisEvent;
        JoystickListener listener;
        while (true) {
            while (Controllers.next()) {
                Controller source = Controllers.getEventSource();
                if (Controllers.isEventButton()) {
                    buttonEvent = new JoystickButtonEvent(source.getName(), Controllers.getEventControlIndex(), source.isButtonPressed(Controllers.getEventControlIndex()));
                    for (int i = 0; i < listeners.size(); i++) {
                        listener = (JoystickListener)listeners.get(i);
                        listener.onButton(buttonEvent);
                    }
                } else if (Controllers.isEventAxis()) {
                    axisEvent = new JoystickAxisEvent(source.getName(), Controllers.getEventControlIndex(), source.getAxisName(Controllers.getEventControlIndex()), source.getAxisValue(Controllers.getEventControlIndex()));
                    for (int i = 0; i < listeners.size(); i++) {
                        listener = (JoystickListener)listeners.get(i);
                        listener.onAxis(axisEvent);
                    }
                }
            }
            try {
                Thread.sleep(5);
            } catch(InterruptedException exc) {
            }
        }
    }
   
    public static final void initialize() {
        if (instance == null) {
            try {
                instance = new JoystickManager();
                instance.start();
            } catch(LWJGLException exc) {
                throw new RuntimeException("Unable to create Controllers", exc);
            }
        }
    }
   
    public static final void addListener(JoystickListener listener) {
        initialize();
        instance.listeners.add(listener);
    }
   
    public static final void removeListener(JoystickListener listener) {
        initialize();
        instance.listeners.remove(listener);
    }
   
    public static final void removeListeners() {
        initialize();
        instance.listeners.clear();
    }
   
    public static final void setDeadZone(String controller, int axis, float value) {
        initialize();
        Controller c = getController(controller);
        if (c != null) {
            c.setDeadZone(axis, value);
        }
    }
   
    public static final String[] getControllers() {
        initialize();
        String[] controllers = new String[Controllers.getControllerCount()];
        for (int i = 0; i < controllers.length; i++) {
            controllers[i] = Controllers.getController(i).getName();
        }
        return controllers;
    }
   
    private static final Controller getController(String controller) {
        int count = Controllers.getControllerCount();
        for (int i = 0; i < count; i++) {
            if (Controllers.getController(i).getName().equals(controller)) {
                return Controllers.getController(i);
            }
        }
        return null;
    }
}



/**
 * @author Matthew D. Hicks
 */
public interface JoystickListener {
    public void onButton(JoystickButtonEvent evt);
   
    public void onAxis(JoystickAxisEvent evt);
}



/**
 * @author Matthew D. Hicks
 */

public class JoystickButtonEvent {
    private String controller;
    private int button;
    private boolean pressed;
   
    public JoystickButtonEvent(String controller, int button, boolean pressed) {
        this.controller = controller;
        this.button = button;
        this.pressed = pressed;
    }
   
    public String getController() {
        return controller;
    }
   
    public int getButton() {
        return button;
    }
   
    public boolean isPressed() {
        return pressed;
    }
   
    public boolean isReleased() {
        return !pressed;
    }
}



/**
 * @author Matthew D. Hicks
 */
public class JoystickAxisEvent {
    private String controller;
    private int axis;
    private String axisName;
    private float axisValue;
   
    public JoystickAxisEvent(String controller, int axis, String axisName, float axisValue) {
        this.controller = controller;
        this.axis = axis;
        this.axisName = axisName;
        this.axisValue = axisValue;
    }
   
    public String getController() {
        return controller;
    }
   
    public int getAxis() {
        return axis;
    }
   
    public String getAxisName() {
        return axisName;
    }
   
    public float getAxisValue() {
        return axisValue;
    }
}



Finally, here's a simple example of how to use it:

JoystickManager.initialize();

JoystickManager.addListener(new JoystickListener() {
            public void onButton(JoystickButtonEvent evt) {
                System.out.println("Button Event: " + evt.getButton() + ", " + evt.isPressed());
            }

            public void onAxis(JoystickAxisEvent evt) {
                System.out.println("Axis Event: " + evt.getAxis() + ", " + evt.getAxisName() + ", " + evt.getAxisValue());
            }           
        });



This does require more dependencies to LWJGL via JInput and some other JARs that come with LWJGL but if we're developing on it anyway, we might as well go all the way.  When JOGL support gets finished we can create an implementation for that as well.

Hope you like it. :)

darkfrog

Hey, Joysticksupport is definately of use in jME, great you are working on it! - I'm using an own implementation (without lwjgl) at this time…



But there are some things that I would like to have for jME joystick support additionally:

  • an Interface that does not depend on LWJGL
  • that Interface should support polling, too
  • no busy wait!
  • possibility to run single threaded

    But this could be achieved easily starting from your Implementation.

    If you want to have a looping thread, have a look if lwjgl provides a blocking next() method…

An interface that doesn't depend on LWJGL meaning that would it would use an interface that has an implementation that is dependent on LWJGL (and an implementation for any other system we support as well) or do you mean one that is not dependent on the OpenGL implementation at all?



I've added support for polling in JoystickManager now, see it at the end of this post.



Not sure what you mean by "no busy wait"?



The updated JoystickManager can now run in a single-threaded mode so long as you never make a call to any of the following methods:

  startThread()

  addListener(JoystickListener)

  removeListener(JoystickListener)

  removeListeners()



I added the polling methods which allow for running single-threaded.



I actually initially thought that next() was blocking…unfortunately it is not and I have not been able to figure out a way to make it that way.  If you or anyone else figures out a way it would be very simple to change the code to work that way as essentially I just created a temporary work-around that has an outer infinite loop currently.  Oh, I also made the thread be a daemon thread so it doesn't cause the game to hang when it wants to shutdown.


import java.util.*;

import org.lwjgl.*;
import org.lwjgl.input.*;

/**
 * @author Matthew D. Hicks
 */
public class JoystickManager extends Thread {
    private static JoystickManager instance;
   
    private ArrayList listeners;
   
    private JoystickManager() throws LWJGLException {
        Controllers.create();
        listeners = new ArrayList();
    }
   
    public void run() {
        Controllers.clearEvents();
        JoystickButtonEvent buttonEvent;
        JoystickAxisEvent axisEvent;
        JoystickListener listener;
        while (true) {
            while (Controllers.next()) {
                Controller source = Controllers.getEventSource();
                if (Controllers.isEventButton()) {
                    buttonEvent = new JoystickButtonEvent(source.getName(), Controllers.getEventControlIndex(), source.isButtonPressed(Controllers.getEventControlIndex()));
                    for (int i = 0; i < listeners.size(); i++) {
                        listener = (JoystickListener)listeners.get(i);
                        listener.onButton(buttonEvent);
                    }
                } else if (Controllers.isEventAxis()) {
                    axisEvent = new JoystickAxisEvent(source.getName(), Controllers.getEventControlIndex(), source.getAxisName(Controllers.getEventControlIndex()), source.getAxisValue(Controllers.getEventControlIndex()));
                    for (int i = 0; i < listeners.size(); i++) {
                        listener = (JoystickListener)listeners.get(i);
                        listener.onAxis(axisEvent);
                    }
                }
            }
            try {
                Thread.sleep(5);
            } catch(InterruptedException exc) {
            }
        }
    }
   
    public static final void initialize() {
        if (instance == null) {
            try {
                instance = new JoystickManager();
            } catch(LWJGLException exc) {
                throw new RuntimeException("Unable to create Controllers", exc);
            }
        }
    }
   
    public static final void startThread() {
        initialize();
        if (!instance.isAlive()) {
            instance.setDaemon(true);
            instance.start();
        }
    }
   
    public static final void addListener(JoystickListener listener) {
        initialize();
        startThread();
        instance.listeners.add(listener);
    }
   
    public static final void removeListener(JoystickListener listener) {
        initialize();
        startThread();
        instance.listeners.remove(listener);
    }
   
    public static final void removeListeners() {
        initialize();
        startThread();
        instance.listeners.clear();
    }
   
    public static final void setDeadZone(String controller, int axis, float value) {
        initialize();
        Controller c = getController(controller);
        if (c != null) {
            c.setDeadZone(axis, value);
        }
    }
   
    public static final String[] getControllers() {
        initialize();
        String[] controllers = new String[Controllers.getControllerCount()];
        for (int i = 0; i < controllers.length; i++) {
            controllers[i] = Controllers.getController(i).getName();
        }
        return controllers;
    }
   
    public static final String[] getAxisNames(String controller) {
        initialize();
        Controller c = getController(controller);
        String[] axises = new String[c.getAxisCount()];
        for (int i = 0; i < axises.length; i++) {
            axises[i] = c.getAxisName(i);
        }
        return axises;
    }
   
    public static final float getAxisValue(String controller, int axis) {
        initialize();
        Controller c = getController(controller);
        return c.getAxisValue(axis);
    }
   
    public static final int getButtonCount(String controller) {
        initialize();
        return getController(controller).getButtonCount();
    }
   
    public static final boolean getButtonStatus(String controller, int button) {
        initialize();
        Controller c = getController(controller);
        return c.isButtonPressed(button);
    }
   
    private static final Controller getController(String controller) {
        int count = Controllers.getControllerCount();
        for (int i = 0; i < count; i++) {
            if (Controllers.getController(i).getName().equals(controller)) {
                return Controllers.getController(i);
            }
        }
        return null;
    }
}

I want to add the primary reason I wanted to go with an event system was because I want the ability to create games where people can re-map keys.  Obviously polling makes that idea very difficult.  In an event system I just grab the first even down the line when they are re-mapping and just replace it with the key (button, axis, etc.) from the event.



darkfrog

Oops, I forgot to add poll() to the methods for polling:


    public static final float getAxisValue(String controller, int axis) {
        initialize();
        Controller c = getController(controller);
        c.poll();
        return c.getAxisValue(axis);
    }
   
    public static final boolean getButtonStatus(String controller, int button) {
        initialize();
        Controller c = getController(controller);
        c.poll();
        return c.isButtonPressed(button);
    }



After looking at the current Keyboard and Mouse architecture I really think the system I've written for joystick support is far more powerful.  If you guys agree then I will go ahead and write a similar system for Mouse and Keyboard support.  As it is, is there any way to get an event of what keys are currently being pressed?  Is there any way to find out if the mouse is currently moving or one of its buttons is being pressed?  I have somewhat limited use of Keyboard and Mouse support in jME and may just be speaking of ignorance, but I did not see any simple means of doing this.

Further, this is one of those places in which multi-threading can be a very good thing (not to say it should ever be required though...the current joystick support gives single-threaded and multi-threaded support). :)

darkfrog

Inspired by your post I have put this together as an abstract layer and an LWJGL-implementation. I have added ‘Joystick’ objects and removed the event classes as they needed object creation constantly which would cause lags when the gc kicks in.

Have a look: zip

Looks ok?



I still have some issues with the lwjgl implementation - posting over at their forums… I’ll get back here when that’s solved.

Well, honestly the main benefit I saw to what I wrote was the event handling. :o



Also, I'm not a big fan of inner-classes in the majority of cases.  It has a valid use here, but still just not a fan. :-p



I guess that's fine for an implementation for now.  I don't see how gc is going to have a lot of objects to collect only the ints are really created every event?



darkfrog

darkfrog said:

Well, honestly the main benefit I saw to what I wrote was the event handling. :o

It's still in! Only parameters instead of objects - those were the ones that the gc had to collect.

Yeah, sorry, I guess I skipped over that part. :o



I think that will be fine for now.  Thanks for getting to this so quickly.



darkfrog

Doh, I think we've got a problem here.  I was just writing a game with mouse and joystick support and I found that when I call Controllers.create() it makes it so MouseLook no longer works.  I've tried the initialization before and after the MouseLook declaration, doesn't make any difference.



Could someone verify this and let me know?



darkfrog

Had the same problem. lwjgl people told me to do Controllers.create() before Display.create() which works fine for me  :slight_smile:

Ok, joystick support is in CVS. I removed the thread-based polling as an application should do this on its own to avoind confusion with threading issues.

Please test and report problems.