Change enemy movement upon collision

72 Views Asked by At

I want my enemy to keep moving in a direction for say, up, and when it detects a collision, it changes direction to the right, and then another collision, and so on. My enemy is already stopping at the objects, but I'm having trouble figuring out the logic on how to change its direction.

My enemy class

package application;

import java.util.ArrayList;

import javafx.animation.AnimationTimer;
import javafx.fxml.FXML;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Rectangle;

public class Enemy extends Sprite{
    
    @FXML private ImageView enemy;
    double x;
    double y;

    public boolean canUp = true;
    public boolean canDown = true;
    public boolean canLeft = true; 
    public boolean canRight = true;
    
    private int movementVariable = 1;
    PlayerController playerController;
    
    public Enemy(ImageView enemy, double x, double y, PlayerController playerController) {
        super(x,y,enemy);
        this.enemy = enemy;
        this.x = x;
        this.y = y;
        this.playerController = playerController;
    }

    public void moveEnemy(ImageView player, AnchorPane scene, ArrayList<Rectangle> unbreakableObjects) {
        timer.start();
    }

    AnimationTimer timer = new AnimationTimer() {
        @Override
        public void handle(long timestamp) {
            double currentX = enemy.getLayoutX();
            double currentY = enemy.getLayoutY();

            // Update the enemy's position based on movement direction
            if (canUp) {
                enemy.setLayoutY(enemy.getLayoutY() - movementVariable);
                canUp = true;
                canDown = false;
                canLeft = false;
                canRight = false;
            } else if (canDown) {
                enemy.setLayoutY(enemy.getLayoutY() + movementVariable);
                canUp = false;
                canDown = true;
                canLeft = false;
                canRight = false;
            } else if (canLeft) {
                enemy.setLayoutX(enemy.getLayoutX() - movementVariable);
                canUp = false;
                canDown = false;
                canLeft = true;
                canRight = false;
            } else if (canRight) {
                enemy.setLayoutX(enemy.getLayoutX() + movementVariable);
                canUp = false;
                canDown = false;
                canLeft = false;
                canRight = true;
            }

            playerController.checkEnemyCollision(currentX, currentY);
        }
    }; 

    void moveUp() {
        enemy.setLayoutY(enemy.getLayoutY() - movementVariable);
    }
    
    public void moveDown() {
        enemy.setLayoutY(enemy.getLayoutY() + movementVariable);
    }
    
    public void moveLeft() {
        enemy.setLayoutX(enemy.getLayoutX() + movementVariable);
    }
    
    public void moveRight() {
        enemy.setLayoutX(enemy.getLayoutX() - movementVariable);
    }
}

My controller

package application;

import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;

import javafx.animation.AnimationTimer;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;

public class PlayerController implements Initializable{
    
    @FXML
    private ImageView player;
    private Player playerComponent;
    
    @FXML private AnchorPane scene;
    
    private double x = 0;
    private double y = 0;
    private int scoreCounter = 0;
    
    @FXML private int lives;
    @FXML private Text score;
    
    @FXML private Rectangle Bed;
    @FXML private Rectangle Plant;
    @FXML private Rectangle Shelf1;
    @FXML private Rectangle Shelf2;
    @FXML private Rectangle Box1;
    @FXML private Rectangle Box2;
    @FXML private Rectangle Box3;
    @FXML private Rectangle Box4;
    @FXML private Rectangle Cabinet1;
    @FXML private Rectangle TVset;
    @FXML private Rectangle chair1;
    @FXML private Rectangle chair2;
    @FXML private Rectangle Table1;
    @FXML private Rectangle Desk1;
    @FXML private Rectangle Desk2;
    @FXML private Rectangle Box5;
    @FXML private Rectangle Box6;
    @FXML private Rectangle Cabinet2;
    @FXML private Rectangle RightWall;
    @FXML private Rectangle TopWall;
    @FXML private Rectangle LeftWall;
    @FXML private Rectangle BottomWall;
    
    @FXML private ImageView breakable1;
    @FXML private ImageView breakable2;
    @FXML private ImageView breakable3;
    @FXML private ImageView breakable4;
    @FXML private ImageView breakable5;
    @FXML private ImageView breakable6;
    @FXML private ImageView breakable7;
    @FXML private ImageView breakable8;
    @FXML private ImageView breakable9;
    @FXML private ImageView breakable10;
    @FXML private ImageView breakable11;
    @FXML private ImageView breakable12;
    @FXML private ImageView breakable13;
    @FXML private ImageView breakable14;
    @FXML private ImageView breakable15;
    @FXML private ImageView breakable16;
    @FXML private ImageView breakable17;
    @FXML private ImageView breakable18;
    @FXML private ImageView breakable19;
    @FXML private ImageView breakable20;
    @FXML private ImageView breakable21;
    @FXML private ImageView breakable22;
    @FXML private ImageView breakable23;
    @FXML private ImageView breakable24;
    
    @FXML private ImageView Enemy1;
    private Enemy enemy1Component;

    private ArrayList<Rectangle> unbreakableObjects = new ArrayList<>();
    private ArrayList<ImageView> breakableObjects = new ArrayList<>();
    
    CollisionHandler collisionHandler = new CollisionHandler();
    
    AnimationTimer gameLoop;
    
    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        playerComponent = new Player(player, x, y, lives, scoreCounter, this);
        enemy1Component = new Enemy(Enemy1, x, y, this);
        
        gameLoop = new AnimationTimer() {

            @Override
            public void handle(long arg0) {
                playerComponent.makeMovable(player, scene, unbreakableObjects);
                enemy1Component.moveEnemy(Enemy1, scene, unbreakableObjects);
            }   
        };
        
        gameLoop.start();
        
        // add the unbreakable objects
        unbreakableObjects.add(Bed);
        unbreakableObjects.add(Cabinet1);
        unbreakableObjects.add(Shelf1);
        unbreakableObjects.add(Shelf2);
        unbreakableObjects.add(Box1);
        unbreakableObjects.add(Box2);
        unbreakableObjects.add(Box3);
        unbreakableObjects.add(Box4);
        unbreakableObjects.add(Plant);
        unbreakableObjects.add(TVset);
        unbreakableObjects.add(chair1);
        unbreakableObjects.add(chair2);
        unbreakableObjects.add(Table1);
        unbreakableObjects.add(Desk1);
        unbreakableObjects.add(Desk2);
        unbreakableObjects.add(Box5);
        unbreakableObjects.add(Box6);
        unbreakableObjects.add(Cabinet2);
        unbreakableObjects.add(RightWall);
        unbreakableObjects.add(TopWall);
        unbreakableObjects.add(LeftWall);
        unbreakableObjects.add(BottomWall);
        
        // add the breakable objects
        breakableObjects.add(breakable1);
        breakableObjects.add(breakable2);
        breakableObjects.add(breakable3);
        breakableObjects.add(breakable4);
        breakableObjects.add(breakable5);
        breakableObjects.add(breakable6);
        breakableObjects.add(breakable7);
        breakableObjects.add(breakable8);
        breakableObjects.add(breakable9);
        breakableObjects.add(breakable10);
        breakableObjects.add(breakable11);
        breakableObjects.add(breakable12);
        breakableObjects.add(breakable13);
        breakableObjects.add(breakable14);
        breakableObjects.add(breakable15);
        breakableObjects.add(breakable16);
        breakableObjects.add(breakable17);
        breakableObjects.add(breakable18);
        breakableObjects.add(breakable19);
        breakableObjects.add(breakable20);
        breakableObjects.add(breakable21);
        breakableObjects.add(breakable22);
        breakableObjects.add(breakable23);
        breakableObjects.add(breakable24);
    }
    
    public void checkCollision(double currentX, double currentY) {
        if (collisionHandler.checkCollisionUnbreakables(player, unbreakableObjects)) {
            player.setLayoutX(currentX);
            player.setLayoutY(currentY);
        }
        
        if (collisionHandler.checkCollisionBreakables(player, breakableObjects) ) {
            player.setLayoutX(currentX);
            player.setLayoutY(currentY);
            pointChecker();
        }   
    }
    
    public void checkEnemyCollision(double currentX, double currentY) {
        if (collisionHandler.checkCollisionUnbreakables(Enemy1, unbreakableObjects)) {
            Enemy1.setLayoutX(currentX);
            Enemy1.setLayoutY(currentY);
        }
        
        if (collisionHandler.checkCollisionBreakables(Enemy1, breakableObjects)) {
            Enemy1.setLayoutX(currentX);
            Enemy1.setLayoutY(currentY);
        }
    }
    
    private void pointChecker() {
        playerComponent.incrementScore();       score.setText(String.valueOf(playerComponent.getScore()));
        System.out.println("PLAYER SCORE: " + playerComponent.getScore());
    }   
}

I tried using Boolean flags to change direction upon collision, but it just doesn't move when it detects a collision.

1

There are 1 best solutions below

0
SedJ601 On

This code is 100% @James_D's code. The code shows a nice way to handle bouncing off other nodes or bounds when a collision is detected. See the method checkCollision.

import static java.lang.Math.PI;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;
import static javafx.scene.paint.Color.BLACK;
import static javafx.scene.paint.Color.BLUE;
import static javafx.scene.paint.Color.BROWN;
import static javafx.scene.paint.Color.GREEN;
import static javafx.scene.paint.Color.PINK;
import static javafx.scene.paint.Color.RED;
import static javafx.scene.paint.Color.YELLOW;

import java.util.ListIterator;
import java.util.Random;
import java.util.concurrent.Callable;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;

public class App extends Application {

    private ObservableList<Ball> balls = FXCollections.observableArrayList();
    private static final int NUM_BALLS = 400;
    private static final double MIN_RADIUS = 5 ;
    private static final double MAX_RADIUS = 15 ;
    private static final double MIN_SPEED = 50 ;
    private static final double MAX_SPEED = 250 ;
    private static final Color[] COLORS = new Color[] { RED, YELLOW, GREEN,
            BROWN, BLUE, PINK, BLACK };
    
    private final FrameStats frameStats = new FrameStats() ;

    @Override
    public void start(Stage primaryStage) {
        final Pane ballContainer = new Pane();

        constrainBallsOnResize(ballContainer);

        ballContainer.addEventHandler(MouseEvent.MOUSE_CLICKED,
                new EventHandler<MouseEvent>() {
                    @Override
                    public void handle(MouseEvent event) {
                        if (event.getClickCount() == 2) {
                            balls.clear();
                            createBalls(NUM_BALLS, MIN_RADIUS, MAX_RADIUS, MIN_SPEED, MAX_SPEED, ballContainer.getWidth()/2, ballContainer.getHeight()/2);
                        }
                    }
                });

        balls.addListener(new ListChangeListener<Ball>() {
            @Override
            public void onChanged(Change<? extends Ball> change) {
                while (change.next()) {
                    for (Ball b : change.getAddedSubList()) {
                        ballContainer.getChildren().add(b.getView());
                    }
                    for (Ball b : change.getRemoved()) {
                        ballContainer.getChildren().remove(b.getView());
                    }
                }
            }
        });

        createBalls(NUM_BALLS, MIN_RADIUS, MAX_RADIUS, MIN_SPEED, MAX_SPEED, 400, 300);
        
        final BorderPane root = new BorderPane();
        final Label stats = new Label() ;
        stats.textProperty().bind(frameStats.textProperty());
        
        root.setCenter(ballContainer);
        root.setBottom(stats);

        final Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();

        startAnimation(ballContainer);
    }

    private void startAnimation(final Pane ballContainer) {
        final LongProperty lastUpdateTime = new SimpleLongProperty(0);
        final AnimationTimer timer = new AnimationTimer() {
            @Override
            public void handle(long timestamp) {
                if (lastUpdateTime.get() > 0) {
                    long elapsedTime = timestamp - lastUpdateTime.get();
                    checkCollisions(ballContainer.getWidth(), ballContainer.getHeight());
                    updateWorld(elapsedTime);
                    frameStats.addFrame(elapsedTime);
                }
                lastUpdateTime.set(timestamp);
            }

        };
        timer.start();
    }

    private void updateWorld(long elapsedTime) {
        double elapsedSeconds = elapsedTime / 1_000_000_000.0;
        for (Ball b : balls) {
            b.setCenterX(b.getCenterX() + elapsedSeconds * b.getXVelocity());
            b.setCenterY(b.getCenterY() + elapsedSeconds * b.getYVelocity());
        }
    }

    private void checkCollisions(double maxX, double maxY) {
        for (ListIterator<Ball> slowIt = balls.listIterator(); slowIt.hasNext();) {
            Ball b1 = slowIt.next();
            // check wall collisions:
            double xVel = b1.getXVelocity();
            double yVel = b1.getYVelocity();
            if ((b1.getCenterX() - b1.getRadius() <= 0 && xVel < 0)
                    || (b1.getCenterX() + b1.getRadius() >= maxX && xVel > 0)) {
                b1.setXVelocity(-xVel);
            }
            if ((b1.getCenterY() - b1.getRadius() <= 0 && yVel < 0)
                    || (b1.getCenterY() + b1.getRadius() >= maxY && yVel > 0)) {
                b1.setYVelocity(-yVel);
            }
            for (ListIterator<Ball> fastIt = balls.listIterator(slowIt.nextIndex()); fastIt.hasNext();) {
                Ball b2 = fastIt.next();
                // performance hack: both colliding(...) and bounce(...) need deltaX and deltaY, so compute them once here:
                final double deltaX = b2.getCenterX() - b1.getCenterX() ;
                final double deltaY = b2.getCenterY() - b1.getCenterY() ;
                if (colliding(b1, b2, deltaX, deltaY)) {
                    bounce(b1, b2, deltaX, deltaY);
                }
            }
        }
    }


    public boolean colliding(final Ball b1, final Ball b2, final double deltaX, final double deltaY) {
        // square of distance between balls is s^2 = (x2-x1)^2 + (y2-y1)^2
        // balls are "overlapping" if s^2 < (r1 + r2)^2
        // We also check that distance is decreasing, i.e.
        // d/dt(s^2) < 0:
        // 2(x2-x1)(x2'-x1') + 2(y2-y1)(y2'-y1') < 0

        final double radiusSum = b1.getRadius() + b2.getRadius();
        if (deltaX * deltaX + deltaY * deltaY <= radiusSum * radiusSum) {
            if ( deltaX * (b2.getXVelocity() - b1.getXVelocity()) 
                    + deltaY * (b2.getYVelocity() - b1.getYVelocity()) < 0) {
                return true;
            }
        }
        return false;
    }
    
    private void bounce(final Ball b1, final Ball b2, final double deltaX, final double deltaY) {
        final double distance = sqrt(deltaX * deltaX + deltaY * deltaY) ;
        final double unitContactX = deltaX / distance ;
        final double unitContactY = deltaY / distance ;
        
        final double xVelocity1 = b1.getXVelocity();
        final double yVelocity1 = b1.getYVelocity();
        final double xVelocity2 = b2.getXVelocity();
        final double yVelocity2 = b2.getYVelocity();

        final double u1 = xVelocity1 * unitContactX + yVelocity1 * unitContactY ; // velocity of ball 1 parallel to contact vector
        final double u2 = xVelocity2 * unitContactX + yVelocity2 * unitContactY ; // same for ball 2
        
        final double massSum = b1.getMass() + b2.getMass() ;
        final double massDiff = b1.getMass() - b2.getMass() ;
        
        final double v1 = ( 2*b2.getMass()*u2 + u1 * massDiff ) / massSum ; // These equations are derived for one-dimensional collision by
        final double v2 = ( 2*b1.getMass()*u1 - u2 * massDiff ) / massSum ; // solving equations for conservation of momentum and conservation of energy
        
        final double u1PerpX = xVelocity1 - u1 * unitContactX ; // Components of ball 1 velocity in direction perpendicular
        final double u1PerpY = yVelocity1 - u1 * unitContactY ; // to contact vector. This doesn't change with collision
        final double u2PerpX = xVelocity2 - u2 * unitContactX ; // Same for ball 2....
        final double u2PerpY = yVelocity2 - u2 * unitContactY ; 
        
        b1.setXVelocity(v1 * unitContactX + u1PerpX);
        b1.setYVelocity(v1 * unitContactY + u1PerpY);
        b2.setXVelocity(v2 * unitContactX + u2PerpX);
        b2.setYVelocity(v2 * unitContactY + u2PerpY);
        
    }

    private void createBalls(int numBalls, double minRadius, double maxRadius, double minSpeed, double maxSpeed, double initialX, double initialY) {
        final Random rng = new Random();
        for (int i = 0; i < numBalls; i++) {
            double radius = minRadius + (maxRadius-minRadius) * rng.nextDouble();
            double mass = Math.pow((radius / 40), 3);
            
            final double speed = minSpeed + (maxSpeed - minSpeed) * rng.nextDouble();
            final double angle = 2 * PI * rng.nextDouble();
            Ball ball = new Ball(initialX, initialY, radius, speed*cos(angle),
                    speed*sin(angle), mass);
            ball.getView().setFill(COLORS[i % COLORS.length]);
//            ball.getView().setFill(i==0 ? RED : TRANSPARENT);
            balls.add(ball);
        }
    }
    
    private void constrainBallsOnResize(final Pane ballContainer) {
        ballContainer.widthProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable,
                    Number oldValue, Number newValue) {
                if (newValue.doubleValue() < oldValue.doubleValue()) {
                    for (Ball b : balls) {
                        double max = newValue.doubleValue() - b.getRadius();
                        if (b.getCenterX() > max) {
                            b.setCenterX(max);
                        }
                    }
                }
            }
        });

        ballContainer.heightProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> observable,
                    Number oldValue, Number newValue) {
                if (newValue.doubleValue() < oldValue.doubleValue()) {
                    for (Ball b : balls) {
                        double max = newValue.doubleValue() - b.getRadius();
                        if (b.getCenterY() > max) {
                            b.setCenterY(max);
                        }
                    }
                }
            }

        });
    }
    
    private static class Ball {
        private final DoubleProperty xVelocity ; // pixels per second
        private final DoubleProperty yVelocity ; 
        private final ReadOnlyDoubleWrapper speed ;
        private final double mass; // arbitrary units
        private final double radius; // pixels

        private final Circle view;

        public Ball(double centerX, double centerY, double radius,
                double xVelocity, double yVelocity, double mass) {

            this.view = new Circle(centerX, centerY, radius);
            this.xVelocity = new SimpleDoubleProperty(this, "xVelocity", xVelocity);
            this.yVelocity = new SimpleDoubleProperty(this, "yVelocity", yVelocity);
            this.speed = new ReadOnlyDoubleWrapper(this, "speed");
            speed.bind(Bindings.createDoubleBinding(new Callable<Double>() {
                
                @Override
                public Double call() throws Exception {
                    final double xVel = getXVelocity();
                    final double yVel = getYVelocity();
                    return sqrt(xVel * xVel + yVel * yVel);
                }
            }, this.xVelocity, this.yVelocity));
            this.mass = mass;
            this.radius = radius;
            view.setRadius(radius);
        }

        public double getMass() {
            return mass;
        }

        public double getRadius() {
            return radius;
        }

        public final double getXVelocity() {
            return xVelocity.get();
        }

        public final void setXVelocity(double xVelocity) {
            this.xVelocity.set(xVelocity);
        }

        public final DoubleProperty xVelocityProperty() {
            return xVelocity;
        }

        public final double getYVelocity() {
            return yVelocity.get();
        }

        public final void setYVelocity(double yVelocity) {
            this.yVelocity.set(yVelocity);
        }

        public final DoubleProperty yVelocityProperty() {
            return yVelocity;
        }
        
        public final double getSpeed() {
            return speed.get();
        }
        
        public final ReadOnlyDoubleProperty speedProperty() {
            return speed.getReadOnlyProperty() ;
        }

        public final double getCenterX() {
            return view.getCenterX();
        }

        public final void setCenterX(double centerX) {
            view.setCenterX(centerX);
        }

        public final DoubleProperty centerXProperty() {
            return view.centerXProperty();
        }

        public final double getCenterY() {
            return view.getCenterY();
        }

        public final void setCenterY(double centerY) {
            view.setCenterY(centerY);
        }

        public final DoubleProperty centerYProperty() {
            return view.centerYProperty();
        }

        public Shape getView() {
            return view;
        }
    }
    
    private static class FrameStats {
        private long frameCount ;
        private double meanFrameInterval ; // millis
        private final ReadOnlyStringWrapper text = new ReadOnlyStringWrapper(this, "text", "Frame count: 0 Average frame interval: N/A");
        
        public long getFrameCount() {
            return frameCount;
        }
        public double getMeanFrameInterval() {
            return meanFrameInterval;
        }
        
        public void addFrame(long frameDurationNanos) {
            meanFrameInterval = (meanFrameInterval * frameCount + frameDurationNanos / 1_000_000.0) / (frameCount + 1) ;
            frameCount++ ;
            text.set(toString());
        }
        
        public String getText() {
            return text.get();
        }
        
        public ReadOnlyStringProperty textProperty() {
            return text.getReadOnlyProperty() ;
        }
        
        @Override
        public String toString() {
            return String.format("Frame count: %,d Average frame interval: %.3f milliseconds", getFrameCount(), getMeanFrameInterval());
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

The original code can be found at James D's Gist.

https://gist.github.com/james-d/8327842