Jump to content

  • Log In with Google      Sign In   
  • Create Account


Acceleration based movement and snapping on a grid based map


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
4 replies to this topic

#1 desrik   Members   -  Reputation: 132

Like
0Likes
Like

Posted 31 January 2014 - 05:00 PM

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, 31 January 2014 - 05:05 PM.


Sponsor:

#2 Steve_Segreto   Crossbones+   -  Reputation: 1483

Like
0Likes
Like

Posted 31 January 2014 - 10:23 PM

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.

 

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);
}



#3 ferrous   Members   -  Reputation: 1779

Like
0Likes
Like

Posted 31 January 2014 - 10:40 PM

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.



#4 desrik   Members   -  Reputation: 132

Like
0Likes
Like

Posted 01 February 2014 - 12:06 AM

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.



#5 ferrous   Members   -  Reputation: 1779

Like
0Likes
Like

Posted 01 February 2014 - 11:28 PM

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






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS