THEORY AND IMPLEMENTATION OF MAC GAME INPUT by Morgan Aldridge In this article I'de like to discuss various concepts that you should consider when obtaining input for a Mac Game. I won't go into the InputSprocket or anything like that, I'll just use the standard Mac OS Toolbox. I know that there may be issues with Carbon and Cocoa, but this is designed to help the beginner or intermediate developer, and it'll be a few years yet before the beginner will be starting out developing for Mac OS X (and there are still plenty of pre-PowerPC machines that can be developed for too!) With that said let's get down to business. Before we actually start in with the learning, I'd like to say that you are expected to understand how to handle Mac OS Toolbox Events, if you don't know them, don't worry, it shouldn't be too hard to learn along, but you should read through some of the tutorial at Macintosh C (http://www.mactech.com/macintosh-c/), especially Chapter 2 which discusses "Low Level and Operating System Events". If you get entirely stuck, feel free to drop me, or another fellow developer, a line. First, let's discuss the various situations you may find yourself trying to get input under. Basically, you can break down input situations into two basic categories: (1) Situations where you would like to poll an input device as quickly as possivle, and (2) situations where you would like to poll an input device as accurately as possible. FASTER INPUT RESPONSE Let's examine situation (1), polling an input device as quickly as possible. Why would you want to do this anyway? Well, most likely, you want your game to be more responsive, this is best for arcade games and twitch shooters (i.e. games like Quake) in which the user is going to want the computer to be able to react to input as quickly as the user can react to what he (or she) sees on the screen. As most of you probably know by now, the Mac OS is event based (meaning that when the user does something in your application, the Mac OS will tell your application), this works well for clicking on buttons in dialog boxes, but to be able to catch user actions faster, it is often best to bypass the event driven nature of the Mac OS. To do this, instead of handling mouse clicks or key presses when they occur, you'll want to check to see if the mouse button is being pressed or whether a key is being pressed, this way you can hopefully catch it as it's first happening instead of waiting for it to happen and then reacting to it. So how do we catch, for example, a mouse click instead of waiting for a mouse click event? Well, as I explained, we aren't really going to "catch" it, but going to check for, as in this example, a mouse click as quickly as possible so that we can know about it as soon as possible. So, in your event loop, you would check for a mouse click using the Button() command during every iteration of the loop. Now, this is probably the easiest example, so here's the code: while ( /* event loop condition */ ) { if ( Button() ) /* If the mouse button is down */ { /* Fire laser or something */ } } Now, that wasn't so painfull was it? Well, that will check to see if the mouse button is being pressed and fire a laser (or something) if it is, and we'll know when the mouse is down slightly sooner than if we had waited for the OS to tell us that it had been pressed, cool, huh! But, wait... what if you want only a single shot fired when the mouse button is pressed, instead of continuing to fire the laser while the mouse button is held down? Well, then you would probably want the better accuracy which is described in the next section. Checking for mouse clicks was easy, but what about key presses? Well, they're a bit more difficult because you have to check the keymap to see whether or not a specific key has been pressed. To make this easier, I wrote myself a function named IsKeyDown() which ca be found in MacInput.h and MacInput.c, the function is as follows: /********************> IsKeyDown() <*****/ Boolean IsKeyDown( unsigned char *keyMap, unsigned short theKey ) { long keyMapIndex; Boolean isKeyDown; short bitToCheck; /* Calculate the key map index */ keyMapIndex = keyMap[theKey/8]; /* Calculate the individual bit to check */ bitToCheck = theKey%8; /* Check the status of the key */ isKeyDown = ( keyMapIndex >> bitToCheck ) & 0x01; /* Return the status of the key */ return isKeyDown; } Basically, you pass in a key map and key code to IsKeyDown() and it will check the bit for the key code in the key map and see if it is on or not (telling whether or not the key is down). The key map which you pass in is an array of 16 unsigned chars and can be obtained by a call to GetKeys() as in the following example: unsigned char keymap[16]; GetKeys( ( unsigned long * )keymap ); You will need to use GetKeys() every time through your event loop so that you will have the most updated map of which keys are down. The key code that you pass in to IsKeyDown() is the hex code of the specific key which you want to check, you can find the key code by creating a 'KCHR' resource in a resource file and then looking at the "Key" code for the key you want. I have compiled a list of constants for most of the keys in MacInput.h, so you can use, for example, MAC_OPTION_KEY in place of 0x3A. The following is an example of how you might modify the first code example to also use IsKeyDown() to check for key presses: unsigned char keymap[16]; while ( /* event loop condition */ ) { if ( Button() ) /* If the mouse button is down */ { /* Fire laser or something */ } /* Get which keys are down */ GetKeys( ( unsigned long * )keymap ); if ( IsKeyDown( keymap, MAC_ARROW_UP_KEY ) == true ) { /* Increase ship's thrust */ } if ( IsKeyDown( keymap, MAC_ARROW_DOWN_KEY ) == true ) { /* Decrease ship's thrust */ } } As you can see in that example, I have used IsKeyDown() to add the ability to alter a spaceship's thrust by checking to see whether the up or down keys are being held down. Notice that I'm calling GetKeys() within the event loop so that I always have an up-to-date map of which keys are down. That should pretty much explain how to use GetKeys() and IsKeyDown(), so now it's on to the next section. MORE ACCURATE INPUT RESPONSE Now it's time to take a look at Situation (2), polling an input device as acurately as possible. Where in games does this come in handy anyway? Well, although you may want your game to be as responsive as possible, there are times when you don't want it to overreact, as you might say, to user input. To most accurately handle user input, it is usually best to use the Event Manager of the Mac OS to tell you when things are happening, which is what I'll explain first, but I will also go into some tricks for modifying the faster methods described in the previous section so that they can be a bit more accurate. Before we actually dive into the good stuff, there is something that I would like to say about how we get EventRecords from the MacOS. There are two ways that we can get First, let's start with mouse clicks, they're pretty easy, right? Anyway, in the previous section we could check to see if the mouse button was down with a call to the Button() function, there were potential problems with that in that we couldn't really differentiate between individual clicks or just holding the mouse button down. By using the Mac Event manager we can handle each individual mouse click as the Event manager sends them to us. Now, how we do this is that we first use GetNextEvent() to get the next event from the event queue for our application, then we handle it by type (such as mouseDown, mouseUp, keyDown, keyUp, etc). Here's a simple example of the actual code that goes into doing this: EventRecord event; while ( /* event loop condition */ ) { /* Get the next event in the event queue */ if ( GetNextEvent( everyEvent, &event ) ) { /* Handle the type of event it was */ switch ( event->what ) { case mouseDown: /* Fire laser or something */ break; } } } Now, unlike the mouse click handling example I used in the first section, this one will fire our ship's laser only once each time the mouse button is clicked, it won't continue to fire the laser while the mouse button is held down. This method is great for checking for single key presses, double clicks, and the likes, but a quick call to Button() is the way to go when you just want to see if the mouse is being clicked at any particular time. Handling key presses is not all that much harder, it's just another addition to the previous example. It will help you keep a distinction between key presses and won't react too fast which the IsKeyDown() method will under some circumstances. A good example of when IsKeyDown() might react too quickly would be a custom menu system that uses the keyboard as input. In such a situation, if you are going to move up or down one menu item for each press of the up or down arrow keys, it's most likely that IsKeyDown() will get called atleast a couple times during the amount of time it would take for a person to tap either the up or down arrow key once. This would cause the menu system to appear to skip some of the menu items, it is this type of case where accuracy is preferred over speed in which the Event Manager method is preferred. The easiest way to do this is to handle a 'keyDown' event in much the same way that we did with the 'mouseDown' even shown above, but with the minor adjustment that we'll need to decode what key was pressed first, then pass that to some function to deal with. Luckily the Event Manager makes it fairly easy to decode which key was pressed using a bit of code such as the following: char theChar; theChar = event->message & charCodeMask; 'charCodeMask' is a constant declared in Events.h, so you don't really need to worry about that. Now, that code will give us the ASCII character of the key pressed, which is quite a bit simpler to deal with than key codes used in the IsKeyDown() method from the first section. The following is an example of a function which might handle the key (in this case ASCII character which corresponds with the key) and perform actions accordingly: /********************> HandleKeyDown() <*****/ void HandleKeyDown( char theChar ) { switch ( theChar ) { /* Handle the space bar being pressed */ case ' ': /* Select menu item and do its action */ break; case kUpArrowCharCode: /* Select next menu item */ break; case kDownArrowCharCode: /* Select previous menu item */ break; } } Just so you know, 'kUpArrowCharCode' and 'kDownArrowCharCode' are also constants defined in Events.h, there are plenty more in there so please do explore. That should be a fairly straight forward example of how you would handle the ASCII character for a key press, now let's work all of this code into the first Event Manager example: EventRecord event; char theChar; while ( /* event loop condition */ ) { /* Get the next event in the event queue */ if ( GetNextEvent( everyEvent, &event ) ) { /* Handle the type of event it was */ switch ( event->what ) { case mouseDown: /* Fire laser or something */ break; case keyDown: theChar = event->message & charCodeMask; HandleKeyDown( theChar ); break; } } } That example would allow our application to decode what key was pressed, in the event of a key down event, and pass it on to our HandleKeyDown() function which will decide what to do depending on the key pressed. You will notice that this code will only recognize single key presses as key down events, so if you hold down, in this case, the down arrow, the previous menu item will be selected, but it won't continue to select the previous item. In order to get around this and to get the application to recognize not only single key presses, but also to keep handling the key down while the key is held down, we'll also need to handle 'autoKey' events. This is similar to using the IsKeyDown() method except that there will be a delay before it starts repeating the event handling, and it won't repeat as quickly as it would if you were checking for a key being down with IsKeyDown(). Now, you can handle auto key events seperately from key down events, but in most cases you will want it to repeat the same thing that would have been done in the event of a key down event, so here's a modification on the previous example which incorporates handling auto key events (look carefully, it's a very minor change): EventRecord event; char theChar; while ( /* event loop condition */ ) { /* Get the next event in the event queue */ if ( GetNextEvent( everyEvent, &event ) ) { /* Handle the type of event it was */ switch ( event->what ) { case mouseDown: /* Fire laser or something */ break; case keyDown: case autoKey: theChar = event->message & charCodeMask; HandleKeyDown( theChar ); break; } } } Now, that should be enough to help you handle input using the Event Manager, but also be aware that you can also handle events such as 'keyUp' events even though you may never find the need to. CONCLUSION Hopefully this has helped you understand the differences between situations (1) and (2) and how you can handle input in these situations on the Macintosh. You may also realize that with a little ingenuity and some extra coding you can get the examples from the first section to behave like those from the second section. So, if you feel like going that route, feel free to do so, I wrote this more for the beginner and intermediate Mac OS developer, so I felt I should try to keep things simple and show as broad methods as I could.