I am trying to recreate breakout but the interaction betweeen the corner of the blocks and the ball is giving me a massive headache. Bouncing off the edges of the window and the blocks works just fine and bouncing off the windows' corners works perfectly too. For simplicity, all blocks are axis-aligned. Swapping x and y movement only works as intended for 2 out of 8 cases. For the other 6, one or two of the vectors have to be inverted for the collision to be correct, but I can't figure out a way to do this in a simple way. Writing 8 if-statements can't be correct and is prone to cause bugs.
The Ball class is an extension of the Ellipse2D.Double class.
The Block class is an extension of the Rectangle class.
Every question / answer / tutorial I took a look at would either be overcomplicated (not AA rects, rect not on a fixed position), simplified (handle as if two rects collided) or completely off-topic (detect collision / overlap, ball to ball handling).
Here's what I got:
public void move(Paddle p, List<Block> b) {
//called every frame
this.x += dx;
this.y += dy;
checkCollisionsWalls();
// checkCollisionPaddle(p);
checkCollisionBlocks(b);
if (this.dir == 90 || this.dir == 270)
this.dir++;
// make sure ball wont get stuck between two walls of window
}
private void checkCollisionBlocks(List<Block> b) {
for (Block block : b) {
if (this.intersects(block)) {
if (this.x + this.width / 2 >= block.x && this.x + this.width / 2 <= block.x + block.width) {
System.out.println("Top / Bot reflect");
this.dy *= -1;
break;
}
if (this.y + this.height / 2 >= block.y && this.y + this.height / 2 <= block.y + block.height) {
System.out.println("Left / Right reflect");
this.dx *= -1;
break;
}
// if execution got here we must have hit a corner
reflectCorner();
}
}
}
private void reflectCorner() {
if (dx > dy) {
this.dy *= -1;
} else {
this.dx *= -1;
}
if (Math.abs(dx) == Math.abs(dy)) {
this.dy *= -1;
this.dx *= -1;
}
double temp = this.dx;
this.dx = this.dy;
this.dy = temp;
temp = 0;
}
dx and dy are global vars in the Ball class, in fact all of the code is (still) in the Ball class.
EDIT:
Sorry for looking over the part of asking a clear question, I'm a little worked up about it...
What is happening with this current code:
Reflection works perfectly when hitting the bottom-right corner from any direction.
Hitting the top left corner results in the ball getting stuck inside the rect. It then wanders along the edges to the bottom-right corner
When touching the top-right corner while moving parallel to the x-axis and when touching the bottom-left corner while moving parallel to the y-axis, the ball reflects twice, inching in either positive y or positive x direction one pixel per bounce (the axis orthogonally it is moving to).
All other reflections work fine.
What should happen:
The reflectCorner() method should work for all corners like it does for the bottom-right one and not produce results as decribed above.
Question:
How do I do that without overcomplicated if-structures?
If necessary, i can provide the testing setup I used.
I finally managed to make it work on all sides. First I calculated which corner was hit by checking if the distance between the ball's center and any corner was smaller / equal to the ball's radius. Then I'd swap the x and y movement. For two corners that's all that was needed, for the second two I had to invert one of them, depending on corner and direction of the ball.
Finally:
private void reflectCorner(Rectangle rect) {
double temp = this.dx;
this.dx = this.dy;
this.dy = temp;
temp = 0;
if (Math.abs(dx) == Math.abs(dy)) {
this.dy *= -1;
this.dx *= -1;
return;
}
if (Math.sqrt(Math.pow(Math.abs(this.getCenterX() - rect.x), 2)
+ Math.pow(Math.abs(this.getCenterY() - rect.y), 2)) <= this.width / 2) {
System.out.println("topL");
if (dx < dy) {
this.dy *= -1;
} else {
this.dx *= -1;
}
return;
}
if (Math.sqrt(Math.pow(Math.abs(this.getCenterX() - (rect.x + rect.width)), 2)
+ Math.pow(Math.abs(this.getCenterY() - (rect.y + rect.height)), 2)) <= this.width / 2) {
System.out.println("botR");
if (dx > dy) {
this.dy *= -1;
} else {
this.dx *= -1;
}
return;
}
}
More i not necissary, and it's relatively compact. Still, if someone has an even simpler idea, you can still answer and I'll test it.
Related
I need to change Ball Direction after collision with another ball or with a edge of the window.
I managed to do something like that:
y += yMove;
x += xMove;
//if the ball moves to the right edge of the window, turn around.
if(x > width - size)
{
x = width - size;
xMove *= -1;
if (xMove > 0) {
xSpeed = xMove + (Math.random() * (1));
}
if (xMove <= 0) {
xSpeed = xMove - (Math.random() * (1));
}
if (yMove > 0) {
ySpeed = yMove + (Math.random() * (1));
}
if (yMove <= 0) {
ySpeed = yMove - (Math.random() * (1));
}
}
And same for another edges.
I'm trying to use same method for changing direction of balls after they collide with each other, but it's just not working / it's weird. Can anyone help me?
When balls collide, make vector connecting ball centers (N) and normalize it (uN)
Components of velocities parallel to N (normal) are exchanged (due to impulse law)
Components of velocities perpendicular to N (tangential) remain the same
To get components in given local system, use scalar and cross product:
V1t = dot(V1, uN)
V2t = dot(V2, uN)
V1n = cross(V1, uN)
V2n = cross(V2, uN)
after collision
V1t' = V2t
V2t' = V1t
V1n' = V1n
V2n' = V2n
To return into global system (I did not checked signs thoroughly):
V1x = V1t` * uN.X + V2n` * uN.Y
V1y = -V1t` * uN.Y + V2n` * uN.X
(This is essentially dot and cross products again, but I expanded expressions to show different bases)
Note that this approach is like to ball-edge collision, when N is normal to the border and you reverse only one component of velocity vector.
For your BouncingBall class, you can have a method like flipDirection(), but you can have a finer directional control by splitting it into 2 methods which filps the direction of the ball vertically and horizontally.
class BouncingBall{
public void horizontalFlip(){
moveX *= -1;
}
public void verticalFlip(){
moveY *= -1;
}
//To have move control over each direction, you can have a method for each direction.
public void moveNorth(){
moveY = Math.abs(moveY) * -1;
}
public void moveSouth(){
moveY = Math.abs(moveY);
}
public void moveWest(){
moveX = Math.abs(moveX) * -1;
}
public void mpveEast(){
moveX = Math.abs(moveX);
}
}
Depending on how you want the ball to bounce off. In a simple bounce off, the balls can bounce towards 4 possible directions:
North West
North East
South West
South East
The direction of the ball to bounce off will be relative to the position of the ball it is colliding with and you do not want 2 collided balls which move in the same direction to switch direction just because they collided. Hence you need to check the positions of the 2 balls, and flipDirection() becomes insufficinet to achieve that.
if(b1.intersects(b2)){
if(b1.getX() < b2.getX()){ // b1 above b2
b1.moveNorth();
b2.moveSouth();
}
else{
b1.moveSouth();
b2.moveNorth();
}
if(b1.getY() < b2.getY()){ // b1 at left side of b2
b1.moveWest();
b2.moveEast();
}
else{
b1.moveEast();
b2.moveWest();
}
}
For example, to change direction when hitting the walls on the left and right:
if(ball.getPosX() <= 0 || ball.getPosX() >= PNL_WIDTH-Ball.SIZE)
ball.horizontalReverse();
Same logic for verticalReverse.
I'm currently working on a Top-Down-Shooter and having some issues with collision.
My world is made of tiles (64x64). The tiles and the entities are rectangles. The player moves with a speed of e.g 2.74 (and not in pixels for smoother movement). But when it comes to the collision between the player (an entity) and a wall i have some issues. To check if there is a collision i take the current position of my player and his movement speed to calculate where his next position would be and if there is any collision. But i check every pixel on the way, so i cant skip an obstacle even if the movement speed is very high. Let's just say the players current position is X:200 Y:200 and he moves 2.74 Pixels a tick in the x direction. My game now checks if there is any collision at X:201 Y:200, X:202 Y:200 or X:202.74 Y:200 and if not moves the player to that position. If I now try to move the player further in the x direction and there is a wall 0.26 Pixels away the player wont move and leave a tiny gap. I tried to calculate the distance between player and wall and add this amount to the players position but for that I need to know which side of the wall the player hits. Also I want the player to be able to move up and down when the wall he hits is in front of him and the other way around.
Here is my collision method (in Java):
public static boolean collision(float ex, float ey, int width, int height) { // ex, ey would be the next position of the player
if (ex < 0 || ex + width > worldWidth || ey < 0 || ey + height > worldHeight) return true; // checks if this position is in the world
int firstTileX = (int) (ex / Tile.TILE_SIZE); // calculates tiles he could possible collide width
int firstTileY = (int) (ey / Tile.TILE_SIZE);
int lastTileX = (int) ((ex + width - 1) / Tile.TILE_SIZE);
int lastTileY = (int) ((ey + height - 1) / Tile.TILE_SIZE);
for (int y = firstTileY; y <= lastTileY; y++) {
if (y < 0) continue; // checks for out of bounds
if (y >= worldTileHeight) break;
for (int x = firstTileX; x <= lastTileX; x++) {
if (x < 0) continue;
if (x >= worldTileWidth) break;
if (tiles[y][x].solid) return true; // if the tile is solid -> collision found
}
}
return false; // no collision found
}
And my movement method:
public void move(float xa, float ya) {
float nx, ny;
while (xa != 0 || ya != 0) {
nx = x;
ny = y;
if (xa != 0) {
if (Math.abs(xa) > 1) { // if the x-speed is greater than 1
nx = x + MathUtil.abs(xa); // returns -1 for negative numbers and 1 for positiv
xa -= MathUtil.abs(xa);
} else { // less than 1
nx = x + xa;
xa = 0;
}
}
if (ya != 0) { // same here
if (Math.abs(ya) > 1) {
ny = y + MathUtil.abs(ya);
ya -= MathUtil.abs(ya);
} else {
ny = y + ya;
ya = 0;
}
}
if (!Level.collision(nx, ny, width, height)) setPosition(nx, ny); // checks if there is an collision and sets the new position if not
else if (!Level.collision(nx, y, width, height)) x = nx; // if there was a collision check if the player can walk in x direction
else if (!Level.collision(x, ny, width, height)) y = ny; // or in y direction
}
}
My problem is the pretty much the same as CoderMusgrove's problem in his post (Pixel-perfect collision and doubles):
Summary & Question
I have a problem where if the speed of an entity isgreater thanthe distance from the tile it is going into, it will leave at least a pixel in between itself and the tile, and I really don't like this. What kind of algorithm could I use that will find the tiniest difference between the entity and the tile?
If you need any additional information, I will be glad to add it.
Thanks for your help!
Easily resolvable by changing your interpretation.
You are retaining a fractional position for the purpose of fine grained speed. Ignore the fraction for the purpose of collision detection and display (if you were to do sub-pixel rendering, do the collision on the subpixel rendering accurarcy level).
int screenX = (int) Math.round(objX);
int screenY = (int) Math.round(objY);
// rendering and collision detection based on rounded position
I'm trying to make a pinball-style game for a school project for the Android in the SDK in Eclipse.
There's a really weird and super frustrating problem in which objects are moving without any code telling them to. Basically, each Wall instance contains 4 Line objects which are used for collision detection with the Ball. These Lines work the first time, but as soon as the ball collides with them once, then that Line somehow moves to another position on the screen.
I've been debugging it, and wouldn't ask if I hadn't already tried everything, but there is honestly no reason for the Line to shift anywhere. The way I handle a collision is by pushing the Ball to be 1px away from the wall, and then given new dx and dy (velocities) to move away. The code for checking for collisions is below, followed by the function that handles a collision to change the ball's position and velocity. Both are methods in the Ball class.
GameElement[] walls = currLevel.getWalls();
int i, j;
Line[] lines;
Line line;
RectF lineBounds;
boolean hadCollision = false;
for (i = 0; i < walls.length & !hadCollision; i++) {
lines = walls[i].getLines();
for (j = 0; j < lines.length & !hadCollision; j++) {
lineBounds = lines[j].getBounds();
if (lineBounds.intersect(point)) {
paint.setColor(Color.BLUE); // Colour ball blue.
reactToCollision3(lines[j]);
// TEST RESET!!!
//this.x = (float)(648+40);
//this.y = (float)(900-30);
hadCollision = true;
//printWallsLines();
}
}
}
and the function to handle the collision is:
public void reactToCollision3 (Line line) {
float liney = line.sy;
float linex = line.sx;
if (line.rotation == 0.0) { // HORIZONTAL EDGE
if (this.y > liney) { // Ball moving upward hits the bottom of a wall.
this.y = liney + this.radius + 1.0f;
} else { // Ball moving downward hits the top of a wall.
this.y = liney - this.radius - 1.0f;
}
this.dy *= -1.0f;
} else { // VERTICAL EDGE
if (this.x > linex) { // Ball moving leftward hits right edge of a wall.
this.x = linex + this.radius + 1.0f;
} else { // Ball moving rightward hits left edge of a wall.
this.x = linex - this.radius - 1.0f;
}
this.dx *= -1.0f;
}
So when I run this right now, the ball will bounce off a wall the first time it hits it, and then that line (edge of the wall) that it hit will be shifted elsewhere, but is not visible because the Wall is drawn as one unit so the Lines that comprise it don't affect the drawing.
If I comment out the lines for "this.x = ..." and "this.y = ...", then this problem doesn't happen anymore. Also, if I uncomment the test RESET lines for setting the ball's position in the above function, then the line doesn't shift then either. But as soon as I run this, it happens again.
I'm going insane looking for why this would happen. Please give me suggestions.
Thank you!
Did you intend to use bitwise &? (See ** in code) Your test for "!hadCollision" will fail and you will also be masking your wall length. I believe you meant to use &&
for (i = 0; i < walls.length **&** !hadCollision; i++) {
lines = walls[i].getLines();
for (j = 0; j < lines.length **&** !hadCollision; j++) {
lineBounds = lines[j].getBounds();
if (lineBounds.intersect(point)) {
paint.setColor(Color.BLUE); // Colour ball blue.
reactToCollision3(lines[j]);
// TEST RESET!!!
//this.x = (float)(648+40);
//this.y = (float)(900-30);
hadCollision = true;
//printWallsLines();
}
}
}
I am having a problem with collision detection on a tile based level at lower framerates. I have a platform game built in Java with the LibGdx game engine. At a framerate of 60 the game runs fine, but if I try it a 30 FPS the character will fall through tiles when it comes down from a jump.
I figured out that the character moved too fast. I already added something to check if there are any tiles that the character already passed, see "// 1" to "// 1 end" in the comments. I don't think it really helped because the problem still occurs.
Falling through the tiles seems to happen when the character hits corners of tiles, although I am not sure about it. It does not happen on a flat ground. Here is a picture of the problem (left is wrong, right is how it should be):
Again, the problem only happens at lower framerates. I am not sure what I have to change in my code. What am I missing in my code? Or do I have to use a different algorithm?
Here are the most important parts of the collision detection code. collisionY check collisions on the y-axis, collisionX on the x-axis. CheckTiles(checkX) helps finding the tiles that are should be checked (checkX is true if x-axis is checked, if false y-axis is checked):
protected boolean collisionY(Rectangle rect) {
int[] bounds = checkTiles(false);
Array<Rectangle> tiles = world.getTiles(bounds[0], bounds[1], bounds[2], bounds[3]);
rect.y += velocity.y;
if(velocity.y < 0 ) {
grounded = false;
}
for (Rectangle tile : tiles) {
if (rect.overlaps(tile)) {
if (velocity.y > 0) {
this.setY(tile.y - this.getHeight());
}
else {
// 1 Check if there are tiles above
Rectangle r = null;
int i = 1;
Rectangle r1 = null;
do {
r1 = r;
r = world.getTile(tile.x, tile.y + i);
i++;
} while (r != null);
if(r1 != null) {
this.setY(r1.y + r1.height);
}
// 1 end
else {
this.setY(tile.y + tile.height);
}
hitGround();
}
return true;
}
}
}
protected boolean collisionX(Rectangle rect) {
int[] bounds = checkTiles(true);
Array<Rectangle> tiles = world.getTiles(bounds[0], bounds[1], bounds[2], bounds[3]);
rect.x += velocity.x;
for (Rectangle tile : tiles) {
if (rect.overlaps(tile)) {
return true;
}
}
return false;
}
protected int[] checkTiles(boolean checkX) {
int startX, startY, endX, endY;
if(checkX) {
if (velocity.x > 0) {
startX = endX = (int) (this.getX() + this.getWidth() + velocity.x);
}
else {
startX = endX = (int) (this.getX() + velocity.x);
}
startY = (int) (this.getY());
endY = (int) (this.getY() + this.getHeight());
}
else {
if (velocity.y > 0) {
startY = endY = (int) (this.getY() + this.getHeight() + velocity.y); //
}
else {
startY = endY = (int) (this.getY() + velocity.y);
}
startX = (int) (this.getX());
endX = (int) (this.getX() + this.getWidth());
}
return new int[]{startX, startY, endX, endY};
}
The "Check if tile above" code will only be run if A) rect.overlaps(tile) was true, and B) velocity.y > 0 was false. I suspect in the case you care about, this code is simply not being executed. The character is either not overlapping the tile, so the check for tiles above doesn't occur, or its velocity is not such that the check occurs. However, I don't completely understand how the velocity works (how do up and down correspond to positive and negative values?) and I'm not familiar with this game engine.
I would try a different approach, though. Instead of first moving the character (which is what I think the line rect.y += velocity.y; does), and then trying to check if it has gone too far or passed through a tile, I would take the direction in which it's moving (velocity.y) and look for the first tile that it would hit going that way. If there is one, then place the character on that tile. If there's nothing in its way for velocity.y units in that direction, then it gets to move the whole distance in this time slice.
I am struggling with what must be a basic concept, but can you have a look at my issue?
I have the code where: ai moves the player bat, HEIGHT = total height of Display, and batHeight is the size of the pong paddle/bat:
public void ai(int bally, int HEIGHT, int batHeight) {
if (bally < this.y + ySize / 2) {
if (this.y <= 0) {
System.out.println("Upper Bound");
y = 0;
} else {
y -= 2;
}
}
if (bally > this.y + ySize / 2) {
if (this.y >= HEIGHT - batHeight) {
System.out.println("Lower Bounds");
y = HEIGHT - batHeight;
} else {
y += 2;
}
}
}
The above does exactly what I want it to do. Pong Bat moves up, and when it hits the top of the screen, it prints the console line, and stops the Bat. Exactly the same happens at the bottom of the screen. It prints the console, and stops the bat. It does this every time with no issues.
Now, if I modify the code slightly:
public void ai(int bally, int HEIGHT, int batHeight) {
if (bally < this.y + ySize / 2) {
if (this.y <= 0) {
System.out.println("Upper Bound");
y = 0;
} else {
if(rand.nextInt(2)+1 == 1){
y -= 2;
}else{
y -=3;
}
}
}
if (bally > this.y + ySize / 2) {
if (this.y >= HEIGHT - batHeight) {
System.out.println("Lower Bounds");
y = HEIGHT - batHeight;
} else {
y += 2;
}
}
}
It iterates once, stopping at the top bound, but then it loses itself, and forgets the bounds and the bat moves off the screen. I have Console printing the Bat y position, and it tracks with no issue, accurately displaying its y co-ord, but after the first iteration, it goes to negative y and greater that screen height.
I did have the theory that you cannot nest a IF inside an ELSE statement, so i tried moving it around so that it read:
if(this.y != 0){
if(rand.nextInt(2) + 1 == 1){
//move the paddle at speed 1
} else {
//move paddle at speed 2
}
}else{
//do not move the paddle
}
But that made no difference.
The idea behind the code was to add some chance for the AI bat. Sometimes its fast, and other times it is slower.
Thanks in advance,
Your code from far away looks like this:
for a given time:
if the ball is below the paddle {
if the paddle is below the screen, put it back
else move it down 2 or 3 units
}
if the ball is above the paddle {
if the paddle is above the screen, put it back
else move it up 2 units
}
Imagine the case where the ball is at y = 1 and the paddle is at y = 2. The first if statement will be triggered (1 < 2), the paddle is not outside (2 > 0), so it moves down 2 or 3 units. Let's say 3, for argument's sake. Now, paddle is at y = -1, the ball is still at y = 1. Now, the condition for the second big if is true! So we enter it: the paddle's not above, and we move it up two units. Now, the paddle is at y = 1...
It is clear that it should not have entered the second loop. So, stick an else in front of it, because it should only ever enter one :)