I have implemented basic collision detection in my top down 2d scrolling game. If the player moves a large distance, it will skip over the areas around it, because the collision detection checks around the player after the movement. As well as this, the player can get stuck in tiles.
I have tried recursively cutting the motion in half to check if it can find a smaller amount of motion that doesn't end in a collision, but I can't seem to get this to work for either problems. It also causes a StackOverflow error.
In the following code, x() represents the player's X location, from the bottom left. y() is the Y location. size is the player's width and height, and prevX and curX are temporary variables. The tiles method gets all tile locations in a certain radius from the player.
move(motionx, motiony);
if (x() - size()/2 < 0)x(size()/2);
if (y() - size()/2 < 0)y(size()/2);
if (x() + size()/2 > game.mapWidth * game.chunkWidth * size)x(game.mapWidth*game.chunkWidth*size - size()/2);
if (y() + size()/2 > game.mapHeight * game.chunkHeight * size)y(game.mapHeight*game.chunkHeight*size - size()/2);
float curX = x();
boolean updateX = true;
boolean updateY = true;
for (float[] tile : game.tiles((float)Math.ceil((double)size()/(double)size))) {
Tile t = game.tile(tile[0], tile[1]);
if (t == null) continue;
if (t.solid && game.collision(game.coordinate(), tile)) {
factor(1.0f - t.bounce, 1.0f - t.bounce);
x(game.prevX);
updateX = false;
if (t.solid && game.collision(game.coordinate(), tile)) {
x(curX);
updateX = true;
y(game.prevY);
updateY = false;
if (t.solid && game.collision(game.coordinate(), tile)) {
x(game.prevX);
updateX = false;
}
}
}
}
if (updateX)game.prevX = x();
if (updateY)game.prevY = y();
I hoped this would flawlessly prevent a player from moving onto a solid tile, but it obviously has problems. I am not great at collisions, but am willing to try anything. It sometimes skips over tiles and/or gets stuck.
Related
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 am creating a Bouncing Ball Animation with JavaFX similar to the bouncing windows logo screen saver. The code I have now is decent but it will only bounce the ball in a clockwise manner. This is good generally but eventually the ball works itself around to a counter-clockwise rotation in which case it no longer looks realistic. I am stuck trying to find a way to calculate how the ball should bounce; in my mind it really comes down to what angel the ball comes in at. I am Using an AnimationTimer which Translates the ball a set amount each frame. When the Bounds of the ball meet a boundary the translating direction is changed it is at this meeting that I need a suggestion...
BallAnimation is an inner class.
class BallAnimation extends AnimationTimer{
private final Sphere ball;
private double movex = 0;
private double movey = 0;
private double xvariation = 0;
private double yvariation = 0;
private boolean right = true;
private boolean up = false;
private boolean changeColorRandomly = true;
private double rate = 1;
public BallAnimation(Sphere ball){
this.ball = ball;
ball.setLayoutX(200);
ball.setLayoutY(50);
}
public void handle(long now){
move(right,up);
Bounds ballBounds = ball.localToScene(ball.getBoundsInLocal());
if(ballBounds.intersects(rightWall.getBoundsInParent())){
calculateMotion(rightWall);
randomBounceAngle();
setRandomColor();
}
if(ballBounds.intersects(leftWall.getBoundsInParent())){
calculateMotion(leftWall);
randomBounceAngle();
setRandomColor();
}
if(ballBounds.intersects(ceiling.getBoundsInParent())){
calculateMotion(ceiling);
randomBounceAngle();
setRandomColor();
}
if(ballBounds.intersects(floor.getBoundsInParent())){
calculateMotion(floor);
randomBounceAngle();
setRandomColor();
}
}
private void calculateMotion(Line touchedWall){
if(touchedWall.equals(rightWall)){
right = false;
up = false;
}
if(touchedWall.equals(leftWall)){
right = true;
up = true;
}
if(touchedWall.equals(ceiling)){
right = true;
up = false;
}
if(touchedWall.equals(floor)){
right = false;
up = true;
}
}
public void move(boolean right, boolean up){
if(right && !up){
ball.setTranslateX((movex += (getRate() + xvariation)));
ball.setTranslateY((movey += (getRate() + yvariation)));
}
if(right && up){
ball.setTranslateX((movex += (getRate() + xvariation)));
ball.setTranslateY((movey -= (getRate() + yvariation)));
}
if(!right && up){
ball.setTranslateX((movex -= (getRate() + xvariation)));
ball.setTranslateY((movey -= (getRate() + yvariation)));
}
if(!right && !up){
ball.setTranslateX((movex -= (getRate() + xvariation)));
ball.setTranslateY((movey += (getRate() + yvariation)));
}
System.out.println("("+movex+", "+movey+")");
}
public double getRate(){
return rate;
}
public void setRate(double rate){
this.rate = rate;
}
public void randomBounceAngle(){
double ran = Math.random();
if(ran >= .50){
//shallow bounce angle
xvariation = 3;
yvariation = 2;
}else{
//sharp bounce angle
xvariation = 2;
yvariation = 3;
}
}
... The problem is when the ball hits the right boundary it bounces down and away, the bottom it bounces up and left, left boundary: up and right, ceiling: right and down. This is fine most of the time but sometimes it needs to bounce the other way.
Well, in a world of perfect physics, in angle is equal to out angle. If you are using an x/y axis, For reflection off the x-axis, negate the y component of the ball's velocity. For reflection off the y-axis, negate the x component of the ball's velocity.
I re-wrote pong in javascript using layers and detecting keyboard strokes for paddle control (this was in '00 or '01 with Netscape 4.7x). I cheated, and set up functions to move the ball in 8 directions. If the ball was traveling along an axis (straight left/right or up/down) a quick random number provided a different bounce coming out. Otherwise, bounce out at same angle in.
Here is a function to reflect a vector around a normal. It can be used to create a bounce, by reflecting the velocity vector of the ball around the normal of the wall (or the normal of the side of another object) that the ball is bouncing off of.
private Point2D reflect(Point2D vector, Point2D normal) {
return vector.subtract(normal.multiply(vector.dotProduct(normal) * 2));
}
It is part of an implementation for a sample breakout game I created based on the example code in this question.
The code shown for the vector-based reflection uses the formula provided in the answer to this question, which is translated directly to JavaFX classes:
How to get a reflection vector?
π=πβ2(πβ
π)π where πβ
π is the dot product and π must be normalized.
Please note that, if you search, there are many math tutorials and Stackoverflow questions that talk about functions and methods for performing reflection.
A particular case for balls bouncing off vertical or horizontal surfaces such as walls or bricks in a breakout game is that the lines which the ball is bouncing off of are parallel to the x and y axes of the coordinate system, so the bounce can be performed by negating the x or y values of the velocity vector. See the example code in this question or answers to other questions on reflection for an example of this simplification if it is something you wish to use.
if (topWall) {
dy = dy * -1;
}
if (leftWall || rightWall) {
dx = dx * -1;
}
if(bottomWall) {
dy = dy * -1;
}
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();
}
}
}
First of all i want to say that my English isnt that good, and I'm a beginner programmer. So take it easy with me :L
So I'm making a 2D game where ground is randomly spawned. And its made of blocks...
How I do this is first i create the blocks and then I add them to Rectangle ArrayList. Blocks render correctly. But they won't take any collision when player hits them.
At this moment collision doesn't work at all. When i press D (right) player runs towards right ignoring collision complitely. When i press A (left) player don't move at all.
First I make this ArrayList:
static ArrayList<Rectangle> BlockArray = new ArrayList<Rectangle>();
Then I give blocks their X,Y,Width,Height... values in a for loop and after that I add them to the list like this :
BlockArray.add(Block[i]);
Then In player class I run this function every render loop. It should tell if player can move to right or left or none...:
ArrayList<Rectangle> rects = WorldGenerator.BlockArray;
for(int i = 0; i < rects.size(); i++) {
// LEFT
if(player.x >= rects.get(i).getX() + rects.get(i).getWidth() && player.y >= rects.get(i).getY() + rects.get(i).getHeight()){
canGoLeft = true;
}
else{
canGoLeft = false;
}
// RIGHT
if(player.x <= rects.get(i).getX() && player.y >= rects.get(i).getY() + rects.get(i).getHeight()){
canGoRight = true;
}
else{
canGoRight = false;
}
}
And then finally when user gives input it checks if those booleans are true or not :
if (Gdx.input.isKeyPressed(Keys.A) && canGoLeft == true){
player.x -= delta * 350f;
}
if (Gdx.input.isKeyPressed(Keys.D) && canGoRight == true){
player.x += delta * 350f;
}
So thats my code. Hopyfully I didn't forget to mention something. And hopefully someone can help me to solve this problem. Also Like I said before I'm beginner at programming so I might have just a stupid fail in game logic...
As far as your collision logic goes, you are changing canGoRight and canGoLeft for each rectangle regardless of previous collision checks. This means that effectively, the last rectangle in your list is the only one being checked for collisions.
To resolve that issue, you'll want to change it to be like this (I just added a ! to the conditions, you should rework them rather just inverting the final result):
ArrayList<Rectangle> rects = WorldGenerator.BlockArray;
canGoLeft = true;
canGoRight = true;
for(int i = 0; i < rects.size(); i++) {
// LEFT
if(!(player.x >= rects.get(i).getX() + rects.get(i).getWidth() && player.y >= rects.get(i).getY() + rects.get(i).getHeight())) {
canGoLeft = false;
}
// RIGHT
if(!(player.x <= rects.get(i).getX() && player.y >= rects.get(i).getY() + rects.get(i).getHeight())) {
canGoRight = false;
}
}
This way, you assume they can move in a given direction, and any single rectangle that would block that direction will prevent movement that direction.
EDIT: I also looked into your conditions, and it looks like any rectangle to the right of the player will prevent them going to the left, not just a rectangle whose right side is up against the left side of the player. A much better comparison would be the difference between the player's and rectangle's half-widths and half-heights. This article can explain in detail why.
As far as bigger picture goes, I mentioned in a comment that collision detection has already been written many times, and libgdx includes solid collision detection code that you should use, rather than writing your own collision detection code.
I have a circle that moves from point A to a random point B. When the object nears point B, a new random target location gets chosen. If the circle is moving parallel to the X-axis or Y-axis the object goes through all the pixels in the way and leaves a solid trace. But if the circle moves diagonally, it skips pixels and shakes slightly, making the animation not smooth and leaves a trace with unpainted pixels.
My algorithm is:
calculate the X and Y distances
check if the circle is near
if so, choose the new destination
if 2. is true, find the real distance using Pythagoras' theorem
if 2. is true, calculate the X and Y speed (the change of the coordinates)
set the new coordinates (no matter if 2. is true or not)
And here is the code:
public void move ()//Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅
{
//finds the X and Y distance to the destination
int triangleX = nextLocationX - coorX;
int triangleY = nextLocationY - coorY;
//if too near the coordinates of the destination changes
if (Math.abs(triangleX) <= Math.abs(speedX) || Math.abs(triangleY) <= Math.abs(speedY))//setting the new target
{
//setting the new destinatio
int randInt;
for (;;)//I don't want the the new destination to be that same spot
{
randInt= randGen.nextInt(appletX);
if (randInt != nextLocationX)
{
nextLocationX = randInt + radius;
break;
}
}
for (;;)
{
randInt = randGen.nextInt(appletY);
if (randInt != nextLocationY)
{
nextLocationY = randInt + radius;
break;
}
}
//calculating the change of the circle's X and Y coordinates
triangleX = nextLocationX - coorX;
triangleY = nextLocationY - coorY;
speedX = ((double)(speed * triangleX) / (Math.sqrt (Math.pow(triangleX, 2) + Math.pow(triangleY, 2))));
speedY = ((double)(speed * triangleY) / (Math.sqrt (Math.pow(triangleX, 2) + Math.pow(triangleY, 2))));
}
//the realCoor variables are from type double
//they are the exact coordinates of the circle
//If I only use integers, the circle almost
//never reaches it's destination
//unless the change of the coordinates gets calculated
//after every time they change
realCoorX = realCoorX + speedX;
realCoorY = realCoorY + speedY;
coorX = (int)Math.round(realCoorX);
coorY = (int)Math.round(realCoorY);
}
I suspect that the problem is in the calculation of the change of the coordinates.
For me this sounds like a Aliasing problem. You would have the same problem if you draw(!) a line that is not aligned with the coordinate axis. As you know, i.e. diagonal lines need "half filled" pixels to look smooth.
Solution for you would be (depending on the technology for rendering) to use floating point position calculation.