Advertisment

User Interaction in Games 

author-image
PCQ Bureau
New Update

In this series, till now we've seen how to create graphics for games. In this article, we will talk about things that differentiate a game from computer animation. The key factor in this is interactivity, as it adds to the game's playability. Getting input from the user is only half the battle; the more important half is for the game to respond well to this input. Also the game must follow some laws to govern the motion of sprites in the game. Another concern that arises from this is the matter of collision detection. We will handle both these issues here. Another topic that we'll talk about is that of multi-threading. This topic, however, does not have much to do with games and is platform dependant. We will adhere to the Windows platform.

Advertisment

User input



Though the keyboard doesn't support force feedback, the sheer versatility of the device and ease of programming that it offers, made us choose it as the input device for this game. The simplest way to get keyboard input is to poll the keyboard. In win32 this is done with the function GetAsyncKeyState(). As the function name suggests, it does not wait for key press. It just returns the current key state. Another policy decision to be made here is, what keys to use for what actions. Our game requires four actions: move left, move right, jump and accelerate. To avoid the action of braking, we just assume the velocity to return to a constant value when the acceleration key is released. Hence, we can poll the left and right cursor for lateral movement, up cursor for acceleration and spacebar for jump. To add some spice to the game, we replaced the left and right cursor functions with those of the left and right mouse buttons.

Direct

Hit!
Applies to:

.Game developers
USP:

Configure the keyboard as an input device in your game

Links:

www.opengl.org

The following code snippet from main3-game.c will show you how to do this.

Advertisment

void keypress()



{


while(1)


{


if(GetAsyncKeyState(VK_ESCAPE) && 0x80)


exit(0);


.


.


.


.


critical.jump_pressed=0;


critical.whenjump--;


LeaveCriticalSection(&cs);


}


}


}













To maintain coordination between sections we set aside a few bytes of memory for a structure containing flags that say which action is to be performed. So, if the left_pressed flag is set then the rendering code has to take appropriate action by translating the racer to the left or technically the -x direction. The code that checks the keyboard state is implemented with threads. So, before we go on to the response part of the game, we will deal with threads. However, if you wish not to implement threads (since many people think of threads for input as overkill) then you can run this function normally by calling it from within the rendering function. Our advice would be, understand threads, try both approaches and then choose your own

method.

Threads: Theory and how to use them



Theoretically a thread is supposed to be the fundamental unit of execution. A program in execution is a process and threads form a part of the process. This definition is by no means perfect, but is sufficient for anybody to start off programming. Also a program has at least one thread. And each of the threads in a program runs independent of each other and in C/C++ can be written as function, and made to start and stop with the _beginthread() and _endthread() functions, provided in win32. Before compiling, though, you have to make sure that the multithreaded option switch is passed. Once you begin a thread it will continue executing and you can easily track it with its TID and the task manager. That's all there is to threads, but the issue here is that of concurrent access of the shared data by simultaneously executing threads. The same rules as that of databases apply here. No other operation should be allowed when data is being modified. Simultaneous reads are harmless. 

Advertisment

Fortunately for us we don't have to go about implementing a mutual exclusion algorithm, says Dekkers algorithm! win32 solves this with the idea of critical sections. Mutual exclusion is guaranteed when a data area is defined as a critical section.

Function calls related to this are: EnterCriticalSection(), LeaveCriticalSection() and InitializeCriticalSection(). Each of these functions take a long pointer to a structure of CRITICAL_SECTION type and is self explanatory, effectively solving the problem of mutual exclusion. One reminder: whatever is supposed to be done in the critical section should be done really fast, because while one thread is holding the critical section, all other threads have to wait for access to that data. Avoid calculations in the critical section, instead pre-calculate and assign values in the critical section. The following code from main-game.c will make it clear.

EnterCriticalSection(&cs);



if(critical.left_pressed)


{


printf("left");


racerx=racerx-10;


}


if(critical.right_pressed)


{


printf("Right");


racerx=racerx+10;


}


LeaveCriticalSection(&cs);









Advertisment

Response to user input and collision detection



Now we have to deal with the game's response to the user's input. As you may have realized already, all resulting values are not legal. As in our game, the user should be allowed to play on, only if the racer is above the track, unless a jump is in progress. That leads to a simple form of collision detection. So, before assigning the new values to the racer, we have to see whether they are valid or not. This has to be performed in the rendering loop. The code presented here is from the draw() function of main3-game.c and is pretty simple, just if statements and their outcomes. As a programmer, you are free to modify these statements.

void Draw()



{


static int speed=2,x=0,z=-100;


.


.


.


if(z<=200){


if(racerx<-50 || racerx>10)


{


MessageBox(0,"You fell off!!","collission Detection",0);


exit(0);


}


glBegin(GL_POLYGON);


glTexCoord2f(0.0f,0.0f);


.


.


. glEnd();


}





if(z<=400)


{


.


.


.


glEnd();


}


.


.


.


if(z<=1000)


{


if(racerx<-10 || racerx>10)


{


MessageBox(0,"You Fell off","collission Detection",0);


exit(0);


}


glBegin(GL_POLYGON);


glTexCoord2f(0.0f,0.0f); glVertex3f(-10.0f,0.0f,-810);


glTexCoord2f(1.0f,0.0f); glVertex3f(10.0f,0.0f,-810);


.


.


.


if(z==1000)


exit(0);


}










































The coordinates of the racer are checked against the coordinates of the track. If the racer is not on the track, his game is over. Law of gravity governs its jump part, so once the jump key is hit, for no matter how long it is pressed, the racer must return to the track after a stipulated period of time. This is done by checking the start and end z values and translating them in the y axis appropriately. For the more mathematically inclined, you could link the y value to an equation of simple harmonic motion for a more natural feel. Also, the racer should slow down when the accelerate key is released. We handled this with the help of z coordinates and by decreasing the speed uniformly with distance.

Advertisment

Scope for improvement



Sound is a vast topic and so will be handled separately in another article, which can be easily incorporated into the existing program. 

Another concept is that of key-framing. The current game will run at different speeds on different machines and will depend totally on the machines' capability. This isn't exactly what we want. To make sure that the extra frames, generated by the more powerful accelerator boards, contribute to smoothness and not speed, the program execution is liked to time and this is called time-based key-framing. These need strong base in win32 and so before you go out to key frame your game, get the game part of things right. Most of the time, during development, a simple delay loop or sleep statement will bring your game to manageable speeds. Besides, if you are generating more frames per second than you need, you are better off improving the quality of your graphics and the geometrical complexity which will naturally use up more computing power, putting all that hard earned money to better use. Also the normal human eye will not be able to distinguish between 70 fps and 100 fps when it is key-framed. Both are just as smooth!

Other front that needs improvement is level design. Levels must be stored externally and loaded into memory as and when required, not hard-wired into the game like we have done. What we have done here is the simple show and tell method of writing a game. 

Lastly, the graphics can be further improved. Start off small and keep advancing. As each function improves, the whole game improves and one fine day you may even think of putting it out into the market! 

Rakesh Iyer

Advertisment