I didn't want to assume that the user of any game I develop will be using the same control device. So, I've set up to accept player movement using keyboard, joystick or wheel.
I've tested two each of wheel and joystick for control and discovered interesting things. The keyboard was pretty basic and easy to assume consistent input. Not so with different wheels and joysticks.
The joysticks I currently have are:
->Logitech Attack 3
->Saitek ST90
The wheels are:
->Logitech WingMan Formual Force GP
->Logitech Momo
One of the things I discovered is that while all the devices will be recognized by 'dbPerformChecklistControlDevices', in some combinations, only one of the devices is actually readable.
All the devices are USB.
I can put either wheel with the Logitech joystick and actively switch between them during game play. But only one wheel is readable if both are plugged in (the Wingman, for some unknown reason). If I plug in the Saitek joystick with any other one plugged in at the same time, the Saitek joystick is not readable. It doesn't appear to matter which port they are plugged into.
I scavenged a bunch of my code to create a simple tester. Part of my game code has a calibration section for joysticks and wheels. Most of that code is not included and some of the support functions have been modified from my originals. Feel free to use any of this code. The keyboard commands to switch stuff are slightly tempermental, it depends on how fast your computer is.
Once you select the device for the wheel or joystick (using the 2 and 1 keys, respectively), you can switch between controlling device using the 'k', 'j' and 'w' keys. If you are testing multiple devices of the same type, switch off the type before changing the device and then switch back.
Only the devices that are plugged in when the application starts will be recognised by the program. So, if you physically change devices, you need to restart the application.
#include "DarkSDK.h"
// ===================================================================================
// Common System Includes
// ===================================================================================
#include <stdio.h>
#include <stdarg.h>
// ===================================================================================
// Constants Declarations
// ===================================================================================
// key scan codes of interest
#define ciScanCode_bumpjoystick 2 // this is '1'
#define ciScanCode_bumpwheel 3 // this is '2'
#define ciScanCode_keyboard 37 // this is 'k'
#define ciScanCode_joystick 36 // this is 'j'
#define ciScanCode_wheel 17 // this is 'w'
// some font info
#define font_height 16
// ===================================================================================
// Structure Declarations
// ===================================================================================
// name and number associations for devices
typedef struct
{
char devname[30];
int devnumb;
} fyDevNameNumber;
// wheel and joystick definitions
typedef struct
{
int devicecnt;
int cntrlnumb;
int wheelnumb;
int sticknumb;
char cntrlname[256];
char wheelname[256];
char stickname[256];
} fyWheelAndJoy;
// values for calculating
typedef struct
{
int xdiv;
int ydiv;
} fyStickCalculate;
typedef struct
{
int xdiv;
int ydiv;
int zdiv;
int ymax;
int zmax;
int ctype;
// calculation type:
// 1: {ymax = control device y)/ydiv - {zmax - joy twist z) / zdiv
// 2: {ymax - joy slider a)/ydiv
} fyWheelCalculate;
typedef struct
{
float dist;
float angle;
} fyAngleDistance;
typedef fyAngleDistance& fyAngleDistanceRef;
// ===================================================================================
// Global Variables Declarations
// ===================================================================================
// controller information
static fyWheelAndJoy WandJ;
static fyStickCalculate StickCalc;
static fyWheelCalculate WheelCalc;
static char** devNames = NULL;
static fyDevNameNumber devNameNumber[3];
// ===================================================================================
// Support Functions
// ===================================================================================
char* CreateStringCopy(char* src)
{
int l;
char* dst;
// get the length of the name
l = strlen(src);
// create the space for the name
dst = new char[l+1];
// store the name
strcpy(dst,src);
// return the new thing
return dst;
}
void doDbTextNoInc(int xloc,int ypos,const char* format,...)
{
char szTxt[1024];
va_list marker;
// initialise variable arguments
va_start(marker,format);
// make local string
vsprintf(szTxt,format,marker);
// close it off
va_end(marker);
// write it out
dbText(xloc,ypos,szTxt);
}
void doDbText(int xloc,int& ypos,const char* format,...)
{
char szTxt[1024];
va_list marker;
// initialise variable arguments
va_start(marker,format);
// make local string
vsprintf(szTxt,format,marker);
// close it off
va_end(marker);
// write it out
dbText(xloc,ypos,szTxt);
// update the y location
ypos += font_height;
}
void doDbTextNoInc(DWORD ink,int xloc,int ypos,const char* format,...)
{
char szTxt[1024];
va_list marker;
// initialise variable arguments
va_start(marker,format);
// make local string
vsprintf(szTxt,format,marker);
// close it off
va_end(marker);
// set the ink
dbInk(ink,0);
// write it out
dbText(xloc,ypos,szTxt);
}
void doDbText(DWORD ink,int xloc,int& ypos,const char* format,...)
{
char szTxt[1024];
va_list marker;
// initialise variable arguments
va_start(marker,format);
// make local string
vsprintf(szTxt,format,marker);
// close it off
va_end(marker);
// set the ink
dbInk(ink,0);
// write it out
dbText(xloc,ypos,szTxt);
// update the y location
ypos += font_height;
}
void SelectKeyboard()
{
WandJ.cntrlnumb = 0;
strcpy(WandJ.cntrlname,"keyboard");
}
void SelectJoystick()
{
// do we have an index for the joystick
if (WandJ.sticknumb > -1)
{
// yes, select the joystick as active device
WandJ.cntrlnumb = 1;
strcpy(WandJ.cntrlname,WandJ.stickname);
dbSetControlDevice((char*)WandJ.cntrlname);
}
}
void SelectSteeringWheel()
{
// do we have an index for the wheel
if (WandJ.wheelnumb > -1)
{
// yes, select the wheel as active device
WandJ.cntrlnumb = 2;
strcpy(WandJ.cntrlname,WandJ.wheelname);
dbSetControlDevice((char*)WandJ.cntrlname);
}
}
void NextDevice()
{
// increment device number, wrap to zero
WandJ.cntrlnumb++;
if (WandJ.cntrlnumb > 2) WandJ.cntrlnumb = 0;
}
void BumpActiveDevice()
{
// increment device number, wrap to zero
NextDevice();
// make sure we have a device for the number
while (devNameNumber[WandJ.cntrlnumb].devnumb == -1) NextDevice();
// select the proper device
switch (WandJ.cntrlnumb)
{
case 0: SelectKeyboard(); break;
case 1: SelectJoystick(); break;
case 2: SelectSteeringWheel(); break;
}
}
void BumpJoystick()
{
// increment device index for joystick
WandJ.sticknumb++;
// check for too far
if (WandJ.sticknumb >= WandJ.devicecnt)
{
// indicate no device assigned
devNameNumber[1].devnumb = -1;
WandJ.sticknumb = -1;
strcpy(WandJ.stickname,"<unselected>");
// if control was on joystick, set to keyboard
if (WandJ.cntrlnumb == 1) WandJ.cntrlnumb = 0;
} else {
// copy the name of the current device
strcpy(WandJ.stickname,devNames[WandJ.sticknumb]);
// store the device number
devNameNumber[1].devnumb = WandJ.sticknumb;
}
}
void BumpSteeringWheel()
{
// increment device index for wheel
WandJ.wheelnumb++;
// check for too far
if (WandJ.wheelnumb >= WandJ.devicecnt)
{
// indicate no device assigned
devNameNumber[2].devnumb = -1;
WandJ.wheelnumb = -1;
strcpy(WandJ.wheelname,"<unselected>");
// if control was on wheel, set to keyboard
if (WandJ.cntrlnumb == 2) WandJ.cntrlnumb = 0;
} else {
// copy the name of the current device
strcpy(WandJ.wheelname,devNames[WandJ.wheelnumb]);
// store the device number
devNameNumber[2].devnumb = WandJ.wheelnumb;
}
}
char* ShowControllingDevice()
{
// select the proper device
switch (WandJ.cntrlnumb)
{
case 0: return ("Keyboard");
default: return dbGetControlDevice();
}
}
char* ActiveDeviceNameIs()
{
return (char*)devNameNumber[WandJ.cntrlnumb].devname;
}
char* JoystickNameIs()
{
return (char*)WandJ.stickname;
}
char* SteeringWheelNameIs()
{
return (char*)WandJ.wheelname;
}
void InitialiseGameDevices()
{
int i, c;
// do some pre-cleaning
if (devNames != NULL)
{
// clean up pieces
for (i=0;i<WandJ.devicecnt;i++) free(devNames[i]);
// remove the object
free(devNames);
}
// set up the strings/numbers for basic device types
strcpy(devNameNumber[0].devname,"keyboard");
strcpy(devNameNumber[1].devname,"joystick");
strcpy(devNameNumber[2].devname,"steering wheel");
devNameNumber[0].devnumb = 99;
devNameNumber[1].devnumb = -1;
devNameNumber[2].devnumb = -1;
// initialise the count
WandJ.devicecnt = 0;
// assume using the keyboard
WandJ.cntrlnumb = 0;
strcpy(WandJ.cntrlname,"keyboard");
// assume no wheel or joystick
strcpy(WandJ.wheelname,"<unselected>");
strcpy(WandJ.stickname,"<unselected>");
WandJ.wheelnumb = -1;
WandJ.sticknumb = -1;
// set some default values for joystick and wheel
StickCalc.xdiv = 100;
StickCalc.ydiv = 100;
WheelCalc.xdiv = 100;
WheelCalc.ydiv = 200;
WheelCalc.ymax = 1000;
WheelCalc.zmax = 65535;
WheelCalc.zdiv = 6553;
WheelCalc.ctype = 1;
// get the list of devices
dbPerformChecklistControlDevices();
// get the count of devices
c = dbChecklistQuantity();
// (re)create the device name list
devNames = new char*[c];
// store the device names
for (i=0;i<c;i++)
{
// increment the number of devices
WandJ.devicecnt++;
// get the name
devNames[i] = CreateStringCopy(dbChecklistString(i+1));
}
// clean up
dbEmptyChecklist();
}
void SetDistanceAndAngle(fyAngleDistanceRef angdst)
{
float angle = angdst.angle;
float movedist = 0.0;
// based on WandJ.cntrlnumb, use key or joystick or wheel
switch (WandJ.cntrlnumb)
{
case 0: // keyboard control
// handle rotation
if ( dbLeftKey() == 1 )
{
if ( dbControlKey() == 1 )
{
angle = dbWrapValue(angle - 15.0f);
} else {
angle = dbWrapValue(angle - 2.0f);
}
}
if ( dbRightKey() == 1 )
{
if ( dbControlKey() == 1 )
{
angle = dbWrapValue(angle + 15.0f);
} else {
angle = dbWrapValue(angle + 2.0f);
}
}
// check for forward/backward motion
if ( dbDownKey() == 1 ) movedist = -1.0f;
if ( dbUpKey() == 1 ) movedist = 1.0f;
// apply accelerators
if ( dbShiftKey() == 1 ) movedist *= 5.0f;
if ( dbControlKey() == 1 ) movedist *= 20.0f;
break;
case 1: // joystick control
// get the rotation
angle = dbWrapValue(angle + (float)(dbControlDeviceX()/StickCalc.xdiv));
// now the forward motion
movedist = -1.0f * (dbControlDeviceY()/StickCalc.ydiv);
break;
case 2: // wheel control
// get the rotation
angle = dbWrapValue(angle + (float)(dbControlDeviceX()/WheelCalc.xdiv));
// now the forward motion
switch (WheelCalc.ctype)
{
case 1:
movedist = (float)((WheelCalc.ymax - dbControlDeviceY()) / WheelCalc.ydiv) - ((WheelCalc.zmax - dbJoystickTwistZ()) / WheelCalc.zdiv);
break;
case 2:
movedist = (float)(WheelCalc.ymax - dbJoystickSliderA()) / WheelCalc.ydiv;
break;
}
break;
}
// reset angle
angdst.angle = angle;
// store the distance
angdst.dist = movedist;
}
// ===================================================================================
// Main Program
// ===================================================================================
void DarkGDK (void)
{
// local variables
int ypos, i;
char szTxt[256];
char szSet[256];
fyAngleDistance angdst;
int cdy_init,jtz_init,jsa_init,cdy_chg,jtz_chg,jsa_chg,ctype;
// set display mode and position
if(dbCheckDisplayMode(640,480,16)) dbSetDisplayMode(640,480,16);
dbSetWindowPosition(10,10);
// set to basic windows frame
dbSetWindowLayout(1,0,1);
// set the title
dbSetWindowTitle("Control Device Tester");
// set sync on and to low rate for easier keyboard collection
dbSyncOn();
dbSyncRate(10);
// initialise game devices
InitialiseGameDevices();
// set the text font and size
dbSetTextFont("Courier");
dbSetTextSize(font_height);
// offer the menu
//ControlDeviceMenu();
while (LoopGDK())
{
// start with a fresh display
dbCLS();
// initialise location
ypos = 8;
// display current information
doDbText(dbRgb(0,255,255),10,ypos,"Active Device: %s",ActiveDeviceNameIs());
doDbText(10,ypos,"Joystick: %s",JoystickNameIs());
doDbText(10,ypos,"Steering Wheel: %s",SteeringWheelNameIs());
// display some instructions
ypos += font_height;
doDbText(dbRgb(255,125,125),10,ypos,"Instructions (press indicated key):");
doDbText(10,ypos,">>Make Keyboard Active - k");
doDbText(10,ypos,">>Make Joystick Active - j");
doDbText(10,ypos,">>Make Wheel Active - w");
doDbText(10,ypos,">>Change Joystick Device - 1");
doDbText(10,ypos,">>Change Wheel Device - 2");
// display what is happening
ypos += font_height;
doDbText(dbRgb(255,255,0),10,ypos,"Control Device Name: %s",
ShowControllingDevice());
doDbTextNoInc(dbRgb(255,0,255),10,ypos,"cd x:%d",dbControlDeviceX());
doDbTextNoInc(10+100,ypos,"cd y:%d",dbControlDeviceY());
doDbText(10+200,ypos,"cd z:%d",dbControlDeviceZ());
doDbTextNoInc(10,ypos,"jt x:%d",dbJoystickTwistX());
doDbTextNoInc(10+100,ypos,"jt y:%d",dbJoystickTwistY());
doDbText(10+200,ypos,"jt z:%d",dbJoystickTwistZ());
doDbTextNoInc(10,ypos,"js a:%d",dbJoystickSliderA());
doDbTextNoInc(10+100,ypos,"js b:%d",dbJoystickSliderB());
doDbTextNoInc(10+200,ypos,"js c:%d",dbJoystickSliderC());
doDbText(10+300,ypos,"js d:%d",dbJoystickSliderD());
strcpy(szSet,"fire x");
for (i=0;i<=5;i++)
{
sprintf(szTxt," %d:%d",i,dbJoystickFireX(i));
strcat(szSet,szTxt);
}
doDbText(10,ypos,szSet);
strcpy(szSet,"fire x");
for (i=6;i<=11;i++)
{
sprintf(szTxt," %d:%d",i,dbJoystickFireX(i));
strcat(szSet,szTxt);
}
doDbText(10,ypos,szSet);
// something extra for wheels
if (WandJ.cntrlnumb==2)
{
switch (ctype)
{
case 0:
doDbText(10,ypos,"wheel type: <none>");
break;
case 1:
doDbText(10,ypos,"wheel type: twist");
break;
case 2:
doDbText(10,ypos,"wheel type: slide");
break;
}
if (cdy_init != dbControlDeviceY()) cdy_chg = 1;
if (jtz_init != dbJoystickTwistZ()) jtz_chg = 1;
if (jsa_init != dbJoystickSliderA()) jsa_chg = 1;
if ((cdy_chg==1) && (jtz_chg==1))
{
ctype = 1;
WheelCalc.ctype = 1;
}
if (jsa_chg == 1)
{
ctype = 2;
WheelCalc.ctype = 2;
}
}
// clear player angle, during actual game play, you would leave this
// alone once it was initiallised, this is the value used to point
// the player
// but here we are just trying to show how the calculations will
// affect the current value
angdst.angle = 0;
// get movement direction and speed and new angle based on device reading
SetDistanceAndAngle(angdst);
// display calculations
ypos += font_height;
doDbText(dbRgb(0,255,255),10,ypos,"Calculations:");
doDbText(10,ypos,"angle:%7.2f",angdst.angle);
doDbText(10,ypos,"speed:%7.2f",angdst.dist);
// show how to control using the keyboard
ypos += font_height;
doDbText(dbRgb(0,255,0),10,ypos,"Control using Keyboard:");
doDbText(10,ypos," speed & direction - up/down arrow, vary speed w/ctrl & shift");
doDbText(10,ypos," turn angle - left/right arrow, vary speed w/ctrl ");
// check for keyboard input
i = 0;
switch (dbScanCode())
{
case ciScanCode_keyboard: SelectKeyboard(); i = 1; break;
case ciScanCode_joystick: SelectJoystick(); i = 1; break;
case ciScanCode_wheel: SelectSteeringWheel(); i = 1; break;
case ciScanCode_bumpjoystick: BumpJoystick(); i = 1; break;
case ciScanCode_bumpwheel: BumpSteeringWheel(); i = 1; break;
}
// check for change
if (i)
{
// set initial conditions again, this is used to
// help determine the 'type' of wheel calculations
cdy_init = dbControlDeviceY();
jtz_init = dbJoystickTwistZ();
jsa_init = dbJoystickSliderA();
cdy_chg = 0;
jtz_chg = 0;
jsa_chg = 0;
ctype = 0;
}
// update display
dbSync();
}
// clean up a bit
// do some pre-cleaning
if (devNames != NULL)
{
// clean up pieces
for (i=0;i<WandJ.devicecnt;i++) free(devNames[i]);
// remove the object
free(devNames);
}
}
This has been compiled and tested with the latest Dark SDK using MS Studio .NET 2003 on a generic PC with 2GB RAM, Pentium4 3.20GHz, MS Windows 2000 SP4, NVIDIA GeForce FX 5200.
I recall having difficulty trying to get the force feedback stuff to work with Dark Basic Pro. I haven't tried it with Dark SDK, yet.
Anyhow, this is test code for trying to work with multiple devices, and for working out your own methods of calibration and such.
Cheers,
Ancient Lady
Cheers,
Ancient Lady