[SOLVED] Edge Sliding in Tile Maps (NES Bomberman Style)

Hello!

I have everything working, except one thing. Basically, if two direction are pressed (e.g. UP and DOWN), player will always be able to pass. But if only one direction is pressed (e.g. UP), player won’t be able to move unless he’s perfectly aligned on the tile. If I lower my bomberman collision bound, it will look like he’s inside other tiles and I don’t want that.

This is exactly what I want:
If player is on the edge of the tile and tries to go down, but the tile down is blocked and if tile next to it is walkable, bomberman will gently slide onto it.

My points for collision are same as Kev’s (since his article helped me on free moving entities on tile map), top left, top right, bottom left and bottom right. I was experimenting and realized that in order to get that movement, I will have to check if only ONE point is colliding. But, I still don’t know how to recognize in between which tiles is bomberman. I even managed to create that movement, but it only worked when bomberman went up and down.

This is code used in my game for movement:



	public boolean moveX(float motionX){
		float newX = getX() + motionX;
		float newY = getY();
		
		if(isValidLocation(newX, newY)){
			Map map = getMap();
			PowerUp powerup = (PowerUp) map.getLayer("powerups").getObject((int) (newX / map.getTileWidth()), (int) (newY / map.getTileHeight()));
			if(powerup != null){
				powerup.activate(this);
			}
			
			setPosition(newX, newY);
			return true;
		}
		
		return false;
	}
	
	public boolean moveY(float motionY){
		float newX = getX();
		float newY = getY() + motionY;
		
		if(isValidLocation(newX, newY)){
			Map map = getMap();
			PowerUp powerup = (PowerUp) map.getLayer("powerups").getObject((int) (newX / map.getTileWidth()), (int) (newY / map.getTileHeight()));
			if(powerup != null){
				powerup.activate(this);
			}
			
			setPosition(newX, newY);
			return true;
		}
		
		return false;
	}
	
	public boolean isValidLocation(float x, float y){
		Map map = getMap();
		float tx = x / map.getTileWidth();
		float ty = y / map.getTileHeight();
		
		if(map.isBlocked(this, tx, ty)){
			return false;
		}
		
		if(map.isBlocked(this, tx + size, ty)){
			return false;
		}
		
		if(map.isBlocked(this, tx, ty + size)){
			return false;
		}
		
		if(map.isBlocked(this, tx + size, ty + size)){
			return false;
		}
		
		return true;
	}

Thanks, anyone who tries to help, in advance!

I did not read the whole text + code, but i am currently working on a Bomberman-like game, so i just want to tell you how i did it.
The movement is tilebased and limited to 5 directions: Up, Down, Left, Right and “Not-Moving”.
The character has a current-direction and a target direction.
The target direction is set, when you press/release a movement key.
If your character starts touching another tile, i check the target-direction. If the target-direction != current-direction, i set the current-tirection to the target-direction.
At this time i also set the characters position to the exact tile, so that he is on exactly 1 tile, before moving. IF i don’t do that, a character might be on 2 tiles in y-direction, while moving in x-direction and this would result in wrong collision-detection.

I hope i was clear enough.

If I understand correctly, and as you said it, your movement is tile based, right?

I fully understand tile based movement (smooth transition from tile to tile), but the problem I’m having is with free movement. I don’t want my entities (bombermen, in this case) to be limited to tile by tile movement. I want to be able to move them the same way as in NES Bomberman game. I have the whole bomberman game finished (including free movement) except the edge sliding. Bombermen can’t pass if they’re not aligned nicely, unless they’re going diagonally.

Thanks for trying to help, though!

I can’t look at the game you linked to right now, but is it a usual Bomberman-game? Isn’t bombermans movement tilebased to? I mean in Bomberman you can’t stop movement between 2 tiles or am i wrong here?

Well, that bomberman is the original one and it has free movement. From what I’ve seen, newer bomberman clone/fangames usually have tile based movement. I’ll try to fix this issue with my movement and once I do, I’ll post the code in here. Or I might actually do a tutorial on free movement and edge sliding since I think people might find this helpful. Anyway, I thought people might give me some pointers, but I’ll try again in fixing this issue.

The thing is, as you can see, if you’re not familiar with free movement, the best option is to have tile based movement since you’ll spend days on trying to fix issues with the most basic thing - movement. But then again, once you figure it out, it’ll look better (at least in my opinion).

I worked with free movement to, but in my opinion it just does not fit into my Bomberman-Like-Game.
Free-Movement should actually look exactly the same, expect the player can’t stop between two tiles. And this is, IMHO, a pro, not a contra, as it would be much easier for the player to switch direction, without colliding with the half of a block…
But i guess i have to take a look at the game you posted to understand what you mean.

I just find tile based movement to have too much limitations. At the end of the day, it’s not the movement that makes the game. Anyway, just play the game and you’ll see how much more awesome it is haha.

Just looked at the game yesterday. If i am not wrong, the game is a bit slower then then other bomberman-games and i guess, thats why the free movement works pretty good. It also has some “helping-function”, so that you can easily switch direction.
It also has some tollerance, if only a small part of your body would collide with a block, it just corrects the position a bit, so that you don’t collide anymore.
Also i noticed, that if you press “DOWN” and while pressing “DOWN” you alo press “RIGHT”, it continues to walk down, until it is possible to walk right.

What i noticed is, that it is actually almost the same as tile-based movement, the only difference is, that you can STOP at any time. You can switch direction only on exact tiles, but you can always stop. So you might think about it like tile-based movement, which might make things easier, and make it possible to stop at any time.

I figured out this edge sliding thing yesterday. My movement is completely the same as the one in the NES Bomberman game. I will write a short article about it in a few days. But, the code that handles it all is this:



	public boolean isPositionValid(float x, float y){
		try{
			Map map = getMap();
			
			for(int i = 0; i < collisionPoints.size(); i++){
				int collisionTileX = (int) ((x + collisionPoints.getX(i)) / map.getTileWidth());
				int collisionTileY = (int) ((y + collisionPoints.getY(i)) / map.getTileHeight());
				if(map.isBlocked(collisionTileX, collisionTileY)){
					int tileX = getTileX();
					int tileY = getTileY();
					
					Vector2 position = map.getPositionRelativeToTile(this);
					float edgeX = map.getTileWidth() / edgeSensitivity;
					float edgeY = map.getTileHeight() / edgeSensitivity;
					
					if(face == Map.NORTH){
						if((position.x < edgeX) && (position.x > 1)){
							if(canTry[Map.WEST] && !map.isBlocked(tileX, tileY - 1)){
								moveHorizontally(-(speed * getMap().getGame().getDelta()));
							}
						}else if((position.x > -edgeX) && (position.x < 1)){
							if(canTry[Map.EAST] && !map.isBlocked(tileX, tileY - 1)){
								moveHorizontally(speed * getMap().getGame().getDelta());
							}
						}
					}
					
					if(face == Map.SOUTH){
						if((position.x < edgeX) && (position.x > 1)){
							if(canTry[Map.WEST] && !map.isBlocked(tileX, tileY + 1)){
								moveHorizontally(-(speed * getMap().getGame().getDelta()));
							}
						}else if((position.x > -edgeX) && (position.x < 1)){
							if(canTry[Map.EAST] && !map.isBlocked(tileX, tileY + 1)){
								moveHorizontally(speed * getMap().getGame().getDelta());
							}
						}
					}
					
					if(face == Map.WEST){
						if((position.y < edgeY) && (position.y > 1)){
							if(canTry[Map.NORTH] && !map.isBlocked(tileX - 1, tileY)){
								moveVertically(-(speed * getMap().getGame().getDelta()));
							}
						}else if((position.y > -edgeY) && (position.y < 1)){
							if(canTry[Map.SOUTH] && !map.isBlocked(tileX - 1, tileY)){
								moveVertically(speed * getMap().getGame().getDelta());
							}
						}
					}
					
					if(face == Map.EAST){
						if((position.y < edgeY) && (position.y > 1)){
							if(canTry[Map.NORTH] && !map.isBlocked(tileX + 1, tileY)){
								moveVertically(-(speed * getMap().getGame().getDelta()));
							}
						}else if((position.y > -edgeY) && (position.y < 1)){
							if(canTry[Map.SOUTH] && !map.isBlocked(tileX + 1, tileY)){
								moveVertically(speed * getMap().getGame().getDelta());
							}
						}
					}
					
					return false;
				}
			}
		}catch(StackOverflowError e){
			if((face == Map.WEST) || (face == Map.EAST)){
				canTry[Map.NORTH] = false;
				canTry[Map.SOUTH] = false;
			}
			
			if((face == Map.NORTH) || (face == Map.SOUTH)){
				canTry[Map.WEST] = false;
				canTry[Map.EAST] = false;
			}
			
			return false;
		}
		
		return true;
	}