Sign in to follow this  

Acceleration based movement and snapping on a grid based map

This topic is 1445 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am designing a roguelike and have a vision of the movement style I would like. I have devised a basic implementation of the acceleration and "snap" mechanics, however there are a few things that have me stuck.

 

The general idea is to have a tile based roguelike but with what I will call "freeform" character movement. Basically the entity ( player, monster, vehicle, etc ) has the ability to move independently from the tile grid while the movement keys are held. When the entity stops moving I would like it to decelerate, then smoothly "snap" to the closest valid grid cell. I would also like to retain the ability to move from tile to tile upon single movement keypresses. ( Normal roguelike movement ) ( Not currently implemented )

 

My current acceleration based movement works perfectly, smooth movement to the east and to the south, however the west and north movement is causeing the entity to "skip" tiles. I believe it is an issue with my rounding algorithim (last two lines of the stopMove() function ), but my below average math skills prevent me from figuring out if I'm right and if so, why. This same issue persists with the grid "snap" when the entity stops moving. To the east and south it snaps perfectly. West and north cause the entity to "snap" to the tile 7 tiles further than it should. ( instead of snapping to the closest tile )

 

Here is my relavant movement code so far: ( I'm sure there is room for major code cleansing in these. Sorry if you cringe while reading it. )

void Entity::update(Engine* engine) {
    if (!Engine::keyState[SDLK_UP] && !Engine::keyState[SDLK_RIGHT] && !Engine::keyState[SDLK_DOWN] && !Engine::keyState[SDLK_LEFT]) {
      stopMove();
    }
    
    if ((Engine::keyState[SDLK_UP] && Engine::keyState[SDLK_RIGHT])
      || (Engine::keyState[SDLK_RIGHT] && Engine::keyState[SDLK_DOWN])
      || (Engine::keyState[SDLK_DOWN] && Engine::keyState[SDLK_LEFT])
      || (Engine::keyState[SDLK_LEFT] && Engine::keyState[SDLK_UP]))
      {
        if (Engine::keyState[SDLK_UP] && Engine::keyState[SDLK_RIGHT]) {
          accelX = 5.0;
          accelY = -5.0;
        }
        if (Engine::keyState[SDLK_RIGHT] && Engine::keyState[SDLK_DOWN]) {
          accelX = 5.0;
          accelY = 5.0;
        }
        if (Engine::keyState[SDLK_DOWN] && Engine::keyState[SDLK_LEFT]) {
          accelX = -5.0;
          accelY = 5.0;
        }
        if (Engine::keyState[SDLK_LEFT] && Engine::keyState[SDLK_UP]) {
          accelX = -5.0;
          accelY = -5.0;
        }
      } else {
        if (Engine::keyState[SDLK_LEFT]) {
          accelX = -5.0;
          accelY = 0.0;
          speedY = 0;
        }
        if (Engine::keyState[SDLK_RIGHT]) {
          accelX = 5.0;
          accelY = 0.0;
          speedY = 0;
        }
        if (Engine::keyState[SDLK_UP]) {
          accelY = -5.0;
          accelX = 0.0;
          speedX = 0;
        }
        if (Engine::keyState[SDLK_DOWN]) {
          accelY = 5.0;
          accelX = 0.0;
          speedX = 0;
        }
      }
    
    if ( accelX > maxSpeedX || accelX < -maxSpeedX) { accelX = maxSpeedX; }
    if ( accelY > maxSpeedY || accelY < -maxSpeedY) { accelY = maxSpeedY; }
    
    if (flags & ENTITY_FLAG_GRAVITY) {
       // accelY = 0.75f;
    }
    
    speedX += accelX * FPS::Control.getSpeedFactor();
    speedY += accelY * FPS::Control.getSpeedFactor();
    
    if (speedX > maxSpeedX) speedX = maxSpeedX;
    if (speedX < -maxSpeedX) speedX = -maxSpeedX;
    if (speedY > maxSpeedY) speedY = maxSpeedY;
    if (speedY < -maxSpeedY) speedY = -maxSpeedY;
	animate();
    if ((Engine::keyState[SDLK_LSHIFT]) && (Engine::keyState[SDLK_UP] 
      || Engine::keyState[SDLK_RIGHT] 
      || Engine::keyState[SDLK_DOWN] 
      || Engine::keyState[SDLK_LEFT]))
    {
      speedX = speedX * 3;
      speedY = speedY * 3;
    }
    move(speedX, speedY);
    
}

void Entity::move(float moveX, float moveY) {
  if (moveX == 0 && moveY == 0) {
    return;
  }
  
  double newX = 0;
  double newY = 0;
  
  moveX *= FPS::Control.getSpeedFactor();
  moveY *= FPS::Control.getSpeedFactor();
  
  if (moveX != 0) {
    if (moveX >= 0) {
      newX = FPS::Control.getSpeedFactor();
    } else {
      newX = -FPS::Control.getSpeedFactor();
    }
  }
  
  if (moveY != 0) {
    if (moveY >= 0 ) {
      newY = FPS::Control.getSpeedFactor();
    } else {
      newY = -FPS::Control.getSpeedFactor();
    }
  }
  
  while (true) {
    if (flags & ENTITY_FLAG_GHOST) {
      positionValid((int)(x + newX), (int)(y + newY));
      
      x += newX;
      y += newY;
    } else {
      if (positionValid((int)(x + newX), (int)(y))) {
        x += newX;
      } else {
        speedX = 0;
      }
      
      if (positionValid((int)(x), (int)(y + newY))) {
        y += newY;
      } else {
        speedY = 0;
      }
    }
    
    moveX += -newX;
    moveY += -newY;
    
    if (newX > 0 && moveX <= 0) {
      newX = 0;
    }
    if (newX < 0 && moveX >= 0) {
      newX = 0;
    }
    
    if (newY > 0 && moveY <= 0) {
      newY = 0;
    }
    if (newY < 0 && moveY >= 0) {
      newY = 0;
    }
    
    if (moveX == 0) {
      newX = 0;
    }
    if (moveY == 0) {
      newY = 0;
    }
    
    if (moveX == 0 && moveY == 0) {
      break;
    }
    if (newX == 0 && newY == 0) {
      break;
    }
  }
}

void Entity::stopMove() {
  if (speedX > 0) {
    accelX = -0.5;
  }
  if (speedY > 0) {
    accelY = -0.5;
  }
  
  if (speedX < 0) {
    accelX = 0.5;
  }
  if (speedY < 0) {
    accelY = 0.5;
  }
  
  if (speedX < 3.0f && speedX > -3.0f) {
    accelX = 0;
    speedX = 0;
  }
  if (speedY < 3.0f && speedY > -3.0f) {
    accelY = 0;
    speedY = 0;
  }
  x = ((int)x / TILE_SIZE) * TILE_SIZE;
  y = ((int)y / TILE_SIZE) * TILE_SIZE;
}

EDIT:: Forgot to give the values of the constants. TILE_SIZE is 16, maxSpeedX and maxSpeedY are both 5, and for FPS::Control.getSpeedFactor() it calculates ((SDL_GetTicks() - lastTime) / 1000.0f) * FRAMES_PER_SECOND with FRAMES_PER_SECOND being 32.

Edited by desrik

Share this post


Link to post
Share on other sites

Here is how to snap to a grid. (x,z) range in the larger local space and can be adjusted according to your velocity scheme above.

 

[Source]

void

LocalToGrid( float x, float z, int &row, int &col )

{

// Transform from local space to "grid" space.

float c = (x) / TILE_SIZE_X;

float d = (z) / TILE_SIZE_Z;

// Get the row and column we are in.

row = (int)floorf(d);

col = (int)floorf(c);

}

[/Source]

Share this post


Link to post
Share on other sites

Is it just because you're taking the floor?  try doing (int)floorf(d + 0.5f)  or roundf(), if you have that in your language.

Share this post


Link to post
Share on other sites

Perfect :) Thank you both. Using floor() worked great, but I still don't understand why it was causing tiles to be skipped. Does a straight (int) cast from float just drop the decimals?

 

Thanks again. I'm gonna go see if I can figure out how to work in tile to tile movement now. That's really the only thing left to do in the movement system now. Might not be much to show but when I get it done I might do a youtube video showing what the system looks like in action.

Share this post


Link to post
Share on other sites

Perfect smile.png Thank you both. Using floor() worked great, but I still don't understand why it was causing tiles to be skipped. Does a straight (int) cast from float just drop the decimals?

 

Thanks again. I'm gonna go see if I can figure out how to work in tile to tile movement now. That's really the only thing left to do in the movement system now. Might not be much to show but when I get it done I might do a youtube video showing what the system looks like in action.

Yeah, I think it's just order of operations

 

x = ((int)x / TILE_SIZE) * TILE_SIZE;
y = ((int)y / TILE_SIZE) * TILE_SIZE;

 

You might try:

x = (int)(x / TILE_SIZE) * TILE_SIZE;

y = (int)(y / TILE_SIZE) * TILE_SIZE;

 

As Floor and (int) are the same, they just drop the decimal places, AKA round everything down.  7.99 becomes 7

Share this post


Link to post
Share on other sites
Sign in to follow this