Sign in to follow this  
tomba

smooth keyboard movement

Recommended Posts

hi i am making a 2d rpg game in opengl. the game works fine, except the keyboard function. when my character moves in one direction for a while, it looks ok. as soon as i change the direction, there is a sudden jerk then it continues to be smooth. my question is how do i make it completely smooth. my keyboard code is: switch(key) { case GLUT_KEY_RIGHT : hero_addx=-hero.speed; hero_addy=0; hero.drawy =1; if (!collision_check(tx+1.0f,ty)) { for (u=0;u<10;u++) { Sleep(15); // slow to show the animation hero.x += hero.speed; hero.motion(); // change sprite animation display_gamemode(); // re-render scene } } break; i think the sleep function is messing up the smoothness. is there any better method of moving the character smoothly? Is it possible to move the character in my code without the whole "for loop"? hope this is clear :)

Share this post


Link to post
Share on other sites
What do you mean by sudden jerk?

I also like to add that having any rendering code in your input is general bad design. I n general you would want you game game loop to be in distinct steps, check input, process data (Such as animation, collision) and render to the screen.

Share this post


Link to post
Share on other sites
Not really a jerk but as soon as I press another button, the character stops in a tile for a quick second then moves. I don’t’ want him to have that small freeze. As soon as I press a another button he should move RIGHT away, with no small freeze

Share this post


Link to post
Share on other sites
In that case I would guess that it is your for loop inside the case statement causing you grief. I cant tell until I see the rest of the game loop.

Eg.you press right, the game hits the for loop. While it is still processing the for statement (10 loops), you press left but since it still is in the eight keypresses for loop, it looks like the game is non responsive.

Share this post


Link to post
Share on other sites
is there any way to do this without the "for loop"? i read something about QueryPerformanceCounter and GetTickCount and those in a game loop. can i apply those somehow? also is there a way to read information from keyabord and not register it until a certain condition is met?

Share this post


Link to post
Share on other sites
Ignoring the timer questions (dont want to start introducing more problems and not needed at this stage), in a nutshell:


Start of game loop
If condition is met then
Process game input
else do nothing

Process animation for next frame
Render to scene
Sleep(15)
End Game loop


Althougth depending on what that condition is, it might be better if it was in the object's class/function call if it is directly related to the object rather then the general game code.

Share this post


Link to post
Share on other sites
Make the animation move a certain speed in pixels/second or grid/second.
Don't tie it in with the framerate.
Seconds are the same on all the computers..

For example, make the key only toggle a "Direction" variable,
and then make the Move routine be called aways once per frame.

Then do this:


Move()
{
switch(Player->Direction)
{
case DIR_LEFT:
Player.MoveX(Player.Speed);
break;
case DIR_RIGHT:
Player.MoveX(Player.Speed);
break;
[...]
case DIR_DOWN:
Player.MoveY(Player.Speed);
break;
default:
//no processing;
break;
}
}



In the MoveX/Y function you move the player depending on his speed
AND how much time has actually passed till last frame:
distance = time * speed;

Hope this helps.
[edit reason: not too helpful the first time]

Share this post


Link to post
Share on other sites
ttdeath, can u elaborate how to move a pixel by pixel/sec or gird/sec. all i do is
herox = herox +1; and the render like draw(herox,heroy);

what do u mean by don't tie it in with framerate?

u said "each frame rate", how do i know each frame rate in a game loop?
using a Sleep() in a game loop pauses the game for a certain amount out time, say 7 sec. but then sometimes i need to update different sprites at differnt speed. like change water tile every 5 sec while the chracter animation moves every 2 sec. these two things need to be updated at differnt time, but the sleep() in the loop pauses for the 1 sec, so it does update the character animation or water tile in time. is there a method to keep updating everything differnt sprites at different time or am i just understaing this whole concept rong?

Share this post


Link to post
Share on other sites
I see you are using glut. You may be relying on your operating system key repeat speed. This would cause a "jerk" when you change directions, if I understand you correctly. You press 'd' for example to start smoothly going right. It moves one space right, pauses, and then smoothly scrolls right. Is this what you are experiencing?

If so, you may be interested in this code. Try it out and see for yourself. You will see nice smooth camera movements using the keyboard although key repeat is turned off in glut.



#include <math.h>
#include <GL/glut.h>

#include <stdlib.h>


float angle=0.0,deltaAngle = 0.0,ratio;
float x=0.0f,y=1.75f,z=5.0f;
float lx=0.0f,ly=0.0f,lz=-1.0f;
GLint snowman_display_list;
int deltaMove = 0;


void changeSize(int w, int h)
{

// Prevent a divide by zero, when window is too short
// (you cant make a window of zero width).
if(h == 0)
h = 1;

ratio = 1.0f * w / h;
// Reset the coordinate system before modifying
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// Set the viewport to be the entire window
glViewport(0, 0, w, h);

// Set the clipping volume
gluPerspective(45,ratio,1,1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(x, y, z,
x + lx,y + ly,z + lz,
0.0f,1.0f,0.0f);


}


void drawSnowMan() {


glColor3f(1.0f, 1.0f, 1.0f);

// Draw Body
glTranslatef(0.0f ,0.75f, 0.0f);
glutSolidSphere(0.75f,20,20);


// Draw Head
glTranslatef(0.0f, 1.0f, 0.0f);
glutSolidSphere(0.25f,20,20);

// Draw Eyes
glPushMatrix();
glColor3f(0.0f,0.0f,0.0f);
glTranslatef(0.05f, 0.10f, 0.18f);
glutSolidSphere(0.05f,10,10);
glTranslatef(-0.1f, 0.0f, 0.0f);
glutSolidSphere(0.05f,10,10);
glPopMatrix();

// Draw Nose
glColor3f(1.0f, 0.5f , 0.5f);
glRotatef(0.0f,1.0f, 0.0f, 0.0f);
glutSolidCone(0.08f,0.5f,10,2);
}



GLuint createDL() {
GLuint snowManDL;

// Create the id for the list
snowManDL = glGenLists(1);

// start list
glNewList(snowManDL,GL_COMPILE);

// call the function that contains the rendering commands
drawSnowMan();

// endList
glEndList();

return(snowManDL);
}

void initScene() {

glEnable(GL_DEPTH_TEST);
snowman_display_list = createDL();

}



void orientMe(float ang) {


lx = sin(ang);
lz = -cos(ang);
glLoadIdentity();
gluLookAt(x, y, z,
x + lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}


void moveMeFlat(int i) {
x = x + i*(lx)*0.1;
z = z + i*(lz)*0.1;
glLoadIdentity();
gluLookAt(x, y, z,
x + lx,y + ly,z + lz,
0.0f,1.0f,0.0f);
}

void renderScene(void) {

if (deltaMove)
moveMeFlat(deltaMove);
if (deltaAngle) {
angle += deltaAngle;
orientMe(angle);
}

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw ground

glColor3f(0.9f, 0.9f, 0.9f);
glBegin(GL_QUADS);
glVertex3f(-100.0f, 0.0f, -100.0f);
glVertex3f(-100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, -100.0f);
glEnd();

// Draw 36 SnowMen

for(int i = -3; i < 3; i++)
for(int j=-3; j < 3; j++) {
glPushMatrix();
glTranslatef(i*10.0,0,j * 10.0);
glCallList(snowman_display_list);;
glPopMatrix();
}
glutSwapBuffers();
}

void pressKey(int key, int x, int y) {

switch (key) {
case GLUT_KEY_LEFT : deltaAngle = -0.01f;break;
case GLUT_KEY_RIGHT : deltaAngle = 0.01f;break;
case GLUT_KEY_UP : deltaMove = 1;break;
case GLUT_KEY_DOWN : deltaMove = -1;break;
}
}

void releaseKey(int key, int x, int y) {

switch (key) {
case GLUT_KEY_LEFT :
case GLUT_KEY_RIGHT : deltaAngle = 0.0f;break;
case GLUT_KEY_UP :
case GLUT_KEY_DOWN : deltaMove = 0;break;
}
}

void processNormalKeys(unsigned char key, int x, int y) {

if (key == 27)
exit(0);
}

int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(640,360);
glutCreateWindow("SnowMen from Lighthouse 3D");

initScene();

glutIgnoreKeyRepeat(1);
glutKeyboardFunc(processNormalKeys);
glutSpecialFunc(pressKey);
glutSpecialUpFunc(releaseKey);

glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);

glutReshapeFunc(changeSize);

glutMainLoop();

return(0);
}


Share this post


Link to post
Share on other sites
The idea is to never use sleep.

In the main message loop do this:


while(PeekMessage...)//regular message peeking
{
if new message
{
translate
dispatch
}
else
{
RenderEverything();
MoveEverything();
}
}

class Character
{
int Direction;
}

enum
{
CHR_DIRECTION_NONE = 0,
CHR_DIRECTION_UP,
CHR_DIRECTION_DOWN,
etc
}

//globals, initialized at beginnning
//LARGE_INTEGER timepersecTime
//QueryPerformanceFrequency(&timepersecTime;
//long double deltaTime;
//LARGE_INTEGER lastTime;
//Character chr;
//chr.Direction = CHR_DIRECTION_NONE; //don't move it by default

RenderEverything()
{
LARGE_INTEGER currentTime;
QueryPerformanceCounter(&currentTime);

deltaTime = (double)(currentTime.QuadPart - lastTime.QuadPart)/(double)timepersecTime;
//deltaTime now holds the difference in seconds from last frame
//now save currentTime for use in next frame
lastTime.QuadPart = currentTime.QuadPart;

//bla bla render;
}

MoveEverything()
{
//for each character/monster/water instance

distance = SPEED * deltaTime; //if SPEED is in pixels, distance is in pixels, if meters, then meters, if given in frames (tiles) then the result distance is in tiles, etc; SPEED varies for each instance

switch(chr.Direction)
{
case CHR_DIRECTION_UP:
{

chr.PositionY += distance;
}
}

//when you need the tiles to change every 5 sec, put an extra condition
//that verifies that the time has passed since last move (basically the same //mechanism like the currentTime/lastTime

delta = water.lastTime - currentTime;

if(delta >= DELAY_FOR_INCREMENTING_TILE_ANIMATION)
{
incrementAnimation();
water.lastTime = currentTime;
}
}

//to toggle movement:
//respond to WM_KEYDOWN and WM_KEYUP messages
OnKeyDown(key)
{
switch(key)
{
case VK_UP:
chr.Direction = CHR_DIRECTION_UP;
break;
case VK_DOWN:
chr.Direction = CHR_DIRECTION_DOWN;
break;
//etc
}
}

OnKeyUp(key)
{
chr.Direction = CHR_DIRECTION_NONE;
}



Basically use a speed variable (float) to varry the speed of any instance.
Use windows' messaging system to toggle a direction variable upon key down message, then to toggle it back to default (stationary) when user lifts his finger (key up). The rest of animation that need to be run once in every few seconds, keep track of their last increment time and increment once the delay has passed.

Share this post


Link to post
Share on other sites
ttdeath, i understand ur code mostly except for the message part. i googled it and found a website that deals with this, but they don't explain what it is. so what is this?:

if new message
{
translate
dispatch
}


what is a message? and how do i know if i received a message from a keyboard?
what is translate and dispatch?

btw the website was : http://www.mvps.org/directx/articles/writing_the_game_loop.htm

Share this post


Link to post
Share on other sites
Quote:
Original post by ttdeath
The idea is to never use sleep.

(off topic) If you never use sleep or a sleep like function, your program is going to eat up as many CPU cycles as it can. Even using Sleep(1) somewhere in the main loop can reduce CPU usage by at least 50%.

(On topic) What jdaniel said about the GLUT relying on the repeat key seems to be correct. I remember a similar problem in one of my older programs.

Share this post


Link to post
Share on other sites
to yaustar:
yes, you are right. But it can introduce performance problems
as other threads might refuse to yeild CPU time.

to tomba:

Messages is the way Windows works.
Everything is controlled by message queues, which are nothing more than simple commands stacked one upon the other. (you can visualise them as stacks(arrays) of integers).

To receive commands, you use the MSG structure and the PeekMessage / GetMessage functions. MSG.message would contain the integer value of the message which you
can compare/use as a series of enumerations that start with "WM_".
WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYUP.
As in

if(MSD.message == WM_MOUSEMOVE)
{
//the user moved the mouse, yee!
}



Translate and Dispatch are the regular way of passing the messages from your code that reads them (via PeekMessage) to the window that you have created.

I have not programmed under opengl, but you should have similar code as:


MSG msg;
// prime the message structure
PeekMessage( &mssg, NULL, 0, 0, PM_NOREMOVE);
// run till completed
while (msg.message!=WM_QUIT)
{
// is there a message to process?
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
//translate the accelerators, if any, (CTRL+S, etc), and dispatch the message to your window
//PLACE A
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
RenderEverything();
MoveEverything();
//Sleep(1); //?? :)
}
}



There are 2 places where you can check for messages in your code:
PLACE A, and your window's message processing routine (where your messages
sent by Dispatch arrive).

Instead of:
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
do
{
switch(msg)
{
case WM_KEYDOWN
...
}

TranslateMessage(&msg);
DispatchMessage(&msg);
}

Depending on what else your window is doing, you may want to
not dispatch the WM_KEYDOWN/etc messages at all, after you process them,
but this can quickly get you into trouble/deadlocks, if you don't know what you are doing.

The safer place for such a message processing swithc (keydown)
is in your window's message processing loop. To locate that procedure
look at the window's registration code.

wc.lpszClassName = "GenericAppClass";
wc.lpfnWndProc = MainWndProc;
wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = (HBRUSH)( COLOR_WINDOW+1 );
wc.lpszMenuName = "GenericAppMenu";
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
RegisterClass( &wc );



The function that processes messages for this window would be foundd here:
wc.lpfnWndProc = MainWndProc;

Share this post


Link to post
Share on other sites
This is in DirectX, I always use a timer, but I think your problem may be somewhere else.


if (GetTickCount() - start >= 30)
{
//reset timing
start = GetTickCount();
//Check for key presses
if (Key_Down(DIK_UP))
{
currDirection = UP;

}
else if (Key_Down(DIK_DOWN))
{
currDirection = DOWN;

}
else if (Key_Down(DIK_LEFT))
{

currDirection = LEFT;
}
else if (Key_Down(DIK_RIGHT))
{

currDirection = RIGHT;
}

else if (Key_Down(DIK_SPACE))
{
currDirection = ATTACK;
}
else
{
currDirection = STOP;
}


PlayerMovement();

Share this post


Link to post
Share on other sites
thx for all the help guys. ttdeath, i will try your method.
but after messign around i found the prob was acutally the
gult key process method. i think this is what jdaniel was talking about. i tried using
GetKeyState() in a game loop and it works prefectly. i am still gonna try ttdeath method and if i get better results. and once again thx for all the help everyone

Share this post


Link to post
Share on other sites
I am not sure, just my opinion, if you wish for a smooth key transition/movement, try not to have a global Keystate variable like bool Key[256], and then use the window messaging loop to update the keystate, eg true for down and false for up. During the game loop you then process the Keystate.

This is because if you are to process the Keystate like key up or key down in the window messaging loop, it will only be process when a message is sent to the window, hence you will experience jerkiness, and if you are using switch statement to process the input, you might not be able to move diagional, eg. press the UP and LEFT arrow keys down respectively will probably only make you move in the direction of the last key pressed.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this