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.
Related
I have a GUI Maze game that I am creating and I am having trouble implementing walls that prevent the player from moving. The walls are rectangles but the player is a circle so I had to create a collision detection method between a rectangle and a circle. The problem I am having is that my collidesWith() method is in the player class and it takes a single wall as a parameter and returns a string that tells you which side of the wall is the player intersecting with. This means I can only check if the player is colliding with one wall at a time. Here is the code for the collidesWith() method. x and y are the x and y coordinates of the player since this method is in the player class:
I should mention that the code above doesn't exactly work how I wanted it to since it only works when the player is coming from the sides. If the player is coming from the top or bottom, the player just goes through the wall.
The reason I need this method to return a string to tell me where the player is coming from is so that I can explicitly restrict the movement of the player when the up, down, left, right keys are pressed. This is in another class where all the GUI components are. Here is the code for that. I have created a wall object on top so that it can be passed as a parameter here when checking for intersection:
In my view, it is better to avoid to iterate through the whole array of wallsArraylist. If I understood correctly, it is necessary to explore neighbour cells, if yes, then you can explore neighbours in a two-dimensional array using this approach:
(pseudo-code)
row_limit = count(array);
if(row_limit > 0){
column_limit = count(array[0]);
for(x = max(0, i-1); x <= min(i+1, row_limit); x++){
for(y = max(0, j-1); y <= min(j+1, column_limit); y++){
if(x != i || y != j){
print array[x][y];
}
}
}
}
In addition, try to avoid magic strings. Replace "comingFromRight" and etc to constant string:
public static final String COMING_FROM_RIGHT = "comingFromRight";
So, my problem is that I'm currently working on a path-finding technique in an open world with tiles of various sizes. The object needs to find an optimal path to a destination inside an infinite world (it generates on the fly), which is filled with tiles of various sizes (which are not located on a set grid - they can have any location and size - and neither have to be integers). (The object has access to the data of all the tiles via and ArrayList). Now some factors that make this problem more difficult:
The objects itself has a size and cannot move through tiles. Therefore, it is possible for a path to exist that is too narrow for the object to move through.
The target destination may itself be a moving object.
It is possible for there to be dozens of such objects at the same time - so it is necessary for the algorithm to either be light on the system or for the path to be calculated in a few separate ticks of the program.
I tried implementing solutions for maze-solving techniques, but the main problem is the in most mazes, the tiles can only have very specific coordinates (such as whole numbers) and are always the same size.
I also tried rendering the scene as a giant conventional maze where tiles are actually pixels of tiles (so if i have a 20x40 tile it becomes a 20x40 block of 1x1 tiles), but ran into performance issues and the still didn't solve the issue with a path potentially being to narrow for the object to fit through.
EDIT:
Terribly sorry for my poor wording before, that happens when I'm trying to rush to a solution without fully understanding the question. So what I'm using the algorithm for at the moment is for NPC enemies to find their way to the player around obstacles. Here is an example of a scene:
The black circle with an arrow is the player, the black bush-like blobs are the NPC enemies. So this my my current algorithm I'm using for the enemy AI:
void move() { //part of the Enemy class, this function is called once each tick for every enemy
PVector velocity = new PVector(speed*game.dt, 0); //speed is a pre-set float denoting the enemy's speed, game.dt is deltatim
velocity.rotate(atan2(game.player.location.y-location.y, game.player.location.x-location.x)); //game.player.location is a PVector of the player's position, location is a PVector of this enemy's position
boolean init_collision = getTileCollision(); //getTileCollision is a boolean of whether this enemy is colliding with any tile
location.add(velocity);
boolean collision = getTileCollision();
if (!init_collision && collision) { //if the enemy happens to spawn inside a tile, let is move out of it before checking for collision
location.sub(velocity);
if (desired_heading != -1) { //desired heading is the angle, in radians, of which 90-degree angle the enemy wants to move in, by default set to -1 (see my descrition of this algorithm below)
velocity = new PVector(speed*game.dt, 0);
velocity.rotate(desired_heading);
location.add(velocity);
if (getTileCollision()) {
location.sub(velocity);
velocity = new PVector(speed*game.dt, 0);
velocity.rotate(current_heading); //current heading the an angle, in radians, of which 90-degree angle the enemy is currently moving in. set to -1 by default but can not equal -1 if desired_heading is not -1
location.add(velocity);
if (getTileCollision()) {
location.sub(velocity);
desired_heading = -1;
current_heading = -1;
}
} else {
desired_heading = -1;
current_heading = -1;
}
} else {
float original_heading = velocity.heading();
desired_heading = radians(round(degrees(velocity.heading())/90.0)*90.0); //round to the nearest 90 degrees
velocity = new PVector(speed*game.dt, 0);
velocity.rotate(desired_heading);
location.add(velocity);
if (getTileCollision()) {
location.sub(velocity);
}
float turn = radians(90);
while (true) { //if it cant move, try rotating 90 degrees and moving
velocity.rotate(turn);
location.add(velocity);
if (!getTileCollision() && abs(round(degrees(current_heading)) - round(degrees(velocity.heading()))) != 180) {
current_heading = velocity.heading();
break;
} else {
location.sub(velocity);
}
}
}
} else {
desired_heading = -1;
current_heading = -1;
}
}
So what my terrible code hopes to accomplish is the the enemy first tries to move directly at the player. If it encounters an obstacle, it will round its angle to the nearest 90 degrees, set desired_heading to this and try to move through. If it cant, it will rotate another 90 degrees and so forth, always keeping the original rounded angle in mind.
This doesn't work remotely well as first of all, rotating 90 degrees has a 50% chance to go in the exact wrong diretion, so I tried adding
if (abs(original_heading - velocity.heading()+turn) < abs(original_heading - velocity.heading()-turn)) {
turn = radians(-90);
}
right before the while (true) but that broke the algorithm completely (sometimes the enemy will freeze in deep thought and not move ever again).
What am I doing terribly wrong? Should I try a different algorithm or does this one have potential?
I hope this is a better question now...
Here is a link to a video I recorded of my issue: https://sendvid.com/rjpi6vnw
You'll notice that initially, I start at tile (0, 0) then after moving my character up and down multiple times, the screen will only go up to (1,0). So I lost a whole row of playable map. I only lose part of the map when my screen adjusts itself. You'll understand what I mean in a moment. I have a class called Player, and in it I have methods called moveRight(), moveLeft(), moveUp(), and moveDown(). I'm excluding all useless classes and methods in order to not waste your time. Here are my moveDown() and moveUp() methods:
public void moveUp(){
locY1 -= defaultMoveAmount;
if(viewShouldMoveVertically(locX1, locY1) == true){ //locX1 and locY1 refers to the player's bounds location as set by setBounds()
Display.uni.moveMapDown(defaultMoveAmount); //Display.uni just means in the Display class
}
}
public void moveDown(){
locY1 += defaultMoveAmount;
if(viewShouldMoveVertically(locX1, locY1) == true){
Display.uni.moveMapUp(defaultMoveAmount); //defaultMoveAmount is the # of pixels the player moves each time the program updates
}
}
So I have KeyListeners that decide when these methods are called. The viewShouldMoveVertically() method is as follows:
public boolean viewShouldMoveVertically(int X1, int Y1){
if(Y1 < screenCenterY){ //screenCenterY is the number of vertical pixels on my screen/2
return false;
}
return true;
}
The moveMapUp() or moveMapDown() method is then called in the Display class:
int backgroundX1 = 0;
int backgroundY1 = 0;
public void moveMapUp(int moveAmt){
backgroundY1 -= moveAmt;
background.setBounds(backgroundX1, backgroundY1, backgroundX2 , backgroundY2);
}
public void moveMapDown(int moveAmt){
backgroundY1 += moveAmt;
background.setBounds(backgroundX1, backgroundY1, backgroundX2 , backgroundY2);
}
So if you can't view the video at the link I posted, I'll describe the issue. When my character moves close to the edge of the map, I obviously wouldn't want the camera to show areas off of the map. So the camera stops, but my character may continue walking up to the border of the map. If my character is within 540 pixels of the top of the map, the camera won't move(I'm running on a 1920x1080 display). This is intended. When I move the character more than 540 pixels from the top of the map, the camera will now move with the player since he's in the center of the screen. But the issue is that IF and ONLY IF the camera ends up moving away from the top of the map, then I now lose exactly "defaultMoveAmount" pixels from the viewable area when I return to the top again. I can't seem to figure out how to fix this issue. Now, a little more you may end up wanting to know: I have the same issue moving horizontally as I have moving vertically. It is set up in the same way, so there was no point in making you guys read extra code. When viewing the video at the link, I have to click on the play button at the bottom left, or else it tries to make me add an extension to Chrome or something. The solution to my program's issue may end up being quite simple, but I just can't seem to figure it out. I ended up getting sick of it and decided getting a little help would be better than giving up for now. I am a beginner to programming, as I've only had 1 year of programming experience from an AP Compute Science class. I'm sure you may see a few things that seem dumb, and I welcome any suggestions or comments you may have, but please be aware that I am fairly new to this stuff. Primarily motion. And finally, I did not post a compile-able section of code due to things such as the graphics that are required. While I'm on here, if you have any suggestions or good references for figuring out whether a character is within an area in a large tile-like map, such as a door that can be opened, it would be appreciated.
I have solved the issue. Here is my updated viewShouldMoveVertically() method:
public boolean viewShouldMoveVertically(int X1, int Y1){
if( Y1 <= screenCenterY && previousY1 > screenCenterY ){ //if just touched highest camera point from below
return true;
}else if( Y1 >= mapPixelTotalY-screenCenterY && previousY1 < mapPixelTotalY-screenCenterY ){ //if just touched lowest camera point from above
return true;
} else if( Y1 <= screenCenterY){ //if just touched highest camera point from above or is just above
return false;
} else if( Y1 >= mapPixelTotalY-screenCenterY ){ //If touched lowest camera point from below or is just below
return false;
}
return true;
}
It turns out that when the player touches the "highest camera point from below" or the "highest camera point from above", different results are required. If touching the highest camera view from below, the camera still needs to move upwards just one more time. But when touching that same point from above, the camera shouldn't move, since the player was coming from an area that the camera wouldn't be vertically moving at.You can see my solution in the code. Also, I added a "previousY1" that records what the last Y1 value was, so that I can tell whether the player came from above or below.
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 am using DigitalOnScreenControl to Move Player Sprite.But the Problem is that My Player Sprite goes out of Emulator Screen.I want player sprite be restricted to move in particular bounds and also camera to focus on player sprite as and when my sprite moves on.
i am trying this code:
public Scene onLoadScene() {
// Auto-generated method stub
this.mEngine.registerUpdateHandler(new FPSLogger());
final Scene mScene=new Scene();
mScene.setBackground(new ColorBackground(0.09804f, 0.6274f, 0.8784f));
final int centerX=(CAMERA_WIDTH-this.mPlayerTextureRegion.getWidth())/2;
final int centerY=(CAMERA_HEIGHT-this.mPlayerTextureRegion.getHeight())/2;
final Sprite player=new Sprite(centerX, centerY, this.mPlayerTextureRegion);
this.mCamera.setChaseEntity(player);
final PhysicsHandler physicsHandler=new PhysicsHandler(player);
player.registerUpdateHandler(physicsHandler);
mScene.attachChild(player);
this.mDigitalOnScreenControl=new DigitalOnScreenControl(0,CAMERA_HEIGHT-mOnScreenControlBaseTextureRegion.getHeight(),this.mCamera,this.mOnScreenControlBaseTextureRegion,this.mOnScreenControlKnobTextureRegion,0.1f,new IOnScreenControlListener() {
#Override
public void onControlChange(BaseOnScreenControl pBaseOnScreenControl,
float pValueX, float pValueY) {
// TODO Auto-generated method stub
physicsHandler.setVelocity(pValueX*100, pValueY*100);
}
});
this.mDigitalOnScreenControl.getControlBase().setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
this.mDigitalOnScreenControl.getControlBase().setAlpha(0.5f);
this.mDigitalOnScreenControl.getControlBase().setScaleCenter(0, 128);
this.mDigitalOnScreenControl.getControlBase().setScale(1.25f);
this.mDigitalOnScreenControl.getControlKnob().setScale(1.25f);
this.mDigitalOnScreenControl.refreshControlKnobPosition();
mScene.setChildScene(this.mDigitalOnScreenControl);
return mScene;
}
Honestly I am Very new in andEngine Development
please Help me With Heart
Thank in advance.
Binding the Player to the Camera
What you're asking for is a form of collision handling. Essentially, when you've moved the player outside of the scene, you want to move the player back into the scene, and you want to do that in the same update as you've moved the player, immediately after moving the player, so the out-of-scene location never gets rendered.
Lets take apart what I just said. You move the player here:
physicsHandler.setVelocity(pValueX*100, pValueY*100);
Moving the player with a physicsHandler is perfectly reasonable and takes into account variable frame-rate so, so far, good job. Now you want to move them back into the scene if they're outside it. So we're going to need a new method. Lets throw in a method call right after moving the player (we'll call it "adjustToSceneBinding()" so you have this:
physicsHandler.setVelocity(pValueX*100, pValueY*100);
adjustToSceneBinding(player);
Sidenote, I'd rather have this method be on the player (i.e., player.adjustToSceneBinding()), but your player object is just a sprite. I'd suggest you change that to a custom object that extends sprite so you can throw in your own code there. In any event, lets make the adjustToSceneBinding() method.
public boolean adjustToSceneBinding(Sprite player) {
// Correct the X Boundaries.
if (player.getX() < 0) {
player.setX(0);
} else if (player.getX() + player.getWidth() > CAMERA_WIDTH) {
player.setX(CAMERA_WIDTH - player.getWidth());
}
}
See what I did? If the player's X coordinate is less than zero, move them back to zero. If the player's X PLUS the player's width is more than the camera width, move them back to the camera width minus the player width. Don't forget the player X coordinate represents the left side of the sprite. That's why I'm taking into account the width in addition to the position for calculating the right side.
The above code only addresses left/right movement, but I'm going to let you extrapolate how to do the same for up down.
Binding the Player to the Map Size Instead
Now, what we did so far was really intended for the situation where the map "size" is no larger than the screen. But often what you'll want is a larger map than the screen size and a camera that chases the player around. To do that, just replace the code above, where I referred to the CAMERA_WIDTH with a number that represents the width of your "map". In other words, the map can be much larger than the camera. If you use larger numbers there (than, say, the width of the camera), and you set the camera to chase the player entity (as you have), then you'll have a map where the player can move around until they hit the bounds of the map.
Hope this helps! :)