Jump to content
  • Advertisement
Sign in to follow this  

Player Movement in a tile engine

This topic is 4882 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

Hey, guys I've got a question that I can't seem to find any info on, so heres hoping someone here can point me in the right direction. I've got a tile engine game and I'm struggling with how to controll the players movement. when I move the character left, right, up or down it behaves fine. I check all the tiles along the leading eadge of the characters bounding box and check to see if he's move into any solid areas. if he has then I back him up to the very edge. Now this works fine for the 4 cardinal directions, but whenever I have a velocity that is off the axis (for example velocity.x = 12 and velocity.y = 10) then I start running into strange glitches. I've tried using axis projection to back the character out of the collision appropriately but then the character jumps along edges or sometimes skips right through them... the problem comes from the fact that a character can sometimes collide with 2 or more tiles and the projection vectors that back the character out of collision don't match (obviously) any one have any advice as to how I should handle this or where I can find some info that might help? Heres the code for the collision detection in case you're wondering
POINT level::move(LPOBJ o) {
	RECT objPos;
	int txStart, txEnd;
	int tyStart, tyEnd;
	int tx; // tilemap xposition
	int ty;
	int tv; // tilemap attribute value
	POINT newVel;
	bool collision = false;

	objPos = o->getBoundingBox();
	objPos.left   += o->m_velocity.x;
	objPos.right  += o->m_velocity.x;
	objPos.top    += o->m_velocity.y;
	objPos.bottom += o->m_velocity.y;
	txStart = objPos.left   / m_tileWidth;  // tilemap start position
	txEnd   = objPos.right  / m_tileWidth;  // tilemap end position
	tyStart = objPos.top    / m_tileHeight; // tilemap start position
	tyEnd   = objPos.bottom / m_tileHeight; // tilemap end position

	newVel.x = o->m_velocity.x;
	newVel.y = o->m_velocity.y;

	//
	// check the horizontal edges
	//
	if (o->m_velocity.y < 0) {
		for (tx=txStart; tx <= txEnd; tx++) {
			// check top row
			tv = m_layer[0].tilemap[tyStart * m_layer[0].max_tile_x + tx];
			if (tv & SOLID_BIT) {
				newVel.y += (tyStart * m_tileHeight + m_tileHeight) - objPos.top;
				collision = true;
				g_Error.write("\ntop collision!");
				break;
			}
		}
	}
	else if (o->m_velocity.y > 0) {
		for (tx=txStart; tx <= txEnd; tx++) {
			// check bottom row
			tv = m_layer[0].tilemap[tyEnd * m_layer[0].max_tile_x + tx];

			if (tv & SOLID_BIT) {
				newVel.y -= objPos.bottom - (tyEnd * m_tileHeight - 1);
				g_Error.write("\nbottom collision!");
				collision = true;
				break;
			}
		}
	}

	if (o->m_velocity.x < 0) {
		for (ty=tyStart; ty <= tyEnd; ty++) {
			// check top row
			tv = m_layer[0].tilemap[ty * m_layer[0].max_tile_x + txStart];
			if (tv & SOLID_BIT) {
				newVel.x += (txStart * m_tileWidth + m_tileWidth) - objPos.left;
				g_Error.write("\nleft collision!");
				collision = true;
				break;
			}
		}
	}
	else if (o->m_velocity.x > 0) {
		for (ty=tyStart; ty <= tyEnd; ty++) {
			// check bottom row
			tv = m_layer[0].tilemap[ty * m_layer[0].max_tile_x + txEnd];

			if (tv & SOLID_BIT) {
				newVel.x -= objPos.right - (txEnd * m_tileWidth - 1);
				g_Error.write("\nright collision!");
				collision = true;
				break;
			}
		}
	}

	if (collision) {
		if (abs(newVel.x) < abs(newVel.y)) {
			newVel.y = 0;
		}
		else {
			newVel.x = 0;
		}
	}
	else
		g_Error.write("\nno collision!");

	return newVel;
}

and heres the code that uses the collision
void PlayerTestMove(LPOBJ o) {
	POINT v;

	v.x = v.y = 0;

	if (g_Input.getKey(DIK_LEFT))
		v.x = -HORIZ;

	if (g_Input.getKey(DIK_RIGHT))
		v.x = HORIZ;

	if (g_Input.getKey(DIK_UP))
		v.y = -VERT;

	if (g_Input.getKey(DIK_DOWN))
		v.y = VERT;

	o->setVelocity(v);

	POINT nv = g_Level->move(o);

	o->setVelocity(nv);

	o->updatePosition();
}

Share this post


Link to post
Share on other sites
Advertisement
Well, this is a very tricky problem. The only resonable way to solve it is to assign a position in pixels (or whatever) for each player, rather than give it tile coordinates. Then you can check on which tile[s] the player is sitting on. If the player is trying to move into an tile that is blocked, just block the entire move (don't allow him to move in that direction).

Share this post


Link to post
Share on other sites
There are certainly ways to handle the situations you describe - it just depends on how much work you want to do.

Perhaps you'd like to have your character 'slide' along surfaces that s/he runs into, as in most 3D FPSs (in this case of course in 2D only). The code for this can get fairly complicated, as there are various special cases (one of them - running into two surfaces at once - you mentioned in your post). So it might take some work, but the results would be professional and pleasing to the player.

The algorithms involved are too much to sum up in this post, but I can make some recommendations:

1. Look for the paper 'Improved Collision Detection and Response', by Kasper Fauerby. It describes collision and sliding between spheres/ellipsoids and meshes in 3D. The same concepts can be applied in 2D.

2. Search for 'sliding' or 'collision detection sliding' on this forum and on google. On this forum at least many threads will turn up with a lot of info.

3. The same algorithm can work with AABBs as well. Look at the Quake 1 or 2 source code, or google for 'quake collision detection'.

Good luck!

Share this post


Link to post
Share on other sites
What worked for me:
WARNING: This is my retyping an old function. Not sure if this will work right off, but the concept is right.

bool checkCollision(Player player)
{
int NumTraveledTilesX = (player.getVelocityX() + projectedVelocityX) / tileSize;
int NumTraveledTilesY = (player.getVelocityY() + projectedVelocityY) / tileSize;

for(int i = player.getCurRow(); i < (player.getCurRow() + NumTraveledTilesX); i++)
{
for(int j = player.getCurCol(); j < (player.getCurCol() + NumTraveledTilesY); j++)
{
if(tileMap[j].isSolid())
{
return false;
}
}
}

return true;
}



Basically, this determines an area in which the player can/will move with the velocity projection. If there's a solid block in that area, don't move the player. It handles both x and y velocities, so it'll work with non-cardinal directions. Tweak that function and you should have a simple, efficient tile-level collision detector. You can modify this function so it's pixel precise, as versus tile level. You'll need to know the player's size or current size(if the player has an animation that changes size). So yeah, tell me if that works.

Share this post


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

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!