Tag 20 – Kollision mit Tiles

veröffentlicht am: 20. Dezember 2012

day20Heyho,
heute ist bereits der 20. Dezember und wir wollen uns mit Kollisionen mit der Tilemap beschäftigen.
Dazu nutzen wir das bereits genutze Tutorial zur Tilemap und fügen noch einen Spieler hinzu.

Die Spielerklasse ist eine Unterklasse von Image, wie im vorherigen Tutorial beschrieben
Fürs erste bewegen wir den Spieler mit folgendem code. (dx und dy für die Richtungen werden wir später noch brauchen).

int dx=0,dy=0;
		
// move player
if(Gdx.input.isKeyPressed(Keys.LEFT)){
	dx=-1;
}
if(Gdx.input.isKeyPressed(Keys.RIGHT)){
	dx=1;
}
if(Gdx.input.isKeyPressed(Keys.DOWN)){
	dy=-1;
}
if(Gdx.input.isKeyPressed(Keys.UP)){
	dy=1;
}

player.setXY(player.getX()+dx*delta*300,player.getY()+dy*delta*300);

Als Kamera nutze ich die Orthographic Letterbox Kamera von Tag 15.
Diese soll allerdings so scrollen, dass sich der Spieler immer in der Mitte befindet.

// scroll so that the player is in the center
camera.position.x=player.getX()-player.getWidth()/2;
camera.position.y=player.getY()-player.getHeight()/2;

Zusätzlich habe ich noch eingebaut, dass die Kamera nur so weit scrollt, wie die Tilemap auch existiert.
createlayer

// do not let the camera show places, where there is no map
if(camera.position.x<400){
	camera.position.x=400;
}else if(camera.position.x>map.width*map.tileWidth-400){
	camera.position.x=map.width*map.tileWidth-400;
}
		
if(camera.position.y<240){
	camera.position.y=240;
}else if(camera.position.y>map.height*map.tileHeight-240){
	camera.position.y=map.height*map.tileHeight-240;
}

camera.update();

In Tiled erstellen wir für unsere Map noch eine neues Tileset names metatiles, dass nur ein Tile enthält. Auf einer neuen Ebene, die wir collision nennen, markieren wir mit diesen Tiles die Stellen, die unser Spieler nicht betreten darf.
(Man kann die sichtbarkeit der Ebene auf 50% setzen, damit man noch sieht, was drunter ist)
collisionlayer

Diese Kollisionsebene wollen wir natürlich nicht malen. Allerdings brauchen wir die Tiles später noch, deswegen speichern wir den Layer ab.

TiledLayer collision;
// [...]

// search the collision layer
for(int i=0;i<map.layers.size();i++){
	if(map.layers.get(i).name.equals("collision")){
		collision=map.layers.get(i);
		map.layers.remove(i); // remove the collision layer.
		break;
	}
}
// define a free tile
freeTile=collision.tiles[0][0];

Nun wollen wir, dass der Spieler hinten auch hinter Bäume oder Häuser laufen kann. Dazu legt man diese Tiles auf einen noch höheren Layer. Diesen Layer wollen wir nun erst rendern, nachdem der Spieler gerendert ist.
Dafür nutzen wir eine andere Version der render()-Methode des TiledMapRenderer, die einen Int-Array annimmt, der sagt, welche Ebenen gezeichnet werden sollen:

// render background map
tileMapRenderer.render(camera,new int[]{0,1});
		
// render player
batch.setProjectionMatrix(camera.combined);
batch.begin();
player.draw(batch, 1f);
batch.end();

// render foreground map
tileMapRenderer.render(camera,new int[]{2});

Auch wollen wir, dass der Spieler nicht außerhalb der Map läuft.

// collision with x-borders
if(player.getX()<0){
	player.setX(0);
}else if(player.getX()>map.tileWidth*map.width-player.getWidth()){
	player.setX(map.tileWidth*map.width-player.getWidth());
}
		
// collision with y-borders
if(player.getY()<0){
	player.setY(0);
}else if(player.getY()>map.tileHeight*map.height-player.getHeight()){
	player.setY(-player.getHeight()+map.tileHeight*map.height);
}

Die eigentliche Kollisionsabfrage habe ich nun dem Tutorial http://www.tonypa.pri.ee/tbw/tut05.html entnommen.

Es gibt eine Methode getMyCorners(), die überprüft, ob die Tiles Ecken des Spielers frei sind.

boolean upleft,downleft,upright,downright;
	
/**
 * calculate the corners
 * @param pX
 * @param pY
 */
public void getMyCorners(float pX,float pY){
		
	// calculate corner coordinates
	int downY=(int) Math.floor(map.height-(pY)/map.tileHeight);
	int upY=(int) Math.floor(map.height-(pY+player.getHeight())/map.tileHeight);
	int leftX=(int) Math.floor((pX)/map.tileWidth);
	int rightX=(int) Math.floor((pX+player.getWidth())/map.tileWidth);

	// check if the in the corner is a wall
	upleft=isFree(leftX,upY);
	downleft=isFree(leftX,downY);
	upright=isFree(rightX,upY);
	downright=isFree(rightX,downY);
}

Nun berechnen wir zuerst, wo der Spieler im nächsten Frame in Y-Richtung stehen würde und rufen getMyCorners() auf.
Falls die Ecken nun nicht mehr betretbar sind, müssen wir den Spieler genau an die Ecke des Tiles zurücksetzen.

// which tiles the hero will be standing on after this frame
int xtile=(int) ( (player.getX()+dx*delta*300) /map.tileWidth);
int ytile=(int) ( (player.getY()+dy*delta*300) /map.tileHeight);		
		
getMyCorners(player.getX(),player.getY()+dy*delta*300);
	
if(dy==1){
	if(upleft&&upright){
		player.setY(player.getY()+dy*delta*300);
	}else{
		player.setY( (ytile+1)*map.tileHeight-player.getHeight());
	}
}else if(dy==-1){
	if(downleft&&downright){
		player.setY(player.getY()+dy*delta*300);
	}else{
		player.setY( (ytile+1)*map.tileHeight+1);
	}
}

Dann das ganze nochmal in X-Richtung:

getMyCorners(player.getX()+dx*delta*300,player.getY());
				
if(dx==-1){
	if(downleft && upleft){
		player.setX(player.getX()+dx*delta*300);
	}else{
		player.setX( (xtile+1)*map.tileWidth);
	}
}else if(dx==1){
	if(downright && upright){
		player.setX(player.getX()+dx*delta*300);
	}else{
		player.setX( (xtile+1)*map.tileWidth-player.getWidth()-1);
	}
}

Und fertig ist unsere Kollisionsabfrage. Wenn man das ganze erstmal zum Laufen gebracht hat, scheint es im Nachhinein ziemlich einfach.
Diese Art von Kollisionsüberprüfung funktioniert allerdings nur, wenn der Spieler kleiner ist als die Tiles. Wenn er größer wäre, müsste man alle Tiles überprüfen, die er berührt und ihn wieder dementsprechend zurücksetzen.

Falls du einen guten Code für beliebig große Spieler (oder jedenfalls zwei Tile-hohe wie bei Legend of Zelda the minish cap) kennst, schreib doch ein Kommentar!

geposted in libgdx

12 Antworten zu “Tag 20 – Kollision mit Tiles”

  1. Jerry sagt:

    Hi, is this code complete ? Can you help me and write code, whitch begins „search the collision layer..“ ? And can you describe function isFree ? Thank you very much

    • bitowl sagt:

      hi,

      first I define a tile that is free by having it at a fixed placed in the map (top left):

      
      FREE_TILE=collisionL.tiles[0][0];
      

      In isFree() I simply check, if this tile is the same as the free tile:

      
      public boolean isFree(int pX,int pY){
        if(collisionL.tiles[pY][pX]==FREE_TILE){
           return true;
        }else{
           return falsE;
        }
      }
      

      I do not understand what you mean with „whitch begins “search the collision layer..”“. If you can clarify that I maybe can help you further.

      greetings
      bitowl

  2. Jerry sagt:

    Thank you very much, I have to explain to you my first question – In the fourth box with the code I see:
    TiledLayer collision;
    // […]

    // search the collision layer
    for(int i=0;i

    It is all. But I will try it alone. Thanks

  3. Brent sagt:

    Hi Bitowl, these tutorials are awesome, thanks. Is this code complete? I was wondering if the ints „dx“ and „dy“ are simply declared as such, or are they being multiplied with delta in an update method somewhere? Does libgdx/java interpret those as directionX and directionY natively?

    • Brent sagt:

      Sorry, saw what you did now. So you’ve just got all this code out in the classes, or is any of it under an update method or something similar?

      • bitowl sagt:

        at the moment all of this code is in the IngameScreen class. (most of it in the render()-method)
        You could put the code which is dealing with maps into an own class (I did this for a small game I created in one day with libgdx as a christmas present for a friend).

        greetings
        bitowl

  4. angel sagt:

    in

    // define a free tile
    freeTile=collision.tiles[0][0];

    which type is „freeTile“ ?

  5. Tif sagt:

    Hi. Im having a hard time reviewing the code as i dont know where to put the others. Can i have your source code? 😀

  6. William sagt:

    Excellent! Pixel perfect tile collision feels great, and the algo is very easy to understand.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.