BGE: Adding a Python interface for handling joysticks without needing logic bricks...
authorMitchell Stokes <mogurijin@gmail.com>
Fri, 21 Dec 2012 02:28:59 +0000 (02:28 +0000)
committerMitchell Stokes <mogurijin@gmail.com>
Fri, 21 Dec 2012 02:28:59 +0000 (02:28 +0000)
if bge.logic.joysticks[0]:
    activate_player_one()

if bge.logic.joysticks[1]:
    activate_player_two()

etc..

The interface exposed by SCA_PythonJoystick is very similar to the joystick logic brick except for one key difference: axis values are normalized to a -1.0 to 1.0 range instead of -32767 to 32767, which is what the logic brick exposed.

doc/python_api/rst/bge.logic.rst
doc/python_api/rst/bge.types.rst
source/gameengine/GameLogic/CMakeLists.txt
source/gameengine/GameLogic/Joystick/SCA_Joystick.cpp
source/gameengine/GameLogic/Joystick/SCA_Joystick.h
source/gameengine/GameLogic/SCA_JoystickManager.cpp
source/gameengine/GameLogic/SCA_PythonJoystick.cpp [new file with mode: 0644]
source/gameengine/GameLogic/SCA_PythonJoystick.h [new file with mode: 0644]
source/gameengine/Ketsji/KX_PythonInit.cpp
source/gameengine/Ketsji/KX_PythonInitTypes.cpp

index 7d20aa3..ee39f76 100644 (file)
@@ -125,6 +125,10 @@ Variables
 
    The current mouse wrapped in an :class:`~bge.types.SCA_PythonMouse` object.
 
+.. data:: joysticks
+
+   A list of attached joysticks. The list size it he maximum number of supported joysticks. If no joystick is available for a given slot, the slot is set to None.
+
 *****************
 General functions
 *****************
index 8cf9ccb..85429a0 100644 (file)
@@ -141,6 +141,74 @@ Types
       
       :type: boolean
 
+.. class:: SCA_PythonJoystick(PyObjectPlus)
+
+   A Python interface to a joystick.
+
+   .. attribute:: name
+
+      The name assigned to the joystick by the operating system. (read-only)
+         
+      :type: string
+
+   .. attribute:: activeButtons
+
+      A list of active button values. (read-only)
+         
+      :type: list
+
+   .. attribute:: axisValues
+
+      The state of the joysticks axis as a list of values :data:`numAxis` long. (read-only).
+
+      :type: list of ints.
+
+      Each specifying the value of an axis between -1.0 and 1.0 depending on how far the axis is pushed, 0 for nothing.
+      The first 2 values are used by most joysticks and gamepads for directional control. 3rd and 4th values are only on some joysticks and can be used for arbitary controls.
+
+      * left:[-1.0, 0.0, ...]
+      * right:[1.0, 0.0, ...]
+      * up:[0.0, -1.0, ...]
+      * down:[0.0, 1.0, ...]
+
+   .. attribute:: hatValues
+
+      The state of the joysticks hats as a list of values :data:`numHats` long. (read-only).
+
+      :type: list of ints
+
+      Each specifying the direction of the hat from 1 to 12, 0 when inactive.
+
+      Hat directions are as follows...
+
+      * 0:None
+      * 1:Up
+      * 2:Right
+      * 4:Down
+      * 8:Left
+      * 3:Up - Right
+      * 6:Down - Right
+      * 12:Down - Left
+      * 9:Up - Left
+
+   .. attribute:: numAxis
+
+      The number of axes for the joystick at this index. (read-only).
+
+      :type: integer
+
+   .. attribute:: numButtons
+
+      The number of buttons for the joystick at this index. (read-only).
+
+      :type: integer
+
+   .. attribute:: numHats
+
+      The number of hats for the joystick at this index. (read-only).
+
+      :type: integer
+
 .. class:: SCA_IObject(CValue)
 
    This class has no python functions
@@ -3977,7 +4045,7 @@ Types
 
       :type: list of ints.
 
-      Each spesifying the value of an axis between -32767 and 32767 depending on how far the axis is pushed, 0 for nothing.
+      Each specifying the value of an axis between -32767 and 32767 depending on how far the axis is pushed, 0 for nothing.
       The first 2 values are used by most joysticks and gamepads for directional control. 3rd and 4th values are only on some joysticks and can be used for arbitary controls.
 
       * left:[-32767, 0, ...]
@@ -4001,7 +4069,7 @@ Types
 
       :type: list of ints
 
-      Each spesifying the direction of the hat from 1 to 12, 0 when inactive.
+      Each specifying the direction of the hat from 1 to 12, 0 when inactive.
 
       Hat directions are as follows...
 
index e511704..ad357bd 100644 (file)
@@ -71,6 +71,7 @@ set(SRC
        SCA_PropertyEventManager.cpp
        SCA_PropertySensor.cpp
        SCA_PythonController.cpp
+       SCA_PythonJoystick.cpp
        SCA_PythonKeyboard.cpp
        SCA_PythonMouse.cpp
        SCA_RandomActuator.cpp
@@ -114,6 +115,7 @@ set(SRC
        SCA_PropertyEventManager.h
        SCA_PropertySensor.h
        SCA_PythonController.h
+       SCA_PythonJoystick.h
        SCA_PythonKeyboard.h
        SCA_PythonMouse.h
        SCA_RandomActuator.h
index 8b343be..b7fbe95 100644 (file)
@@ -321,3 +321,12 @@ int SCA_Joystick::pAxisTest(int axisnum)
        return 0;
 #endif /* WITH_SDL */
 }
+
+const char *SCA_Joystick::GetName()
+{
+#ifdef WITH_SDL
+       return SDL_JoystickName(m_joyindex);
+#else /* WITH_SDL */
+       return "";
+#endif /* WITH_SDL */
+}
index 912484a..dd9fbef 100644 (file)
@@ -192,6 +192,11 @@ public:
         * Test if the joystick is connected
         */
        int Connected(void);
+
+       /**
+        * Name of the joytsick
+        */
+       const char *GetName();
 };
 
 #endif
index c21db79..780e4e9 100644 (file)
@@ -60,14 +60,16 @@ SCA_JoystickManager::~SCA_JoystickManager()
 
 void SCA_JoystickManager::NextFrame(double curtime,double deltatime)
 {
+       // We should always handle events in case we want to grab them with Python
+#ifdef WITH_SDL
+       SCA_Joystick::HandleEvents(); /* Handle all SDL Joystick events */
+#endif
+
        if (m_sensors.Empty()) {
                return;
        }
        else {
                ;
-#ifdef WITH_SDL
-               SCA_Joystick::HandleEvents(); /* Handle all SDL Joystick events */
-#endif
                SG_DList::iterator<SCA_JoystickSensor> it(m_sensors);
                for (it.begin();!it.end();++it)
                {
diff --git a/source/gameengine/GameLogic/SCA_PythonJoystick.cpp b/source/gameengine/GameLogic/SCA_PythonJoystick.cpp
new file mode 100644 (file)
index 0000000..ee79211
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Mitchell Stokes.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file gameengine/GameLogic/SCA_PythonJoystick.cpp
+ *  \ingroup gamelogic
+ */
+
+
+#include "SCA_PythonJoystick.h"
+#include "./Joystick/SCA_Joystick.h"
+#include "SCA_IInputDevice.h"
+
+//#include "GHOST_C-api.h"
+
+/* ------------------------------------------------------------------------- */
+/* Native functions                                                          */
+/* ------------------------------------------------------------------------- */
+
+SCA_PythonJoystick::SCA_PythonJoystick(SCA_Joystick* joystick)
+: PyObjectPlus(),
+m_joystick(joystick)
+{
+#ifdef WITH_PYTHON
+       m_event_dict = PyDict_New();
+#endif
+}
+
+SCA_PythonJoystick::~SCA_PythonJoystick()
+{
+#ifdef WITH_PYTHON
+       PyDict_Clear(m_event_dict);
+       Py_DECREF(m_event_dict);
+#endif
+}
+
+#ifdef WITH_PYTHON
+
+/* ------------------------------------------------------------------------- */
+/* Python functions                                                          */
+/* ------------------------------------------------------------------------- */
+PyObject* SCA_PythonJoystick::py_repr(void)
+{
+       return PyUnicode_FromString(m_joystick->GetName());
+}
+
+
+/* Integration hooks ------------------------------------------------------- */
+PyTypeObject SCA_PythonJoystick::Type = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       "SCA_PythonJoystick",
+       sizeof(PyObjectPlus_Proxy),
+       0,
+       py_base_dealloc,
+       0,
+       0,
+       0,
+       0,
+       py_base_repr,
+       0,0,0,0,0,0,0,0,0,
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+       0,0,0,0,0,0,0,
+       Methods,
+       0,
+       0,
+       &PyObjectPlus::Type,
+       0,0,0,0,0,0,
+       py_base_new
+};
+
+PyMethodDef SCA_PythonJoystick::Methods[] = {
+       {NULL,NULL} //Sentinel
+};
+
+PyAttributeDef SCA_PythonJoystick::Attributes[] = {
+       KX_PYATTRIBUTE_RO_FUNCTION("numButtons", SCA_PythonJoystick, pyattr_get_num_x),
+       KX_PYATTRIBUTE_RO_FUNCTION("numHats", SCA_PythonJoystick, pyattr_get_num_x),
+       KX_PYATTRIBUTE_RO_FUNCTION("numAxis", SCA_PythonJoystick, pyattr_get_num_x),
+       KX_PYATTRIBUTE_RO_FUNCTION("activeButtons", SCA_PythonJoystick, pyattr_get_active_buttons),
+       KX_PYATTRIBUTE_RO_FUNCTION("hatValues", SCA_PythonJoystick, pyattr_get_hat_values),
+       KX_PYATTRIBUTE_RO_FUNCTION("axisValues", SCA_PythonJoystick, pyattr_get_axis_values),
+       KX_PYATTRIBUTE_RO_FUNCTION("name", SCA_PythonJoystick, pyattr_get_name),
+       { NULL }        //Sentinel
+};
+
+// Use one function for numAxis, numButtons, and numHats
+PyObject* SCA_PythonJoystick::pyattr_get_num_x(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       SCA_PythonJoystick* self = static_cast<SCA_PythonJoystick*>(self_v);
+
+       if (strcmp(attrdef->m_name, "numButtons") == 0)
+               return PyLong_FromLong(self->m_joystick->GetNumberOfButtons());
+       else if (strcmp(attrdef->m_name, "numAxis") == 0)
+               return PyLong_FromLong(self->m_joystick->GetNumberOfAxes());
+       else if (strcmp(attrdef->m_name, "numHats") == 0)
+               return PyLong_FromLong(self->m_joystick->GetNumberOfHats());
+
+       // If we got here, we have a problem...
+       PyErr_SetString(PyExc_AttributeError, "invalid attribute");
+       return NULL;
+}
+
+PyObject* SCA_PythonJoystick::pyattr_get_active_buttons(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       SCA_PythonJoystick* self = static_cast<SCA_PythonJoystick*>(self_v);
+       
+       int button_index = self->m_joystick->GetNumberOfButtons();
+
+       PyObject *list = PyList_New(0);
+       PyObject *value;
+
+       for (int i=0; i < self->m_joystick->GetNumberOfButtons(); i++) {
+               if (self->m_joystick->aButtonPressIsPositive(i)) {
+                       value = PyLong_FromSsize_t(i);
+                       PyList_Append(list, value);
+                       Py_DECREF(value);
+               }
+       }
+
+       return list;
+}
+
+PyObject* SCA_PythonJoystick::pyattr_get_hat_values(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       SCA_PythonJoystick* self = static_cast<SCA_PythonJoystick*>(self_v);
+       
+       int hat_index = self->m_joystick->GetNumberOfHats();
+       PyObject *list = PyList_New(hat_index);
+       
+       while (hat_index--) {
+               PyList_SET_ITEM(list, hat_index, PyLong_FromLong(self->m_joystick->GetHat(hat_index)));
+       }
+       
+       return list;
+}
+
+PyObject* SCA_PythonJoystick::pyattr_get_axis_values(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       SCA_PythonJoystick* self = static_cast<SCA_PythonJoystick*>(self_v);
+       
+       int axis_index = self->m_joystick->GetNumberOfAxes();
+       PyObject *list = PyList_New(axis_index);
+       int position;
+       
+       while (axis_index--) {
+               position = self->m_joystick->GetAxisPosition(axis_index);
+
+               // We get back a range from -32768 to 32767, so we use an if here to
+               // get a perfect -1.0 to 1.0 mapping. Some oddball system might have an
+               // actual min of -32767 for shorts, so we use SHRT_MIN/MAX to be safe.
+               if (position < 0)
+                       PyList_SET_ITEM(list, axis_index, PyFloat_FromDouble(position/((double)-SHRT_MIN)));
+               else
+                       PyList_SET_ITEM(list, axis_index, PyFloat_FromDouble(position/(double)SHRT_MAX));
+       }
+       
+       return list;
+}
+
+PyObject* SCA_PythonJoystick::pyattr_get_name(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
+{
+       SCA_PythonJoystick* self = static_cast<SCA_PythonJoystick*>(self_v);
+
+       return PyUnicode_FromString(self->m_joystick->GetName());
+}
+#endif
diff --git a/source/gameengine/GameLogic/SCA_PythonJoystick.h b/source/gameengine/GameLogic/SCA_PythonJoystick.h
new file mode 100644 (file)
index 0000000..15c6285
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Mitchell Stokes.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file SCA_PythonJoystick.h
+ *  \ingroup gamelogic
+ */
+
+#ifndef __SCA_PYTHONJOYSTICK_H__
+#define __SCA_PYTHONJOYSTICK_H__
+
+#include "PyObjectPlus.h"
+
+class SCA_PythonJoystick : public PyObjectPlus
+{
+       Py_Header
+private:
+       class SCA_Joystick *m_joystick;
+#ifdef WITH_PYTHON
+       PyObject* m_event_dict;
+#endif
+public:
+       SCA_PythonJoystick(class SCA_Joystick* joystick);
+       virtual ~SCA_PythonJoystick();
+
+#ifdef WITH_PYTHON
+       virtual PyObject* py_repr(void);
+
+       static PyObject*        pyattr_get_num_x(void* self_v, const KX_PYATTRIBUTE_DEF *attrdef);
+       static PyObject*        pyattr_get_active_buttons(void* self_v, const KX_PYATTRIBUTE_DEF *attrdef);
+       static PyObject*        pyattr_get_hat_values(void* self_v, const KX_PYATTRIBUTE_DEF *attrdef);
+       static PyObject*        pyattr_get_axis_values(void* self_v, const KX_PYATTRIBUTE_DEF *attrdef);
+       static PyObject*        pyattr_get_name(void* self_v, const KX_PYATTRIBUTE_DEF *attrdef);
+#endif
+};
+
+#endif //__SCA_PYTHONJOYSTICK_H__
+
index 996be97..df8f6eb 100644 (file)
@@ -92,6 +92,8 @@ extern "C" {
 #include "SCA_PropertySensor.h"
 #include "SCA_RandomActuator.h"
 #include "SCA_KeyboardSensor.h" /* IsPrintable, ToCharacter */
+#include "SCA_JoystickManager.h" /* JOYINDEX_MAX */
+#include "SCA_PythonJoystick.h"
 #include "SCA_PythonKeyboard.h"
 #include "SCA_PythonMouse.h"
 #include "KX_ConstraintActuator.h"
@@ -151,6 +153,7 @@ static char gp_GamePythonPathOrig[FILE_MAX] = ""; // not super happy about this,
 
 static SCA_PythonKeyboard* gp_PythonKeyboard = NULL;
 static SCA_PythonMouse* gp_PythonMouse = NULL;
+static SCA_PythonJoystick* gp_PythonJoysticks[JOYINDEX_MAX] = {NULL};
 #endif // WITH_PYTHON
 
 static KX_Scene*       gp_KetsjiScene = NULL;
@@ -1420,6 +1423,22 @@ PyObject *initGameLogic(KX_KetsjiEngine *engine, KX_Scene* scene) // quick hack
        gp_PythonMouse = new SCA_PythonMouse(gp_KetsjiEngine->GetMouseDevice(), gp_Canvas);
        PyDict_SetItemString(d, "mouse", gp_PythonMouse->NewProxy(true));
 
+       PyObject* joylist = PyList_New(JOYINDEX_MAX);
+       SCA_JoystickManager* joyevent = (SCA_JoystickManager*)gp_KetsjiScene->GetLogicManager()->FindEventManager(SCA_EventManager::JOY_EVENTMGR);
+       for (int i=0; i<JOYINDEX_MAX; ++i) {
+               SCA_Joystick* joy = joyevent->GetJoystickDevice(i);
+               if (joy && joy->Connected()) {
+                       gp_PythonJoysticks[i] = new SCA_PythonJoystick(joy);
+                       PyObject* tmp = gp_PythonJoysticks[i]->NewProxy(true);
+                       Py_INCREF(tmp);
+                       PyList_SET_ITEM(joylist, i, tmp);
+               } else  {
+                       Py_INCREF(Py_None);
+                       PyList_SET_ITEM(joylist, i, Py_None);
+               }
+       }
+       PyDict_SetItemString(d, "joysticks", joylist);
+
        ErrorObject = PyUnicode_FromString("GameLogic.error");
        PyDict_SetItemString(d, "error", ErrorObject);
        Py_DECREF(ErrorObject);
@@ -1937,6 +1956,13 @@ void exitGamePlayerPythonScripting()
        delete gp_PythonMouse;
        gp_PythonMouse = NULL;
 
+       for (int i=0; i<JOYINDEX_MAX; ++i) {
+               if (gp_PythonJoysticks[i]) {
+                       delete gp_PythonJoysticks[i];
+                       gp_PythonJoysticks[i] = NULL;
+               }
+       }
+
        /* since python restarts we cant let the python backup of the sys.path hang around in a global pointer */
        restorePySysObjects(); /* get back the original sys.path and clear the backup */
        
@@ -1985,6 +2011,13 @@ void exitGamePythonScripting()
        delete gp_PythonMouse;
        gp_PythonMouse = NULL;
 
+       for (int i=0; i<JOYINDEX_MAX; ++i) {
+               if (gp_PythonJoysticks[i]) {
+                       delete gp_PythonJoysticks[i];
+                       gp_PythonJoysticks[i] = NULL;
+               }
+       }
+
        restorePySysObjects(); /* get back the original sys.path and clear the backup */
        bpy_import_main_set(NULL);
        PyObjectPlus::ClearDeprecationWarning();
index 9717306..10c210c 100644 (file)
@@ -81,6 +81,7 @@
 #include "SCA_RandomSensor.h"
 #include "SCA_XNORController.h"
 #include "SCA_XORController.h"
+#include "SCA_PythonJoystick.h"
 #include "SCA_PythonKeyboard.h"
 #include "SCA_PythonMouse.h"
 #include "KX_IpoActuator.h"
@@ -250,6 +251,7 @@ void initPyTypes(void)
                PyType_Ready_Attr(dict, SCA_XNORController, init_getset);
                PyType_Ready_Attr(dict, SCA_XORController, init_getset);
                PyType_Ready_Attr(dict, SCA_IController, init_getset);
+               PyType_Ready_Attr(dict, SCA_PythonJoystick, init_getset);
                PyType_Ready_Attr(dict, SCA_PythonKeyboard, init_getset);
                PyType_Ready_Attr(dict, SCA_PythonMouse, init_getset);
        }