Votre travail dans cette mission consiste à écrire le jeu d’arcade classique de Breakout, qui a été inventé par Steve Wozniak avant de fonder Apple avec Steve Jobs (moment de silence). Il s’agit d’une tâche importante, mais tout à fait gérable tant que le problème ne sera pas fragmenté.

Le jeu d'évasion

Dans Breakout, la configuration initiale du monde apparaît comme indiqué à droite. Les rectangles colorés dans la partie supérieure de l'écran sont des briques, et le rectangle légèrement plus grand en bas est le pagayer. La palette est dans une position fixe dans la dimension verticale, mais se déplace d'avant en arrière sur l'écran avec la souris jusqu'à atteindre le bord de son espace.

Un jeu complet consiste en trois tours. À chaque tour, une balle est lancée du centre de la fenêtre vers le bas de l'écran selon un angle aléatoire. Cette balle rebondit sur la raquette et les murs du monde, conformément au principe physique généralement exprimé par "l'angle d'incidence est égal à l'angle de réflexion" (ce qui s'avère très facile à mettre en œuvre, comme nous le verrons plus loin dans ce document) . Ainsi, après deux rebonds - un de la palette et un de la droite mur - la balle pourrait avoir la trajectoire montrée dans le deuxième diagramme. (Notez que la ligne en pointillé est là pour montrer le chemin de la balle et n'apparaîtra pas à l'écran.)

Comme vous pouvez le voir sur le deuxième diagramme, la balle est sur le point de se heurter à l’une des briques de la rangée inférieure. Lorsque cela se produit, la balle rebondit comme pour toute autre collision, mais la brique disparaît. Le troisième diagramme montre à quoi le jeu ressemble après la collision et après que le joueur a déplacé la raquette pour l'aligner sur la balle qui approche.

Le jeu sur un tour continue ainsi jusqu'à ce que l'une des deux conditions soit remplie:

  1. La balle frappe le mur inférieur, ce qui signifie que le joueur doit l'avoir manquée avec la raquette. Dans ce cas, le tour se termine et la balle suivante est servie si le joueur a encore des tours. Sinon, le jeu se termine par une perte pour le joueur.
  2. La dernière brique est éliminée. Dans ce cas, le joueur gagne et le jeu se termine immédiatement.

Le succès de cette mission dépendra de la division du problème en plusieurs éléments gérables et de la résolution de chaque problème avant de passer au suivant. Les sections suivantes décrivent une approche progressive du problème.

1. Créer des briques

Avant de commencer à jouer, vous devez configurer les différentes pièces. Par conséquent, il est probablement logique d'implémenter la méthode run en deux appels: un pour configurer le jeu et un pour le jouer. Une partie importante de la configuration consiste à créer les rangées de briques en haut du jeu, qui ressemblent à ceci:

Le nombre, les dimensions et l'espacement des briques sont spécifiés à l'aide de constantes nommées dans le fichier de démarrage, tout comme la distance entre le haut de la fenêtre et la première ligne de briques. La seule valeur que vous devez calculer est la coordonnée x de la première colonne, qui doit être choisie de sorte que les briques soient centrées dans la fenêtre, l'espace restant restant divisé également entre les côtés gauche et droit. La couleur des briques reste constante pour deux rangées et s’écoule dans les rangées suivantes séquence: ROUGE, ORANGE, JAUNE, VERT, CYAN.

2. Créer la pagaie

L'étape suivante consiste à créer la palette. Il n'y a qu'une seule palette, qui est un GRect rempli. Vous connaissez même sa position par rapport au bas de la fenêtre.

Le défi dans la création de la palette est de la faire suivre la souris. Ici, cependant, il suffit de faire attention à la coordonnée x de la souris car la position y de la palette est fixe.

Si vous appelez la méthode (peut-être à partir de run):

addMouseListeners();

Et implémenter la méthode:

public void mouseMoved(MouseEvent e)

Ensuite, chaque fois que la souris est déplacée, la méthode mouseMoved sera exécutée.

Vous pouvez en apprendre plus sur MouseEvents ici.

3. Faire bouger la balle

À un niveau, créer la balle est facile, étant donné que ce n'est qu'un GOval rempli. La partie intéressante consiste à le faire bouger et à rebondir de manière appropriée. Vous avez maintenant dépassé la phase de configuration et la phase de lecture du jeu. Pour commencer, créez une balle et placez-la au centre de la fenêtre. Ceci étant dit, gardez à l’esprit que les coordonnées du GOval ne spécifient pas l’emplacement du centre de la balle mais plutôt son coin supérieur gauche.

Le programme doit garder trace de la vitesse de la balle, qui se compose de deux composants distincts, que vous déclarerez vraisemblablement comme des variables d'instance comme celle-ci:

private double vx, vy;

Les composantes de la vitesse représentent le changement de position qui se produit à chaque pas de temps. Initialement, la balle doit être dirigée vers le bas et vous pouvez essayer une vitesse de départ de +3.0 pour vy (rappelez-vous que les valeurs y en Java augmentent lorsque vous vous déplacez vers le bas de l'écran). Le jeu serait ennuyeux si chaque balle suivait le même parcours, vous devriez donc choisir la composante vx au hasard. Vous devriez simplement faire ce qui suit:

  1. Déclarez une variable d'instance rgen, qui servira de générateur de nombre aléatoire:

    private RandomGenerator rgen = RandomGenerator.getInstance();
    Rappelez-vous que les variables d'instance sont déclarées en dehors de toute méthode mais à l'intérieur de la classe.
  2. Initialisez la variable vx comme suit: vx = rgen.nextDouble (1.0, 3.0); if (rgen.nextBoolean (0.5)) vx = -vx; Ce code définit vx comme un double aléatoire compris entre 1,0 et 3,0, puis le rend négatif la moitié du temps. Cette stratégie fonctionne beaucoup mieux pour Breakout que d'appeler nextDouble (-3.0, +3.0) qui pourrait générer une balle allant plus ou moins directement vers le bas. Cela rendrait la vie beaucoup trop facile pour le joueur.

Une fois que vous avez fait cela, votre prochain défi consiste à faire rebondir le ballon dans le monde entier, en ignorant totalement la pagaie et les briques. Pour ce faire, vous devez vérifier si les coordonnées de la balle ont dépassé la limite, en tenant compte du fait que la balle a une taille non nulle. Ainsi, pour voir si la balle a rebondi sur le mur de droite, vous devez voir si les coordonnées du bord droit de la balle sont devenues plus grandes que la largeur de la fenêtre. les trois autres directions sont traitées de la même manière. Pour le moment, faites rebondir la balle sur le mur du bas pour pouvoir la regarder faire son chemin autour du monde.

Calculer ce qui se passe après un rebond est extrêmement simple. Si une balle rebondit sur le mur supérieur ou inférieur, tout ce que vous avez à faire est d’inverser le signe du vy. Symétriquement, les rebonds des murs latéraux inversent simplement le signe de vx.

4. Vérifier les collisions

Maintenant vient la partie intéressante. Afin de faire de Breakout un véritable jeu, vous devez être capable de dire si la balle entre en collision avec un autre objet dans la fenêtre. Comme le font souvent les scientifiques, il est utile de commencer par formuler une hypothèse simplificatrice, puis de l'assouplir ultérieurement. Supposons que la balle soit un seul point plutôt qu'un cercle. Dans ce cas, comment pouvez-vous savoir s'il est entré en collision avec un autre objet?

Il y a une méthode:

public GObject getElementAt(double x, double y)
qui prend une position dans la fenêtre et retourne l'objet graphique à cet endroit, le cas échéant. Si aucun objet graphique ne couvre cette position, getElementAt renvoie la constante spéciale null. S'il en existe plus d'un, getElementAt choisit toujours celui qui se trouve le plus en haut de la pile, celui qui semble être en face de l'écran.

La chose la plus facile à faire, qui est en fait typique des jeux informatiques réels, est de vérifier quelques points soigneusement choisis à l'extérieur du ballon et de voir si l'un de ces points a heurté quelque chose. Dès que vous trouvez quelque chose sur l'un de ces points, vous pouvez déclarer que le ballon est entré en collision avec cet objet.

Dans votre implémentation, vérifiez les quatre points d’angle de la case dans laquelle est inscrite la balle. Rappelez-vous qu'un GOval est défini en fonction de son rectangle de délimitation, de sorte que si le coin supérieur gauche de la balle se trouve au point (x, y), les autres coins se trouveront aux emplacements indiqués dans ce diagramme:

Ces points ont l’avantage d’être en dehors du ballon, mais néanmoins suffisamment proches pour faire croire que des collisions se sont produites. Ainsi, pour chacun de ces quatre points, vous devez:

  1. Appelez getElementAt à cet endroit pour voir si quelque chose s'y trouve.
  2. Si la valeur que vous récupérez n'est pas nulle, vous n'avez pas besoin de chercher plus loin et pouvez prendre cette valeur comme objet GO avec lequel la collision s'est produite.
  3. Si getElementAt renvoie null pour un coin particulier, continuez et essayez le prochain coin.
  4. Si vous franchissez les quatre virages sans vous heurter à une collision, il n'y en a pas.

Il serait très utile d’écrire cette section de code séparément.

public GObject getCollidingObject()
Cela renvoie l'objet impliqué dans la collision, le cas échéant, et null autrement. Vous pouvez ensuite l'utiliser dans une déclaration du type:
GObject collider = getCollidingObject();

A partir de là, il ne vous reste plus qu'à décider quoi faire en cas de collision. Il n'y a que deux possibilités. Tout d’abord, l’objet que vous récupérez peut être la palette, que vous pouvez tester en vérifiant

if (collider == paddle)

Si c'est la pagaie, vous devez faire rebondir la balle pour qu'elle commence à monter. Si ce n'est pas la raquette, la seule autre chose qui pourrait l'être est une brique, car ce sont les seuls autres objets au monde. Encore une fois, vous devez provoquer un rebond dans la direction verticale, mais vous devez également enlever la brique. Pour ce faire, il vous suffit de le supprimer de l'écran en appelant la méthode remove.

touches supplémentaires

Si vous en êtes là, vous avez fait toutes les tâches difficiles. Il y a cependant quelques détails supplémentaires que vous pourriez prendre en compte si vous en avez le temps.

  1. Prenez soin de l'affaire lorsque la balle frappe le mur du bas. Dans le prototype que vous avez construit, la balle rebondit sur ce mur comme tous les autres, mais le jeu est difficile à perdre. Modifiez la structure de votre boucle afin qu’elle vérifie si le mur du fond est une de ses conditions de terminaison.
  2. Recherchez l'autre condition finale, qui frappe la dernière brique. Comment savez-vous quand vous l'avez fait? Bien qu’il existe d’autres moyens de le faire, l’un des plus faciles est de laisser votre programme garder une trace du nombre de briques restantes. Chaque fois que vous en frappez un, soustrayez-en un de ce compteur. Lorsque le compte atteint zéro, vous devez avoir terminé. Il serait bon de donner au joueur un petit retour d’information indiquant au moins si le jeu a été gagné ou perdu.
  3. Testez votre programme pour voir qu'il fonctionne. Si vous pensez que tout fonctionne, voici quelque chose à essayer: juste avant que la balle passe le niveau de la raquette, déplacez-la rapidement pour que la raquette entre en collision avec la balle plutôt que l'inverse. Est-ce que tout fonctionne toujours ou si votre balle semble "collée" à la pagaie? Cette erreur se produit parce que la balle entre en collision avec la palette, change de direction, puis entre à nouveau avec la palette avant de s’échapper. Comment pouvez-vous résoudre ce problème?

Démo

Les extensions

Si le jeu de base fonctionne bien, ce serait une excellente occasion d'aller au-delà des attentes. Voici quelques idées d’extensions possibles (bien entendu, nous vous encourageons à faire preuve d’imagination pour proposer d’autres idées):

  • Ajouter des sons.

    La version qui s'exécute en tant qu'applet sur la page d'affectation du CS 106A émet un bref son de rebond chaque fois que la balle entre en collision avec une brique ou une palette. Cette extension s'avère très facile. Le projet de démarrage contient un fichier de clip audio appelé bounce.au qui contient ce son. Vous pouvez charger le son en écrivant

    AudioClip bounceClip = MediaTools.loadAudioClip("res/bounce.au");

    et plus tard jouer en appelant

    bounceClip.play();
    Les bibliothèques Java facilitent certaines choses.

  • Améliorer le contrôle de l'utilisateur sur les rebonds. Le programme devient plutôt ennuyeux si la seule chose que le joueur doit faire est de frapper la balle. C'est beaucoup plus intéressant si le joueur peut contrôler le ballon en le frappant à différentes parties de la raquette. La façon dont fonctionnait l'ancien jeu d'arcade était que la balle rebondirait dans les directions x et y si vous la frappiez au bord de la palette d'où venait la balle. La version Web implémente cette fonctionnalité.
  • Utilise ton imagination Qu'est-ce que vous avez toujours voulu faire avec un jeu de ce genre?