Acceleration based movement and snapping on a grid based map

Started by
3 comments, last by ferrous 10 years, 2 months ago

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.

Advertisement

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]

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.

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.

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

This topic is closed to new replies.

Advertisement