2015-08-06 1 views
1

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

+0

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) –

+0

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

Répondre

7

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.

enter image description here

Les triangles (véhicules) suivront les cercles (attracteur) et ralentir quand ils se rapprocher de lui et arrêter ensuite.

+1

Wow, Roland, le lien Nature of Code est l'une des meilleures ressources que j'ai jamais vues. Merci beaucoup pour ça ... – jewelsea

+0

@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

+0

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