简体   繁体   中英

How can I fix this collision detection block?

I've been working on this brickbreaker game in Java using the LibGDX framework. This is my first java game. I have a little experience with other languages but am having a bit of trouble. I have my collision detection set up and it works for the most part. The ball bounces in the incorrect direction sometimes and sometimes it will hit a block it shouldn't. I've been searching a bit but have hard trouble translating it to my game.

My code really sucks as the ball only moves at a 45 degree angle..Not very realistic, that will be my next step after getting this fixed.

public void checkBrickCollision()
{
    for (int i=0;i<level.brickCount;i++) {

            if (level.bricks[i].GetVisible() == true) {
                if (level.bricks[i].getRect().overlaps(ball.getRect()))
                {



        int xd = (int) Math.abs( (ball.ballRect.x + ball.ballRect.width - level.bricks[i].brickRect.x - level.bricks[i].getRect().width) /2 );
        int yd = (int) Math.abs( (ball.ballRect.y + ball.ballRect.height - level.bricks[i].brickRect.y - level.bricks[i].getRect().height) /2 );

        if (xd > yd)
        {
            // Collision on top or bottom, reverse y velocity
            ball.ballSpeedY = -ball.ballSpeedY;
            Score score = new Score(level.bricks[i].getScore(),(int)level.bricks[i].brickRect.x,(int)level.bricks[i].brickRect.y);
            scoreList.add(score);
            level.bricks[i].Destroy();

            System.out.println("Top/Bottom");
            return;
        }


        if (yd > xd)
        {
            // Collision on left or right, reverse x velocity
            ball.ballSpeedX = -ball.ballSpeedX;
            Score score = new Score(level.bricks[i].getScore(),(int)level.bricks[i].brickRect.x,(int)level.bricks[i].brickRect.y);
            scoreList.add(score);
            level.bricks[i].Destroy();

            System.out.println("Sides");
            return;
        }

        if (xd == yd)
        {
            // Collision on corners, reverse both
            ball.ballSpeedX = -ball.ballSpeedX;
            ball.ballSpeedY = -ball.ballSpeedY;
            Score score = new Score(level.bricks[i].getScore(),(int)level.bricks[i].brickRect.x,(int)level.bricks[i].brickRect.y);
            scoreList.add(score);
            level.bricks[i].Destroy();

            System.out.println("Corners");
            return;
        }           



                }       
}
}
}

I suggest you place your level.bricks[] array in an ArrayList list so you can cheaply remove() and/or destroy() them from your lists on the fly, avoiding checking for nulls each iteration in the level.bricks array (unless your setup doesn't mind null's in your array). Plus it will save you from having to check the size of the array, each iteration, for all the bricks, during every render cycle...

List<Brick> brickList = new ArrayList<Brick>();
for (brick: level.bricks) {
    brickList.add( new Brick(brick) );
}
//-------or-------
//if the brick object is a Sprite
for (brick: level.bricks) {
    brickList.add(brick);
}

I believe the issue of the wrong bricks being detected as "hit" has something to do with trying to compensate for the ball texture and bounds being a Rectangle . I would recommend using the com.badlogic.gdx.math.Circle class to define the bounds of the ball. The following is a dirty custom collision wrapper for com.badlogic.gdx.math.Intersector that should work with what you were attempting above. It assumes the visual pixels of the ball extend to the edge of the texture and that the ball texture is square:

public class Collider {
    private String bounceType = "";
    private boolean vertical = false;
    private boolean horizontal = false;

    // Segment Vertices of the Rectangle
    private Vector2 leftStart = new Vector2();
    private Vector2 leftEnd = new Vector2();
    private Vector2 topStart = new Vector2();
    private Vector2 topEnd = new Vector2();
    private Vector2 rightStart = new Vector2();
    private Vector2 rightEnd = new Vector2();
    private Vector2 bottomStart = new Vector2();
    private Vector2 bottomEnd = new Vector2();

    private Vector2 center = new Vector2();
    private Circle ballBounds = new Circle();

    // Pointers passed once during construction
    private Ball ball;
    private List<Brick> brickList;
    private List<Score> scoreList;

    /**
     * Constructor requires that ball and brickList pointers are given.
     * <p>
     * Runs the updateBallBounds() method after assigning the parameters
     *
     *@param ball points to the ball to be used for the collision calculations
     *@param brickList points to a list of brick objects to check against
     *@param scoreList points to a list of score objects to track the score
     */
    public Collider(Ball ball, List<Brick> brickList, List<Score> scoreList) {
        this.ball = ball;
        this.brickList = brickList;
        updateBallBounds(this.ball, this.ballBounds);
    }

    /**
     * Sets the position and radius of the bounding circle
     * for the given ball with a rectangular shape in order
     * to prepare it for bounds checking.
     *
     * @param ball The ball object to 
     * @param bounds The circle object that will store the bounds information
     */
    private void updateBallBounds(Ball ball, Circle bounds) {    
        bounds.set( (ball.ballRect.x + (ball.ballRect.width/2)),  //Center x pos
                    (ball.ballRect.y + (ball.ballRect.height/2)), //Center y pos
                    (ball.ballRect.width / 2) ); //Radius of ball
    }

    /**
     * Builds the start and end Vectors for each of the segments
     * of the provided rectangle. Also builds the center Vector for
     * the ball.
     * <p>
     * Used to prepare for finding which segments of the rectangle the 
     * ball is intersecting 
     *  
     * @param brickRect The rectangle to process the line segments from
     */
    private setVectors(Rectangle brickRect) {
        leftStart.set(brickRect.x, brickRect.y);
        leftEnd.set(brickRect.x, brickRect.height);
        topStart.set(brickRect.x, brickRect.height);
        topEnd.set(brickRect.width, brickRect.height);
        rightStart.set(Poyline( brickRect.width, brickRect.y);
        rightEnd .set(brickRect.width, brickRect.height);
        bottomStart.set(brickRect.x, brickRect.y);
        bottomEnd.set(brickRect.width, brickRect.y);

        center.set(ballBounds.x, ballBounds.y);
    }

    /**
     * Finds bricks in the list that the ball is currently
     * colliding with.
     * <p>
     * For every rectangle that the ball is currently colliding with,
     * the method calls the setVectors() method to prepare the start-end
     * vertices for the processCollision() method.
     * <p>
     * WARNING: this may not handle multiple collision very well. It
     * should work, but you most likely will not give very good results 
     * on multiple brick hit detected. You should think about including 
     * a break statement after the first collision is detected and let 
     * the Collider find an additional collision during the next render()
     * call.
     */  
    public void detectCollisions() {
        updateBallBounds(ball, ballBounds);
        for (brick: brickList) {
            if( Intersector.overlaps(ballBounds, brick.brickRect) ) {
                setVectors(brick.brickRect);
                processCollision(brick, brick.brickRect);
                //break;
            }
        }
    }

    /**
     * Detects how to handle the collision based on the segments being hit.
     *
     *@param brick the brick found by detectCollision() method. will be
     *             destroyed after collision is processed
     */  
    public void processCollision(Brick brick) {
        if ( Intersector.intersectSegmentCircle( topStart, topEnd,
                                                 center * center,
                                                 ballBounds.radius ) ||
             Intersector.intersectSegmentCircle( bottomStart, bottomEnd,
                                                 center * center,
                                                 ballBounds.radius )) {
            vertical = true;
        }

        if ( Intersector.intersectSegmentCircle( leftStart, leftEnd,
                                                 center * center,
                                                 ballBounds.radius ) ||
             Intersector.intersectSegmentCircle( rightStart, rightEnd,
                                                 center * center,
                                                 ballBounds.radius ) ) {
            horizontal = true;
        }

        // The following could return the value to a calling entity.
        // Then the game logic of what to do here would be decoupled.
        if (vertical && horizontal) {
            bounceType = "CORNER"
        } else if (vertical && !horizontal) {
            bounceType = "VERTICAL"
        } else if () {
            bounceType = "HORIZONTAL"
        } else {
            // The game blows up...
        }

        switch (bounceType) {
            case "VERTICAL":
                ball.ballSpeedY = -ball.ballSpeedY;
                break;
            case "HORIZONTAL":
                ball.ballSpeedX = -ball.ballSpeedX;
                break;
            case "CORNER":
                ball.ballSpeedY = -ball.ballSpeedY;
                ball.ballSpeedX = -ball.ballSpeedX;
                break;
            default: // Try not to blow up the game...
                break;
        }
        Score score = new Score(brick.getScore(), brick.x, brick.y)
        scoreList.add(score);
        brickList.remove(brick);
        brick.destroy();
    }
}

I'm sure there are errors here, it is not tested. In addition to the previous assumptions, it doesn't stop the brick from being rendered in your array (unless the destroy() takes care of that, and hopefully not by looking for nulls as it renders...).

The basic structure could be made better...

  • With some Except, try, catch stuff.
  • You could add more Typed constructors and methods to cover more shapes.
  • Space Partitioning could be added with a method to map the static bounds on construction. Then your dynamic objects could log the partitions they currently occupy and only query collision results for the other objects logged in those partition lists.
  • I maintain that the implementation of this class should be decoupled and processed somewhere else.
  • That would also let you create a collision class or event that gets returned/triggered.

Hope this helped.


I would also recommend that you look into Box2D. Your requirements seem simple enough for an easy learning cure on it's use. This page can show you how it is configured and used with LibGDX. This would allow you to quickly implement material properties on objects, spin on the ball, speed changes, angular rebounds..., all kinds of goodness with automagic that will save your brain some cycles. The physics engine does all the math for you. You just initialize and step it, listening for events (like collisions).

Good luck with your game.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM