What I mean is that when I collide with the side of an entity and want to jump, I cannot move right/left as I have a flag which prevents this when I collide with the entity on the right/left side, like so:
This is code code I use to detect collision:
public void onCollision(Wrapper wrapper) {
if (horizontal_velocity == 0 && vertical_velocity == 0) {
return;
}
Entity e1 = wrapper.thisEntity; //the object we are controlling
Entity e2 = wrapper.otherEntity; //the object we are impacting with
Rectangle rect1 = wrapper.thisEntityRectangle;
Rectangle rect2 = wrapper.otherEntityRectangle;
int e1y = (int) rect1.getY(), e1x = (int) rect1.getX(), e1w = (int) rect1.getWidth(), e1h = (int) rect1.getHeight();
int e2y = (int) rect2.getY(), e2x = (int) rect2.getX(), e2w = (int) rect2.getWidth(), e2h = (int) rect2.getHeight();
e1x += e1w / 2;
e1w /= 2;
e1y += e1h / 2;
e1h /= 2;
//horizontal interaction -- x
/* ---+\\\\\\+---
* |------|
* | |
* e1 | e2 | e1
* | |
* |------|
* ---+\\\\\\+---
*/
boolean touchingSide = false;
if(e1y > e2y && e1y < e2y + e2h) {
//touchingSide = true;
if(e1x > e2x - e1w && e1x < e2x) { //going right || hitting left side
collisionsHappening.put(wrapper.otherEntity.getID(), "right");
canMoveRight = false;
horizontal_velocity = 0;
setX(e2x - e1w*2);
}
if(e1x < e2x + e2w + e1w && e1x > e2x) { //going left || hitting right side
collisionsHappening.put(wrapper.otherEntity.getID(), "left");
canMoveLeft = false;
horizontal_velocity = 0;
setX(e2x + e2w);
}
}
//vertical interaction -- y
/*
* \| e1 |\
* \+--+------+--+\
* \\\\| |\\\\
* \\\\| e2 |\\\\
* \\\\| |\\\\
* \+--+------+--+\
* \| e1 |\
*
*/
if(e1x > e2x - e1w && e1x < e2x + e2w && !touchingSide) {
if(e1y + e1h > e2y && e1y < e2y) { //going down || hitting top side
collisionsHappening.put(wrapper.otherEntity.getID(), collisionsHappening.get(wrapper.otherEntity.getID()) + "|top");
setY(e2y - e1h * 2);
vertical_velocity = 0;
//readyNextTurn = true;
isFalling = false;
onGround = true;
}
if(e1y - e1h < e2y + e2h && e1y > e2y) { //going up || hitting bottom side
collisionsHappening.put(wrapper.otherEntity.getID(), collisionsHappening.get(wrapper.otherEntity.getID()) + "|bottom");
setY(e2y + e2h + e1h);
vertical_velocity = 0;
//readyNextTurn = true;
isFalling = false;
onGround = true;
}
}
}
..and where the code is affected by the canMoveRight and canMoveLeft flags:
public void key(int keyCode) {
try {
onEdge();
switch (keyList.get(keyCode)) {
...
case MOVE_RIGHT:
for(Entity e : objectsCollidingWith) { //check if still colliding, if not, reset variable canMoveLeft inorder to not impair movement
if(collisionsHappening.get(e.getID()) == "right") {
canMoveRightNextFrame = true;
}
}
canMoveLeft = true;
if (!canMoveRight && !canMoveRightNextFrame) break;
if(canMoveRightNextFrame) {
canMoveRightNextFrame = false;
System.out.println(canMoveRightNextFrame);
}
if (horizontal_velocity == 0) {
horizontal_velocity = getXDisplacement();
}
changeX(horizontal_velocity);
direction = "right";
//horizontal_velocity = 0;
break;
case MOVE_LEFT:
for(Entity e : objectsCollidingWith) { //check if still colliding, if not, reset variable canMoveLeft inorder to not impair movement
if(collisionsHappening.get(e.getID()) == "left") {
canMoveLeftNextFrame = true;
}
}
canMoveRight = true;
if (!canMoveLeft && !canMoveLeftNextFrame) {
break;
}
canMoveLeftNextFrame = canMoveLeftNextFrame ? true : false; //if true, make false
if (horizontal_velocity == 0) {
horizontal_velocity = getXDisplacement();
}
changeX(-horizontal_velocity);
direction = "left";
//horizontal_velocity = 0;
break;
case JUMP:
jump();
}
} catch (NullPointerException e) {
//ignore - a key with no action pressed
}
}
What I have tried
When I look for collisions - not react to them as shown in the above code - I tried to implement a HashMap in which I put a String - either "left" or "right" - to the key Entity.getID() which returns a unique name, like so (after the lines of code that are relevant are // <<<) :
public Wrapper[] collisions(Entity thisEntity) {
ArrayList<Wrapper> wrappers = new ArrayList<Wrapper>();
Player p = null;
boolean isPlayer = thisEntity instanceof Player;
boolean onGround = false;
if (isPlayer) {
p = (Player) thisEntity;
}
try {
for (Entity e : getLevel().getEntities()) {
if (!thisEntity.equals(e)) {
collisionsHappening.put(e.getID(), ""); // <<<
objectsCollidingWith.add(e); // <<<
if (thisEntity.getRect().intersects(e.getRect())) {
wrappers.add(new Wrapper(thisEntity, e, thisEntity.getRect(), e.getRect(),
thisEntity.getRect().intersection(e.getRect())));
onGround = true;
//collisionsHappening.put(e.getID(), true);
//System.out.println("collision between " + e.getID() + " + " + thisEntity.getID());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (isPlayer) {
p.isTouching = onGround;
if (onGround) {
//System.out.println(onGround);
}
}
//Entity[] result = list.toArray(new Entity[1]);
return wrappers.toArray(new Wrapper[0]);
}
This system relies on keeping a flag 1 frame after the collision ceases in-order to remove the flags canMoveRight or canMoveLeft in the lines of code
for(Entity e : objectsCollidingWith) { //check if still colliding, if not, reset variable canMoveLeft inorder to not impair movement
if(collisionsHappening.get(e.getID()) == "left") {
canMoveLeftNextFrame = true;
}
}
canMoveRight = true;
if (!canMoveLeft && !canMoveLeftNextFrame) {
break;
}
if(canMoveLeftNextFrame) {
canMoveLeftNextFrame = false; //if true, make false
}
...
...and same for the canMoveRight flag...
However, this does not work, and I still cannot move mid-jump after I "scrape" the edge of an Entity unless I move in the opposite direction to "reset" the flag for the desired direction (line: case MOVE_RIGHT: ... canMoveLeft = true; and case MOVE_LEFT: ... canMoveRight = true;), as seen in the animation.
So, to conclude:
How can I make this system work?
Is there a more efficient/better way of doing this kind of thing?
Do you recommend I redo my collision?
Thanks.
It is actually impossible to approach this problem in such an event driven way for a game more advanced then Snake. I recommend that you focus on a simple update loop which updates each entity. Each entity then updates their own velocity and then position. Then check for collision and handle response. The key event handlers should not have any logic and be simply changing flags which the entities can poll.
As for the the collision and response part, that is where the meat of a simple platform game engine lies, and there are many different approaches. In the modern world of off the shelf physics engines, e.g. Box2D, you could simply let the physics system handle things. But from the sounds of things, you are not looking for that and what you want is what I call logic based physics.
If your world and entities consist of axis aligned boxes, you can separate horizontal and vertical motion into two discrete steps, for each entity. For each axis, each object tries to move as far as it can for (depending on velocity and collisions) if the object hits something, generate a collision event and set the velocity to zero on that axis. Repeat for the other axis and you will have a simple platform game engine. For this method you will need to handle the state of two objects touching carefully due to floating point numerical error. I recommend doing collision detection with integers (in the scale of the screen coordinates).
If you are looking to have an environment and entities which are not restricted to the axis aligned boxes, then you will have to get more complex and no longer rely on simply handling X then Y movement to give you things like a box sliding on the floor or down a wall. In this case I suggest you keep track of what surface your entity is standing or walking on so that you can handle their movement differently in each state. When they are in the air, normal movement, but when they are on some surface, the movement is constrained to horizontal movement and the vertical position is adjusted to keep them on the surface. You then will need to detect when they change states.
I hope you find this helpful. It turns out that it is not an easy topic to Google. (Update loops, collision detection, physics of motion, numerical integration and the geometry of collision response are all easy to Google). Most programmers seem to just take the approach of keep fiddling with it till it works, and so it never gets documented well.
P.S. Implicit Euler integration method is fine for most games.
Related
NOOBIE Question... please help! I have a tile-based game I'm building where I'm moving an icon up down left and right. The dimensions of the game is 15 tiles x 15 tiles. When I'm approaching the boundaries, how can I prevent the icon from moving off the "map". I'm using W (up) A (left) S (down) and D (right) for directional input.
What would be a good way to prevent that icon from going off the map once it's reached the maximum point on the X or Y axis? I defined PlayerY and PlayerX as points where the player exists on the map, but this is the code i currently have for movement.
if (choice.equals("w")){
playerY--;
}
else if (choice.equals("s")){
playerY++;
}
else if (choice.equals("d")){
playerX++;
}
else if (choice.equals("a")){
playerX--;
}
Would you put something here where you say something like player is at max value and d inputted, do nothing? But I don't know how you'd say "do nothing"...
if (player Y == 15 && choice.equals("d")){
________;
}
Sorry again for the dumb question... I'm relatively new to Java and still trying to get my bearings
I suggest maintaining some constants for the maximum x and y size. Then, add logic which checks the boundaries before making any move:
final int X_DIM = 15; // assuming the board be represented by an array, then 14 is the
final int Y_DIM = 15; // maximum index
if (choice.equals("w")) {
if (playerY > 0) {
playerY--;
}
else {
System.out.println("Error: Cannot make an off-board move");
}
}
else if (choice.equals("s")) {
if (playerY < Y_DIM - 1) {
playerY++;
}
else {
System.out.println("Error: Cannot make an off-board move");
}
}
else if (choice.equals("d")) {
if (playerX < Y_DIM - 1) {
playerX++;
}
else {
System.out.println("Error: Cannot make an off-board move");
}
}
else if (choice.equals("a")) {
if (playerX > 0) {
playerX--;
}
else {
System.out.println("Error: Cannot make an off-board move");
}
}
Before make an move, you have to check whether the move is valid or not. If the move is valid, move it. If not, don't make the move and optionally show a warning for the player.
boolean isValidMove(choice){
int nextX, nextY;
if (choice.equals("w")){
nextY = playerY-1;
}
else if (choice.equals("s")){
nextY = playerY+1;
}
else if (choice.equals("d")){
nextX = playerX+1;
}
else if (choice.equals("a")){
nextX = playerX-1;
}
//immediately return false if X or Y out of board
if(nextX<0||nextX>=15) return false;
if(nextY<0||nextY>=15) return false;
return true; //return true if nextX and nextY is in the board
}
In main method where run the game:
choice = //user input
if(isValidMove(choice)){
//do the move request and represent board game
} else{
//optionally show a warning to players
}
I suggest starting with a helper method to determine whether or not any given position is on the map or not:
private boolean isOnMap(int x, int y) {
return x >= 0 && x < 15
&& y >= 0 && y < 15;
}
Then, you can use that helper method to decide whether or not the move the user requested is valid, and only make the move if it is. I'd split the logic into two steps to make this easier to work with: first step is to naively calculate the new position that the user wants to move to, and then the second step is to decide if that new position is a valid move or not
// Step 1: calculate new position from requested move
int newX = playerX;
int newY = playerY;
if (choice.equals("w")){
newY--;
}
else if (choice.equals("s")){
newY++;
}
else if (choice.equals("d")){
newX++;
}
else if (choice.equals("a")){
newY--;
}
// Step 2: apply the new position, but only if the move is valid
if (isOnMap(newX, newY)) {
playerX = newX;
playerY = newY;
}
Just check if it is on the last or first tiles or not, like this:
if (choice.equals("w")){
if ( playerY - 1 >= 0 ) { // if the next player move in Y axis is positive, then move
playerY--;
}
}
else if (choice.equals("s")){
if ( playerY + 1 < 15 ) { // if the next player move in Y axis is less than 15 (because you have 15 tiles), then move
playerY++;
}
}
else if (choice.equals("d")){
if ( playerX + 1 < 15 ) { // if the next player move in X axis is less than 15 (because you have 15 tiles), then move
playerX++;
}
}
else if (choice.equals("a")){
if ( playerX - 1 >= 0 ) { // if the next player move in X axis is positive, then move
playerX--;
}
}
In my game, the player navigates a maze. I can't figure out how to do proper collision detection with the walls. It is easy to do collision detection for staying in a certain area:
if (x > rightWallX - playerWidth) x = rightWallX - playerWidth;
if (x < leftWallX) x = leftWallX;
//...
But how would I do collision detection for many walls?
I can do plain collision detection without correction (like if (intersecting) return true;), but I can't correct this correctly. If I just store the old x and y and reset them, then
The object never actually touches the wall
If the object can go up but is blocked to the right, it won't go up, it will just not move.
How is collision detection in a maze done?
The easiest way, once you have solved collision detection, to fix the collision is to move the actor to the closest valid position to where the actor would be were it not for the object it collides with. This assumes no inertia, but it is sufficient for maze-like games or top-down map-crawling games.
If you want to simplify your calculations further, you can limit yourself to detecting if changing the actor's x or y coordinate would be better. If your actor has an axis-aligned rectangular hit-box and all obstacles are axis-aligned rectangular as well (the simplest case), this assumption is indeed correct. However, the results might not be satisfactory in some other cases (potential artifact: speed boost from gliding diagonal walls - not the case in most maze games).
Keep in mind multiple collisions could happen concurrently (pushing against two walls). If there are no sharp angles between two walls that an actor could both intersect (say, if all your obstacles are axis aligned and sufficiently spaced), fixing each collision in turn will suffice - just don't stop after the first collision.
You can use Rectangle.intersects() method:
public Rectangle Player(){
return new Rectangle(PlayerX,PlayerY,PlayerWidth,PlayerHeight);
//we do this for getting players x and y values every tick
}
if(Player().intersects(new Rectangle(0,0,100,50)))//if(player touching wall)
new Rectangle(0,0,100,50) is just an example you can change it.
Ok so i'm currently making a 2D top down view game and I'm not sure how you created your maze. However, in my game my Level is created from a Tile[][] tiles = new Tile[levelWidth][levelHeight]; array. The way i handled collision detection was by checking the surrounding tiles to see if they were solid.
This is my getTile method.
public Tile[][] getTile(int x, int y) {
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
return new VoidTile();
} else {
return tiles[x][y];
}
}
In my Tile.java class i have a isSolid() method which returns whether the tile is solid or not. All of my tiles extend my Tile.java so they inherit this method and I override it in their constructor. As i said previously, I am not sure whether or not you use the same style of level implementation as i do. However, It is good practice to do it this way :)
Personally, I am not a big fan of using the .intersects() and .contains() methods for Sprite collision detection. I mainly use them for buttons and alike.
Ok so,
In my player.java class i have a checkBlockedDirection(int x, int y) method and it looks like this.
public void checkBlockedDirection(int x, int y) {
boolean u = map.getTile(x, y - 1).isSolid();
boolean d = map.getTile(x, y + 1).isSolid();
boolean l = map.getTile(x - 1, y).isSolid();
boolean r = map.getTile(x + 1, y).isSolid();
if (u) {
uBlocked = true;
System.out.println("up tile blocked");
} else {
uBlocked = false;
}
if (d) {
dBlocked = true;
System.out.println("down tile blocked");
} else {
dBlocked = false;
}
if (l) {
lBlocked = true;
System.out.println("left tile blocked");
} else {
lBlocked = false;
}
if (r) {
rBlocked = true;
System.out.println("right tile blocked");
} else {
rBlocked = false;
}
}
Then in my player update method i have this
public void tick() {
float dx = 0;
float dy = 0;
if (input.up.isPressed()) {
direction = 0;
} else if (input.down.isPressed()) {
direction = 2;
} else if (input.left.isPressed()) {
direction = 3;
} else if (input.right.isPressed()) {
direction = 1;
} else {
direction = 4; // standing
}
checkBlockedDirection((int)x, (int)y);
if (input.up.isPressed() && y > 0 && !uBlocked) {
dy += -speed;
} else if (input.down.isPressed() && y < map.getHeight() - 1 && !dBlocked) {
dy += speed;
} else if (input.left.isPressed() && x > 0 && !lBlocked) {
dx += -speed;
} else if (input.right.isPressed() && x < map.getWidth() - 1 && !rBlocked) {
dx += speed;
}
x += dx;
y += dy;
}
Basically it just checks whether or not the blocks up, down, left, or right are solid. If they are solid then it wont move and if they arent solid then you can move in the desired direction.
Not sure if this helps or not but it's just my take on this kind of grid collision detection :)
Hope this helps :)
Enjoy
So basically i'm currently working on a small project where the player will be able to fight stuff through move but the gravity is giving me kind of a problem. So basically, i got this method, checkGravity() which will check if isGravityApplicable() return true (does the entity's bottom collider(a Line2D) collide with one of the foothold?(another line2D))
If isGravityApplicable() returns true, then the gravity should be applied but the problem that i have is that anything above 1 pixel Y is way too quickly and even, moving the character by one pixel is extremely fast. I'm not sure whether i should fix my game loop or what?
public boolean isGravityApplicable() {
for (Line2D line : frame.getMap().getFootholds()) {
/*Does the bottom collider intersect the foothold?*/
/*v THIS CHECK DOESN'T WORK CORRECTLY FOR SOME REASONS v*/
if (!GraphicHelper.getLineCameraRelative(getBottomCollider(), frame.getCam()).intersectsLine(line)) {
return true;
}
/*Above check returned false, if the velocity.y is above 1, then it might
skip the line since it would be skipping 5 pixels for example at once.
this check should resolve that.*/
if (velocity.y > 0) {
Line2D collider = getPositionToVelocityCollider();
if (!collider.intersectsLine(line)) {
position.y = (int) line.getY1(); //Y1 or Y2, same sh*t.
return true;
}
}
}
return false;
}
public void checkGravity() {
if (isGravityApplicable()) {
if (isJumping) { //Player has jumped
velocity.y += 1;
velocity.y = velocity.y > TERMINAL_ACCELERATION ? (int) TERMINAL_ACCELERATION : velocity.y;
if (velocity.y < 0) {
isFalling = true;
}
} else if (isFalling) { //Player is currently falling but not from jumping. Most likely just spawned
velocity.y = 1;
/*Anything higher than 1 is WAY too quickly...*/
//velocity.y += 1;
//velocity.y = velocity.y > TERMINAL_VELOCITY ? (int) TERMINAL_VELOCITY : velocity.y;
} else if (isJumping && isFalling) { //Player has jumped and has reached its highest point, falling back down
velocity.y = 1;
/*Anything higher than 1 is WAY too quickly...*/
//velocity.y += 1;
//velocity.y = velocity.y > TERMINAL_VELOCITY ? (int) TERMINAL_VELOCITY : velocity.y;
} else { //Player hasn't jumped and has not started falling, he most likely just spawned
isFalling = true;
velocity.y = 1;
}
} else {
isFalling = false;
isJumping = false;
}
}
Thanks in advance. Oh and if there is anything i could of done better like the collisions which consist of simple lines all around the entities, please tell me. :) Thanks.
Nevermind guys, for some reason the update/tick method was called twice in the loop for some reasons. I guess i had a brain fart or something.
I'm trying to move sprites to stop the frames in center(or move them to certain x position) when right or left pressed on screen. There are 3 sprites created using box.java in the view, placed one after another with padding, stored in arraylist.
The problem: No smooth movement and doesn't stop in the center of each frames after movement has begun, sometimes all boxes are moving on top of each others, padding is totally lost. Please let me know what I'm doing wrong, thanks a lot!
//BEGINING OF BOX.JAVA >> The problem is in this class!
//This goes in Update();
private void boxMove()
{
int get_moved_pos = getMovedPos(); //get moved pos
int sprite_size = view.getSpriteSize(); //get sprite arraylist size
currentDirection = view.getDirection(); //get direction "left" or "right" from view
if(currentDirection == "right" && isMoving == false)
{
setSpriteMovedNext();
}else
if(currentDirection == "left" && isMoving == false)
{
setSpriteMovedPrev();
}
if(currentDirection != lastDirection)
{
lastDirection = currentDirection;
//MOVE RIGHT
if(currentDirection == "right" && get_moved_pos > 0) //move left and make sure that moved pos isn't overlapping / or moving to empty space
{
//Animate left until it reaches the new x position
if(x > get_new_pos_left)
{
x -= pSpeedX;
}
Log.d("RIGHT","POS: " + get_moved_pos);
}else
//MOVE LEFT
if(currentDirection == "left" && get_moved_pos < sprite_size-1) //move left and make sure that moved pos isn't overlapping / or moving to empty space
{
//Animate right until it reaches the new x position
if(x < get_new_pos_right)
{
x += pSpeedX;
}
}
}
}
//Call when screen is touched (in View.java), to set a new position to move to.
public void resetMoving()
{
isMoving = false;
this.lastDirection = "";
Log.d("RESET", "MOVING RESET");
}
public int getMovedPos()
{
return this.smoved_pos;
}
private void setSpriteMovedNext()
{
int get_max_moved = getMovedPos();
int s_size = view.getSpriteSize();
if (isMoving == false) //take a break between movements
{
if(get_max_moved < s_size-1)
{
Log.d("NEXT", "CALLED");
this.get_new_pos_right = x + view.getNextPosX(); //current x and next stop position
this.smoved_pos += 1;
this.isMoving = true; //set to avoid double touch
Log.d("NEXT", "X POS SET: " + get_max_moved);
}
}
}
private void setSpriteMovedPrev()
{
int get_max_moved = getMovedPos();
if (isMoving == false) //take a break between movements
{
if(get_max_moved > 0)
{
Log.d("PREV", "CALLED");
this.get_new_pos_left = x - view.getNextPosX(); //get current x pos and prev stop position
this.smoved_pos -= 1; //to limit the movements
this.isMoving = true; //set to avoid double touch
Log.d("PREV", "X POS SET: " + get_max_moved);
}
}
}
//END OF BOX.JAVA
//VIEW
//Add boxes
public void addBox()
{
int TOTAL_BOXES = 3;
int padding_left = 200;
int padding_tmp = this.getWidth()/2;
box.clear(); //clear old
//Box 1
box.add(new Boxes(box, this, "box1",
padding_tmp,
this.getHeight()/2,
boxSpriteImage, 1, 2, 0, 0));
padding_tmp += boxSpriteImage.getWidth()/TOTAL_BOXES + padding_left;
//Box 2
box.add(new Boxes(box, this, "box2",
padding_tmp,
this.getHeight()/2,
boxSpriteImage, 1, 2, 1, 1));
padding_tmp += boxSpriteImage.getWidth()/TOTAL_BOXES + padding_left;
//Box 3
box.add(new Boxes(box, this, "box3",
padding_tmp,
this.getHeight()/2,
boxSpriteImage, 1, 2, 2, 1));
}
public boolean onTouchEvent(MotionEvent event){
if (System.currentTimeMillis() - lastClick > 100){
lastClick = System.currentTimeMillis();
float x = event.getX();
float y = event.getY();
synchronized (getHolder())
{
if(isBoxWindow() == true)
{
if(x >= this.getWidth()/2)
{
Direction = "right";
}else
{
Direction = "left";
}
}
}
}
//called in box.java to get next x pos to move
public float getNextPosX()
{
int PADDING = 200; //padding between frames
next_pos_x = boxSprite.getWidth()/TOTAL_COLUMNS + PADDING;
return next_pos_x;
}
I think your error is in the if statements, where you compare currentDirection and lastDirection (I'm assuming that lastDirection is a String) with other Strings using the == operator. The == almost operator never works when you want to compare Objects for equality. You should use the equals() method.
For eg.
if(currentDirection != lastDirection)
should be written as:
if(!currentDirection.equals(lastDirection)
Make such changes in your code(They are needed at many places!) and I think your problem should be solved.
A good debugging practice would be logging data about your app, from each of the if blocks, to see if each of them is executed. You could have found out if your if statements are being executed.
EDIT: Why have you put this code?
if (System.currentTimeMillis() - lastClick > 100)
This means onTouchEvents are only interpreted after 100ms. remove it and check, probably that's what is causing the problem.
Alrite, decided to use onFling() method and call via View instead of adding the animations separately into the class itself, works really well when called box.get(i).update() in a loop of all added boxes, all of them animated equally. Thanks udiboy.
I am working on an Android game similar to the Rush Hour/Traffic Jam/Blocked puzzle games. The board is a square containing rectangular pieces. Long pieces may only move horizontally, and tall pieces may only move vertically. The object is to free the red piece and move it out of the board. This game is only my second ever programming project in any language, so any tips or best practices would be appreciated along with your answer.
I have a class for the game pieces called Pieces that describes how they are sized and drawn to the screen, gives them drag-and-drop functionality, and detects and handles collisions.
I then have an activity class called GameView which creates my layout and creates Pieces objects to add to a RelativeLayout called Board. I have considered making Board its own class, but haven't needed to yet.
Here's what my work in progress looks like:
My Question:
Most of this works perfectly fine except for my collision handling. It seems to be detecting collisions well but instead of pushing the pieces outside of each other when there is a collision, it frantically snaps back and forth between (what seems to be) where the piece is being dragged to and where it should be. It looks something like this:
Another oddity: when the dragged piece collides with a piece to its left, the collision handling seems to work perfectly. Only piece above, below, and to the right cause problems.
Here's the collision code:
#Override
public boolean onTouchEvent(MotionEvent event){
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//check if touch is on piece
if (eventX > x && eventX < (x+width) && eventY > y && eventY < (y+height)){
initialX=x;
initialY=y;
break;
}else{
return false;
}
case MotionEvent.ACTION_MOVE:
//determine if piece should move horizontally or vertically
if(width>height){
for (Pieces piece : aPieces) {
//if object equals itself in array, skip to next object
if(piece==this){
continue;
}
//if next to another piece,
//do not allow to move any further towards said piece
if(eventX<x&&(x==piece.right+1)){
return false;
}else if(eventX>x&&(x==piece.x-width-1)){
return false;
}
//move normally if no collision
//if collision, do not allow to move through other piece
if(collides(this,piece)==false){
x = (eventX-(width/2));
}else if(collidesLeft(this,piece)){
x = piece.right+1;
break;
}else if(collidesRight(this,piece)){
x = piece.x-width-1;
break;
}
}
break;
}else if(height>width){
for (Pieces piece : aPieces) {
if(piece==this){
continue;
}else if(collides(this,piece)==false){
y = (eventY-(height/2));
}else if(collidesUp(this,piece)){
y = piece.bottom+1;
break;
}else if(collidesDown(this,piece)){
y = piece.y-height-1;
break;
}
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
// end move
if(this.moves()){
GameView.counter++;
}
initialX=x;
initialY=y;
break;
}
// parse puzzle
invalidate();
return true;
}
This takes place during onDraw:
width = sizedBitmap.getWidth();
height = sizedBitmap.getHeight();
right = x+width;
bottom = y+height;
My collision-test methods look like this with different math for each:
private boolean collidesDown(Pieces piece1, Pieces piece2){
float x1 = piece1.x;
float y1 = piece1.y;
float r1 = piece1.right;
float b1 = piece1.bottom;
float x2 = piece2.x;
float y2 = piece2.y;
float r2 = piece2.right;
float b2 = piece2.bottom;
if((y1<y2)&&(y1<b2)&&(b1>=y2)&&(b1<b2)&&((x1>=x2&&x1<=r2)||(r1>=x2&&x1<=r2))){
return true;
}else{
return false;
}
}
private boolean collides(Pieces piece1, Pieces piece2){
if(collidesLeft(piece1,piece2)){
return true;
}else if(collidesRight(piece1,piece2)){
return true;
}else if(collidesUp(piece1,piece2)){
return true;
}else if(collidesDown(piece1,piece2)){
return true;
}else{
return false;
}
}
As a second question, should my x,y,right,bottom,width,height variables be ints instead of floats like they are now?
Also, any suggestions on how to implement things better would be greatly appreciated, even if not relevant to the question! Thanks in advance for the help and for sitting through such a long question!
Update:
I have gotten it working almost perfectly with the following code (this doesn't include the code for vertical pieces):
#Override
public boolean onTouchEvent(MotionEvent event){
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//check if touch is on piece
if (eventX > x && eventX < (x+width) && eventY > y && eventY < (y+height)){
initialX=x;
initialY=y;
break;
}else{
return false;
}
case MotionEvent.ACTION_MOVE:
//determine if piece should move horizontally or vertically
if(width>height){
for (Pieces piece : aPieces) {
//if object equals itself in array, skip to next object
if(piece==this){
continue;
}
//check if there the possibility for a horizontal collision
if(this.isAllignedHorizontallyWith(piece)){
//check for and handle collisions while moving left
if(this.isRightOf(piece)){
if(eventX>piece.right+(width/2)){
x = (int)(eventX-(width/2)); //move normally
}else{
x = piece.right+1;
}
}
//check for and handle collisions while moving right
if(this.isLeftOf(piece)){
if(eventX<piece.x-(width/2)){
x = (int)(eventX-(width/2));
}else{
x = piece.x-width-1;
}
}
break;
}else{
x = (int)(eventX-(width/2));
}
The only problem with this code is that it only detects collisions between the moving piece and one other (with preference to one on the left). If there is a piece to collide with on the left and another on the right, it will only detect collisions with the one on the left. I think this is because once it finds a possible collision, it handles it without finishing looping through the array holding all the pieces. How do I get it to check for multiple possible collisions at the same time?
My best guess is that the dragged piece is dragged into the other piece, then moved back so it is no longer colliding, then dragged into the other piece again. At least that is what I would expect if collision response happens after drawing.
On a side note, the following code makes me wonder a bit:
//if next to another piece,
//do not allow to move any further towards said piece
if(eventX<x&&(x==piece.right+1)){
return false;
}else if(eventX>x&&(x==piece.x-width-1)){
return false;
}
Checking for equality (==) on floats is generally a very bad idea, see zillions of "floating point precision" topics. Alternative a) use ints instead of floats. b) use ranged comparisions (>=, etc.). Don't use (a) though, there are many drawbacks with tiny (time) steps and such.
Try using the same approach as with the touch detection instead:
//check if touch is on piece
if (eventX > x && eventX < (x+width) && eventY > y && eventY < (y+height))
You should be able to do this more simply :)
Note that I made some assumptions on how you store the direction of movement, which you may have to adjust to your likings.
Also note that I treat piece1 as the moving piece we are concerned with, and piece2 simply as an object it collides with. So piece1 is repositioned, piece2 is not.
// Horizontal:
if( piece1.isMovingLeft )
piece1.x += Math.max( 0, piece2.right - piece1.x );
else if( piece1.isMovingRight )
piece1.x -= Math.max( 0, piece1.right - piece2.x );
// Vertical:
if( piece1.isMovingUp )
piece1.y -= Math.max( 0, piece2.bottom - piece1.y );
else if( piece1.isMovingDown )
piece1.y += Math.max( 0, piece1.bottom - piece2.y )
What I do here is as follows:
If the piece is moving in a certain direction, we move it back (a little bit) in the opposite direction to compensate for (possible) collision. We need to move it back by exactly the amount of overlapping pixels.
The amount of overlap, when moving left, for instance, is the right side of the second object minus the left side of the first object. (Negative means no overlap.)
The Math.max( 0, overlap ) ensures that said negative values become 0, i.e. no collision leads to no compensation.
The compensation is then applied in the direction opposite of movement, effectively taking piece1 out of piece2. (You can then choose to invert its movement direction or respond in any further way.)
You can apply this simple routine to every possible combination of two pieces, and they will no longer penetrate.
I hope this helps you out!
In reply to your second question:
The only problem with this code is that it only detects collisions between the moving piece and one other (with preference to one on the left). If there is a piece to collide with on the left and another on the right, it will only detect collisions with the one on the left.
There is a break statement within your loop - specifically, in the left collision check. ;)
I made a few changes and, so far, it has been working perfectly. Here is my new code:
case MotionEvent.ACTION_MOVE:
//determine if piece should move horizontally or vertically
if(width>height){
for (Pieces piece : aPieces) {
//if object equals itself in array, skip to next object
if(piece==this){
continue;
}
//check if there the possibility for a horizontal collision
if(this.isAllignedHorizontallyWith(piece)){
//check for and handle collisions while moving left
if(this.isRightOf(piece)){
if(eventX>piece.right+(width/2)){
x = (int)(eventX-(width/2)); //move normally
continue;
}else{
x = piece.right+1;
}
}else if(this.isLeftOf(piece)){ //check for and handle collisions while moving right
if(eventX<piece.x-(width/2)){
x = (int)(eventX-(width/2));
continue;
}else{
x = piece.x-width-1;
}
}else{
continue;
}
break;
}else{
x = (int)(eventX-(width/2));
}
}
}
if(height>width){
for (Pieces piece : aPieces) {
//if object equals itself in array, skip to next object
if(piece==this){
continue;
}
//check if there the possibility for a vertical collision
if(this.isAllignedVerticallyWith(piece)){
//check for and handle collisions while moving up
if(this.isBelow(piece)){
if(eventY>piece.bottom+(height/2)){
y = (int)(eventY-(height/2)); //move normally
continue;
}else{
y = piece.bottom+1;
}
}else if(this.isAbove(piece)){ //check for and handle collisions while moving down
if(eventY<piece.y-(height/2)){
y = (int)(eventY-(height/2));
continue;
}else{
y = piece.y-height-1;
}
}else{
continue;
}
break;
}else{
y = (int)(eventY-(height/2));
}
}
}
invalidate();
break;
And these are the methods I use:
private boolean isLeftOf(Pieces piece) {
int r1 = this.right;
int x2 = piece.initialX;
boolean bool = false;
if (r1<=x2){
for(Pieces piece2: aPieces){
if (piece2==piece || piece2==this){
continue;
}
if (this.isAllignedHorizontallyWith(piece2) && r1<=piece2.initialX){
if (piece.x>piece2.x){
bool = false;
break;
}else{
bool = true;
continue;
}
}else{
bool = true;
}
}
}else{
bool = false;
}
return bool;
}
private boolean isRightOf(Pieces piece) {
//True means that "this" is right of "piece" and "piece" is farther right than other possible pieces
int x1 = this.initialX;
int r2 = piece.right;
boolean bool = false;
if (x1>=r2){
for(Pieces piece2: aPieces){
if (piece2==piece || piece2==this){
continue;
}
if (this.isAllignedHorizontallyWith(piece2) && x1>=piece2.right){
if (piece.x<piece2.x){
bool = false;
break;
}else{
bool = true;
continue;
}
}else{
bool = true;
}
}
}else{
bool = false;
}
return bool;
}
private boolean isBelow(Pieces piece){
int y1 = this.initialY;
int b2 = piece.bottom;
boolean bool = false;
if (y1>=b2){
for(Pieces piece2: aPieces){
if (piece2==piece || piece2==this){
continue;
}
if (this.isAllignedVerticallyWith(piece2) && y1>=piece2.bottom){
if (piece.y<piece2.y){
bool = false;
break;
}else{
bool = true;
continue;
}
}else{
bool = true;
}
}
}else{
bool = false;
}
return bool;
}
private boolean isAbove(Pieces piece){
int y2 = piece.initialY;
int b1 = this.bottom;
boolean bool = false;
if (b1<=y2){
for(Pieces piece2: aPieces){
if (piece2==piece || piece2==this){
continue;
}
if (this.isAllignedVerticallyWith(piece2) && b1<=piece2.y){
if (piece.y>piece2.y){
bool = false;
break;
}else{
bool = true;
continue;
}
}else{
bool = true;
}
}
}else{
bool = false;
}
return bool;
}
private boolean isAllignedHorizontallyWith(Pieces piece) {
int y1 = this.y;
int b1 = this.bottom;
int y2 = piece.y;
int b2 = piece.bottom;
if((y1>=y2&&y1<=b2)||(b1>=y2&&b1<=b2)){
return true;
}else{
return false;
}
}
private boolean isAllignedVerticallyWith(Pieces piece) {
int x1 = this.x;
int r1 = this.right;
int x2 = piece.x;
int r2 = piece.right;
if((x1>=x2&&x1<=r2)||(r1>=x2&&r1<=r2)){
return true;
}else{
return false;
}
}
This could be commented better and probably done much simpler, but I am leaving it here for reference.
Nothing just get the your imageviews Left,right,top,bottom, and intersect with current x and y and find this solution more about this go to this link its also help i hop you got a right answer on this link
/technical/game-programming/collision-detection-algorithm-r754">http://www.gamedev.net/page/resources//technical/game-programming/collision-detection-algorithm-r754