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.
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 trying to make the player rotate and face a target position, and have encountered a problem I am unable to fix.
When the ship is rotating towards the target position, when it reaches directly below the target position, it goes crazy and moves left and right while going downwards (away from the target position) and eventually off the screen.
I do not want to limit the screen as it will look weird when it is slowly rotating if it is near the screen limit. In this case if I do limit the screen, it will still get stuck at the bottom as it keeps turning left and right. I uploaded an image on imgur to try to better explain my problem.
Yellow is the direction ship is moving (it is rotating towards the target position but will cross the red line), Green is an imaginary vector towards the target position, Red is another imaginary vector which represents whenever the ship goes crazy. When the green line goes on top of the red line, the ship begins to bug out and go out the screen.
public void ai() {
switch (playerArrived()) {
case 0:
break;
case 1:
pTargetPos.set(ran(0, Gdx.graphics.getWidth()), ran(0, Gdx.graphics.getHeight()));
System.out.println("Player Destination Reached: " + pPos.x + "," + pPos.y + " Moving to new point: " + pTargetPos.x + "," + pTargetPos.y);
break;
}
turn();
move();
ePlayerBody.set(pPos.x, pPos.y, pWidth);
}
public int playerArrived() {
if (pTargetPos.x < pPos.x + pWidth && pTargetPos.x > pPos.x - pWidth && pTargetPos.y < pPos.y + pHeight && pTargetPos.y > pPos.y - pHeight) {
return 1;
} else {
return 0;
}
}
public float ran(float low, float high) {
return (float) (Math.random() * (high - low + 1)) + low;
}
public void move() {
pVel.set(pTargetPos.x - pPos.x, pTargetPos.y - pPos.y);
pVel.nor();
pVel.x *= sMaxSpeed + Gdx.graphics.getDeltaTime();
pVel.y *= sMaxSpeed + Gdx.graphics.getDeltaTime();
pVel.setAngle(pNewRotation + 90);
pPos.add(pVel);
}
public void turn() {
pRotation = ((Math.atan2(pTargetPos.x - pPos.x, -(pTargetPos.y - pPos.y)) * 180.0d / Math.PI)+180.0f);
pNewRotation += (pRotation - pNewRotation) * Gdx.graphics.getDeltaTime();
System.out.println(pRotation+" "+pNewRotation);
}
I uploaded an image on imgur to try to better explain my problem
When your ship is directly below, and just a little bit to the left of, the target, then the call Math.atan2(pTargetPos.x - pPos.x, -(pTargetPos.y - pPos.y)) will return a value very close to π/2. When your ship is directly below, and just a little to the right of, the target, that call will return -π/2. As you cross the red line, your direction will flip-flop by 180 degrees (π radians). That's why it's "going crazy".
You need to figure out a better way to determine what angle you should turn towards. I'd offer suggestions on that, but I'm still unclear on exactly what behaviour you are expecting.
This worked for me:
public void turn() {
pRotation = ((tPos.y - pPos.y) / (tPos.x - pPos.x) * 180.0d / Math.PI);
pNewRotation += (pRotation - pNewRotation) * Gdx.graphics.getDeltaTime();
}
public void move() {
pVel.set(pDir);
pVel.nor();
pVel.x *= sMaxSpeed + Gdx.graphics.getDeltaTime();
pVel.y *= sMaxSpeed + Gdx.graphics.getDeltaTime();
pVel.setAngle(pNewRotation + 90);
pPos.add(pVel);
}
I want to implement a kind of drums. For every hit I want to play a song. So I need to detect every "hit" and the position. before I start implementing a function who will analyse the positions and detect "hits" I want to be sure that there is no other solution, so is there any event, gesture detection who allow me to detect that ?
As far as I know there is no "event" that is defined natively other than stream callbacks which are called when you receive data such as the joint positions and depth image which should be enough to get you started.
I would take a look at this:https://code.google.com/p/kineticspace/ in order to know what to expect or how to proceed with your own code.
Once you manage to get the skeleton data just find where the hand is at that time, put a threshold to its position and start tracking for a certain amount time and see if its movement path fits your pattern for a particular gesture such as "translation in y direction for x amount of seconds". Then you have very simple "hit" gesture detection. This can get as complex as you need, but there is not much to it at the basics in terms of what you receive from the library side.
Good luck.
I made a drum kit with the Kinect using this is a wonderful class for placing and using boxes in Kinect.
Import Libraries:
import processing.opengl.*;
import SimpleOpenNI.*;
Use something like this bit of code inside your Setup()
myTrigger = new Hotpoint(200, 10, 800, size);
Use the methods inside your draw()
if(myTrigger.currentlyHit()) {
myTrigger.play();
println("trigger hit");
}
Use the following methods inside this class!
class Hotpoint {
PVector center;
color fillColor;
color strokeColor;
int size;
int pointsIncluded;
int maxPoints;
boolean wasJustHit;
int threshold;
Hotpoint(float centerX, float centerY, float centerZ, int boxSize) {
center = new PVector(centerX, centerY, centerZ);
size = boxSize;
pointsIncluded = 0;
maxPoints = 1000;
threshold = 0;
fillColor = strokeColor = color(random(255), random(255), random(255));
}
void setThreshold( int newThreshold ){
threshold = newThreshold;
}
void setMaxPoints(int newMaxPoints) {
maxPoints = newMaxPoints;
}
void setColor(float red, float blue, float green){
fillColor = strokeColor = color(red, blue, green);
}
boolean check(PVector point) {
boolean result = false;
if (point.x > center.x - size/2 && point.x < center.x + size/2) {
if (point.y > center.y - size/2 && point.y < center.y + size/2) {
if (point.z > center.z - size/2 && point.z < center.z + size/2) {
result = true;
pointsIncluded++;
}
}
}
return result;
}
void draw() {
pushMatrix();
translate(center.x, center.y, center.z);
fill(red(fillColor), blue(fillColor), green(fillColor),
255 * percentIncluded());
stroke(red(strokeColor), blue(strokeColor), green(strokeColor), 255);
box(size);
popMatrix();
}
float percentIncluded() {
return map(pointsIncluded, 0, maxPoints, 0, 1);
}
boolean currentlyHit() {
return (pointsIncluded > threshold);
}
boolean isHit() {
return currentlyHit() && !wasJustHit;
}
void clear() {
wasJustHit = currentlyHit();
pointsIncluded = 0;
}
}
So I understand that I'm not coding this the best way possible at the moment; this is a sort of test run. What I'm trying to do is wall collisions using rectangles and the intersects property (sorry if I'm not using the correct terminology). So far I have 2 rectangles on screen. 1 the player controls and the other which the play is colliding with. When they collide the player stops moving. The problem is that if the player is trying to move into the rectangle while they are already colliding then the player can't move in any direction perpendicular to the movement ie if the player is holding the right arrow key moving into the rectangle, then they cannot move up or down. The game works on the premise that if your x or y coordinates aren't valid, then you will be moved back to the last valid coordinate recorded but I'm having trouble detecting the valid x and y coordinate separately. Here is the code:
public void Collision()
{
if(x < 0)
x = 0;
if(x > 400 - width)
x = 400 - width;
if(y < 0)
y = 0;
if(y > 300 - height)
y = 300 - height;
rect1 = new Rectangle(x, y, 16, 16);
rect2 = new Rectangle(sx, sy, wid, hei);
if(!rect1.intersects(rect2))
{
validX = true;
validY = true;
}
else
{
validX = false;
validY = false;
}
if(validX)
{
lastValidX = x;
}
if(validY)
{
lastValidY = y;
}
if(!validX)
{
x = lastValidX;
}
if(!validY)
{
y = lastValidY;
}
}
The Collision() method in the Guy class is where I'm having the trouble I believe. Yes my code is pretty messy right now but this is only a test.
Thanks, David.
You can implement what you're describing by doing extra logic around here (i.e. detecting cases when one is false and the other is true):
if(!rect1.intersects(rect2))
{
validX = true;
validY = true;
}
else
{
validX = false;
validY = false;
}
However, it seems like maybe you shouldn't be allowing the rectangles to ever be in a "colliding" state in the first place. For example, you can change the Move method to do something like
public void Move()
{
int oldX = x, oldY = y;
x += dx;
y += dy;
if (Collision()) {
x = oldX;
y = oldY;
}
}
I have been working on this artificial intelligence method for a while. It basically has an int for each direction the enemy could go if a wall were blocking his path to the player. This doesn't work in most cases. Sometimes the enemy will go through cracks it can't fit through. Other times it will be stuck on walls that have obvious gaps in them. I will attach my code, but if it looks too inefficient or just not the way to solve it I'm not opposed to changing my approach altogether. I just would like to know how these things are done normally, so that I can implement it in a better (and working!) way.
My code:
public void update(ArrayList<Wall> walls, Player p){
findPlayer(p.getX(), p.getY());
boolean isCollision = false;
System.out.println(isCollision);
//if movement straight towards the player is blocked, move along the walls
for(Wall w : walls){
if(Helper.isBoundingBoxCollision((int)(x + vectorToPlayer.getDX() * SPEED), (int)(y + vectorToPlayer.getDY() * SPEED), width, height, w.getX(), w.getY(), w.width, w.height)){
isCollision = true;
if(Math.abs(vectorToPlayer.getDX()) > Math.abs(vectorToPlayer.getDY())){
if(vectorToPlayer.getDX() > 0)
WALL_COLLISION = 3;
else
WALL_COLLISION = 1;
}
else if(Math.abs(vectorToPlayer.getDX()) < Math.abs(vectorToPlayer.getDY())){
if(vectorToPlayer.getDY() > 0)
WALL_COLLISION = 0;
else
WALL_COLLISION = 2;
}
}
}
//System.out.println(isCollision);
//set the direction to the straight on vector, to be reset if there is a collision on this path
direction = vectorToPlayer;
if(isCollision){
//reset the variable, don't mind that what this is named is completely opposite = PIMPIN'
isCollision = false;
//scale dem walls son, and see when the path is clear
for(Wall w : walls){
if(WALL_COLLISION == 0 && !Helper.isBoundingBoxCollision(x + SPEED, y, width, height, w.getX(), w.getY(), w.width, w.height)){
WALL_COLLISION = 3;
isCollision = true;
}
else if(WALL_COLLISION == 1 && !Helper.isBoundingBoxCollision(x, y + SPEED, width, height, w.getX(), w.getY(), w.width, w.height)){
WALL_COLLISION--;
isCollision = true;
}
else if(WALL_COLLISION == 2 && !Helper.isBoundingBoxCollision(x - SPEED, y, width, height, w.getX(), w.getY(), w.width, w.height)){
WALL_COLLISION--;
isCollision = true;
}
else if(WALL_COLLISION == 3 && !Helper.isBoundingBoxCollision(x, y - SPEED, width, height, w.getX(), w.getY(), w.width, w.height)){
WALL_COLLISION--;
isCollision = true;
}
}
//if there is NOT a wall on the designated side, set the vector accoridingly
if(isCollision){
if(WALL_COLLISION == 0)
direction = new NVector(0, 1);
else if(WALL_COLLISION == 1)
direction = new NVector(1, 0);
else if(WALL_COLLISION == 2)
direction = new NVector(0, -1);
else if(WALL_COLLISION == 3)
direction = new NVector(-1, 0);
}
}
x += Math.round(direction.getDX()*SPEED);
y += Math.round(direction.getDY()*SPEED);
}
It appears that what you are currently trying to implement is known as Steering, but the way these things are normally done would be Pathfinding. Which you decide to use depends on your application. Steering is done by moving toward your target but changing direction if there is an obstacle, and it is not guaranteed to reach its destination. Pathfinding is usually done by constructing a graph of waypoints or areas that are "walkable" and then using an algorithm such as Dijkstra's to traverse it.