Collision detection

So here we are. We have Pete wandering around on the screen, with the ability to jump, and looking pretty snazzy. We need to take the next step and have him interact with the level we created.

When it comes to collision detection, there is a vast array of different methods, concepts, and techniques that get used when making platformers. However, for what we are trying to achieve here, giving you a taster on using LibGDX to make games, we can take a simplistic approach.

The approach we are going to take is a two-phase detection. Firstly, we find out what cells Pete is currently covering, and then we work out if these cells contain the solid ground tiles. Then, we will resolve the collision by moving Pete out of the cells area.

So, to start with, we need to work out which cells Pete currently covers. We can do this by, firstly, using Pete's position and size, and then converting that to cells. We know that our level is 640x480 units big and our cells are 16x16 and, therefore, know that our grid is 40x30 cells. Given that we know Pete is of size 16x16 as well, we know, therefore, that the maximum amount of cells he could cover is four, with the minimum being one.

So let's start coding! First, we should define a class that will contain the information about the cell that we need. This will know the cell and the x and y cell coordinates that we will require. I created an inner class in GameScreen called CollisionCell. The following is the code:

private class CollisionCell {
  private final TiledMapTileLayer.Cell cell;
  private final int cellX;
  private final int cellY;

  public CollisionCell(TiledMapTileLayer.Cell cell, int cellX, int cellY) {
    this.cell = cell;
    this.cellX = cellX;
    this.cellY = cellY;
  }

  public boolean isEmpty() {
    return cell == null;
  }
}

As you can see in the preceding code, it simply holds information. The only addition is that we add an isEmpty() method, as if the cell is null, and it is a blank cell.

In our GameScreen class, let's now create a method called whichCellsDoesPeteCover(). This is essentially going to figure out which cells Pete covers. In this method, we will look at Pete's x and y position in the level, and then query the tile map for the cells that he covers. The following code shows how this is achieved:

private Array<CollisionCell> whichCellsDoesPeteCover() {
  float x = pete.getX();
  float y = pete.getY();
  Array<CollisionCell> cellsCovered = new Array<CollisionCell>();
  float cellX = x / CELL_SIZE;
  float cellY = y / CELL_SIZE;

  int bottomLeftCellX = MathUtils.floor(cellX);
  int bottomLeftCellY = MathUtils.floor(cellY);

  TiledMapTileLayer tiledMapTileLayer = (TiledMapTileLayer) tiledMap.getLayers().get(0);

  cellsCovered.add(new CollisionCell(tiledMapTileLayer.getCell(bottomLeftCellX, bottomLeftCellY), bottomLeftCellX, bottomLeftCellY));

  if (cellX % 1 != 0 && cellY % 1 != 0) {
    int topRightCellX = bottomLeftCellX + 1;
    int topRightCellY = bottomLeftCellY + 1;
    cellsCovered.add(new CollisionCell(tiledMapTileLayer.getCell(topRightCellX, topRightCellY), topRightCellX, topRightCellY));
  }

  if (cellX % 1 != 0) {
    int bottomRightCellX = bottomLeftCellX + 1;
    int bottomRightCellY = bottomLeftCellY;
    cellsCovered.add(new CollisionCell(tiledMapTileLayer.getCell(bottomRightCellX, bottomRightCellY), bottomRightCellX, bottomRightCellY));
  }

  if (cellY % 1 != 0) {
    int topLeftCellX = bottomLeftCellX;
    int topLeftCellY = bottomLeftCellY + 1;
    cellsCovered.add(new CollisionCell(tiledMapTileLayer.getCell(topLeftCellX, topLeftCellY), topLeftCellX, topLeftCellY));
  }
  return cellsCovered;
}

In the preceding code, we first get Pete's x and y position; we then divide that by the size of a cell, which is currently 16 units. This gives us a decimal value of the cell x and y in the grid.

We gain access to TiledMapTiledLayer by cheekily accessing the zero element of the layers array of the TiledMap. We can get away with this because we only have one layer. As your games get more and more complex, there will be multiple layers; here is where you can start naming the layers and requesting them by name.

Next, we floor those values to give us the bottom-left cell that Pete covers. There is a chance that this cell is the only cell Pete covers; however, more often than not, there will be more than one. That is where the next few lines of code come in; we take the modulus of the cellX and cellY values to give us the remainder. We can use this to determine if Pete overlays another cell. So, for example, if the cellX value is 3.25, then the modulus will be 0.25, and, therefore, we know that the next cell along will contain Pete. We repeat this for the y coordinate, and then if both have a remainder, we grab the top right most cell.

Finally, we return those cells. This is a good start that we know the cells. Next we need to look at them and decide how to make Pete react. Luckily, the way TiledMapLayer works is that if there isn't a tile in the map, then it will be null!

So, our next step is to filter out the array of the null values. Let's add the following method:

private Array<CollisionCell> filterOutNonTiledCells(Array<CollisionCell> cells) {
  for (Iterator<CollisionCell> iter = cells.iterator(); iter.hasNext(); ) {
    CollisionCell collisionCell = iter.next();
    if (collisionCell.isEmpty()) {
      iter.remove();
    }
  }
  return cells;
}

Here we are simply iterating over the array and ditching the null value. This means that when Pete is in open space, our array will be empty!

Now I think we are ready to handle a collision between Pete and the level!

The first part we will look at is handling Pete landing on the level. If I were you, I would add a line of code to set a starting position for Pete, so that he is out in the open and can fall on to the level. Use the setPosition() method to achieve this; it might take some trial and error, but have a play around. Initially, he should just fall to the bottom of the screen.

Next, let's start to hook up the code we have written. Create a method in GameScreen called handlePeteCollision(), and let's populate it with the following code:

private void handlePeteCollision() {
  Array<CollisionCell> peteCells = whichCellsDoesPeteCover();
  peteCells = filterOutNonTiledCells(peteCells);
  for (CollisionCell cell : peteCells) {
    float cellLevelX = cell.cellX * CELL_SIZE;
    float cellLevelY = cell.cellY * CELL_SIZE;
    Rectangle intersection = new Rectangle();
    Intersector.intersectRectangles(pete.getCollisionRectangle(), new Rectangle(cellLevelX, cellLevelY, CELL_SIZE, CELL_SIZE), intersection);
    if (intersection.getHeight() < intersection.getWidth()) {
      pete.setPosition(pete.getX(), intersection.getY() + intersection.getHeight());
      pete.landed();
    } else if (intersection.getWidth() < intersection.getHeight()) {
      if (intersection.getX() == pete.getX()) {
        pete.setPosition(intersection.getX() + intersection.getWidth(), pete.getY());
      }
      if (intersection.getX() > pete.getX()) {
        pete.setPosition(intersection.getX() - Pete.WIDTH, pete.getY());
      }
    }
  }
}

Here we call the whichCellsDoesPeteCover() method this provides us with cells Pete overlaps, we then filter these, so only cells which are part of the level are left. We are then iterating over these cells, and here we are using a new-to-us class Intersector; this class is amazing, it contains an insane amount of helper methods for working out if shapes overlap, and if they do, what the area is that they over lap by. This is really helpful for us as it will do all the heavy work for us.

So how are we using it? Well, we are using the intersectRectangles() methods that takes two rectangles that will be checked for overlapping, and then a third that is the area of the overlap. Our two rectangles are Pete's collision zone and the cell that we are currently iterating over, and we then create a third one. Next comes the interesting part, Once we have the overlap, we look to see which is bigger: the height or the width of the overlap. This is important because if we don't take that into consideration, we might end up moving Pete into a position that doesn't make sense in the game. So, we look at if the height of the overlap is less than the width, and if that is the case, then that means Pete is closer to the top or bottom of the cell than the left and right. This means you can move Pete up, the caveat here is that this only deals with Pete falling onto cells, rather than jumping up into those cells. If it is the case that the width is less than the height, we then work out if Pete is to the left or the right of the cell in question, and then move him back accordingly.

Finally, we just need to add a call to the handlePeteCollision() method to our update() method:

private void update(float delta) {
  pete.update(delta);
  stopPeteLeavingTheScreen();
  handlePeteCollision();
}

Hopefully, now when you run the project, you will have Pete jumping around your level.

Excellent! We have the beginnings of a platformer; while it won't rival Super Mario at this point, hopefully, it gives you a great starting point for your future platforming games.

Collision detection
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.106.233