sokoloowski / pz1-lab15-bouncing-balls-javafx

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Zadanie dodatkowe - wątki i animacja w JavaFx

Jest to zadanie dodatkowe za 10 punktów

Opis

Celem jest realizacja przykładu z kulkami za pomocą biblioteki JavaFX.

  1. Aplikacja ma składać się z dwóch komponentów
    • Modelu zawierającego kulki
    • Widoku - obiektu javafx.scene.layout.Pane wraz z elementami potomnymi
  2. Model jest animowany przez wątek. Okresowo, np. co 10ms przesuwa kulki. Zapewniamy wzajemne wykluczanie za pomocą semafora lub umieszczając wszelkie operacje na kulkach w bloku synchronicznym synchronized(balls) {...}
  3. Widok jest animowany przez wbudowany watek JavaFX. Realizowane jest to za pomocą obiektu klasy AnimationTimer. W metodzie, którą nazwiemy updateView():
    • usunięte zostaną wszystkie węzły potomne widoku (czyli kulki z poprzedniej iteracji)
    • zachowując wzajemne wykluczanie odczytane zostaną położenia i kolory kulek
    • na ich podstawie zostaną utworzone węzły JavaFx w odpowiednim miejscu

Uwagi techniczne

  • JavaFX nie jest częścią standardowej biblioteki. Musisz wygenerować projekt typu JavaFX korzystający z Mavena lub Gradle. Polecam Mavena.
  • JavaFX może definiować układ komponentów w XML. Nie korzystamy z tego. Po wygenerowaniu wyrzuć plik *.fxml z zasobów oraz usuń HelloControler
  • Możesz zbudować układ GUI bezpośrednio w kodzie - wraz z menu, komponentem Toolbar, polem tekstowym pełniącym rolę komponentu statusbar, itp.
  • Drzewo węzłów JavaFX maożna uaktualniać wyłącznie w wątku JavaFX. W przeciwnym przypadku generowane będą wyjątki.
  • Aby uaktualnić pewne własności komponentów - wyłączane przyciski, uaktualnienie rozmiarów - musisz połączyć ze sobą własności property, np.
    child.widthProperty().bind(parent.widthProperty());
    
    // gdzieś w kodzie
    SimpleBooleanProperty isRunning = new SimpleBooleanProperty(false);
    
    // gdzieś indziej
    button3.disableProperty().bind(somewhereInTheCode.isRunning);

Przykład

rectangles

Klasa pomocnicza do obliczania FPS

import java.util.ArrayDeque;
 
public class MovingAverage {
    int windowSize;
    ArrayDeque<Long> values;
 
    public MovingAverage(int windowSize) {
        this.windowSize = windowSize;
        values =new ArrayDeque<>(windowSize);
    }
    public void add(long value) {
        while(values.size() >= windowSize) values.poll();
        values.offer(value);
    }
    public double getMean() {
        if (values.isEmpty()) return 0;
        double mean = values.stream().mapToDouble(a->a).average().getAsDouble();
        return mean;
    }
}

Aplikacja z widokiem i wątkiem uaktualniającym widok

import java.util.Locale;
import java.util.Random;
 
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
 
public class RandomRectangles extends Application {
    private final Random random = new Random();
    static final int N = 100;
    static final int SIZE = 20;
 
    Group group = new Group();
    Pane root = new Pane();
    MovingAverage mave = new MovingAverage(300);
 
 
    void updateView() {
        // usuń wszystkie węzły potomne grupy
        group.getChildren().clear();
        int w = (int) (root.getWidth() / SIZE);
        int h = (int) (root.getHeight()/SIZE);
        // wygeneruj nowe węzły na podstawie modelu
        for (int i = 0; i < N; i++) {
            int x = random.nextInt(w) * SIZE;
            int y = random.nextInt(h) * SIZE;
            Rectangle r = new Rectangle(x, y, SIZE, SIZE);
            var color = Color.color(random.nextDouble(), random.nextDouble(), random.nextDouble());
            r.setFill(color);
            group.getChildren().add(r);
        }
    }

    @Override
    public void start(final Stage primaryStage) {
        root.getChildren().add(group);
        final Scene scene = new Scene(root, 800, 600, Color.BLACK);
        root.setOpacity(1);
        updateView();
        primaryStage.setScene(scene);
        primaryStage.show();
 
        new AnimationTimer() {
            long lastPaint = -1;
            double fps = 0;
            int counter = 0;
            MovingAverage mave = new MovingAverage(300);
 
            void updateFps() {
                double diff = mave.getMean();
                diff /= 1e9;
                if (diff > 0) {
                    fps = 1 / diff;
                }
            }

            void addFPS() {
                Text text = new Text();
                text.setFont(new Font(40));
                text.setFill(Color.WHITE);
                text.setText(String.format(Locale.US, "%.2f FPS", fps));
                text.setX(10);
                text.setY(50);
                group.getChildren().add(text);
            }

            @Override
            public void handle(long now) {
                if (lastPaint > 0) {
                    mave.add(now - lastPaint)
                };
                lastPaint = now;
                counter++;
                updateView();
                if (counter % 50 == 0) updateFps();
                addFPS();
            }
        }.start();
    }
 
    public static void main(String[] args) {
        launch(args);
    }
}

About


Languages

Language:Java 100.0%