Index de l'article

 

En JavaFx, lors du lancement d'un application, une instance de la classe Stage est créée. Attention : cette classe n'a rien à voir avec la notion de stage dans boardifier. Elle représente en fait la fenêtre primaire de l'application. Si l'on veut ouvrir d'autres fenêtres, il faut donc créer d'autres instance de Stage. Ensuite, pour afficher quelque chose au sein de cette fenêtre, il faut créer une instance de Scene et lui ajouter des éléments graphiques JavaFx (Button, Shape, ...). On peut alors assigner la Scene au Stage et la machinerie interne de JavaFx se débrouille pour effectivement peindre les éléments graphiques à l'écran, et si besoin les repeindre quand leurs caractéristiques (position, dimension ,...) ont changé.

En conclusion, contrairement à Swing, il n'y a pas besoin d'utiliser des instructions pour "peindre" des formes, comme par exemple drawRect(), puisqu'il y a des classes qui représentent ces formes. De plus, il est inutile de demander explicitement à repeindre un élément avec une méthode du style repaint(). JavaFx est donc plus souple à utiliser ... une fois qu'on connaît bien ses classes et leurs interactions !


 Pour créer le visuel des éléments, donc une sous-classe de ElementLook, boardifier repose sur les principes suivants (certains étant communs avec boardifier-console):

  • une sous-classe d'ElementLook prend forcément en paramètre un GameElement (= principe MVC où la vue accède au modèle)
  • le visuel est compris dans une boîte englobante, dont le coin haut-gauche se trouve aux coordonnées virtuelles 0,0.
  • les dimensions de la boîte englobante ne sont pas forcément fixes et peuvent dépendre des paramètres passés au constructeur, ou bien venant du GameElement associé.
  • on crée les différentes parties du visuel grâce à des instances de sous-classes de Shape (Circle, Rectangle, Line, ...) et/ou ImageView (pour afficher une image).
  • on positionne ces instances dans la boîte englobante, en utilisant leurs méthodes propres (par ex, setCenterX() pour Circle)
  • on "ajoute" les instances au look grâce à
    • addShape() pour les sous-classes de Shape,
    • addNode() pour une ImageView.
  • si besoin, on redéfinit les méthodes :
    • onSelectionChange(), car le visuel doit changer lorsqu'on l'élément associé est (dé)sélectionné,
    • onChange(), car le visuel doit changer lors d'un changement d'état (autre que position, visibilité, sélection) de l'élément associé.

Attention :

  • si vous n'ajoutez pas l'instance au look avec addShape() ou addNode(), elle ne sera jamais visible
  • si la position d'une instance n'est pas correcte, ce qui dépasse de la boîte englobante ne sera pas coupé. En revanche, il deviendra difficile de positionner correctement à l'écran l'élément.

 

Par exemple, pour créer le look des pions de "The Hole", PawnLook contient (entre autre) :

...
public class PawnLook extends ElementLook {
    private Circle circle;
    public PawnLook(int radius, GameElement element) {
        super(element);
        Pawn pawn = (Pawn)element; // unless you did somthing stupid in HoleStageView, it IS really a Pawn instance !
        circle = new Circle();
        circle.setRadius(radius);
        if (pawn.getColor() == Pawn.PAWN_BLACK) {
            circle.setFill(Color.BLACK);
        }
        else {
            circle.setFill(Color.RED);
        }
        circle.setCenterX(radius);
        circle.setCenterY(radius);
        // add the circle to the look
        addShape(circle);
        // to fulfill ...
    }
    @Override
    public void onSelectionChange() {
        // to fulfill ...
    } 
    public void onChange() { }
}
 

Remarque : il n'y a pas besoin définir du code pour onChange() car il n'y a aucun changement d'état dans Pawn (à part la position et la sélection). Ce ne serait pas le cas dans un jeu de dames où un pion peut devenir une dame. Il faudrait alors redéfinir onChange() pour modifier son visuel quand le pion devient un reine.


Comme en mode console, tous les looks doivent être instanciés dans la fonction createLooks() d'une sous-classe de GameStageView. Comme dit plus haut, la différence est que les dimensions des looks doivent être données en pixels. Si le jeu comporte plusieurs stages, il faut créer une sous-classe de GameStageView par stage. Dans le cas de "The Hole", il n'y a qu'une seule classe HoleStageView, qui ressemble fortement à celle du mode console :

...
public class HoleStageView extends GameStageView {
    public HoleStageView(String name, GameStageModel gameStageModel) {
        super(name, gameStageModel);
        width = 650;
        height = 450;
    }

    @Override
    public void createLooks() {
        HoleStageModel model = (HoleStageModel)gameStageModel;

        addLook(new HoleBoardLook(320, model.getBoard()));
        addLook(new PawnPotLook(120,420,model.getBlackPot()));
        ... // to fulfill        
}

 


Tous les looks stockés dans les différentes instances de GameStageView vont être affichés dans un Pane, classe JavaFx servant d'espace d'affichage pour les éléments javafx (Shape, ImageView, ...). Ce Pane est représenté par la classe RootPane de boardifier (qui hérite de Pane). Au moment de son instanciation, RootPane contient seulement un rectangle gris et un texte basique. Si on veut changer cet état par défaut, il faut hérité de RootPane, ce qui est la cas dans "The Hole", avec la classe HoleRootPane, qui redéfinit la méthode createDefaultGroup() comme suivant :

...
public class HoleRootPane extends RootPane {
    ...
    @Override
    public void createDefaultGroup() {
        Rectangle frame = new Rectangle(600, 100, Color.LIGHTGREY);
        Text text = new Text("Playing to The Hole");
        text.setFont(new Font(15));
        text.setFill(Color.BLACK);
        text.setX(10);
        text.setY(50);
        // put shapes in the group
        group.getChildren().clear();
        group.getChildren().addAll(frame, text);
    }
}

 


Enfin, la vue globale est représentée par la classe View, dont il faut hériter si l'on a besoin d'une barre de menu. Dans ce cas, il faut redéfinir la méthode createMenuBar() :

...
public class HoleView extends View {

    private MenuItem menuStart;
    private MenuItem menuIntro;
    private MenuItem menuQuit;

    public HoleView(Model model, Stage stage, RootPane rootPane) {
        super(model, stage, rootPane);
    }

    @Override
    protected void createMenuBar() {
        menuBar = new MenuBar();
        Menu menu1 = new Menu("Game");
        menuStart = new MenuItem("New game");
        menuIntro = new MenuItem("Intro");
        menuQuit = new MenuItem("Quit");
        menu1.getItems().add(menuStart);
        menu1.getItems().add(menuIntro);
        menu1.getItems().add(menuQuit);
        menuBar.getMenus().add(menu1);
    }
    ...
}