Java 2D Collision [duplicate] - java

This question already has an answer here:
Closed 11 years ago.
Possible Duplicate:
Java 2D Collision?
Hey guys I have another post regarding this which has just gone dead so I thought i'd try get some fresh answers regarding this.
I'm trying to get my collision detection to work. My collision detection works fine when i come side on to my map blocks, and fine when I only hold up or down and hit a block from the top or bottom. But when i hold up or down aswell as right or left and hit a block from the top or bottom it will go off in funny directions.
Small Video of me demonstrating (Sorry if it's not clear): http://www.youtube.com/watch?v=6ILccRtw8ME Hopefully this helps alot.
If you didn't watch the video here is a picture of the map's I will be using:
Here is my current collision code:
public void checkCollision() {
Rectangle player_rectangle = new Rectangle(player.getX(),player.getY(),32,32);
for(Wall wall : walls) {
Rectangle wall_rectangle = new Rectangle(wall.getX(), wall.getY(), 32,32);
if (player_rectangle.intersects(wall_rectangle)) {
Rectangle intersection = (Rectangle) player_rectangle.createIntersection(wall_rectangle);
if (player.xspeed < 0 && player.x >= intersection.x) {
player.x += intersection.getWidth();
} else {
if (player.xspeed > 0 && player.x <= intersection.x) {
player.x -= intersection.getWidth();
} else {
if (player.yspeed < 0 && player.y >= intersection.y) {
player.y += intersection.getHeight();
}else {
if (player.yspeed > 0 && player.y <= intersection.y) {
player.y -= intersection.getHeight();
}
}
}
}
Print(Integer.toString(intersection.width) + ", " + Integer.toString(intersection.height));
}
}
}
All help will be appreciate, thanks.

This is my first attempt at answering a question on StackOverflow so please be patient with me. I will try and structure this answer by first diagnosing the problem, finding the symptoms that may cause your problem and lastly, present a solution and see if it works with you.
First, let me format your code with the correct level of indentation so that we have a more clear picture of what is going on.
public void checkCollision() {
Rectangle player_rectangle = new Rectangle(player.getX(),
player.getY(), 32, 32);
for (Wall wall : walls) {
Rectangle wall_rectangle = new Rectangle(wall.getX(), wall.getY(),
32, 32);
if (player_rectangle.intersects(wall_rectangle)) {
Rectangle intersection = (Rectangle) player_rectangle
.createIntersection(wall_rectangle);
if (player.xspeed < 0 && player.x >= intersection.x) {
player.x += intersection.getWidth();
} else {
if (player.xspeed > 0 && player.x <= intersection.x) {
player.x -= intersection.getWidth();
} else {
if (player.yspeed < 0 && player.y >= intersection.y) {
player.y += intersection.getHeight();
} else {
if (player.yspeed > 0 && player.y <= intersection.y) {
player.y -= intersection.getHeight();
}
}
}
}
Print(Integer.toString(intersection.width) + ", "
+ Integer.toString(intersection.height));
}
}
}
Now that we have done this, it becomes more clear what you are doing. The first thing I noticed is that you are nesting your conditional statement. To clarify, you are doing this:
if (condition){
// do something
else{
if(anotherCondition){
// do something
else{
//etc
}
}
}
}
What you should be doing is using multiple conditional statements in a row like this:
if (condition){
//do something
}
if (anotherCondition){
//do something else
}
//etc
This is because if you have two case, such as you being in the bottom left corner, you will need to execute the conditions for the intersection to the left as well as intersection to the bottom. However, this is irrelevant for your example since you go through each wall individually.
So why is this important?
Well, it seems to me that if the first condition is not satisfied, then all the other conditions will not execute. If player.xspeed < 0 && player.x >= intersection.x returns true, then even if the other 3 statements nested in it returns true, they will not be executed because it relies on that first statement being false. Therefore when you go to an edge, where two or more of the cases would be true, you only get one of them executed.
Therefore my proposed solution, based on all the above is this:
public void checkCollision() {
Rectangle player_rectangle = new Rectangle(player.getX(),
player.getY(), 32, 32);
for (Wall wall : walls) {
Rectangle wall_rectangle = new Rectangle(wall.getX(), wall.getY(),
32, 32);
if (player_rectangle.intersects(wall_rectangle)) {
Rectangle intersection = (Rectangle) player_rectangle
.createIntersection(wall_rectangle);
if (player.xspeed < 0 && player.x >= intersection.x) {
player.x += intersection.getWidth();
}
if (player.xspeed > 0 && player.x <= intersection.x) {
player.x -= intersection.getWidth();
}
if (player.yspeed < 0 && player.y >= intersection.y) {
player.y += intersection.getHeight();
}
if (player.yspeed > 0 && player.y <= intersection.y) {
player.y -= intersection.getHeight();
}
Print(Integer.toString(intersection.width) + ", "
+ Integer.toString(intersection.height));
}
}
}
Try this out and see if it works. If it does not, then we can take it from there and see how to improve and fix your problem.

I suggest writing a bunch of unit tests for all the different states you can imagine that checkCollision needs to deal with.
This way, you'll:
Easily find out which specific inputs are causing the block to jump (because unit tests are short and well-contained)
Ensure that this functionality won't be broken by future changes (because unit tests are run with each build)
A sample unit test for this would look something like this:
public class CollisionTests {
#Test
public void hitFromLeftHeadOn() throws Exception {
List<Wall> walls = Lists.newArrayList(
new Wall( ... ), // south wall
new Wall( ... ), // east wall
// etc.
);
Player player = new Player(...);
// Set player to collide with the east wall head on
player.setX(...);
player.setY(...);
player.setXSpeed(...);
player.setYSpeed(...);
checkCollision();
// Now check if the checkCollision method worked properly
assertEquals("Player's x coordinate should be set to ...", ..., player.getX());
assertEquals("Player's y coordinate should be set to ...", ..., player.getY());
}
Once you've written one test, it's easy to add more. You can create various helper methods to create the walls, player, etc. so that each test case shrinks down to a few lines.
Here's a tutorial on unit testing.
As an added bonus, if you write a unit test you'll also have written an SSCCE :)

Related

Collision detection between player and tiles (2D)

I have been working on a 2D game, kind of like Terraria, and i have got to the stage where i am ready to do collision detection. I tried it myself, but it... well, its very strange. I was wondering if anybody could help me with this. I store all of my blocks in a 2D array:
Block[][] map = new Block[mw][mh];
where mw is map width and mh is map height (in terms of number of blocks). Each Block is an image that is displayed as 16x16 pixels. Here is what i attempted, i know that it is wrong but i dont really know what i need to do.
private void checkCollision() {
for(int x = -1; x <= 1; x++){
for(int y = -1; y <= 2; y++){
Rectangle obj = new Rectangle((int)Block.getXOnScreen(xblock+x), (int)Block.getYOnScreen(yblock+y), 16, 16);
try{
if(main.map[(int) (xblock+x)][(int) (yblock+y)].solid && obj.intersects(bounds()){
if(y <= -1 && velY > 0){
velY = 0;
System.out.println("Collision below");
onground = true;
}else if(y >= 2 && velY < 0){
velY = 0;
System.out.println("Collision above");
}
if(x <= -1 && velX < 0){
velX = 0;
System.out.println("Collision left");
}else if(x >= 1 && velX > 0){
velX = 0;
System.out.println("Collision right");
}
}
}catch(Exception e){}
}
}
}
I call this method every tick like so, but it doesn't collide with anything so the player just falls.
public void tick(){
xblock += velX;
yblock += velY;
velY += gravity;
checkCollision();
}
If anyone knows how to do collision detection efficiently please can you share this with me. Thanks! :)
Based on the few JavaFX/Swing applications that I once wrote, the way you want to detect collision is by always keeping track of where the object is, and when it moves check to see that it is new location (old + movement count) is not exceeding the limits of the map. If it does, you want to reset its position to be right on limit so that it can go in all other directions that are valid. If you keep trying to move outside of the limits then it should keep resetting your position.
So say the limit of the page is 500 pixels wide, you are in position 495, and each move is 10, next time you move you would in position 505, so that's clearly out of range. Thus you run a check if(currentPosition > limit) {reset position to limit} thus your currentPosition will be set to 500 which is right on the border, and within the limits.

Collision Detection between player and tiles almost works

I have been stuck on making some collision detection in my game (its kind of like Terraria) for a while but i made this code and... well, it kind of works. It works if the collision is above or on the left of the player, but if the collision is on the right, or below, instead of bouncing back, the player accelerates through the blocks until there is empty space. Here is the code that i made:
private void checkCollision() {
for(int x = (int) (xpos-1); x <= xpos+1; x++){
if(x < 0 || x > main.mw-1) continue;
for(int y = (int) (ypos-2); y <= ypos+1; y++){
if(y < 0 || y > main.mh-1) continue;
if(main.map[x][y] == null) continue;
if(!main.map[x][y].solid) continue;
if(main.map[x][y].blocktype == -1) continue;
double distance = Math.sqrt((xpos-x)*(xpos-x) + (ypos-y)*(ypos-y));
if(distance > 1.0){
continue;
}else{
double x_overlap = Math.max(0, Math.min(xpos + 16, x + 16) - Math.max(xpos, x));
double y_overlap = Math.max(0, Math.min(ypos + 32, y + 16) - Math.max(ypos, y));
double overlapArea = x_overlap * y_overlap;
if(overlapArea > 0){
if(x_overlap > y_overlap){
yblock += y_overlap/2;
}
if(x_overlap < y_overlap){
xblock += x_overlap/2;
}
//guessing i need to do something here to make player
go other way if block is on other side
}
}
}
}
}
So how would i make the player bounce back if the block that he is colliding with is on the right or below. Also is there any way i can make this smoother - right now the player be bouncing all over the place. Thanks! :)
What you want to do is keep track of the player's location, and if the location after moving is out of bounds you can reset the player's position to be right on the edge of the limit.
That's how I dealt with collision detection, I answered another question similar to this one though some folk decided to downvote the answer, go figure.

Collision detection with bounce

I'm making a android game for a school project. I'm familiar with java, but not experienced with making games. In my game a ball is controlled by the player. This ball needs to bounce of of walls.
I've tried this in two ways, but both unsuccessful. First try: I'm able to detect overlap, but not able to detect the side the ball hits.
c = the ball, r = the wall
float closestX = c.center.x;
float closestY = c.center.y;
if(c.center.x < r.topLeft.x) {
closestX = r.topLeft.x;
}
else if(c.center.x > r.topLeft.x + r.width) {
closestX = r.topLeft.x + r.width;
}
if(c.center.y < r.topLeft.y) {
closestY = r.topLeft.y;
}
else if(c.center.y > r.topLeft.y + r.height) {
closestY = r.topLeft.y + r.height;
}
return c.center.distSquared(closestX, closestY) < c.radius * c.radius;
So I tried a new approach. But this approach is unstable and treats the ball like a square.
cNew = the ball with the next position, cOld = the ball with the current position, r = wall
if (cNew.center.x + cNew.radius >= r.topLeft.x && cNew.center.x - cNew.radius <= r.topLeft.x + r.width)
{
if (cOld.center.y + cOld.radius < r.topLeft.y && cNew.center.y + cNew.radius >= r.topLeft.y)
{
return Side.TOP;
}
else if (cOld.center.y - cOld.radius > r.topLeft.y + r.height && cNew.center.y - cNew.radius <= r.topLeft.y + r.height)
{
return Side.BOTTOM;
}
}
if (cNew.center.y + cNew.radius >= r.topLeft.y && cNew.center.y - cNew.radius <= r.topLeft.y + r.height)
{
if (cOld.center.x + cOld.radius < r.topLeft.x && cNew.center.x + cNew.radius >= r.topLeft.x)
{
return Side.LEFT;
}
else if (cOld.center.x - cOld.radius > r.topLeft.x + r.width && cNew.center.x - cNew.radius <= r.topLeft.x + r.width)
{
return Side.RIGHT;
}
}
return null;
I need to combine these two is some way, but I haven't been able to find out how.
Help is much appreciated.
Didn't go very carefully with the code (and considering it is your school project, probably I shouldn't be doing your homework), but I believe treating the ball as a square would not have any negative effects if it is just going to bounce off walls. Is there something else you want to do with it?
Your first code is missing out on the fact that the surface of the ball will collide with the wall before its center. You might want to take that into account. And in what sense is your second code unstable?
Some more details would be useful here.
I am going to tell you here how I did :
for the player(and every enemy as well) you need :
x
y
w
h
and :
x velocity
y velocity
and the following point array(lists) :
outline :
upper side
lower side
left side
right side
all together
-all points that are in your ball, every pixel that hasnt alpha=0, for checking collision with walls
Walls :
walls are given as point array(list). For example, analyze the level image, every black pixel is added
Now, do the following :
in your class, have a method for moving
there you need the following logic :
int miny=Integer.MAX_VALUE;
for (Point p:walls) { //For every point in the walls
if (p.x >= (int)x && p.x <= (int)x+w && (int)p.x-(int)x < lower_side.length) {
try {
Point p2=lower_side[(int)p.x-(int)x]; //Get the point that is on the same height as the walls point
if (p.y >= (int)(y+p2.y) && (int)(y+p2.y+yvel) >= p.y && p.y-p2.y-1 < miny) { //Check if you are going to hit the wall, and if it is earlier as the earliest point determined.
miny=p.y-p2.y-1d; //Where is the earliest point where this can happen
}
} catch (Exception bug) {
System.out.println(bug);
}
}
}
apply this to all directions and dimensions.
if (miny != Integer.MAX_VALUE) {
y=miny; //Set position over the wall
yvel=-(yvel*0.75); //Bounce off
}
If you have any questions, feel free to comment.

Looking for a random direction loop for an object

In CS class we made a simple game using a program called greenfoot. This game was much like the game "Frogger" if you are familiar. I am now practicing on my own, and want to make a game similar. My new game is going to be somewhat close to PacMan. The game I made before I control a rocket ship that needs to reach the top of the screen. Meanwhile, I have made randomly selected sizes and speeds for rectangles bouncing of the walls. But, I want to make it more interesting for my new game. I want a loop for these objects that create a random direction when it is first complies, and bounce of the walls and continue on in that direction, much like that famous screen saver that bounces around. Here is my code for the first game, is it anything like this? So ultimately my question is, how do I write a loop for a random direction.
public boolean canMoveRight()
{
if ( getX() + 1 < getWorld().getWidth() )
return true;
else
return false;
}
public boolean canMoveLeft()
{
if ( getX() - 1 > 0 )
return true;
else
return false;
}
public void moveRight()
{
setLocation( getX() + speed, getY() );
}
public void moveLeft()
{
setLocation ( getX() - speed, getY() );
}
public void act()
{
if (right==true)
{
if (canMoveRight() )
{
moveRight();
}
else
{
right = false;
}
}
else
{
if( canMoveLeft() )
moveLeft();
else
right = true;
}
}
Define a Direction enum
e.g.
Up, RightUp,Right... LeftUp.
Use random to pick one.
Direction translates to a change in position of dX,dX, so with a movement step of say 1 pixel UpLeft is -1,-1 (origin top left !)
then in your loop simply add dx to X and dy to Y.
Something along these lines, I guess, would work:
Random random = new Random();
int direction = random.nextInt(1); // gives 0 or 1 randomly
if (direction == 0) {
// move left
} else {
// move right
}
Are you looking for something likes this?
int horz = ((Math.random() * 10) % 10) + 1; //I just choose 10 at random
int vert = ((Math.random() *10) % 10) + 1;
//You can use another random number to choose to negate either horz or vert if you want
//You can also use random numbers to define the start location.
public boolean canMoveHorz()
{
if ( (getX() + horz < getWorld().getWidth() && horz > 0) || (getX() + horz > 0 && horz < 0))
return true;
else
return false;
}
public boolean canMoveVert()
{
if ( (getY() + vert > 0 && vert < 0) || (getY() + vert < getWorld().getHeight() && vert > 0))
return true;
else
return false;
}
public void act() {
if(!canMoveHorz()) {
horz *= -1;
}
if(!canMoveVert()) {
vert *= -1;
}
setLocation(getX() + horz, getY() + vert);
}
This will require some tweaking but what it does is choose 2 random numbers that dictate the speed (vertically and horizontally) for the object. Then it moves the object in those directions until it reaches the edge of the World at which point it negates the speed so the object will move the opposite direction. This is not the same as a true bounce off the walls because I don't do anything with the angle of the hit to determine the angle of the bounce but this should be able to get you started. If you want a more realistic bounce you'll have to do some geometric calculations.

Java 2D Collision?

Hey guys i'm making a 2D java game and i'm trying to figure out how to make a good collision code. I am currently using the following code:
public void checkCollision() {
Rectangle player_rectangle = new Rectangle(player.getX(),player.getY(),32,32);
for(Wall wall : walls) {
Rectangle wall_rectangle = new Rectangle(wall.getX(), wall.getY(), 32,32);
if (player_rectangle.intersects(wall_rectangle)) {
Rectangle intersection = (Rectangle) player_rectangle.createIntersection(wall_rectangle);
if (player.xspeed > 0) {
player.x -= intersection.getWidth();
}
if (player.yspeed > 0) {
player.y -= intersection.getHeight();
}
if (player.xspeed < 0) {
player.x += intersection.getWidth();
}
if (player.yspeed < 0) {
player.y += intersection.getHeight();
}
Print(Integer.toString(intersection.width) + ", " + Integer.toString(intersection.height));
}
}
}
With this code it works fine if you are press one button but if press down and left for example the player will fly off in some random direction.
Here is a picture of the types of maps I have:
Your main problem is in assuming that the player is running directly into the wall. Consider the case where there is a wall rect (100,100,32,32) and the player is at (80,68,32,32). The player is moving down and to the left, so player.xspeed < 0 and player.yspeed > 0; say the next position for the player is (79,69,32,32). The intersection is then (100,100,11,1).
Note that although the player is moving left (as well as down) the wall is actually to the right of the player. This line:
if (player.xspeed < 0) {
player.x += intersection.getWidth();
}
... causes player.x to be set to 90 in a sudden jump.
One thing you could do is check that the player's left-hand side was contained in the intersection, i.e.
if (player.xspeed < 0 && player.x >= intersection.x) {
player.x += intersection.getWidth();
}
Obviously a similar thing needs to be done for the other directions too.

Categories