我希望我的敌人继续朝某个方向移动,例如向上,当它检测到碰撞时,它会向右改变方向,然后再发生另一次碰撞,依此类推。我的敌人已经停在物体上,但我无法弄清楚如何改变其方向的逻辑。
我的敌人阶级
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);
}
}
我的控制器
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());
}
}
我尝试使用布尔标志在碰撞时改变方向,但当它检测到碰撞时它只是不移动。
此代码 100% 是 @James_D 的代码。该代码展示了一种在检测到碰撞时处理从其他节点或边界反弹的好方法。看方法
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);
}
}
原始代码可以在 James D 的 Gist 中找到。