I'm making a 2D game, nothing fancy, with objects (called entities from now on) that should collide with eachother and terrain. Now, detecting collision is not the problem, the problem is when the entities collide with the terrain.
I have a simple .png, upon loading the game I go through all pixels of this image and add all black ones to a list, these are the pixels that is collidable.
Collision-code from Entity-class:
int ceiling = Integer.MAX_VALUE;
int floor = 0;
int rightPoint = 0;
int leftPoint = Integer.MAX_VALUE;
for(Point p : main.terrain.points)
if(getHypoRect().contains(p))
{
if(p.x > rightPoint && right)
rightPoint = p.x;
if(p.x < leftPoint && left)
leftPoint = p.x;
if(p.y < ceiling && up)
ceiling = p.y;
if(p.y > floor && down)
floor = p.y;
}
if(rightPoint != 0)
xCoord = rightPoint - sprite.getImage().getWidth();
else if(right)
xCoord+=speed;
if(leftPoint != Integer.MAX_VALUE)
xCoord = leftPoint;
else if(left)
xCoord-=speed;
if(ceiling != Integer.MAX_VALUE)
yCoord = ceiling;
else if(up)
yCoord-=speed;
if(floor != 0)
yCoord = floor - sprite.getImage().getHeight();
else if(down)
yCoord+=speed;
In the for-loop I go through all points (black pixels) in the terrain, then check if the Entity's collisionbox contains them (speed is added in this method, or rather "hypothetically" added, that's why it has the weird name).
Now, if the x-value of the point is greater than the "rightpoint", and if the Entity is walking to the right, it's overwritten. The same happens with leftPoint ("leftest" point), ceiling and floor.
Then I check if any of these points have been changed, if they have that means we collided with the terrain so we bounce them back. For example, say the Entity is walking to the right and we collided with the terrain, rightPoint have been written with the "rightest" point we collided with so then we set the coordinate so that the Entity is right beside the point that it collided with.
If one would only go at one direction at a time, this would work perfectly, but "weird" things happen when you take multiple inputs into account.
Example: the Entity walks, flat to the floor, towards a ledge on the right, when the Entity is halfway over the ledge, it wants to go down, so both right and down are pressed, what my code will do is check the inputs, put the Entity where it wants to be and then bounce it back if it hit terrain, so it will see that I press right and bounce the Entity back to the end of the ledge.
This is all my own idea of how terraincollision could work, I couldn't find anything online except how to check if a collision took place, but nothing that covers this.
Any idea what could make my solution work, or a pointer to how terraincollision should be done?
Your bouncing back idea maybe problematic. Try instead to ignore moves that would collide with the terrain:
If the player moves down and right, then first check if down would collide, if yes, then ignore that movement, and check for right, if right is possible do right movement, else block, too.
Related
I'm having some difficulty implementing very basic collision within libGDX. The update code for the "player" is as so:
private void updatePosition(float _dt)
{
Vector2 _oldPos = _pos;
_pos.add(_mov.scl(_dt*50));
_bounds.setPosition(_pos.x-8, _pos.y-12);
if(_game.getMap().checkCollision(_bounds))
{
System.out.println("Collision Detected");
_pos = _oldPos;
_bounds.setPosition(_pos.x-8, _pos.y-12);
}
}
So, initially _oldPos is stored as the values of _pos position vector before applying any movement.
Movement is then performed by adding the movement vector _mov (multiplied by the delta-time * 50) to the position vector, the player's bounding rectange _bounds is then updated to this new position.
After this, the player's new bounding Rectangle is checked for intersections against every tile in the game's "map", if an intersection is detected, the player cannot move in that direction, so their position is set to the previous position _oldPos and the bounding rectangle's position is also set to the previous position.
Unfortunately, this doesn't work, the player is able to travel straight through tiles, as seen in this image:
So what is happening here? Am I correct in thinking this code should resolve the detected collision?
What is strange, is that replacing
_pos = _oldPos;
with (making the same move just made in reverse)
_pos.sub(_mov.scl(_dt*50));
Yields very different results, where the player still can travel through solid blocks, but encounters resistance.
This is very confusing, as the following statement should be true:
_oldPos == _pos.sub(_mov.scl(_dt*50));
A better solution for collision detection would be to have a Vector2 velocity, and every frame add velocity to position. You can have a method that tests if Up arrow key is pressed, add 1 (or whatever speed you would like) to velocity. And if down is pressed, subtract 1 (or whatever speed). Then you can have 4 collision rectangles on player, 1 on top of player, bottom, left, and right. You can say
if(top collides with bounds){
if(velocity.y > 0){
set velocity.y = 0.
}
}
And do the same for down, left and right (eg... for bottom, make sure to test if(velocity.y < 0) instead of if(velocity.y > 0).
EDIT: You code is not working because you set oldPos = pos without instantiating a new Vector2. Which means when you add onto pos, it also changes oldPos. So say oldPos = new Vector2(pos);
try to test future position before move. If collision, don't move.
I've finally completed the collision detection, however the collision response is very glitchy (if you try you can go through walls) and that's mainly because I have no information about the collision angle!
I have camPos(x,y,z) coordinates of my camera aswell as the min and max values of model(minX,minY,minZ,maxX,maxY,maxZ).. with a simple test I check if the camPos is interecting the model boundaries:
if(cameraX > xMax || cameraX < xMin) {
collisionX = false;
collisionY = false;
collisionZ = false;
} else {
collisionX = true;
System.out.println("collisionX: "+ collisionX); // collision on x is true!
}
I have acess to all vertice positions and calculated the min max values of the object to create a BoundingBox.
In order to get the right direction in which I want to push the object I need to know in which direction the nearest face is pointing (left,right,forward,back?)
To find out the angle I thought I could use the normal coordinates which I also have acess to, since they indicate a 90 degree angle to the face, right?
console print: //all 'vn' values of cube.obj
xNormals:0.0
yNormals:0.0
zNormals:0.0
xNormals:0.0
yNormals:0.0
zNormals:0.0
xNormals:1.0
yNormals:1.0
zNormals:1.0
xNormals:0.0
yNormals:0.0
zNormals:0.0
xNormals:-1.0
yNormals:-1.0
zNormals:-1.0
xNormals:0.0
yNormals:0.0
zNormals:0.0
Basically I want to know how these normal coordinates have to be applied to the min and max values of the object so that I can define all faces of the BoundingBox for example: face A is defined by xMin_a and xMax_a and faces left, face B is defined by xMin_b and xMax_b and faces right and so on..
I hope it's a bit more understandable, it's quite hard to explain..
Okay I've figured out a way to do it, hope it will help someone else too: I found out how to calculate the faces of a bounding box, it was actually not even that hard and it had absolutely nothing to do with normals.. sorry for the confusion.
As shown in my answer you can calculate the boundingBox of objects with their min/max values.. but you can also define all six faces of the box by extending the check and use a boolean to find out if the respective face collides somewhere:
if(collisionX == true && collisionY == true) {
if(cameraZ+0.2 > zMin_list.get(posIndex)-0.2 && cameraZ+0.2 < zMin_list.get(posIndex)) {
faceA = true;
System.out.println("faceA = " +faceA + " face a is true!");
} else {
faceA = false;
}
}
You can do this for all six faces where A(Forward), B(Back), C(Left), D(Right), E(Up) and F(Down) will define where the collision direction should be:
If the collision is on Face A, the response will be moving backwards (on z axis minus).. That way you will never be able to glitch through a wall because it will always push you away from the collision area.
I have a Pong-360 game in which the arc shaped paddles deflect a ball that should stay within the circular boundary. If the ball does not encounter a paddle when it gets to the boundary, it continues out of bounds and the player to last hit the ball scores. The problem I am having is returning the ball in the correct direction upon impact with a paddle. If the ball contacts a certain half of the paddle it should bounce in that direction but in any case should be returned to the opposite side of the boundary without hitting the same side of the boundary again.
Right now I have accomplished bouncing by dividing the boundary into 16 slices and giving the ball a random angle within a range that depends on which slice it was at on impact. Even this does not work as intended because my math is not correct but it needs to be redone any way. I can not figure out how to obtain an angle that will ensure the ball returns to the opposite half of the boundary no matter where it was hit. I have made several attempts to get the angle from variables such as direction of travel of the ball, current position within the boundary, and position of the paddle that made contact, but so far I have experienced failure. Currently, the code for changing the ball's direction is as follows:
public void bounce(){
boolean changeAngle = false;
if( bluePaddle.intersects( ball.getX(), ball.getY(), ball.getDiameter(), ball.getDiameter() ) ){
lastHit = 1;
changeAngle = true;
}
else if( redPaddle.intersects( ball.getX(), ball.getY(), ball.getDiameter(), ball.getDiameter() ) ){
lastHit = 2;
changeAngle = true;
}
if ( changeAngle ){
// Right side of boundary
if ( ball.getX() > center_x ) {
// Quadrant 4
if ( ball.getY() > center_y ){
// Slice 13
if ( ball.getY() - center_y > Math.sin(3 * Math.PI / 8) ){
angle = (double) ( randNum.nextInt(90) + 90 );
}
// Code for other slices omitted
}//end Quadrant 4
// Code for other quadrants omitted
}//end right side of boundary
// Code for Left side of boundary omitted
ball.setDx( (int) (speed * Math.cos(Math.toRadians(angle))) );
ball.setDy( (int) (speed * Math.sin(Math.toRadians(angle))) );
}//end if (changeAngle)
bouncing = false;
}//end bounce method
As you can see, as it is now, the angle is simply generated at random within a range that I thought would be good for each slice. To emphasize, I primarily need help with the math, implementing it with Java is secondary. The entire code (all .java and .class files) which compiles and runs can be found here: https://github.com/pideltajah/Pong360/tree/master/Pong360
The main method is in the Pong.java file.
Any help will be appreciated.
First, find where it hit on the paddle. You can do this like so in the case of the red paddle (the blue paddle will be similar, but you may need to swap ang0 and ang1):
Edges of paddle are defined by two angles on your circle, ang0 and ang1, where ang0 is the lower edge, and ang1 is the upper edge
Assume center of circle is point (0, 0) and the ball is at point pBall = (xBall, yBall)
The ball will be at a certain angle ballAng = atan2(yBall, xBall) within the range [ang0 .. ang1]
Now convert its angle position on the paddle into a parameter between [0 .. 1].
You can define this as
u = (ballAng - ang0) / (ang1 - ang0);
Now you want to map it to the centerline, like so:
Say that the bottom position of the circle centerline is point p0, and the top of the centerline is point p1
Now define the point of intersection with the centerline as
p = p0 + u * (p1 - p0)
As a velocity vector for the ball, this needs to be the normalized difference vector
velBall = normalize(p - pBall)
Hope this makes sense
[EDIT: made a correction]
I've got a ball that I can move around on a map consisting of equally sized tiles. The player should not be able to walk over the tiles that are darker and have a black border. I've got a multidimensional array of the tiles that I use to check which tiles are solid.
I would like the player to slide against the wall if he is moving both horizontally and vertically into it. The problem is that if he does that he sticks to the wall. I managed to get it working perfectly on each axis, but separately. Here is my code for the horizontal collision checking:
if (vx < 0) {
// checks for solid tiles left of the player
if (level.isBlocked(i, j) || level.isBlocked(i, jj)) {
x = side * (i + 1); // moves player to left side of tile
vx = 0;
}
} else if (vx > 0) {
// checks for solid tiles right of the player
if (level.isBlocked(ii, j) || level.isBlocked(ii, jj)) {
x = (ii * side) - getWidth(); // moves player to right side of tile
vx = 0;
}
}
The level.isBlocked() method checks if that index of the array is occupied by a solid tile. The i and j variables is which index in the array the player's top right corner is located on. The ii and jj variables is which index in the array the player's bottom right corner is located on.
This works fine, but then if I add the same chunk of code beneath but replacing x with y, vx with vy and so on the problem occurs. So I can add either the horizontal or vertical collision handling and it works, but not at the same time. I've seen a few articles explaining I have to separate them or something, but I didn't understand much of them. How can I check collision on both axes and keep the sliding effect?
I finally got it to work. Angelatlarge's answer was helpful in understanding the problem, but I decided to start from scratch. I ended up first calculating the new x and y position and storing them in separate variables. Then I checked the tile under the middle left of the player and the same with the middle right. I then set a boolean to true if the player was standing on a tile because of his horizontal speed. If there was no collision I set the real x variable to the new one I calculated earlier. I then repeated the same thing for the vertical collision.
This is for the horizontal checking:
float newX = x + vx * delta;
boolean xCollision = false;
if (vx < 0) {
int i = level.toIndex(x);
int j = level.toIndex(y + getHeight() / 2);
xCollision = level.isBlocked(i, j);
} else if (vx > 0) {
int i = level.toIndex(x + getWidth());
int j = level.toIndex(y + getHeight() / 2);
xCollision = level.isBlocked(i, j);
}
if (!xCollision) x = newX;
The problem is that with the setup you have, given a block and the player position, and also given the fact that they overlap, you don't know whether the player collided with a vertical or a horizontal wall of the block. So see this more clearly consider the following block and two collision paths
The top path will collide with the left wall, and requires a vx=0; (cessation of horizontal movement), while the bottom path collides with the bottom wall and will require vy=0;, or stopping of the vertical movement.
I think in order to do the kind of collision detection you want, you will want to compute intersections of the player path and the walls of the blocks, not just checking whether the player overlaps a block. You could hack the desired behavior by computing the overlapping rectange of the player rectangle and the block rectangle. Consider the following situation:
where the red seqare represents your player. The fact that the overlap rectangle (the small rectangle occupied where the player is on top of the block) is more wide than it is tall suggests that it was the vertical collision that happened, not a horizontal. This is not foolproof, however. And it still requires you to be able to access the shape of the block, rather than just stesting if a part of the player rectangle overlaps a block.
I'm trying to think of a way to efficiently and neatly determine whether a valid move is being made with a bishop in chess.
The piece will be moved from srcX,srcY to dstX,dstY
This is part of one of my ideas:
if(srcX < dstX && srcY < dstY) {
// Moving towards the top right of the board
// Determine the decrease in X coordinate
int deltaX = dstX-srcX;
// If the move is valid, the Y coordinate will have decreased by the same number as X
int validY = dstY-deltaX;
if(validY == srcY) {
validMove = true;
}
}
but it's going to be a bit long winded, doing that for ever corner.. Can anyone think of a nicer way?
I would break it up into two steps.
1) Is it a valid destination?
2) Are there obstructions?
The first is easy to calculate. Since a bishop can only move diagonals the deltaX and deltaY must be equal.
So, if( abs(srcX-dstX) == abs(srcY-dstY) )
That rules out logically invalid moves.
Then it is a simple matter iterating through the positions in between as you have done to check for obstructions.
If it's a diagonal the x and y move shoudl be the same, so...
return Math.abs(srcx - dstx) == Math.abs(srcy - dsty);
The move is valid if:
Destx-Desty = SourceX - SourceY OR
16 - DestX- DestY = SourceX - SourceY