Chapter 6. Controlling Games with the Keyboard and Mouse

All the graphics in the world wouldn’t save a game if you couldn’t interact with it. There’s a name for a game with no user interaction—it’s called a screensaver! User interaction can come from several different places, but you can generally break it down into a short list of user input devices: the keyboard, the mouse, and joysticks. The keyboard and mouse are by far the two most standard user input devices on computers, and are just about guaranteed to be supported on all personal computers. Joysticks are also quite common, but aren’t nearly as commonplace as keyboards and mice. So, you should consider the keyboard and mouse the two most important game input devices, with joysticks following behind in third place. This hour focuses on interacting with the user via the keyboard and mouse, whereas Hour 7, “Improving Input with Joysticks,” tackles joysticks.

In this hour, you’ll learn:

  • Why user input is so important in games

  • How each of the major types of input devices impact games

  • How to efficiently detect and respond to keyboard input

  • How to handle mouse input

  • How to create a program with an animated graphical object that you can control with the keyboard and mouse

Gaming and User Input

User input is the means by which the user interacts with a game. Considering the fact that user input encompasses the entire communications between a player and a game, you would think that designing an intuitive, efficient interface for user input would be at the top of the list of key game design elements. However, that isn’t always the case. With all the hype these days surrounding real-time, texture-mapped 3D graphics engines and positional 3D audio in games, effective user input support is often overlooked. In fact, user input is perhaps the most overlooked aspect of game design, which is truly a tragedy. It’s a tragedy because user input support in a game directly impacts the playability of the game; and when the user input isn’t effective, the play suffers.

You see, I’m from the old school of game players, and I still remember paying homage to the gods of gaming with my hard earned allowance in arcades, well before there was an option of playing anything other than Pong at home (see the opener for Hour 1, “The Basics of Game Creation”). In return for my quarter offerings, the game gods usually provided me with incredibly fun games that usually had to survive on their playability alone. Because the hardware at that time simply couldn’t provide a very high level of graphic and sound intensity, game developers were forced to make up for it with game play. Of course, they didn’t consider their focus on playability as making up for anything; with the limited graphics and sound capabilities at the time, they didn’t have an option.

Let me give you a quick example of what I’m talking about in regard to playability and user input. One of my all-time favorite games is Ring King, which is a boxing game for the Nintendo Entertainment System (NES). Ring King is definitely considered “old” by current gaming standards—possibly even ancient. Compared to current games, it has weak graphics, animation, and sound. However, I still play the game simply because it plays so well. And that playability is largely based on how the game handles user input; it allows for very subtle timing when you punch and dodge, which goes a long way in a boxing game! Since then, I’ve tried to find a modern replacement for Ring King, but I’ve had no luck. When I get beyond the fancy graphics and sound, I start missing the responsiveness of the controls in my old favorite. I’m still looking, though.

Lest you think I’m being overly critical of current games, plenty of recent games have incredible user input support, along with awesome graphics and sound. However, an equal number of recent games have killer graphics and sound, but little substance when it comes to playability and user input. These types of games might be visually stunning and fun to listen to, but they rarely have any lasting appeal beyond the initial “Wow!”

Now, let me step down from the soapbox and get to the real point, which is that you should carefully plan the user input for your games just like you carefully plan the graphics, sound, and game logic. This doesn’t mean only deciding between supporting a keyboard or a mouse for the user interface. It means putting some real thought into making the user interface as intuitive as possible. You want the controls for the game to feel as natural as possible to the player.

Taking a Look at User Input Devices

Input devices are the physical hardware that allows a user to interact with a game. Input devices all perform the same function: converting information provided by the user into a form understandable by the computer. Input devices form the link between the user and your game. Even though you can’t directly control the input device hardware, you can certainly control how it is interpreted in your game. As I mentioned earlier, there are three primary types of user input devices:

  • The keyboard

  • The mouse

  • Joysticks

Note

Taking a Look at User Input Devices

In case you’re wondering, trackballs are functionally very similar to mice and are often treated just like mice from a software perspective. In fact, the Win32 API doesn’t discern between trackballs and mice, so the mouse support in your games indirectly supports trackballs as well.

The next few sections get you acquainted with these devices and their relationship to game user input.

The Keyboard

The keyboard has been the computer input device of choice since its inception. Although mice, joysticks, flight sticks, and many other user input devices have brought extended input capabilities to the game player, none is as established as the keyboard. At the bare minimum, you can always count on a game player having a keyboard.

The keyboard is a surprisingly useful input device for a wide range of games. The sheer amount of keys alone gives the keyboard appeal for games that require a wide variety of user input. Even more useful in the keyboard is the natural feel of pressing keys for games requiring quick firing and movement. This usefulness is evident in the amount of arcade games that still use buttons, even when powerful digital joysticks are readily available. Keys (or buttons) simply are easier to use in many games, including those with many input combinations.

When assessing the potential use of the keyboard in a game, try to think in terms of the most intuitive user interface possible. For example, any game involving the player moving an object around would benefit from using the arrow keys. A good example is the classic 3D shooter Doom, which makes creative use of a keyboard-specific feature that greatly enhances the playability of the game. The left and right arrow keys, when used alone, rotate the player left and right in the game world. However, when the Shift key is held down, the same left and right arrow keys cause the player to strafe, meaning that the player moves sideways without changing direction. This seemingly small enhancement to the keyboard controls goes a long way when playing the game.

When you’re deciding on specific keys to use for keyboard controls in your game, consider the potential limitations on players using other platforms or hardware configurations. For example, I primarily use a Windows XP PC equipped with a Microsoft Natural keyboard. If you aren’t familiar with these keyboards, they are split down the middle for ergonomic reasons. If you don’t use one of these keyboards, it might not occur to you that key combinations near the center of the keyboard will be separated a few inches for people like me. So, remember that if you use the G and H keys (or other middle keys) in your game, and it plays well for you, it might not work out so well for players with different keyboards.

The most common keys used in games are the arrow keys. If you’re writing an action game, you might also have keys for firing and selecting between weapons. When you’re deciding on the keys to use, keep in mind things like the creative usage of the Shift key in Doom. If you can limit the number of primary game control keys by making use of a secondary key such as Shift, you’ve made the game controls that much easier to use.

The Mouse

Although the keyboard is firmly in place as the most necessary of user input devices, the graphical nature of modern computers establishes the mouse, or a similar pointing device, as a standard input device as well. The mouse, however, doesn’t share the wide range of input applications to games that the keyboard has. This stems from its primary usage as a point-and-click device; if you haven’t noticed, a lot of games don’t follow the point-and-click paradigm.

In regard to gaming, the usefulness of the mouse is dependent totally on the type of game and, therefore, the type of user interaction dictated by the game. However, as quickly as some people write off the mouse as being a useless interface in some games, others praise the fine control it provides. Again, a good example is Doom. Personally, I think the keyboard is much more responsive than the mouse, and the keyboard enables me to get around faster and with more control. But I have friends who feel lost playing the game without a mouse.

Clearly, this is a situation in which the game designers saw a way to provide support for both the keyboard and mouse. With the exception of the most extreme cases, this should be your goal as well. Different game players like different things, and the safest solution is to hedge your bets and support all input devices whenever possible. By following this simple rule, you can develop games that can be appreciated and enjoyed by a broader group of game players.

Joysticks

Although not quite as standard as keyboards and mice, joysticks nonetheless represent a very common component of modern computer systems. Joysticks are in many ways a throwback to video games of old—many of which relied heavily on joysticks for controlling characters, spaceships, and other game objects. Joysticks come in both digital and analog form, although the difference between the two isn’t terribly important from a programming perspective. The primary thing to consider when assessing the role of joysticks in games is whether it makes sense to play the game with a joystick. In other words, does the movement in the game lend itself to being carried out with a vertical stick (or flat game pad), as opposed to using keyboard keys or a mouse? Of course, it’s not a bad idea to support multiple input devices—in which case you might opt to include joystick support just to be flexible.

Note

Joysticks

Most game pads count as joysticks because they provide a similar functionality in terms of providing a multidirectional control for movement, as well as several buttons. From a programming perspective, Windows doesn’t distinguish between joysticks and game pads, which is a good thing for you.

It’s worth mentioning that some games out there seem to be expressly designed for being played with joysticks. For one, the extremely popular game Halo on the XBox console system uses a unique feature that would be difficult to replicate with a keyboard or mouse. Halo allows you to press the joystick inward to activate a rifle scope on your gun. So, if you see some bad guys off in the distance, you can push in on the joystick to zoom with the scope and pick them off one by one. The scope/zoom feature in Halo is an excellent example of an ingenious game feature made possible by a specific type of input device. Of course, the XBox controller had to include a joystick with the push feature in order for Halo to work, so Microsoft deserves some credit on the hardware side of things.

Assessing Keyboard Input for Games

You know that messages are used heavily throughout the Win32 API to deliver notifications regarding things such as window creation, window destruction, window activation, window deactivation, and so on. You may be glad to find out that this same messaging system is used to deliver notifications regarding key presses on the keyboard. More specifically, the Win32 API defines messages called WM_KEYDOWN and WM_KEYUP that inform you whenever a key has been pressed or released, respectively. These two messages are quite easy to handle in a Windows program, and I’d love to show you how to use them, but there’s a problem. The problem is that the standard Windows messaging system is painfully slow when it comes to delivering keyboard messages. Because games rely heavily on responsive controls, it simply isn’t acceptable to work with the Windows keyboard messages.

The bottom line is that none of your games can support keyboards. Okay, I’m kidding. The truth is that there is always a workaround in game programming, and the slow keyboard messaging problem presents a perfect opportunity for a workaround. If you recall, the timing for the Game Engine was established back in Hour 3, “Creating an Engine for Games,” in the WinMain() function. If you recall, the WinMain() function included a program loop that repeated over and over, processing messages for the program. When the function wasn’t processing messages, it spent its time running through cycles of the game engine. In other words, the game engine takes advantage of idle time in the main program loop to carry out its game-related tasks. This idle time is what you can use to your advantage when processing key presses and releases on the keyboard.

Note

Assessing Keyboard Input for Games

The concept of “idle time” with respect to the WinMain() function is a little misleading. When you consider that games don’t typically process very many messages from Windows, the vast majority of the time spent by a game program is idle time, or time not responding to standard Windows messages. This time can therefore be put to use running the game engine, which is what I mean when I talk about running through the game cycles during idle time.

The idea behind this juiced up approach to keyboard event handling is that instead of sitting around waiting for Windows to eventually send you a keyboard message, you constantly check the keyboard to see if any of the keys you’re interested in have been pressed. If so, the game instantly springs into motion and efficiently responds to the key press. If no key is pressed, the game engine hums along with no interruptions. The strategy here is to basically take keyboard processing into your own hands and bypass the standard Windows approach, which ultimately results in much more responsive keyboard controls for your games. You learn how to implement this speedy keyboard handler in code a little later in the hour. For now, let’s take a look at how to respond to mouse events.

Tracking the Mouse

When you move the mouse, a series of events is set off that is very similar to those set off by the keyboard. In fact, the Win32 API includes a series of mouse messages that are used to convey mouse events, similar to how keyboard messages convey keyboard events. You learned in the previous section that the Win32 keyboard messages aren’t up to the task of providing efficient input for games. Fortunately, the same cannot be said of the mouse messages. It turns out that the built-in Win32 approach to handling mouse events via messages works out just fine for games. Following are the mouse messages used to notify Windows programs of mouse events:

  • WM_MOUSEMOVE—. Any mouse movement

  • WM_LBUTTONDOWN—. Left mouse button pressed

  • WM_LBUTTONUP—. Left mouse button released

  • WM_RBUTTONDOWN—. Right mouse button pressed

  • WM_RBUTTONUP—. Right mouse button released

  • WM_MBUTTONDOWN—. Middle mouse button pressed

  • WM_MBUTTONUP—. Middle mouse button released

The first mouse message, WM_MOUSEMOVE, lets you know whenever the mouse has been moved. The remaining messages relay mouse button clicks for the left, right, and middle buttons, respectively. Keep in mind that a mouse button click consists of a button press followed by a button release; you can implement a mouse dragging feature by keeping track of when a mouse button is pressed and released, and watching for mouse movement in between.

Regardless of whether you’re interested in mouse movement or a mouse button click, the important factor regarding the mouse is where the mouse cursor is located. Fortunately, the mouse cursor position is provided with all the previously mentioned mouse messages. It’s packed into the lParam argument that gets sent to the GameEngine::HandleEvent() method. Following is the prototype for this method, just in case you forgot:

LRESULT GameEngine::HandleEvent(HWND hWindow, UINT msg, WPARAM wParam,
  LPARAM lParam);

If you recall, the wParam and lParam arguments are sent along with every Windows message, and contain message-specific information. In the case of the mouse messages, lParam contains the XY position of the mouse cursor packed into its low and high words. Following is an example of a code snippet that extracts the mouse position from the lParam argument in a WM_MOUSEMOVE message handler:

case WM_MOUSEMOVE:
  WORD x = LOWORD(lParam);
  WORD y = HIWORD(lParam);
  return 0;

The wParam argument for the mouse messages includes information about the mouse button states, as well as some keyboard information. More specifically, wParam lets you know if any of the three mouse buttons are down, as well as whether the Shift or Control keys on the keyboard are being pressed. In case you don’t see it, the wParam argument—used in conjunction with WM_MOUSEMOVE—provides you with enough information to implement your own Doom strafing feature! Following are the constants used with the mouse messages to interpret the value of the wParam argument:

  • MK_LBUTTON—. Left mouse button is down

  • MK_RBUTTON—. Right mouse button is down

  • MK_MBUTTON—. Middle mouse button is down

  • MK_SHIFT—. Shift key is down

  • MK_CONTROL—. Control key is down

You can check for any of these mouse constants to see if a button or key is being pressed during the mouse move. The constants are actually flags, which means that they can be combined together in the wParam argument. To check for the presence of an individual flag, you must use the bitwise AND operator (&) to see if the flag is present. Following is an example of checking wParam to see if the right mouse button is down:

if (wParam & MK_RBUTTON)
  // Yep, the right mouse button is down!

This code shows how to use the bitwise AND operator (&) to check for individual flags. This is a technique you use in Hour 7 to check the state of a joystick.

Revamping the Game Engine for Input

Because we’ve already built a game engine for use in carrying out the various tasks associated with game management, it only makes sense to incorporate user input handling into the game engine. Granted, a certain aspect of handling user input is game specific, and therefore must be handled in the code for each individual game. However, there are some general aspects of keyboard and mouse handling that you can incorporate into the game engine to simplify the work required of specific game code.

If you recall, the idea behind the game engine is to provide a certain degree of game functionality in a self-contained class, and then delegate game specific tasks to a series of functions that each game is responsible for providing. For example, the GamePaint() function must be provided in a game to draw the game screen. Although the game engine doesn’t technically provide code for the GamePaint() function, it does make sure that it gets called whenever the game window needs to be painted; it’s up to each game to provide the actual code for GamePaint(), which makes sense because every game draws itself differently. Similar functions need to be added to the game engine to provide games with a means of handling user input.

The next couple of sections guide you through modifications to the game engine to add support for keyboard and mouse input handling.

Adding Keyboard Support

Earlier this hour, you found out that the standard Windows approach to keyboard handling with messages simply isn’t good enough for games. A much better way to handle keyboard input is to repeatedly check the state of the keyboard for specific key presses, and then react accordingly. Using this strategy, much of the burden of keyboard input handling is passed on to the game code, which means that the game engine is primarily responsible only for calling a keyboard handler function to give the game a chance to respond to key presses. Following is what this function looks like:

void HandleKeys();

The HandleKeys() function must be provided as part of the game code, and therefore isn’t included in the game engine. If you don’t want your game to support keyboard input, you can just leave the HandleKeys() function blank. Of course, the game engine must make sure that the HandleKeys() function gets called rapidly enough to give your games a responsive feel. This is accomplished in the WinMain() function within the game engine code. Following is the change made to this function:

if (iTickCount > iTickTrigger)
{
  iTickTrigger = iTickCount +
    GameEngine::GetEngine()->GetFrameDelay();
  HandleKeys();
  GameCycle();
}

The only change to the WinMain() code is the new call to the HandleKeys() function. Notice that this call occurs just before the GameCycle() function, which means that a game gets a chance to respond to keyboard input before every game cycle. Don’t forget; the specifics of handling keyboard input are carried out in each specific game when you create your own HandleKeys() function. You find out how to do this later in the hour.

Adding Mouse Support

Although keyboard input in games is admittedly non-standard in terms of deviating from how things are typically handled in Windows programming, mouse input is handled the old-fashioned way—with messages. It’s not that mouse messages are more efficient than keyboard messages; it’s just harder to notice sluggish mouse input. In other words, mouse messages appear to be fast enough to allow you to create a responsive mouse interface, whereas keyboard messages do not.

In order to support mouse input, games must support the following three functions, which are called by the game engine to pass along mouse events:

void MouseButtonDown(int x, int y, BOOL bLeft);
void MouseButtonUp(int x, int y, BOOL bLeft);
void MouseMove(int x, int y);

In order to connect mouse messages with these mouse handler functions, the game engine must look for the appropriate mouse messages and respond accordingly. The following code includes the new portion of the GameEngine::HandleEvent() method that is responsible for handling mouse messages delivered to the main game window.

case LBUTTONDOWN:
  // Handle left mouse button press
  MouseButtonDown(LOWORD(lParam), HIWORD(lParam), TRUE);
  return 0;

case WM_LBUTTONUP:
  // Handle left mouse button release
  MouseButtonUp(LOWORD(lParam), HIWORD(lParam), TRUE);
  return 0;

case WM_RBUTTONDOWN:
  // Handle right mouse button press
  MouseButtonDown(LOWORD(lParam), HIWORD(lParam), FALSE);
  return 0;

case WM_RBUTTONUP:
  // Handle right mouse button release
  MouseButtonUp(LOWORD(lParam), HIWORD(lParam), FALSE);
  return 0;

case WM_MOUSEMOVE:
  // Handle mouse movement
  MouseMove(LOWORD(lParam), HIWORD(lParam));
  return 0;

This code handles the following mouse messages: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUP, and WM_MOUSEMOVE. Each piece of message handler code simply calls one of the mouse functions with the appropriate arguments. The first and second arguments to all the mouse functions include the X and Y position of the mouse cursor at the moment the message was delivered. The last argument to the mouse button functions is a Boolean value that identifies whether the left (TRUE) or right (FALSE) mouse button is involved in the event.

As you can hopefully tell from the code, the idea behind the mouse functions is to allow games to simply provide MouseButtonDown(), MouseButtonUp(), and MouseMove() functions, as opposed to getting involved with message handling. So, to support the mouse in your games, all you have to do is provide code for these three functions.

Sprucing Up the Bitmap Class

You’ve now made the necessary changes to the game engine to prepare it for keyboard and mouse input. However, there is another minor change you need to make that doesn’t technically have anything to do with input. I’m referring to bitmap transparency, which allows bitmaps to not always appear as square graphical objects. Don’t get me wrong; bitmaps definitely are square graphical objects, but they don’t necessarily have to be drawn that way. The idea behind transparency is that you can identify a color as the transparent color, which is then used to indicate parts of a bitmap that are transparent. When the bitmap is drawn, pixels of the transparent color aren’t drawn, and the background shows through.

Note

Sprucing Up the Bitmap Class

Why is there a discussion of bitmap transparency in an hour focused on keyboard and mouse input? The answer has to do with the fact that I want you to view the game engine as a work in progress that is constantly evolving and picking up new features. In this case, the program example at the end of this hour benefits greatly from bitmap transparency, so it only makes sense to add the feature here. You’ll continue to make small improvements to the game engine throughout the book even if they don’t tie in directly to the topic at hand. The end result will be a game engine with a lot of slick little features that will make your games all the more fun.

From a graphics creation perspective, you create bitmaps with transparency by selecting a color that isn’t used in your graphics, such as “hot purple,” which is also known as magenta. You then use magenta to fill areas of your bitmaps that need to appear transparent. It’s then up to the revamped game engine to make sure that these transparent regions don’t get drawn with the rest of the bitmap. You obviously don’t want magenta borders around your images!

The trick to making bitmap transparency work in the game engine is to expand the existing Bitmap::Draw() method so that it supports transparency. This is accomplished by adding two new arguments:

  • bTrans—. a Boolean value that indicates whether the bitmap should be drawn with transparency

  • crTransColor—. the transparent color of the bitmap

It’s important to try and make changes to the game engine that don’t cause problems with programs that we’ve already written. So, rather than add these two arguments to the Draw() method and require them of all bitmaps, it’s much better to add them and provide default values:

void Draw(HDC hDC, int x, int y, BOOL bTrans = FALSE,
  COLORREF crTransColor = RGB(255, 0, 255));

If you notice, the default value of bTrans is FALSE, which means that if you leave off the argument, transparency isn’t used. This works great for existing code because it doesn’t change the way the Draw() method already worked. In case you’re curious, the default color specified in crTransColor (RGB(255, 0, 255)) is magenta, so if you stick with that color as your transparent color, you won’t have to specify a transparent color in the Draw() method.

The only significant changes to the Draw() method involve checking the transparency argument, and then drawing the bitmap with transparency using the Win32 TransparentBlt() function if the argument is TRUE. Otherwise, it’s business as usual with the BitBlt() function being used to draw bitmaps without transparency.

Note

crTransColor—

Although the TransparentBlt() function is part of the Win32 API, it isn’t as widely supported as the traditional BitBlt() function. More specifically, the function isn’t supported in versions of Windows prior to Windows 98, such as Windows 95.

The TransparentBlt() function is part of the Win32 API, but it requires the inclusion of a special library called msimg32.lib in order for your games to compile properly. This is a standard library that should be included with your compiler, but you’ll need to make sure that it is linked in with any programs that use the TransparentBlt() function. If you aren’t familiar with altering linker settings for your compiler, just take a look at the compiler documentation and find out how to add additional libraries to a project; it typically involves simply entering the name of the library, msimg32.lib in this case, in a project settings window. Or, if you happen to be using Microsoft Visual Studio, you can follow these steps:

  1. Open the project in Visual Studio (Visual C++).

  2. Right-click on the project’s folder in Solution Explorer, and click Properties in the pop-up menu.

  3. Click the Linker folder in the left pane of the Properties window, and then click Input.

  4. Click next to Additional Dependencies in the right pane, and type msimg32.lib.

  5. Click the OK button to accept the changes to the project.

After completing these steps, you can safely compile a program and know that the msimg32.lib library is being successfully linked into the executable program file.

Note

crTransColor—

The source code for the examples in the book is located on the accompanying CD-ROM, and includes Visual C++ project files with the appropriate linker settings already made.

Building the UFO Program Example

In order to really get a feel for how keyboard and mouse input works in games, it’s helpful to work through a complete example. The remainder of this hour focuses on a program example called UFO. Although this program isn’t technically a game, it’s by far the closest thing you’ve seen to a game yet. It involves a flying saucer that you control with the keyboard and mouse. You’re able to fly the flying saucer around a bitmap background image. Perhaps most important is the fact that the UFO program demonstrates how good of a feel you can create for game controls. More specifically, the arrow keys on the keyboard are surprisingly responsive in the UFO program.

Although you haven’t really learned about animation yet, the UFO program example makes use of animation to allow you to fly the flying saucer. Fortunately, the program is simple enough that you can get by without knowing the specifics about animation. All you really need to know is that you can alter the position of a bitmap image to simulate movement on the screen. This occurs thanks to the game engine, which redraws the bitmap every game cycle. So, by altering the position of an image and redrawing it repeatedly, you create the effect of movement. The UFO program example reveals how this task is accomplished, as well as how the keyboard and mouse fit into the picture.

Note

Building the UFO Program Example

You learn the details of how animation works in Hour 9, “A Crash Course in Game Animation.”

Writing the Program Code

The header file for the UFO program example lays the groundwork for the meat of the program, which carries out the details of the flying saucer animation and user input. Listing 6.1 contains the code for the UFO.h header file, which declares global variables used to control the flying saucer.

Example 6.1. The UFO.h Header File Declares Global Variables Used to Keep Track of the Flying Saucer

 1: #pragma once
 2:
 3: //-----------------------------------------------------------------
 4: // Include Files
 5: //-----------------------------------------------------------------
 6: #include <windows.h>
 7: #include "Resource.h"
 8: #include "GameEngine.h"
 9: #include "Bitmap.h"
10:
11: //-----------------------------------------------------------------
12: // Global Variables
13: //-----------------------------------------------------------------
14: HINSTANCE   _hInstance;
15: GameEngine* _pGame;
16: const int   _iMAXSPEED = 8;
17: Bitmap*     _pBackground;
18: Bitmap*     _pSaucer;
19: int         _iSaucerX, _iSaucerY;
20: int         _iSpeedX, _iSpeedY;

The first thing of interest in this code is the _iMAXSPEED constant, which establishes the maximum speed of the flying saucer (line 16). The speed of the flying saucer is how many pixels it can travel in a given direction in each game cycle. So, the value of the _iMAXSPEED constant means that the flying saucer can never travel more than 8 pixels in the horizontal or vertical direction in a game cycle.

The _pBackground and _pSaucer global variables store the two bitmaps used in the program, which correspond to a night sky background image and the flying saucer image (lines 17 and 18). The remaining variables pertain to the flying saucer, and include its XY position (line 19) and XY speed (line 20). The XY position of the flying saucer is specified relative to the game screen. The XY speed, on the other hand, simply tells the program how many pixels the flying saucer should be moved per game cycle; negative values for the speed variables indicate that the flying saucer is moving in the opposite direction.

With the global variables in place, we can move on to the game functions. The first game function to consider is GameInitialize(), which creates the game engine and establishes the frame rate. The frame rate for the program is set to 30 frames per second. This is a relatively high frame rate for games, but it results in much smoother motion for the flying saucer, as you soon find out.

Next on the game function agenda is the GameStart() function, which creates and loads the flying saucer bitmaps, as well as sets the initial flying saucer position and speed (Listing 6.2).

Example 6.2. The GameStart() Function Performs Startup Tasks for the UFO Program Example

 1: void GameStart(HWND hWindow)
 2: {
 3:   // Create and load the background and saucer bitmaps
 4:   HDC hDC = GetDC(hWindow);
 5:   _pBackground = new Bitmap(hDC, IDB_BACKGROUND, _hInstance);
 6:   _pSaucer = new Bitmap(hDC, IDB_SAUCER, _hInstance);
 7:
 8:   // Set the initial saucer position and speed
 9:   _iSaucerX = 250 - (_pSaucer->GetWidth() / 2);
10:   _iSaucerY = 200 - (_pSaucer->GetHeight() / 2);
11:   _iSpeedX = 0;
12:   _iSpeedY = 0;
13: }

The GameStart() function is used to initialize data pertaining to the program such as the bitmaps and other global variables. The flying saucer position is initially set to the middle of the game screen (lines 9 and 10), and then the speed of the saucer is set to 0 so that it isn’t moving (lines 11 and 12).

You might think that a program with an animated flying saucer cruising over a background image would require a complex GamePaint() function. However, Listing 6.3 shows how this simply isn’t the case.

Example 6.3. The GamePaint() Function Draws the Background and Flying Saucer Bitmaps

 1: void GamePaint(HDC hDC)
 2: {
 3:   // Draw the background and saucer bitmaps
 4:   _pBackground->Draw(hDC, 0, 0);
 5:   _pSaucer->Draw(hDC, _iSaucerX, _iSaucerY, TRUE);
 6: }

As the code reveals, the GamePaint() function for the UFO program is painfully simple. Aside from the standard code that you’re now getting accustomed to seeing in the GamePaint() function, all the function does is draw the background and flying saucer bitmaps. The background bitmap is drawn at the origin (0, 0) of the game screen (line 4), whereas the flying saucer is drawn at its current position (line 5). Notice that TRUE is passed as the last argument to the Draw() method when drawing the flying saucer, which indicates that the saucer is to be drawn with transparency using the default transparent color.

The GameCycle() function is a little more interesting than the others you’ve seen because it is actually responsible for updating the position of the flying saucer based on its speed. Listing 6.4 shows how this is accomplished in the code for the GameCycle() function.

Example 6.4. The GameCycle() Function Updates the Saucer Position and Then Repaints the Game Screen

 1: void GameCycle()
 2: {
 3:   // Update the saucer position
 4:   _iSaucerX = min(500 - _pSaucer->GetWidth(), max(0, _iSaucerX + _iSpeedX));
 5:   _iSaucerY = min(320, max(0, _iSaucerY + _iSpeedY));
 6:
 7:   // Force a repaint to redraw the saucer
 8:   InvalidateRect(_pGame->GetWindow(), NULL, FALSE);
 9: }

The GameCycle() function updates the position of the flying saucer by adding its speed to its position. If the speed is negative, the saucer will move to the left and/or up, whereas positive speed values move the saucer right and/or down. The seemingly tricky code for setting the position must also take into account the boundaries of the game screen so that the flying saucer can’t be flown off into oblivion. Granted, the concept of flying off the screen might sound interesting, but it turns out being quite confusing! Another option would be to wrap the saucer around to the other side of the screen if it goes over the boundary, which is how games like Asteroids solved this problem, but I opted for the simpler solution of just stopping it at the edges. After updating the position of the flying saucer, the GameCycle() function forces a repaint of the game screen to reflect the new saucer position (line 8 ).

The flying saucer is now being drawn and updated properly, but you still don’t have a way to change its speed so that it can fly. This is accomplished first by handling keyboard input in the HandleKeys() function, which is shown in Listing 6.5.

Example 6.5. The HandleKeys() Function Checks the Status of the Arrow Keys, Which Are Used to Control the Flying Saucer

 1: void HandleKeys()
 2: {
 3:   // Change the speed of the saucer in response to arrow key presses
 4:   if (GetAsyncKeyState(VK_LEFT) < 0)
 5:     _iSpeedX = max(-_iMAXSPEED, --_iSpeedX);
 6:   else if (GetAsyncKeyState(VK_RIGHT) < 0)
 7:     _iSpeedX = min(_iMAXSPEED, ++_iSpeedX);
 8:   if (GetAsyncKeyState(VK_UP) < 0)
 9:     _iSpeedY = max(-_iMAXSPEED, --_iSpeedY);
10:   else if (GetAsyncKeyState(VK_DOWN) < 0)
11:     _iSpeedY = min(_iMAXSPEED, ++_iSpeedY);
12: }

The HandleKeys() function uses the Win32 GetAsyncKeyState() function to check the status of the arrow keys (VK_LEFT, VK_RIGHT, VK_UP, and VK_DOWN) and see if any of them are being pressed. If so, the speed of the flying saucer is adjusted appropriately. Notice that the newly calculated speed is always checked against the _iMAXSPEED global constant to make sure that a speed limit is enforced. Even flying saucers are required to stay within the speed limit!

Note

The HandleKeys() Function Checks the Status of the Arrow Keys, Which Are Used to Control the Flying Saucer

The GetAsyncKeyState() function is part of the Win32 API, and provides a means of obtaining the state of any key on the keyboard at any time. You specify which key you’re looking for by using its virtual key code; Windows defines virtual key codes for all the keys on a standard keyboard. Common key codes for games include VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_CONTROL, VK_SHIFT, and VK_RETURN.

If you thought handling keyboard input in the UFO program was easy, wait until you see how the mouse is handled. To make things a little more interesting, both mouse buttons are used in this program. The left mouse button sets the flying saucer position to the current mouse cursor position, whereas the right mouse button sets the speed of the flying saucer to 0. So, you can use the mouse to quickly get control of the flying saucer; just right-click to stop it and then left-click to position it wherever you want. Listing 6.6 shows the code for the MouseButtonDown() function, which makes this mouse magic possible.

Example 6.6. The MouseButtonDown() Function Uses the Left and Right Mouse Buttons to Move the Flying Saucer to the Current Mouse Position and Stop the Flying Saucer, Respectively

 1: void MouseButtonDown(int x, int y, BOOL bLeft)
 2: {
 3:   if (bLeft)
 4:   {
 5:     // Set the saucer position to the mouse position
 6:     _iSaucerX = x - (_pSaucer->GetWidth() / 2);
 7:     _iSaucerY = y - (_pSaucer->GetHeight() / 2);
 8:   }
 9:   else
10:   {
11:     // Stop the saucer
12:     _iSpeedX = 0;
13:     _iSpeedY = 0;
14:   }
15: }

The first step in this code is to check and see which one of the mouse buttons was pressed—left or right (line 3). I know, most PC mice these days have three buttons, but I wanted to keep the game engine relatively simple, so I just focused on the two most important buttons. If the left mouse button was pressed, the function calculates the position of the flying saucer so that it is centered on the current mouse cursor position (lines 6 and 7). If the right button was pressed, the speed of the flying saucer is set to 0 (lines 12 and 13).

Note

The MouseButtonDown() Function Uses the Left and Right Mouse Buttons to Move the Flying Saucer to the Current Mouse Position and Stop the Flying Saucer, Respectively

If you determine that supporting two mouse buttons is simply too much of a weakness for the game engine, feel free to modify it on your own. It primarily involves changing the third argument of the MouseButtonDown() function so that it can convey more than two values—one for each of the three mouse buttons.

Testing the Finished Product

The UFO program example is the closest thing you’ve seen to a game thus far, and is quite interesting in terms of allowing you to fly around an animated graphical object. Hopefully, you’ll be pleasantly surprised by the responsiveness of the program’s keyboard controls. Figure 6.1 shows the UFO program in action as the flying saucer does a flyby of some desert cacti.

The UFO program example demonstrates how to control an animated graphical object with the keyboard and mouse.

Figure 6.1. The UFO program example demonstrates how to control an animated graphical object with the keyboard and mouse.

If you guide the flying saucer to the edge of the game screen, it will stop, which is to be expected given the program code you just worked through. There are a variety of different ways to tweak this program and make it more intriguing, such as wrapping the flying saucer from one side of the screen to the other, which is why I hope you spend some time tinkering with the code. You’ll find some ideas for modifying the program in the Exercises for the lesson, which are coming up.

Summary

The ability to effectively communicate with the people that play your games is a critical factor of game design and development. In one direction a game communicates by displaying graphics and playing sounds and music, but in the other direction the user responds by interacting with a physical input device of some sort. It’s very important for game developers to master the fine art of responding to user input through a variety of different user input devices. The keyboard and mouse are the two fundamental user input devices that you can count on virtually all people having. This hour showed you how to handle and respond to keyboard and mouse input in an efficient manner specifically suited to games.

Beyond the keyboard and mouse, it’s up to your resources and the specific needs of each game to determine whether you should support additional input devices. Hour 7 tackles the subject of joystick input, which is the next most important input device for games.

Q&A

Q1:

Are there any games that wouldn’t require keyboard support?

A1:

Sure. Any game that requires extensive point-and-click style input, such as a card game, would probably be fine without any keyboard support. In fact, you create a memory game in Hour 8, “Example Game: Brainiac,” that is perfectly suited for mouse input alone. However, if you can figure out a way to add keyboard controls to mouse-centric games, by all means go for it.

Q2:

Is it possible to mix keyboard and mouse controls?

A2:

Of course! This actually hits on an interesting point not directly covered in this hour: Many games work great by combining mouse and keyboard controls together. A good example is the strafing feature in Doom, which also works with the mouse; it is activated by holding down the Shift key while moving the mouse left or right.

Workshop

The Workshop is designed to help you anticipate possible questions, review what you’ve learned, and begin learning how to put your knowledge into practice. The answers to the quiz can be found in Appendix A, “Quiz Answers.”

Quiz

1:

If you had to choose between supporting the keyboard or the mouse, which would you choose?

2:

What do you have to do differently in a game to support trackballs?

3:

Why is it important to extract information from the lParam argument when responding to a mouse message?

Exercises

  1. Experiment with different values for the _iMAXSPEED global variable in the UFO program example to see how it affects the flying saucer’s speed.

  2. Modify the UFO program example so that you can click and drag the mouse to move the flying saucer around.

  3. Modify the UFO program example so that the flying saucer wraps off the screen and appears on the other side when it reaches a side edge.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.172.132