READING THE CONTROLLERS What's the purpose of writing a game if you can't interact with the applcation. Might as well watch a video then. After all, a good game involves human input/feedback. In this document, my aim is to show how to read input from standard and analogue controllers. Any suggestions or criticisms on this document welcome. May I apologise for any errors that may occur, since I don't have the original analogue controller, only the Dual Shock which I am working from. James Chow aka jc 3 June 1998 james@chowfam.demon.co.uk 6 July 1998 - Slight correction to paragraph Understanding the System Buffers. By Mike Kavallierou. Thanks. --------------------------------------------------------------- Overview -------- During the vertical retrace, the status of the controllers is read into the system buffers, and this is utilised in our game. The status of the controllers includes, whether the a controller is present or not, the type of the controller present, and of course which buttons have been pressed and the position of the joysticks if any. Understanding the System Buffers -------------------------------- The system buffers can hold up 34 bytes of information, regarding the status of the controllers. Byte Information 0 Whether controller is connected or not. 1 Upper 4 bits - type of controller Lower 4 bits - half the number of bytes of received data 2-33 Received data Byte 0 holds 0x00 if the controller is connected, and 0xff is not. Byte 1, upper 4 bits holds a number to uniquely identify the type of controller connected. Standard controllers have a value of 0x4. Analogue pads, including the Dual Shock, have the same id number as that of standard controllers when the analogue mode light is off. But in analogue mode, when the light is red, the controller has an id value of 0x7. Analogue joysticks, which the non-Dual Shock analogue pad supports, when the analogue mode light is green, the id value is 0x5. Personally, I've never used the lower 4 bits of byte 1 to tell me how much received data there has been. To configure our game to whichever controller, we already know which it will be and so that information becomes redundant. For the standard controller and analogue controllers, bytes 2 and 3 hold the information regarding button presses. And for the analogue controller, bytes 4 to 7 holds the information regarding the position of the joysticks. The bit positions corresponding to the buttons can be found in the User Guide. For the analogue controller: Byte Information 4 Right Joystick Horizontal position 5 Right Joystick Vertical position 6 Left Joystick Horizontal position 7 Left Joystick Vertical position A value of 0 for vertical positions represents UP on the joystick, increasing to 255 for DOWN. A value of 0 for horizontal postions represents LEFT on the joystick, increasing to 255 for RIGHT. Reading the Received Data ------------------------- By an unfortunate quirk of fate (or simply because it is easier to implement in hardware), a bit value of 1 indicates a button is not pressed, and a bit value of 0 indicates a button press. However, this is easy to overcome. We can simply invert the bit values. To extract byte 2 and 3 to occupy a single variable, and invert them so 1 indicates button press, we do - //bb0 initialised to point to controller //buffer in system memory already int padstate = ~(*(bb0+3) | *(bb0+2)<<8); To read whether a particular button is pressed, we use the corresponding bit mask (with nice names - fortunately), already defined in the pad.h files located on the development CD-ROM. if (padstate & PADstart) { //start pressed //process as appropriate } Recommendations --------------- Unfortunately, simply reading the button presses/joystick positions is not good enough. Suppose, we are reading the analogue values for our game, when the controller is removed. The system buffers do NOT clear the analogue values, and so we will reading non-existent input. The Quality Assurance Group which test the games, specify that certain conditions be met before a game is released. As game programmers, even at the hobbyist level, it is good practice to follow these guidelines. This information can be found in the Peripherals web page under Manual Additions. But briefly - A game must be played with the standard controller, (from controller port 1) for a single player game. Input from controllers not supported should be ignored. Controllers should be able to be identified from option screens. Games should go into pause state if diconnected during play. This leads to code which follows such as if (pad connected) if (pad supported) process input else ignore input else no input Demonstration ------------- For demonstration purposes, the recommendations will be ignored (!). But this should be clear as to the purpose of the demo. Another thing to notice, is that I'm using my own pad reading code, so the pad.h file is not that on the CD-ROM, (I've added my own stuff). Game Overview: To display the controller information on screen. Exit when Start and Select pressed on controller 1. /*********/ /* pad.h */ /*********/ #define PADLup (1<<12) #define PADLdown (1<<14) #define PADLleft (1<<15) #define PADLright (1<<13) #define PADRup (1<<4) #define PADRdown (1<<6) #define PADRleft (1<<7) #define PADRright (1<<5) #define PADstart (1<<11) #define PADselect (1<<8) #define PADL1 (1<<2) #define PADL2 (1<<0) #define PADL3 (1<<9) #define PADR1 (1<<3) #define PADR2 (1<<1) #define PADR3 (1<<10) #define PADin 0x00 #define PADout 0xff #define PADstandard 0x4 #define PADanalogue 0x5 #define PADdualshck 0x7 void initPads(void); int padConnected(int port); int padType(int port); int padState(int port); int padLHorz(int port); //L analogue X value int padLVert(int port); //L analogue Y value int padRHorz(int port); //R analogue X value int padRVert(int port); //R analogue Y value //Horz analogue values: 0 left, 255 right //Vert analogue values: 0 up, 255 down /*********/ /* pad.c */ /*********/ #include "libps.h" #include "pad.h" volatile u_char *bb[2]; void initPads(void) { GetPadBuf(&bb[0],&bb[1]); } int padConnected(int port) { return *bb[port]; } int padType(int port) { return (*(bb[port]+1))>>4; } int padState(int port) { return ~(*(bb[port]+3) | *(bb[port]+2)<<8); } int padLHorz(int port) { return *(bb[port]+6); } int padLVert(int port) { return *(bb[port]+7); } int padRHorz(int port) { return *(bb[port]+4); } int padRVert(int port) { return *(bb[port]+5); } /**********/ /* main.c */ /**********/ #include "libps.h" #include "pad.h" #define OTLEN (1) #define SCNW (320) #define SCNH (240) //utility function for printing out in binary char* toBinary(long var,int len,char* text) { long mask = 1; int i; for (i=len-1; i>=0; i--) { if (var&mask) text[i] = '1'; else text[i] = '0'; mask = mask << 1; } text[len] = '\0'; return text; } void initGame(void) { FntLoad(960,256); FntOpen(0,0,SCNW,SCNH,0,512); initPads(); } int updateGame(void) { if (padConnected(0) == PADin) if ((padType(0) == PADstandard)|| (padType(0) == PADanalogue)|| (padType(0) == PADdualshck)) if ((padState(0) & PADstart)&& (padState(0) & PADselect)) return 1; return 0; } void drawGame(void) { int i; char text[33]; FntPrint(" BINARY INTEGER\n\n"); for (i=0;i<2;i++) { FntPrint("CONTROLLER %d\n",i+1); FntPrint("CONNECTED? %s %d\n", toBinary(padConnected(i),8,text),padConnected(i)); FntPrint("TYPE? %s %d\n", toBinary(padType(i),4,text),padType(i)); FntPrint("BUTTON VALS: %s\n",toBinary(padState(i),16,text)); FntPrint("LEFT HORZ: %s %d\n", toBinary(padLHorz(i),8,text),padLHorz(i)); FntPrint("LEFT VERT: %s %d\n", toBinary(padLVert(i),8,text),padLVert(i)); FntPrint("RIGHT HORZ: %s %d\n", toBinary(padRHorz(i),8,text),padRHorz(i)); FntPrint("RIGHT VERT: %s %d\n", toBinary(padRVert(i),8,text),padRVert(i)); FntPrint("\n\n"); } FntFlush(-1); } void main(void) { GsOT ot[2]; GsOT_TAG ottags[2][1<