Shinrai

How to achieve smooth tile-based movement with C++ and SDL?

4 posts in this topic

I've been wondering how to work out the logic for creating smooth tile-based movement for a player.

These are the functions being used to handle player movement as of now.  (Input and animation is already being handled, but not in the code.  I just want to show the basics of how the logic works for now.)

void Player::startMovingUp() {
    mVelocityY = -kWalkSpeed;
    mDirectionFacing = UP;
}

void Player::startMovingDown() {
    mVelocityY = kWalkSpeed;
    mDirectionFacing = DOWN;
}

void Player::startMovingLeft() {
    mVelocityX = -kWalkSpeed;
    mDirectionFacing = LEFT;
}

void Player::startMovingRight() {
    mVelocityX = kWalkSpeed;
    mDirectionFacing = RIGHT;
}

void Player::stopMoving() {
    mVelocityX = 0;
    mVelocityY = 0;
}

All of these values are updated in the player update function. (Everything is time-based, rather than frame-based. That's where the elapsed_time_ms variable comes in.)

void Player::update(int elapsed_time_ms) {
    mX += round(mVelocityX * elapsed_time_ms);
    mY += round(mVelocityY * elapsed_time_ms);
}

This is perfectly fine to move the player around. But I want my player to be moving on a tiled map, locked to a grid, rather than just moving freely like this.

Each tile in the game have fixed dimensions of 16 by 16 pixels.  This may be important, so I'm just leaving it here.

0

Share this post


Link to post
Share on other sites

Well there are a few ways you can do this. One way is quite simply loop over the tiles performing bound tests based on the player position, and pass on the positional data stored in the player tile, and set that directly to the players pos. The other way is to just divide the pixel position of the player by the tile fixed height, and width like so

x = 100 / 16 = 3

y = 100 / 16 = 3

This you can use as an index into the tilemap, or however you're storing this to get the positional data directly, and set it to the player.

EDIT

Ah, I misinterpreted. I thought you were looking to have player snapping to individual grid cells

Edited by markypooch
0

Share this post


Link to post
Share on other sites

Simply interpolate between the old and new positions in the grid and ignore input during the animation.  Perhaps something like:

void Player::update(float dt) {
	if ( this->move_state == MoveState::STOPPED ) {
		int2 dir = this->process_move_input();
		if ( dir ) {
			this->move_state = MoveState::MOVING;
			this->move_timer = this->move_speed; // in tiles per second
			this->old_pos = this->pos;
			this->pos += dir;
		}
	}
	else if ( this->move_state == MoveState::MOVING ) {
		this->move_timer -= dt;
		if ( this->move_timer <= 0 ) {
			this->move_timer = 0;
			this->move_state = MoveState::STOPPED;
		}
	}
}

void Player::draw() {
	int2 pos_px;

	if ( this->move_state == MoveState::MOVING ) {
		// move_timer goes from 1 to 0, so math on positions is reversed from the traditional A→B interpolation
		pos_px = this->pos + float2(this->old_pos - this->pos) * (this->move_timer / this->move_speed) * TILE_SIZE_PX;
	}
	else {
		pos_px = this->pos * TILE_SIZE_PX;
	}

	// draw avatar at pos_px
}

 

4

Share this post


Link to post
Share on other sites

In the past I've actually done away with the "moving"/"stopped" state indicator and just checked whether the interpolation value was in the normalized range.

if(move_timer > 0) {
  moving
}
else {
  listening to input
}
Edited by Khatharr
1

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