4 Replies - 1784 Views - Last Post: 22 July 2014 - 11:26 AM Rate Topic: -----

#1 Axman10  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 36
  • Joined: 17-November 11

2D Collisions with Tilemap

Posted 21 June 2014 - 07:54 PM

I'm killing myself with this. I've read tutorial after tutorial about it and they all make it sound so easy.

Collision detection was easy. I have managed to figure out which tiles I'm colliding with, it's the resolution I'm having issues with.

I'm using the method of moving the minimum distance of either the players speed or the distance to the nearest solid tile.

void Player::update()
{
	/* CALCULATE NEXT MOVEMENT */

	switch (x_dir)
	{
	case DIR::LEFT:
		if (cur_speed_x < -max_speed_x) cur_speed_x -= accel_x;
		else cur_speed_x = -max_speed_x;
		break;
	case DIR::RIGHT:
		if (cur_speed_x > max_speed_x) cur_speed_x += accel_x;
		else cur_speed_x = max_speed_x;
		break;
	case DIR::STOP:
		if (cur_speed_x != 0) cur_speed_x -= (friction * (cur_speed_x < 0 ? -1 : 1));
		if (cur_speed_x < friction && cur_speed_x > -friction) cur_speed_x = 0;
		break;
	default:
		break;
	}

	cur_speed_y += gravity;
	if (cur_speed_y > max_speed_y) cur_speed_y = max_speed_y;

	if (isOnGround) cur_speed_y = 0;

	/* ACCOUNT FOR COLLISIONS THEN APPLY MOVEMENT */

	setPoint(level->getNextLoc(this, cur_speed_x, cur_speed_y));
	
	/* CHECK IF ON GROUND */

	isOnGround = false;

	int tile_left = int(p.x) / level->getTileWidth();
	int tile_right = int(p.x + double(width) - 0.01) / level->getTileWidth();

	if (p.x < 0) tile_left -= 1;
	if (p.x + width < 0) tile_right -= 1;

	for (int x = tile_left; x <= tile_right; x++) // check below
	{
		if (level->isTileSolid(double(x*level->getTileWidth()), p.y + height + 1.0))
		{
			isOnGround = true;
		}
		if (level->isTileSolid(double(x*level->getTileWidth()), p.y - 0.1)) cur_speed_y = 0;
	}

	/* CHECK IF OUTSIDE LEVEL */

	if (p.y > level->getLevelHeight())
		respawn();
	if (level->getLevelWidth() >= RES::GBL::WIDTH)
	{
		if (p.x + width < 0 || p.x > level->getLevelWidth())
			respawn();
	}
}


Point Level::getNextLoc(Entity *object, double vx, double vy)
{
	if (vx == 0.0f && vy == 0.0f)
		return object->getPoint();  // Object is not moving, return current location.

	bool moving_up = vy < 0;
	bool moving_left = vx < 0;

	int tile_left = 0;
	int tile_right = 0;
	int tile_top = 0;
	int tile_bottom = 0;

	/* CALCULATE BROAD PHASE TILES*/

	if (moving_left)
	{
		tile_left = int(object->getX()+vx) / tile_width;
		tile_right = int(object->getX() + object->getWidth() - 0.01) / tile_width;
	}
	else
	{
		tile_left = int(object->getX()) / tile_width;
		tile_right = int(object->getX() + object->getWidth() + vx - 0.01) / tile_width;
	}

	if (moving_up)
	{
		tile_top = int(object->getY()+vy) / tile_width;
		tile_bottom = int(object->getY() + object->getHeight() - 0.01) / tile_height;
	}
	else
	{
		tile_top = int(object->getY()) / tile_width;
		tile_bottom = int(object->getY() + object->getHeight() + vy - 0.01) / tile_height;
	}

	Point new_pos;

	new_pos.x = object->getX() + vx;
	new_pos.y = object->getY() + vy;

	bool v_col = false;
	bool h_col = false;

	if (vy != 0.0f)
	{
		for (int x = tile_left; x <= tile_right; x++)
		{
			if (moving_up) // CHECK ABOVE
			{
				if (isTileSolid(double(x*tile_width), new_pos.y))
				{
					if (!isTileSolid(double(x*tile_width), new_pos.y + tile_height)) // Hidden edge detection
					{
						new_pos.y = getTileLoc(double(x*tile_width), new_pos.y).y + tile_height;
					}
				}
			}
			else // CHECK BELOW
			{
				if (isTileSolid(double(x*tile_width), new_pos.y + object->getHeight() - 0.01))
				{
					if (!isTileSolid(double(x*tile_width), new_pos.y + object->getHeight() - tile_height)) // Hidden edge detection
					{
						new_pos.y = getTileLoc(double(x*tile_width), new_pos.y + object->getHeight() - 0.01).y - object->getHeight();
					}
				}
			}
		}
	}

	if (vx != 0.0f)
	{
		for (int y = tile_top; y <= tile_bottom; y++)
		{
			if (moving_left) // CHECK LEFT
			{
				if (isTileSolid(new_pos.x, double(y*tile_height)))
				{
					if (!isTileSolid(new_pos.x + tile_width, double(y*tile_height))) // Hidden edge detection
					{
						new_pos.x = getTileLoc(new_pos.x, double(y*tile_height)).x + tile_width;
					}
				}
			}
			else // CHECK RIGHT
			{
				if (isTileSolid(new_pos.x + object->getWidth() - 0.01, double(y*tile_height)))
				{
					if (!isTileSolid(new_pos.x + object->getWidth() - tile_width, double(y*tile_height))) // Hidden edge detection
					{
						new_pos.x = getTileLoc(new_pos.x + object->getWidth() - 0.01, double(y*tile_height)).x - object->getWidth();
					}
				}
			}
		}
	}

	return new_pos;
}


I fixed my issue with colliding with multiple tiles, but colliding with single tiles now cause the player to moved horizontally and vertically out always.

I don't need someone to fix my problem, I just need someone to explain what I'm doing wrong so I can figure it out from there. I 'thought' I was doing exactly what one of these pages was saying to do but I'm not getting results.

http://gamedev.stack...ate/25052#25052
http://gamedev.stack...n-2d-platformer
http://gamedev.stack...g-on-wall-tiles

This post has been edited by Axman10: 21 June 2014 - 08:05 PM


Is This A Good Question/Topic? 0
  • +

Replies To: 2D Collisions with Tilemap

#2 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 1009
  • View blog
  • Posts: 4,197
  • Joined: 14-February 08

Re: 2D Collisions with Tilemap

Posted 23 June 2014 - 04:51 AM

You could check out this book which has a full tilemap section including collisions and exporting from the tiled map editor tool

http://www.amazon.co...l/dp/1849696829
Was This Post Helpful? 0
  • +
  • -

#3 Axman10  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 36
  • Joined: 17-November 11

Re: 2D Collisions with Tilemap

Posted 22 July 2014 - 12:07 AM

That book didn't really have what I was looking for. I can't even find the collision code in it's source code :|

So I've got it working very roughly and with many bugs.

I've been staring at this code for way too long and I can't see the mistakes that I'm sure are there. So, I'm working on one issue specifically at the moment:

The first time the object lands on a tile, one collision is detected and handled correctly. If I jump up and land back down on the platform, two collisions are detected. I cannot figure out why!

Player update function:

void Player::update()
{
	/* CALCULATE NEXT MOVEMENT */

	switch (x_dir)
	{
	case DIR::LEFT:
		if (cur_speed_x < -max_speed_x) cur_speed_x -= accel_x;
		else cur_speed_x = -max_speed_x;
		break;
	case DIR::RIGHT:
		if (cur_speed_x > max_speed_x) cur_speed_x += accel_x;
		else cur_speed_x = max_speed_x;
		break;
	case DIR::STOP:
		if (cur_speed_x != 0) cur_speed_x -= (friction * (cur_speed_x < 0 ? -1 : 1));
		if (cur_speed_x < friction && cur_speed_x > -friction) cur_speed_x = 0;
		break;
	default:
		break;
	}

	if (!isOnGround) // apply gravity
	{
		cur_speed_y += gravity;
		if (cur_speed_y > max_speed_y) cur_speed_y = max_speed_y;
	}

	/* ACCOUNT FOR COLLISIONS THEN APPLY MOVEMENT */

	float normal_x, normal_y;
	float collision_time = level->sweptAABB(this, cur_speed_x, cur_speed_y, normal_x, normal_y);

	p.x += cur_speed_x * collision_time;
	p.y += cur_speed_y * collision_time;

	float remaining_time = 1.0f - collision_time;

	if (collision_time < 1.0f)
	{
		float dotprod = (cur_speed_x * normal_y + cur_speed_y * normal_x) * remaining_time;
		cur_speed_x = dotprod * normal_y;
		cur_speed_y = dotprod * normal_x;

		float collision_time = level->sweptAABB(this, cur_speed_x, cur_speed_y, normal_x, normal_y);
		
		p.x += cur_speed_x * collision_time;
		p.y += cur_speed_y * collision_time;
	}

	/* CHECK IF ON GROUND */

	isOnGround = false;

	int tile_left = int(p.x) / level->getTileWidth();
	int tile_right = int(p.x + double(width) - 0.01) / level->getTileWidth();

	if (p.x < 0) tile_left -= 1;
	if (p.x + width < 0) tile_right -= 1;

	for (int x = tile_left; x <= tile_right; x++) // check below
	{
		if (level->isTileSolid(double(x*level->getTileWidth()), p.y + height))
		{
			isOnGround = true;
			cur_speed_y = 0;
		}
		if (level->isTileSolid(double(x*level->getTileWidth()), p.y - 0.1)) cur_speed_y = 0;
	}


sweptAABB function:

float Level::sweptAABB(Entity *object, double vx, double vy, float & normal_x, float & normal_y)
{
	bool moving_up = vy < 0.0f;
	bool moving_left = vx < 0.0f;

	double tile_left = 0;
	double tile_right = 0;
	double tile_top = 0;
	double tile_bottom = 0;

	/* CALCULATE BROAD PHASE TILES  ** 0.01 is so we don't detect tiles below or right of players bounding box*/

	tile_left = std::min(object->getX(), object->getX() + vx);
	tile_right = std::max(object->getX() + object->getWidth() - 0.01, object->getX() + object->getWidth() + vx - 0.01);

	tile_top = std::min(object->getY(), object->getY() + vy);
	tile_bottom = std::max(object->getY() + object->getHeight() - 0.01, object->getY() + object->getHeight() + vy - 0.01);


	//AXE::axeprintf("%f, %f", object->getY() + object->getHeight(), tile_bottom);
	float collision_time = 1.0f;
	normal_x = 0.0f;
	normal_y = 0.0f;

	for (double y = tile_top; y <= tile_bottom+tile_height; y += tile_height) // loop through tiles occupied by object
	{
		for (double x = tile_left; x <= tile_right+tile_width; x += tile_width)
		{
			if (!isTileSolid(x, y)) continue; // if tile is not solid continue to next check

			if (vy != 0.0f) // if moving vertically
			{
				if (moving_up) // self explanitory
				{
					if (!isTileSolid(x, y + tile_height)) // if tile below tile currently being checked is solid, ignore hidden edge
					{
  						double edge = getTileLoc(x, y).y + tile_height; // bottom edge of tile
						double t = (object->getY() - edge) / -vy; // distance between top of object and bottom of edge divided by velocity to calculate time to collision

						if (t < collision_time) // if current check is less than a previous check, we have a closer collision
						{
							collision_time = t;
							normal_x = 0.0f;
							normal_y = 1.0f;
						}
					}
				}
				else
				{
					if (!isTileSolid(x, y - tile_height))
					{
						double edge = getTileLoc(x, y).y;
						double t = (edge - (object->getY() + object->getHeight())) / vy;

						if (t < collision_time)
						{
							collision_time = t;
							normal_x = 0.0f;
							normal_y = -1.0f;
						}
					}
				}
			}
			if (vx != 0.0f)
			{
				if (moving_left)
				{
					if (!isTileSolid(x + tile_width, y))
					{
						double edge = getTileLoc(x, y).x + tile_width;
						double t = (object->getX() - edge) / -vx;

						if (t > 0.0f && t < collision_time)
						{
							collision_time = t;
							normal_x = 1.0f;
							normal_y = 0.0f;
						}
					}
				}
				else
				{
					if (!isTileSolid(x - tile_width, y))
					{
						double edge = getTileLoc(x, y).x;
						double t = (edge - (object->getX() + object->getWidth())) / vx;

						if (t > 0.0f && t < collision_time)
						{
							collision_time = t;
							normal_x = -1.0f;
							normal_y = 0.0f;
						}
					}
				}
			}
		}
	}

	if (collision_time != 1.0f)
	{
		printf("\n");
		AXE::axeprintf("vx = %f, vy = %f", vx, vy);
		AXE::axeprintf("%f, n_x = %f, n_y = %f", collision_time, normal_x, normal_y);
	}

	return collision_time;
}


I have been following this tutorial.

This post has been edited by Axman10: 22 July 2014 - 12:20 AM

Was This Post Helpful? 0
  • +
  • -

#4 Axman10  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 36
  • Joined: 17-November 11

Re: 2D Collisions with Tilemap

Posted 22 July 2014 - 10:22 AM

double edge = getTileLoc(x, y).y;
double t = (edge - (object->getY() + object->getHeight())) / vy;


This code is acting very strange. When the second collision is detected on the fall, it t ends up set to 5.81812821381e-008. Which doesnt make sense because in debug mode, the calulation is (960 - (928 + 18)) / 1, which should equal 14.
Was This Post Helpful? 0
  • +
  • -

#5 BBeck  Icon User is offline

  • Here to help.
  • member icon


Reputation: 581
  • View blog
  • Posts: 1,290
  • Joined: 24-April 12

Re: 2D Collisions with Tilemap

Posted 22 July 2014 - 11:26 AM

Run it in a debugger. Set a break point on that section of code. Evaluate the value of each to make sure they are what you think they should be. Step into the methods if they are not returning the correct value. You can seperate this out into more lines of code for the purpose of debugging, to make it more clear their values, if needed. That should make it clear why, if there is a final result that is incorrect.

I would specifically watch out for rounding errors and overflow errors caused by implicit data type conversions or just unhandled overflow of a variable going out of range. Whatever is causing it, a debugger should be able to tell you.
Was This Post Helpful? 1
  • +
  • -

Page 1 of 1