Je suis nouveau à Javafx et j'essaye de faire un jeu avec lui. Pour cela, j'ai besoin d'un mouvement fluide de certains objets sur l'écran. Je ne suis pas sûr, ce qui est le meilleur moyen. J'ai démarré un fichier test avec un rectangle. Je voulais que le rectangle se déplace le long d'un chemin jusqu'à la position de clic. Je peux le faire apparaître en réglant simplement la position. Alors j'ai pensé que je pouvais faire de petits pas et que le mouvement apparaîtrait fluide. Mais ça ne fonctionne pas de cette façon. Soit parce que le mouvement est rapide, donc je devrais faire attendre le processus (je voulais utiliser des threads dans ce but) ou parce que l'intepreter java n'est pas séquentiell et donc il montre juste la position finale. Peut-être les deux ou quelque chose que je n'ai pas inventé. Maintenant, je voudrais savoir si mes pensées sur ce sujet sont bonnes et s'il y a une façon plus élégante d'atteindre mon objectif. J'espère que vous pouvez me donner quelques conseils! salutations Felixdéplacer des objets sur l'écran dans javafx
Répondre
Ce que vous devez faire pour votre jeu de voiture est de lire The Nature of Code de Daniel Shiffman, en particulier chapter 6.3 The Steering Force.
Le livre est très facile à comprendre. Vous pouvez appliquer le code à JavaFX. Je ne vais pas entrer dans les détails, vous devez apprendre JavaFX vous-même. Voici donc le code:
Vous avez besoin d'un AnimationTimer dans lequel vous appliquez des forces, déplacez vos objets en fonction des forces et affichez vos nœuds JavaFX dans l'interface utilisateur en fonction de l'emplacement de vos objets.
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);
}
}
Alors vous avez besoin d'une classe de sprite général dans lequel vous accumulez les forces d'accélération, appliquer une accélération à la vitesse, la vitesse à l'emplacement. Lis juste le livre. C'est plutôt simple. Dans la démo, mon sprite est juste un triangle, j'ai implémenté une méthode utilitaire pour le créer.
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);
}
}
La démo a un attracteur, dans votre cas, il sera juste un clic de souris. Cliquez simplement sur le cercle et faites-le glisser. Les véhicules vont le suivre.
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;
}
}
Voici le code pour glisser:
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;
}
}
La couche de surface de jeu serait juste une piste de course:
Layer.java
package application;
import javafx.scene.layout.Pane;
public class Layer extends Pane {
public Layer(double width, double height) {
setPrefSize(width, height);
}
}
Ensuite, vous avez besoin de paramètres de classe
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;
}
La classe utilitaire permet de créer l'image de la flèche et pour les valeurs de cartographie:
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;
}
}
Et bien sûr la classe pour les calculs vectoriels
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);
}
}
Voici à quoi ça ressemble.
Les triangles (véhicules) suivront les cercles (attracteur) et ralentir quand ils se rapprocher de lui et arrêter ensuite.
Wow, Roland, le lien Nature of Code est l'une des meilleures ressources que j'ai jamais vues. Merci beaucoup pour ça ... – jewelsea
@jewelsea: Oui, il l'explique très bien. Je l'ai acheté instantanément. Et le code peut être facilement mis en correspondance avec JavaFX. Il explique également chaque chapitre sur Vimeo dans son canal, e. g. le [6.3 The Steering Force] (https://vimeo.com/63089179) que j'ai mentionné dans la réponse. – Roland
Salut Roland, c'était une réponse incroyable et sera certainement utile :) Je vais m'en tenir au livre. Je travaille avec le pattern MVC donc je vais mettre le mouvement avec la force de direction dans un package modell. Je voulais utiliser des fixations pour connecter la voiture modèle à son objet de vue correspondant et ajuster la classe de vecteur à cet effet. Malheureusement, je dois lier chaque fois que le mouvement est exécuté. Je préférerais rendre la liaison globale mais cela ne fonctionne pas jusqu'à présent, puisque ma méthode initialize et la méthode move sont des opérations séparées. Avez-vous une idée avec ça? – Felix
Votre question est un peu large, car il n'y a vraiment pas assez d'informations sur ce que vous essayez de faire. Si vous déplacez simplement un rectangle le long d'un chemin, un ['PathTransition'] (http://docs.oracle.com/javase/8/javafx/api/javafx/animation/PathTransition.html) le fera, et En général, l '[Animation API] (http://www.oracle.com/pls/topic/lookup?ctx=javase80&id=JFXTE149) peut suffire. Pour des jeux et des simulations plus complexes, vous aurez peut-être besoin d'un ['AnimationTimer'] (http://docs.oracle.com/javase/8/javafx/api/javafx/animation/AnimationTimer.html) –
Hey thanx pour votre réponse. Je vais essayer d'être plus précis: mon objectif est de faire une course automobile. En cliquant sur le screne, vous donnez à votre voiture une direction et une accélération qui ne seront pas appliquées de manière immatérielle. le mouvement sera exécuté après avoir appuyé sur un bouton. J'ai essayé le PathTransition que vous avez testé. Je travaille bien sur le screne, mais si j'essaie d'obtenir la position du rectangle, il montre seulement 0,0. J'ai besoin de la position pour d'autres calculs, par ex. si la voiture est toujours sur la piste et bien sûr pour le mouvement suivant. Je vais vérifier l'API Animation et AnimationTimer suivant. Merci encore! – Felix