我是Javafx的新手,我正试图用它制作游戏。 为此,我需要屏幕上某些物体的流畅运动。 我不确定,哪种方式最好。 我用一些矩形启动了一个测试文件。我希望矩形沿着路径移动到点击位置。我可以通过设置位置使它出现在那里。所以我认为我可以做出更小的步骤然后运动会显得流畅。但它不会这样工作。要么是因为移动要快,所以我需要让进程等待(我想为此目的使用线程)或者因为java解释器不是sequentiell因此它只显示最终位置。也许是我没有提出的两件事或者事情。 现在我想知道天气我对这个主题的看法是正确的,如果有更优雅的方式来实现我的目标。 我希望你能给我一些建议! 关于费利克斯
答案 0 :(得分:8)
你需要为你的汽车游戏做的是阅读Daniel Shiffman的The Nature of Code,尤其是chapter 6.3 The Steering Force。
这本书很容易理解。您可以将代码应用于JavaFX。我不会详细介绍,你必须自己学习JavaFX。所以这里只是代码:
您需要一个AnimationTimer,您可以在其中应用力,根据力移动对象,并根据对象的位置在UI中显示JavaFX节点。
Main.java
package application;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
static Random random = new Random();
Layer playfield;
List<Attractor> allAttractors = new ArrayList<>();
List<Vehicle> allVehicles = new ArrayList<>();
AnimationTimer gameLoop;
Vector2D mouseLocation = new Vector2D( 0, 0);
Scene scene;
MouseGestures mouseGestures = new MouseGestures();
@Override
public void start(Stage primaryStage) {
// create containers
BorderPane root = new BorderPane();
// playfield for our Sprites
playfield = new Layer( Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);
// entire game as layers
Pane layerPane = new Pane();
layerPane.getChildren().addAll(playfield);
root.setCenter(layerPane);
scene = new Scene(root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
// add content
prepareGame();
// add mouse location listener
addListeners();
// run animation loop
startGame();
}
private void prepareGame() {
// add vehicles
for( int i = 0; i < Settings.VEHICLE_COUNT; i++) {
addVehicles();
}
// add attractors
for( int i = 0; i < Settings.ATTRACTOR_COUNT; i++) {
addAttractors();
}
}
private void startGame() {
// start game
gameLoop = new AnimationTimer() {
@Override
public void handle(long now) {
// currently we have only 1 attractor
Attractor attractor = allAttractors.get(0);
// seek attractor location, apply force to get towards it
allVehicles.forEach(vehicle -> {
vehicle.seek( attractor.getLocation());
});
// move sprite
allVehicles.forEach(Sprite::move);
// update in fx scene
allVehicles.forEach(Sprite::display);
allAttractors.forEach(Sprite::display);
}
};
gameLoop.start();
}
/**
* Add single vehicle to list of vehicles and to the playfield
*/
private void addVehicles() {
Layer layer = playfield;
// random location
double x = random.nextDouble() * layer.getWidth();
double y = random.nextDouble() * layer.getHeight();
// dimensions
double width = 50;
double height = width / 2.0;
// create vehicle data
Vector2D location = new Vector2D( x,y);
Vector2D velocity = new Vector2D( 0,0);
Vector2D acceleration = new Vector2D( 0,0);
// create sprite and add to layer
Vehicle vehicle = new Vehicle( layer, location, velocity, acceleration, width, height);
// register vehicle
allVehicles.add(vehicle);
}
private void addAttractors() {
Layer layer = playfield;
// center attractor
double x = layer.getWidth() / 2;
double y = layer.getHeight() / 2;
// dimensions
double width = 100;
double height = 100;
// create attractor data
Vector2D location = new Vector2D( x,y);
Vector2D velocity = new Vector2D( 0,0);
Vector2D acceleration = new Vector2D( 0,0);
// create attractor and add to layer
Attractor attractor = new Attractor( layer, location, velocity, acceleration, width, height);
// register sprite
allAttractors.add(attractor);
}
private void addListeners() {
// capture mouse position
scene.addEventFilter(MouseEvent.ANY, e -> {
mouseLocation.set(e.getX(), e.getY());
});
// move attractors via mouse
for( Attractor attractor: allAttractors) {
mouseGestures.makeDraggable(attractor);
}
}
public static void main(String[] args) {
launch(args);
}
}
然后你需要一个普通的精灵类,你可以在其中积累加速力,将加速度加速到速度,速度到位置。刚看完这本书。这很简单。
package application;
import javafx.scene.Node;
import javafx.scene.layout.Region;
public abstract class Sprite extends Region {
Vector2D location;
Vector2D velocity;
Vector2D acceleration;
double maxForce = Settings.SPRITE_MAX_FORCE;
double maxSpeed = Settings.SPRITE_MAX_SPEED;
Node view;
// view dimensions
double width;
double height;
double centerX;
double centerY;
double radius;
double angle;
Layer layer = null;
public Sprite( Layer layer, Vector2D location, Vector2D velocity, Vector2D acceleration, double width, double height) {
this.layer = layer;
this.location = location;
this.velocity = velocity;
this.acceleration = acceleration;
this.width = width;
this.height = height;
this.centerX = width / 2;
this.centerY = height / 2;
this.view = createView();
setPrefSize(width, height);
// add view to this node
getChildren().add( view);
// add this node to layer
layer.getChildren().add( this);
}
public abstract Node createView();
public void applyForce(Vector2D force) {
acceleration.add(force);
}
public void move() {
// set velocity depending on acceleration
velocity.add(acceleration);
// limit velocity to max speed
velocity.limit(maxSpeed);
// change location depending on velocity
location.add(velocity);
// angle: towards velocity (ie target)
angle = velocity.heading2D();
// clear acceleration
acceleration.multiply(0);
}
/**
* Move sprite towards target
*/
public void seek(Vector2D target) {
Vector2D desired = Vector2D.subtract(target, location);
// The distance is the magnitude of the vector pointing from location to target.
double d = desired.magnitude();
desired.normalize();
// If we are closer than 100 pixels...
if (d < Settings.SPRITE_SLOW_DOWN_DISTANCE) {
// ...set the magnitude according to how close we are.
double m = Utils.map(d, 0, Settings.SPRITE_SLOW_DOWN_DISTANCE, 0, maxSpeed);
desired.multiply(m);
}
// Otherwise, proceed at maximum speed.
else {
desired.multiply(maxSpeed);
}
// The usual steering = desired - velocity
Vector2D steer = Vector2D.subtract(desired, velocity);
steer.limit(maxForce);
applyForce(steer);
}
/**
* Update node position
*/
public void display() {
relocate(location.x - centerX, location.y - centerY);
setRotate(Math.toDegrees( angle));
}
public Vector2D getVelocity() {
return velocity;
}
public Vector2D getLocation() {
return location;
}
public void setLocation( double x, double y) {
location.x = x;
location.y = y;
}
public void setLocationOffset( double x, double y) {
location.x += x;
location.y += y;
}
}
在演示中,我的精灵只是一个三角形,我实现了一个实用工具方法来创建它。
Vehicle.java
package application;
import javafx.scene.Node;
public class Vehicle extends Sprite {
public Vehicle(Layer layer, Vector2D location, Vector2D velocity, Vector2D acceleration, double width, double height) {
super(layer, location, velocity, acceleration, width, height);
}
@Override
public Node createView() {
return Utils.createArrowImageView( (int) width);
}
}
该演示有一个吸引子,在你的情况下它只是一个鼠标点击。只需单击圆圈并拖动即可。车辆将遵循它。
package application;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class Attractor extends Sprite {
public Attractor(Layer layer, Vector2D location, Vector2D velocity, Vector2D acceleration, double width, double height) {
super(layer, location, velocity, acceleration, width, height);
}
@Override
public Node createView() {
double radius = width / 2;
Circle circle = new Circle( radius);
circle.setCenterX(radius);
circle.setCenterY(radius);
circle.setStroke(Color.GREEN);
circle.setFill(Color.GREEN.deriveColor(1, 1, 1, 0.3));
return circle;
}
}
以下是拖动代码:
MouseGestures.java
package application;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
public class MouseGestures {
final DragContext dragContext = new DragContext();
public void makeDraggable(final Sprite sprite) {
sprite.setOnMousePressed(onMousePressedEventHandler);
sprite.setOnMouseDragged(onMouseDraggedEventHandler);
sprite.setOnMouseReleased(onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
dragContext.x = event.getSceneX();
dragContext.y = event.getSceneY();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Sprite sprite = (Sprite) event.getSource();
double offsetX = event.getSceneX() - dragContext.x;
double offsetY = event.getSceneY() - dragContext.y;
sprite.setLocationOffset(offsetX, offsetY);
dragContext.x = event.getSceneX();
dragContext.y = event.getSceneY();
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
}
};
class DragContext {
double x;
double y;
}
}
playfield图层只是一些赛道:
Layer.java
package application;
import javafx.scene.layout.Pane;
public class Layer extends Pane {
public Layer(double width, double height) {
setPrefSize(width, height);
}
}
然后你需要一些设置类
Settings.java
package application;
public class Settings {
public static double SCENE_WIDTH = 1280;
public static double SCENE_HEIGHT = 720;
public static int ATTRACTOR_COUNT = 1;
public static int VEHICLE_COUNT = 10;
public static double SPRITE_MAX_SPEED = 2;
public static double SPRITE_MAX_FORCE = 0.1;
// distance at which the sprite moves slower towards the target
public static double SPRITE_SLOW_DOWN_DISTANCE = 100;
}
实用程序类用于创建箭头图像和映射值:
Utils.java
package application;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
public class Utils {
public static double map(double value, double currentRangeStart, double currentRangeStop, double targetRangeStart, double targetRangeStop) {
return targetRangeStart + (targetRangeStop - targetRangeStart) * ((value - currentRangeStart) / (currentRangeStop - currentRangeStart));
}
/**
* Create an imageview of a right facing arrow.
* @param size The width. The height is calculated as width / 2.0.
* @param height
* @return
*/
public static ImageView createArrowImageView( double size) {
return createArrowImageView(size, size / 2.0, Color.BLUE, Color.BLUE.deriveColor(1, 1, 1, 0.3), 1);
}
/**
* Create an imageview of a right facing arrow.
* @param width
* @param height
* @return
*/
public static ImageView createArrowImageView( double width, double height, Paint stroke, Paint fill, double strokeWidth) {
return new ImageView( createArrowImage(width, height, stroke, fill, strokeWidth));
}
/**
* Create an image of a right facing arrow.
* @param width
* @param height
* @return
*/
public static Image createArrowImage( double width, double height, Paint stroke, Paint fill, double strokeWidth) {
WritableImage wi;
double arrowWidth = width - strokeWidth * 2;
double arrowHeight = height - strokeWidth * 2;
Polygon arrow = new Polygon( 0, 0, arrowWidth, arrowHeight / 2, 0, arrowHeight); // left/right lines of the arrow
arrow.setStrokeLineJoin(StrokeLineJoin.MITER);
arrow.setStrokeLineCap(StrokeLineCap.SQUARE);
arrow.setStroke(stroke);
arrow.setFill(fill);
arrow.setStrokeWidth(strokeWidth);
SnapshotParameters parameters = new SnapshotParameters();
parameters.setFill(Color.TRANSPARENT);
int imageWidth = (int) width;
int imageHeight = (int) height;
wi = new WritableImage( imageWidth, imageHeight);
arrow.snapshot(parameters, wi);
return wi;
}
}
当然还有矢量计算的类
Vector2D.java
package application;
public class Vector2D {
public double x;
public double y;
public Vector2D(double x, double y) {
this.x = x;
this.y = y;
}
public void set(double x, double y) {
this.x = x;
this.y = y;
}
public double magnitude() {
return (double) Math.sqrt(x * x + y * y);
}
public void add(Vector2D v) {
x += v.x;
y += v.y;
}
public void add(double x, double y) {
this.x += x;
this.y += y;
}
public void multiply(double n) {
x *= n;
y *= n;
}
public void div(double n) {
x /= n;
y /= n;
}
public void normalize() {
double m = magnitude();
if (m != 0 && m != 1) {
div(m);
}
}
public void limit(double max) {
if (magnitude() > max) {
normalize();
multiply(max);
}
}
static public Vector2D subtract(Vector2D v1, Vector2D v2) {
return new Vector2D(v1.x - v2.x, v1.y - v2.y);
}
public double heading2D() {
return Math.atan2(y, x);
}
}
这是它的样子。
三角形(车辆)将跟随圆圈(吸引器)并在接近它时减速然后停止。