Implementing pixel-perfect collision - Libgdx - java

I just started learning game programming through the libgdx framework and I am at the collision detection stage of development. I made a pretty simple game that has some basic bounding-box collision detection system. However, I want to implement pixel-perfect collision for accuracy.
I will be showing snippets of code that I think are important to help you understand what is going on.
A two-dimensional array is created to define the position of the tiles on the screen:
int[][] map = {
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,1,1,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,1,1,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,1,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};
This create() method from my main game class adds a player and three entities to an ArrayList of type Entity.
#Override
public void create () {
batch = new SpriteBatch();
tileTexture = new Texture("block.png");
screenWidth = Gdx.graphics.getWidth();
screenHeight = Gdx.graphics.getHeight();
// add some entities including a player
entities.add(new Player(this, 100, 150, 20, 20, 120.0f, new Texture("player.png")));
entities.add(new Entity(this, 50, 150, 20, 20, 120.0f, new Texture("enemy.png")));
entities.add(new Entity(this, 200, 200, 20, 20, 120.0f, new Texture("enemy.png")));
entities.add(new Entity(this, 180, 50, 20, 20, 120.0f, new Texture("enemy.png")));
}
The render() method draws the tile map and the entities when the game is run. Another class named Entity holds the data of a particular entity (can be a block/player). The data can be the x and y position of that entity.
// draw tile map
// go over each row bottom to top
for(int y = 0; y < mapHeight; y++) {
// go over each column left to right
for(int x = 0; x < mapWidth; x++) {
// tile
if(map[x][y] == 1) {
batch.draw(tileTexture, x * tileSize, y * tileSize);
}
}
}
// draw all entities
for(int i = entities.size() - 1; i >= 0; i--) {
Entity e = entities.get(i);
batch.draw(e.texture, e.x, e.y);
}
This produces:
To check for collision when the player (green block) moves, I have two methods that check if the player is colliding with an entity or a tile.
The tileCollision() method:
public boolean tileCollision(Entity e, Direction direction, float newX, float newY) {
boolean collision = false;
// determine affected tiles
int x1 = (int) Math.floor(Math.min(e.x, newX) / tileSize);
int y1 = (int) Math.floor(Math.min(e.y, newY) / tileSize);
int x2 = (int) Math.floor((Math.max(e.x, newX) + e.width - 0.1f) / tileSize);
int y2 = (int) Math.floor((Math.max(e.y, newY) + e.height - 0.1f) / tileSize);
// tile checks
for(int x = x1; x <= x2; x++) {
for(int y = y1; y <= y2; y++) {
if(map[x][y] == 1) {
collision = true;
e.tileCollision(map[x][y], x, y, newX, newY, direction);
}
}
}
return collision;
}
The line of code e.tileCollision(map[x][y], x, y, newX, newY, direction); in this method calls the tileCollision() method in the Entity class that prints the position of where the block collides with a tile.
To check collisions between entities, we have this method:
public boolean entityCollision(Entity e1, Direction direction, float newX, float newY) {
boolean collision = false;
for(int i = 0; i < entities.size(); i++) {
Entity e2 = entities.get(i);
// we don't want to check for collisions between the same entity
if(e1 != e2) {
// axis aligned rectangle rectangle collision detection
if(newX < e2.x + e2.width && e2.x < newX + e1.width &&
newY < e2.y + e2.height && e2.y < newY + e1.height) {
collision = true;
e1.entityCollision(e2, newX, newY, direction);
}
}
}
return collision;
}
NOTE: The green block can move across an entity but cannot go through tiles. This is because the line e1.entityCollision(e2, newX, newY, direction); calls the entityCollision() method in the class Entity that lets the green block move.
This type of collision detection seems basic and inefficient (time complexity of O(n^2)).
How do I implement pixel-perfect collision in this context?
Additional question: If I want to improve the efficiency, what collision detection system can I use to eliminate unnecessary checks?

Related

Processing - Add hitTest and mousePressed to an Animation with 100 balls

I want to create an ArrayList of ball objects, which should be in a loop until there are 100 pieces.
Now my problem: I must implement a function hitTest, so that when you click on a ball it gets removed. In the same position, there should appear two balls then, which go into a different direction.
Can someone help me? I am so lost...
Here's my code so far:
Ball b;
ArrayList<Ball> balls;
void setup() {
size(800, 800);
balls = new ArrayList<Ball>();
for (int i = 0; i<100; i++) {
drawBall();
}
}
void draw() {
background(255);
//b.update();
for (int i= 0; i<balls.size(); i++) {
balls.get(i).update();
}
}
void drawBall() {
Ball b = new Ball();
balls.add(b);
}
class Ball {
private float x;
private float y;
private float ballSize;
private float dirX;
private float dirY;
private boolean moving = true;
Ball() {
this.x = width/2;
this.y = height/2;
this.ballSize = random(10.0, 30.0);
this.dirX = random(-3.0, 3.0);
this.dirY = random(-3.0, 3.0);
if (this.dirX<1.0 && this.dirX>1.0) //1 statt -1 macht zufälliger { this.dirX = 1.0; }
if (this.dirY<1.0 && this.dirY>1.0) {
this.dirY = 1.0;
}
}
public void update() {
stroke(255);
fill(random(255), random(255), random(255), random(255));
ellipse( this.x, this.y, this.ballSize, this.ballSize);
if (this.moving == true) {
this.x += this.dirX;
this.y += this.dirY;
}
if (this.x+ this.ballSize/2> width ||this.x- this.ballSize/2<0) {
this.dirX= dirX*-1;
}
if (this.y+ this.ballSize/2> height ||this.y- this.ballSize/2<0) {
this.dirY= dirY*-1;
}
}
}
Break your problem down into smaller, simpler steps.
e.g.
when you click on a ball, it gets removed. In the same position, there should appear two balls then, which go into a different direction.
when you click on a ball: you can mix the dist() function (to check if the distance between the mouse and a ball is smaller then the radius) with mouseClicked() (or mousePressed() / mouseReleased())
it gets removed: you already called balls.add(). Similarly you can call balls.remove() passing the ball object or index to remove (depending on the case)
same position: you need to remember (store the coordinates) of the ball that was clicked to add two balls at the same position
different direction: you already do that in the Ball() constructor: you can apply the same logic on each new ball.
Here's a basic sketch to illustrate point 1, using dist():
void draw(){
background(255);
int ballX = 50;
int ballY = 50;
int ballRadius = 35;
if(dist(ballX, ballY, mouseX, mouseY) < ballRadius){
fill(0,192,0);
}else{
fill(192,0,0);
}
ellipse(ballX,ballY, ballRadius * 2, ballRadius * 2);
}
Paste this in a new sketch, run it and you should get the hang of using dist() in the context of your problem.
Regarding points 2,3,4 here's a modified version of your sketch with comments and a slightly different approach: instead of removing a ball to add a new one at the exact location with a different direction, simply randomise the direction. Visually it will look similar to a new ball (except the random size/colour). With the clicked ball being re-used, only a second one is added:
Ball b;
ArrayList<Ball> balls;
void setup() {
size(800, 800);
balls = new ArrayList<Ball>();
for (int i = 0; i<100; i++) {
drawBall();
}
}
void draw() {
background(255);
//b.update();
for (int i= 0; i<balls.size(); i++) {
// pass the mouse coordinates to each ball to check if it's hovered or not
balls.get(i).update(mouseX, mouseY);
}
}
// on mouse pressed
void mousePressed(){
for (int i= 0; i<balls.size(); i++) {
// make current ball reusable in this loop
Ball ball = balls.get(i);
// if ball is hovered
if(ball.isHovered){
// randomize direction of current ball
ball.setRandomDirection();
// add a new ball from the current location
balls.add(ball.copy());
}
}
}
void drawBall() {
Ball b = new Ball();
balls.add(b);
}
class Ball {
private float x;
private float y;
private float ballSize;
private float dirX;
private float dirY;
private boolean moving = true;
private color fillColor;
// property to keep track if the ball is hovered or not
private boolean isHovered;
Ball() {
this.x = width/2;
this.y = height/2;
this.ballSize = random(10.0, 30.0);
this.setRandomDirection();
this.fillColor = color(random(255), random(255), random(255), random(255));
}
// extract random direction calls into a re-usable function (handy for click / collision)
void setRandomDirection(){
this.dirX = random(-3.0, 3.0);
this.dirY = random(-3.0, 3.0);
if (this.dirX<1.0 && this.dirX>1.0) { //1 statt -1 macht zufälliger { this.dirX = 1.0; }
if (this.dirY<1.0 && this.dirY>1.0) {
this.dirY = 1.0;
}
}
}
public void update(int x, int y) {
// euclidean distance between this ball's coordinates a given x y position (e.g. mouse)
isHovered = dist(this.x, this.y, x, y) < this.ballSize / 2;
// optional: use stroke color to visually display highlighted ball
if(isHovered){
stroke(0);
}else{
stroke(255);
}
fill(fillColor);
ellipse( this.x, this.y, this.ballSize, this.ballSize);
if (this.moving == true) {
this.x += this.dirX;
this.y += this.dirY;
}
if (this.x + this.ballSize / 2 > width ||
this.x - this.ballSize / 2 < 0) {
this.dirX= dirX*-1;
}
if (this.y + this.ballSize / 2 > height ||
this.y - this.ballSize / 2 < 0) {
this.dirY= dirY*-1;
}
}
// utility function: simply copies this ball's x,y position to the new one
Ball copy(){
Ball clone = new Ball();
clone.x = this.x;
clone.y = this.y;
return clone;
}
}
The copy() method is flexible enough that it's ease to remove one ball to add two more if that is an absolute must. For example:
// on mouse pressed
void mousePressed(){
for (int i= 0; i<balls.size(); i++) {
// make current ball reusable in this loop
Ball ball = balls.get(i);
// if ball is hovered
if(ball.isHovered){
// add two new balls from the current location
balls.add(ball.copy());
balls.add(ball.copy());
// remove ball
balls.remove(i);
}
}
}

LibGDX - Tilemap Wall Collision Detection issues

I cribbed some code from another web tutorial (SuperKoalio) about detecting a collision between my player, and tiles within a tilemap layer called "walls". The game is an RPG (Gauntlet) style game. The collision detection generally works but occasionally, the player will pass through a wall and appear much further away on the map, or suddenly outside the bounds of the map. Its almost like there are gaps.
The code I have used appears to check the tiles immediately to the left and right of the player, and then performs another check on the tiles above and below the player. I think the idea is fundamentally ok, except that this doesnt take into consideration diagonal movement, which I believe might be causing the player to pass through walls.
The code inside my detection routine is as follows:
public void checkCollisions(float delta) {
////////////////////////////////////////////////////////////////
//collision detection - X
int startX, startY, endX, endY;
startX = (int) (position.x);
endX = (int) (position.x + 1);
startY = (int) (position.y);
endY = (int) (position.y);
playerRect = rectPool.obtain();
playerRect.set(position.x, position.y, 1.0f, 1.0f);
getWalls(startX, startY, endX, endY, walls);
for (Rectangle wall : walls)
{
//Rectangle rectangle = rectangleObject.getRectangle();
if (playerRect.overlaps(wall) && velocity.x != 0) {
velocity.x = 0;
if(facingDir == 3) {
position.x = wall.x+1.1f;
} else if(facingDir == 4) {
position.x = wall.x-1.1f;
}
break;
}
// collision happened
//break;
}
playerRect.x = position.x;
// if the player is moving upwards, check the tiles to the top of it's
// top bounding box edge, otherwise check the ones to the bottom
if (velocity.y > 0)
{
startY = (int) (position.y);
endY = (int) (position.y + 1.1f);
}
else
{
startY = endY = (int) (position.y);
}
startX = (int) (position.x);
endX = (int) (position.x + 1);
getWalls(startX, startY, endX, endY, walls);
//playerRect.y = velocity.y;
for (Rectangle wall : walls)
{
if (playerRect.overlaps(wall) && velocity.y != 0)
{
velocity.y = 0;
//velocity.x = 0;
if(facingDir == 1) {
position.y = wall.y - 1.1f;
} else if(facingDir == 2) {
position.y = wall.y + 1.1f;
}
break;
}
break;
}
playerRect.y = position.y;
rectPool.free(playerRect);
velocity.x = 0;
velocity.y = 0;
}
And the "getWalls()" method which loads the walls layer is as follows:
/**
* Get the walls of the map for collision detection between the player and the walls, so that
* the player cannot pass through.
* #param startX
* #param startY
* #param endX
* #param endY
* #param walls
*/
private void getWalls(int startX, int startY, int endX, int endY, Array<Rectangle> walls)
{
TiledMapTileLayer layer = (TiledMapTileLayer) MapRenderer.map.getLayers().get("walls");
layer.setVisible(false);
rectPool.freeAll(walls);
walls.clear();
for (int y = startY; y <= endY; y++)
{
for (int x = startX; x <= endX; x++)
{
TiledMapTileLayer.Cell cell = layer.getCell(x, y);
if (cell != null)
{
Rectangle rect = rectPool.obtain();
rect.set(x, y, 1, 1);
walls.add(rect);
}
}
}
}
Does anyone have any ideas as to why this routine is not very robust?
I really want to be able to achieve this as simply and effectively as possible. The same technique would also need to be applied to enemies, to stop them from also passing through walls.
Surely there must be a better and more reliable way of doing this, or is it just that there are faults/gaps in my code - for example if I it a wall in a diagonal direction?

Java Test if circles overlap

I have a program to draw 20 circles w/ random rad x and y. After, I need to test which circles are overlapping and if they are, set them cyan, if not set them black. heres what I have so far, the problem, is it always sets it to cyan overlapping or not.
public class AppletP5 extends JApplet{
MyCircle[] circle = new MyCircle[20];
public AppletP5(){
for(int i = 0; i<20; i++){
int x0= (int) (Math.random()*500);
int y0= (int) (Math.random()*500);
int rad0= (int) (30 + Math.random()*70);
circle[i] = new MyCircle(x0,y0,rad0);
}
}
public void paint(Graphics g){
for(int i = 0; i<20; i++){
if(circle[i].isOverlapping(circle) == true){
g.setColor(Color.CYAN);
g.drawOval(circle[i].x,circle[i].y,circle[i].rad*2,circle[i].rad*2);
} else if(circle[i].isOverlapping(circle) == false){
g.setColor(Color.BLACK);
g.drawOval(circle[i].x,circle[i].y,circle[i].rad*2,circle[i].rad*2);
}
}
}
}
public class MyCircle {
protected int x, y, rad;
public MyCircle(int x, int y, int rad){
this.x = x;
this.y = y;
this.rad = rad;
}
public boolean isOverlapping(MyCircle[] circles){
for(MyCircle c : circles){
if(Math.pow(c.rad - rad , 2) >= Math.sqrt(Math.pow(x - c.x, 2) + Math.pow(y - c.y , 2))){
return true;
}
}
return false;
}
}
You need to exclude the current Circle from the comparison, since a circle trivially overlaps itself.
You could go with an easy check as long as you just have one instance of each Circle:
for (MyCirlce c : circles) {
if (c != this && ...)
In addition you are checking if the difference of radii between two circles squared by two is greater than the distance of the two centres? Shouldn't you check for the sum of the radii, eg:
r1 + r2 <= distance(c1, c2)
isOverLapping is incorrect implemented.
Two circles intersect, if the distance between their centers is smaller than the sum of their radii. So:
int radSum = c.rad + rad;
int radDif = c.rad - rad;
int distX = c.x - x + radDif;
int distY = c.y - y + radDif;
if(radSum * radSum < distX * distX + distY * distY)
return true;
Apart from that you'll have to ensure you don't compare a circle with itself. And finally: Math.pow is rather costly, so replace it with the simpler version, if you only want the square of a number.

Detect Collision of multiple BufferedImages Java

I am making a 2d rpg game in java and I have run into a problem. I can make the player move around the stage and I have rocks, trees, walls, etc. on the stage as well. I don't know how to detect the collision and make it to where the player can't move through the object. The code that reads map file and draws image on the canvas is as follows:
public void loadLevel(BufferedImage levelImage){
tiles = new int[levelImage.getWidth()][levelImage.getHeight()];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
Color c = new Color(levelImage.getRGB(x, y));
String h = String.format("%02x%02x%02x", c.getRed(),c.getGreen(),c.getBlue());
switch(h){
case "00ff00"://GRASS Tile - 1
tiles[x][y] = 1;
break;
case "808080"://Stone -2
tiles[x][y] = 2;
break;
case "894627"://Dirt -3
tiles[x][y] = 3;
break;
case "404040"://Rock on Grass -4
tiles[x][y] = 4;
break;
case "00b700"://Tree -5
tiles[x][y] = 5;
break;
case"000000"://Wall -6
tiles[x][y] = 6;
break;
case "cccccc"://Rock on stone -7
tiles[x][y] = 7;
break;
default:
tiles[x][y] = 1;
System.out.println(h);
break;
}
}
}
}
And the player class is as follows:
public class Player {
private int x,y;
public int locx,locy;
private Rectangle playerR;
private ImageManager im;
public boolean up =false,dn = false,lt=false,rt=false,moving = false,canMove = true;
private final int SPEED =2;
public Player(int x, int y, ImageManager im){
this.x = x;
this.y = y;
this.im = im;
locx = x;
locy = y;
playerR = new Rectangle(x,y,16,16);
}
public void tick(){
if (up) {
if(canMove){
y -= SPEED;
locx = x;
locy = y;
playerR.setLocation(locx, locy);
moving = true;
}
else{
y += 1;
canMove=true;
}
}
if (dn) {
y +=SPEED;
locx = x;
locy = y;
moving = true;
}
}
if (lt) {
x -= SPEED;
locx = x;
locy = y;
moving = true;
}
if (rt) {
x+=SPEED;
locx = x;
locy = y;
moving = true;
}
}
if(moving){
System.out.println("PLAYER\tX:"+locx+" Y:"+locy);
moving = false;
}
}
public void render(Graphics g){
g.drawImage(im.player, x, y, Game.TILESIZE*Game.SCALE, Game.TILESIZE*Game.SCALE, null);
}
}
I don't really know how to do collision, but i googled it and people said to make a rectangle for the player and all the objects that the player should collide with, and every time the player moves, move the player's rectangle. Is this the right way to do this?
EDIT EDIT EDIT EDIT
code for when collision is true:
if (rt) {
x+=SPEED;
locx = x;
locy = y;
playerR.setLocation(locx, locy);
for(int i = 0;i<Level.collisions.size();i++){
if(intersects(playerR,Level.collisions.get(i))==true){
x-=SPEED;
locx = x;
playerR.setLocation(locx, locy);
}
}
moving = true;
}
And the intersects method is as follows:
private boolean intersects(Rectangle r1, Rectangle r2){
return r1.intersects(r2);
}
I'm going to focus on your tick method since that is where most of this logic is going. There are a couple changes here. Most notably, we only move the rectangle before checking for collisions. Then loop through all the collideable objects in your level. Once one is found, we reset our x and y and break out of the loop (no sense in looking at any of the other objects since we already found the one we collided with). Then we update our player position. By doing it this way, I centralized the code so it is not being repeated. If you ever see yourself repeating code, there is a pretty good chance that it can be pulled out to a common place, or to a method.
public void tick() {
if (up) {
y -= SPEED;
} else if (dn) {
y += SPEED;
} else if (lt) {
x -= SPEED;
} else if (rt) {
x += SPEED;
}
playerR.setLocation(x, y);
for (Rectangle collideable : Level.collisions) {
if (intersects(playerR, collideable)) {
x = locx;
y = locy;
playerR.setLocation(x, y);
break;
}
}
locx = x;
locy = y;
}
There are different ways to do that. As you talk about a "rpg" i think your view is Isometric (45° top down).
I would do the collision detection in pure 90° top down, as it is easier and, imho, more realistic.
We have 2 possibilities:
Move your Player to the next position. If there is a collision, reset his position.
Calculate the next position, if there would be a collision don't move.
If you want to have a "gliding" collision response, you have to check in which axis the collision will happen, and stop / reset movement for this axis only.
To have a more efficient collision detection only check near objects, which will possibly collide.
Do this by comparing a squared "dangerRadius" with the squared distance between your player and the object:
if ((player.x - object.x)² + (player.y - object.y)² <= dangerRadius²)
// Check for intersection
This will sort out most of the objects by using a simple calculation of:
2 subtractions
1 addition
3 multiplications (the ²)
1 compare (<=)
In your game you should sepparate the logic and the view. So basicly you don't detect, if the two images overlapp, but you check, if the objects in your logic overlap. Then you draw the images on the right position.
Hope this helps.
EDIT: Important: If you update your character, depending on the time between the last and this frame (1/FPS) you have to limit the max timestep. Why? Because if for some reason (maybe slow device?) the FPS are really low, it is possible, that the character moves verry far between 2 frames and for that he could go through an object in 1 frame.
Also if you simply reset the movement on collision or just don't move the distance between you and the object could be big for low FPS. For normal FPS and not to high movementspeed this won't happen/ be noticeable.
I personally am fairly new to Java, though I have worked with C# in the past. I am making a similar game, and for collision detection I just check the locations of the player and objects:
if (z.gettileX() == p.gettileX()){
if (z.gettileY() == p.gettileY()){
System.out.println("Collision!");
}
}
If the player (p) has equal X coordinates and Y coordinates to z(the bad guy), it will send this message and confirm that the two have, in fact, collided. If you can make it inherent in the actual class behind z to check if the coordinates a equal, you can create an unlimited number of in-game objects that detect collision and react in the same way, i.e. walls.
This is probably what your looking for. I've made this class spicificly for collision of multiple objects and for individual side collisions.
abstract class Entity {
private Line2D topLine;
private Line2D bottomLine;
private Line2D leftLine;
private Line2D rightLine;
private Rectangle rectangle;
private Entity entity;
protected boolean top;
protected boolean bottom;
protected boolean left;
protected boolean right;
protected int x;
protected int y;
protected int width;
protected int height;
public Entity(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
updateLinesAndRects();
}
public void updateLinesAndRects() {
topLine = new Line(x + 1, y, width - 2, 0);
bottomLine = new Line(x + 1, y + height, width - 2, height);
leftLine = new Line(x, y + 1, 0, height - 2);
rightLine = new Line(x + width, y + 1, 0, height - 2);
rectangle = new Rectangle(x, y, width, height)
}
public void setCollision(Entity entity) {
this.entity = entity;
top = isColliding(new Line2D[]{topLine, bottomLine, leftLine, rightLine});
bottom = isColliding(new Line2D[]{bottomLine, topLine, leftLine, rightLine});
left = isColliding(new Line2D[]{leftLine, topLine, bottomLine, rightLine});
right = isColliding(new Line2D[]{rightLine, topLine, bottomLine, leftLine});
}
public void updateBounds() {
if(top) y = entity.y + entity.height;
if(bottom) y = entity.y - height;
if(left) x = entity.x + entity.width;
if(right) x = entity.x - width;
}
public boolean isColliding() {
return rectangle.intersects(entity.rect);
}
private boolean isLinesColliding(Line2D[] lines) {
Rectangle rect = entity.getRectangle();
return lines[0].intersects(rect) && !lines[1].intersects(rect) && !lines[2].intersects(rect) && !lines[3].intersects(rect);
}
private Line2D line(float x, float y, float width, float height) {
return new Line2D(new Point2D.Float(x, y), new Point2D.Float(x + width, x + height));
}
public Rectangle getRectangle() {
return rectangle;
}
}
Example:
class Player extends Entity{
Entity[] entities;
public Player(int x, int y, int width, int height) {
super(x, y, width, height);
}
public void update() {
updateLinesAndRects();
for(Entity entity : entities) {
setCollision(entity);
if(top) system.out.println("player is colliding from the top!");
if(isColliding()) system.out.println("player is colliding!");
updateBounds(); // updates the collision bounds for the player from the entities when colliding.
}
}
public void setEntities(Entity[] entities) {
this.entities = entities;
}
}

2D Overhead Tile Generation

I have this code right now:
private void generateLevel() {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
tiles[x + y * width] = random.nextInt(4);
}
}
}
Which allows this method to work:
public Tile getTile(int x, int y) {
if (x < 0 || y < 0 || x >= width || y >= height) return Tile.voidTile;
if (tiles[x + y * width] == 0) return Tile.grass;
if (tiles[x + y * width] == 1) return Tile.stone;
else return Tile.water;
//return Tile.voidTile;
}
Which returns this result:
What I want is smooth, rounded islands with random stone deposits here and there. Would perlin noise be overkill for this? I figured I could just generate a tile, check the tile id next to it and then place it down if the tile adjacent is of the same type. But that would create endless expanses of the same tile. Any help?
The first step I would take is to create objects out of anything on your domain level:
public class Island {
private Point2D center = null;
private int radius = 0;
private List<Deposit> deposits = new ArrayList<Deposit>();
public Island(Point2D center, int radius) {
this.center = center;
this.radius = radius;
}
public void generateDeposits(int numDeposits) {
for (int i = 0; i < numDeposits; i++) {
// TODO: I leave it to you to find an x and y inside the island's
// boundary.
int x = getIntInsideCircle(center, radius);
int y = getIntInsideCircle(center, radius);
if (!depositInLocation(x, y)) {
deposits.add(new StoneDeposit(x, y));
} else {
i--; // TODO: This code could potentially go on forever,
// if we keep generating locations that have been used,
// but I'll leave this for you to handle.
}
}
}
}
public abstract class Deposit {
private Point2D location = null;
public Deposit(Point2D location) {
this.location = location;
}
}
public class StoneDeposit extends Deposit {
// TODO: You can fill this with StoneDeposit specifics.
}
Now we have code that will generate an island with all it's random deposits. The only thing that is left to do is actually place these islands. I'm going to keep it simple and only add one to the map but I'm sure you can figure out how to add more than one (I'll leave some comments with my ideas):
public class Map {
private final int WIDTH = 1000;
private final int HEIGHT = 1000;
private List<Island> islands = new ArrayList<Island>();
public void generate() {
// TODO: If you want to make more, make a for loop.
int radius = 100;
Island island = new Island(new Point2D(WIDTH / 2, HEIGHT / 2), radius);
// TODO: If you are going to add more, then you can't simply add them
// all willy-nilly. You are going to have to check if the islands collide
// and, if they do, find a way to handle that.
// You could let them collide and create a mountain range where they do, or,
// you could try to place the next island in a different position (similar
// to what we used above placing deposits, but both situations require
// code a bit better than what I've included).
islands.add(island);
}
}
Ok, now we've got all the data we need. This brings us to the final point of actually drawing it onto the screen using tiles. I'm not too experienced with this subject, so this might be inefficient, but it should serve as a launching point.
Some of the functions I've generalized (like drawTile(int x, int y, TileType type) because I don't know how you are drawing the tiles to the screen).
// Generate our data.
Map map = new Map();
map.generate();
// Draw to the screen.
// 1. Fill the entire screen with water.
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
drawTile(x, y, Type.WATER);
}
}
// 2. Draw the islands.
// We're going to use this algorithm to draw the circle:
// http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
for (Island island : map.getIslands()) {
int f = 1 - island.getRadius();
int ddF_x = 1;
int ddF_y = -2 * island.getRadius();
int x = 0;
int y = 0;
Point2D center = island.getCenter();
int radius = island.getRadius();
drawTile(center.getX(), center.getY() + radius, TileType.LAND);
drawTile(center.getX(), center.getY() - radius, TileType.LAND);
drawTile(center.getX() + radius, center.getY(), TileType.LAND);
drawTile(center.getX() - radius, center.getY(), TileType.LAND);
while(x < y) {
if(f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
drawTile(center.getX() + x, center.getY() + y, TileType.LAND);
drawTile(center.getX() - x, center.getY() + y, TileType.LAND);
drawTile(center.getX() + x, center.getY() - y, TileType.LAND);
drawTile(center.getX() - x, center.getY() - y, TileType.LAND);
drawTile(center.getX() + y, center.getY() + x, TileType.LAND);
drawTile(center.getX() - y, center.getY() + x, TileType.LAND);
drawTile(center.getX() + y, center.getY() - x, TileType.LAND);
drawTile(center.getX() - y, center.getY() - x, TileType.LAND);
}
// TODO: Now you have to figure out how to fill in the guts of the island.
}
// 3. Draw the deposits.
// TODO: I'll leave this one for you.
So that's basically it - it's not too bad.
You could always go a step further and add tiles that are mostly water but with some shore line. In order to do that you would have to check if the tile you are drawing is an edge or a corner:
+-+-+-+
|1|2|3|
+-+-+-+
+-+-+-+-+
|4|5|6|7|
+-+-+-+-+
+-+-+-+
|8|9|0|
+-+-+-+
Here you can see 2,9, and 4 are all edge tiles. 1, 3, 8, 0 are all corner tiles, and 5 is an interior tile. When you recognize a tile as being a corner then you have to pick all the attached water tiles and draw them as "coast" tiles:
+-+-+
| |x|
+-+-+-+-+
|x|1|2|3|
+-+-+-+-+
+-+-+-+-+
|4|5|6|7|
+-+-+-+-+
+-+-+-+
|8|9|0|
+-+-+-+
There all the x's would be coastal tiles.
I hope this helps a bit.

Categories