Guide ultime du langage de traitement, partie II : Construire un jeu simple
Publié: 2022-03-11Ceci est la deuxième partie du guide ultime du langage de traitement. Dans la première partie, j'ai donné une présentation de base du langage de traitement. La prochaine étape pour vous d'apprendre le traitement est simplement une programmation plus pratique.
Dans cet article, je vais vous montrer comment utiliser Processing pour implémenter votre propre jeu, étape par étape. Chaque étape sera expliquée en détail. Ensuite, nous porterons le jeu sur le Web.
Avant de commencer le didacticiel de traitement, voici le code de l'exercice du logo DVD de la partie précédente. Si vous avez des questions, assurez-vous de laisser un commentaire.
Tutoriel de traitement : un jeu simple
Le jeu que nous allons construire dans ce tutoriel de traitement est une sorte de combinaison de Flappy Bird, Pong et Brick Breaker. La raison pour laquelle j'ai choisi un jeu comme celui-ci est qu'il contient la plupart des concepts avec lesquels les débutants ont du mal à apprendre le développement de jeux. Ceci est basé sur mon expérience lorsque j'étais assistant d'enseignement, aidant les nouveaux programmeurs à apprendre à utiliser Processing. Ces concepts incluent la gravité, les collisions, le maintien des scores, la gestion de différents écrans et les interactions clavier/souris. Flappy Pong les contient tous.
Jouez au jeu maintenant !
Sans utiliser les concepts de programmation orientée objet (POO), il n'est pas facile de créer des jeux complexes, tels que des jeux de plateforme avec plusieurs niveaux, joueurs, entités, etc. Au fur et à mesure que nous avançons, vous verrez comment le code se complique très rapidement. J'ai fait de mon mieux pour garder ce tutoriel de traitement organisé et simple.
Je vous conseille de suivre l'article, de récupérer le code complet, de jouer seul avec, de commencer à penser à votre propre jeu le plus rapidement possible et de commencer à l'implémenter.
Alors commençons.
Construire Flappy Pong
Étape 1 du didacticiel de traitement : initialiser et gérer différents écrans
La première étape consiste à initialiser notre projet. Pour commencer, nous allons écrire notre configuration et dessiner des blocs comme d'habitude, rien d'extraordinaire ou de nouveau. Ensuite, nous allons gérer différents écrans (écran initial, écran de jeu, écran de jeu etc.). Alors la question se pose, comment faire en sorte que Processing affiche la bonne page au bon moment ?
Accomplir cette tâche est assez simple. Nous aurons une variable globale qui stocke les informations de l'écran actuellement actif. On dessine ensuite le contenu de l'écran correct en fonction de la variable. Dans le bloc de dessin, nous aurons une instruction if qui vérifie la variable et affiche le contenu de l'écran en conséquence. Chaque fois que nous voulons changer d'écran, nous changerons cette variable en l'identifiant de l'écran que nous voulons qu'il affiche. Cela dit, voici à quoi ressemble notre squelette de code :
/********* VARIABLES *********/ // We control which screen is active by settings / updating // gameScreen variable. We display the correct screen according // to the value of this variable. // // 0: Initial Screen // 1: Game Screen // 2: Game-over Screen int gameScreen = 0; /********* SETUP BLOCK *********/ void setup() { size(500, 500); } /********* DRAW BLOCK *********/ void draw() { // Display the contents of the current screen if (gameScreen == 0) { initScreen(); } else if (gameScreen == 1) { gameScreen(); } else if (gameScreen == 2) { gameOverScreen(); } } /********* SCREEN CONTENTS *********/ void initScreen() { // codes of initial screen } void gameScreen() { // codes of game screen } void gameOverScreen() { // codes for game over screen } /********* INPUTS *********/ public void mousePressed() { // if we are on the initial screen when clicked, start the game if (gameScreen==0) { startGame(); } } /********* OTHER FUNCTIONS *********/ // This method sets the necessary variables to start the game void startGame() { gameScreen=1; }
Cela peut sembler effrayant au premier abord, mais tout ce que nous avons fait est de construire la structure de base et de séparer les différentes parties avec des blocs de commentaires.
Comme vous pouvez le voir, nous définissons une méthode différente pour chaque écran à afficher. Dans notre bloc draw, nous vérifions simplement la valeur de notre variable gameScreen
, et appelons la méthode correspondante.
Dans la void mousePressed(){...}
, nous écoutons les clics de souris et si l'écran actif est 0, l'écran initial, nous appelons la méthode startGame()
qui démarre le jeu comme prévu. La première ligne de cette méthode change la variable gameScreen
en 1, l'écran de jeu.
Si cela est compris, la prochaine étape consiste à mettre en œuvre notre écran initial. Pour ce faire, nous allons éditer la méthode initScreen()
. Ici ça va:
void initScreen() { background(0); textAlign(CENTER); text("Click to start", height/2, width/2); }
Maintenant, notre écran initial a un fond noir et un texte simple, "Cliquez pour démarrer", situé au milieu et aligné au centre. Mais quand on clique, rien ne se passe. Nous n'avons pas encore spécifié de contenu pour notre écran de jeu. La méthode gameScreen()
ne contient rien, nous ne couvrons donc pas le contenu précédent tiré du dernier écran (le texte) en ayant background()
comme première ligne de dessin. C'est pourquoi le texte est toujours là, même si la ligne text()
n'est plus appelée (tout comme l'exemple de la balle en mouvement de la dernière partie qui laissait une trace derrière) . Le fond est toujours noir pour la même raison. Alors allons-y et commençons à implémenter l'écran de jeu.
void gameScreen() { background(255); }
Après ce changement, vous remarquerez que l'arrière-plan devient blanc et que le texte disparaît.
Étape 2 du didacticiel de traitement : création de la balle et mise en œuvre de la gravité
Maintenant, nous allons commencer à travailler sur l'écran de jeu. Nous allons d'abord créer notre balle. Nous devrions définir des variables pour ses coordonnées, sa couleur et sa taille car nous pourrions vouloir modifier ces valeurs plus tard. Par exemple, si nous voulons augmenter la taille de la balle au fur et à mesure que le joueur marque plus haut, le jeu sera plus difficile. Nous devrons changer sa taille, donc ce devrait être une variable. Nous définirons également la vitesse de la balle après avoir implémenté la gravité.
Tout d'abord, ajoutons ce qui suit :
... int ballX, ballY; int ballSize = 20; int ballColor = color(0); ... void setup() { ... ballX=width/4; ballY=height/5; } ... void gameScreen() { ... drawBall(); } ... void drawBall() { fill(ballColor); ellipse(ballX, ballY, ballSize, ballSize); }
Nous avons défini les coordonnées comme des variables globales, créé une méthode qui dessine la balle, appelée à partir de la méthode gameScreen . La seule chose à laquelle il faut faire attention ici est que nous avons initialisé les coordonnées, mais nous les avons définies dans setup()
. La raison pour laquelle nous avons fait cela est que nous voulions que le ballon commence à un quart de la gauche et à un cinquième du haut. Il n'y a pas de raison particulière pour laquelle nous voulons cela, mais c'est un bon point pour que le ballon commence. Nous devions donc obtenir dynamiquement la width
et la height
de l'esquisse. La taille de l'esquisse est définie dans setup()
, après la première ligne. width
et height
ne sont pas définis avant l'exécution de setup()
, c'est pourquoi nous n'avons pas pu y parvenir si nous avons défini les variables en haut.
La gravité
Maintenant, la mise en œuvre de la gravité est en fait la partie la plus facile. Il n'y a que quelques astuces. Voici d'abord la mise en œuvre :
... float gravity = 1; float ballSpeedVert = 0; ... void gameScreen() { ... applyGravity(); keepInScreen(); } ... void applyGravity() { ballSpeedVert += gravity; ballY += ballSpeedVert; } void makeBounceBottom(float surface) { ballY = surface-(ballSize/2); ballSpeedVert*=-1; } void makeBounceTop(float surface) { ballY = surface+(ballSize/2); ballSpeedVert*=-1; } // keep ball in the screen void keepInScreen() { // ball hits floor if (ballY+(ballSize/2) > height) { makeBounceBottom(height); } // ball hits ceiling if (ballY-(ballSize/2) < 0) { makeBounceTop(0); } }
Et le résultat est :
Tenez vos chevaux, physicien. Je sais que ce n'est pas comme ça que la gravité fonctionne dans la vraie vie. Au lieu de cela, il s'agit plus d'un processus d'animation qu'autre chose. La variable que nous avons définie comme étant la gravity
n'est qu'une valeur numérique - un float
pour que nous puissions utiliser des valeurs décimales, pas seulement des nombres entiers - que nous ajoutons à ballSpeedVert
à chaque boucle. Et ballSpeedVert
est la vitesse verticale de la balle, qui est ajoutée à la coordonnée Y de la balle ( ballY
) sur chaque boucle. Nous regardons les coordonnées de la balle et nous nous assurons qu'elle reste à l'écran. Si nous ne le faisions pas, la balle tomberait à l'infini. Pour l'instant, notre balle ne se déplace que verticalement. Nous surveillons donc les limites du sol et du plafond de l'écran. Avec la méthode keepInScreen()
, nous vérifions si ballY
( + le rayon) est inférieur à height
, et de même ballY
( - le rayon) est supérieur à 0
. Si les conditions ne sont pas remplies, on fait rebondir la balle (du bas ou du haut) avec makeBounceBottom()
et makeBounceTop()
. Pour faire rebondir la balle, nous déplaçons simplement la balle à l'endroit exact où elle devait rebondir et multiplions la vitesse verticale ( ballSpeedVert
) par -1
(multiplier par -1 change le signe). Lorsque la valeur de vitesse a un signe moins, en ajoutant la coordonnée Y, la vitesse devient ballY + (-ballSpeedVert)
, qui est ballY - ballSpeedVert
. Ainsi, la balle change immédiatement de direction avec la même vitesse. Ensuite, lorsque nous ajoutons gravity
à ballSpeedVert
et que ballSpeedVert
a une valeur négative, elle commence à se rapprocher de 0
, finit par devenir 0
et recommence à augmenter. Cela fait monter la balle, monter plus lentement, s'arrêter et commencer à tomber.
Il y a cependant un problème avec notre processus d'animation : la balle continue de rebondir. S'il s'agissait d'un scénario réel, la balle aurait été confrontée à la résistance de l'air et à la friction à chaque fois qu'elle touchait une surface. C'est le comportement que nous souhaitons pour le processus d'animation de notre jeu, il est donc facile de l'implémenter. Nous ajoutons ce qui suit :
... float airfriction = 0.0001; float friction = 0.1; ... void applyGravity() { ... ballSpeedVert -= (ballSpeedVert * airfriction); } void makeBounceBottom(int surface) { ... ballSpeedVert -= (ballSpeedVert * friction); } void makeBounceTop(int surface) { ... ballSpeedVert -= (ballSpeedVert * friction); }
Et maintenant, notre processus d'animation produit ceci :
Comme son nom l'indique, le friction
est le frottement de surface et le airfriction
de l'air est le frottement de l'air. Il est donc évident que friction
doit s'appliquer à chaque fois que la balle touche une surface. airfriction
de l'air doit cependant s'appliquer en permanence. C'est donc ce que nous avons fait. La méthode applyGravity()
s'exécute sur chaque boucle, nous retirons donc 0.0001
% de sa valeur actuelle de ballSpeedVert
sur chaque boucle. makeBounceBottom()
et makeBounceTop()
s'exécutent lorsque la balle touche n'importe quelle surface. Donc, dans ces méthodes, nous avons fait la même chose, mais cette fois avec friction
.
Étape 3 du didacticiel de traitement : création de la raquette
Maintenant, nous avons besoin d'une raquette pour que la balle rebondisse. Nous devrions contrôler le racket. Rendons-le contrôlable avec la souris. Voici le code :
... color racketColor = color(0); float racketWidth = 100; float racketHeight = 10; ... void gameScreen() { ... drawRacket(); ... } ... void drawRacket(){ fill(racketColor); rectMode(CENTER); rect(mouseX, mouseY, racketWidth, racketHeight); }
Nous avons défini la couleur, la largeur et la hauteur de la raquette comme une variable globale, nous pourrions vouloir qu'elles changent pendant le jeu. Nous avons implémenté une méthode drawRacket()
qui fait ce que son nom suggère. Nous réglons le rectMode
au centre, donc notre raquette est alignée au centre de notre curseur.
Maintenant que nous avons créé la raquette, nous devons faire rebondir la balle dessus.
... int racketBounceRate = 20; ... void gameScreen() { ... watchRacketBounce(); ... } ... void watchRacketBounce() { float overhead = mouseY - pmouseY; if ((ballX+(ballSize/2) > mouseX-(racketWidth/2)) && (ballX-(ballSize/2) < mouseX+(racketWidth/2))) { if (dist(ballX, ballY, ballX, mouseY)<=(ballSize/2)+abs(overhead)) { makeBounceBottom(mouseY); // racket moving up if (overhead<0) { ballY+=overhead; ballSpeedVert+=overhead; } } } }
Et voici le résultat:
Ainsi, ce que watchRacketBounce()
fait, c'est qu'il s'assure que la raquette et la balle entrent en collision. Il y a deux choses à vérifier ici, à savoir si la balle et la raquette sont alignées verticalement et horizontalement. La première instruction if vérifie si la coordonnée X du côté droit de la balle est supérieure à la coordonnée X du côté gauche de la raquette (et inversement). Si c'est le cas, la deuxième instruction vérifie si la distance entre la balle et la raquette est inférieure ou égale au rayon de la balle (ce qui signifie qu'elles entrent en collision) . Donc, si ces conditions sont remplies, la méthode makeBounceBottom()
est appelée et la balle rebondit sur notre raquette (à mouseY
, où se trouve la raquette).
Avez-vous remarqué la overhead
variable qui est calculée par mouseY - pmouseY
? Les variables pmouseX
et pmouseY
stockent les coordonnées de la souris à l'image précédente. Comme la souris peut se déplacer très rapidement, il y a de fortes chances que nous ne détections pas correctement la distance entre la balle et la raquette entre les images si la souris se déplace assez rapidement vers la balle. Donc, nous prenons la différence des coordonnées de la souris entre les images et en tenons compte lors de la détection de la distance. Plus la souris se déplace rapidement, plus la distance est acceptable.
Nous utilisons également les overhead
pour une autre raison. Nous détectons dans quel sens la souris se déplace en vérifiant le signe de overhead
. Si l'overhead est négatif, la souris était quelque part en dessous dans l'image précédente, donc notre souris (raquette) se déplace vers le haut. Dans ce cas, nous voulons ajouter une vitesse supplémentaire à la balle et la déplacer un peu plus loin que le rebond normal pour simuler l'effet de frapper la balle avec la raquette. Si l' overhead
est inférieur à 0
, nous l'ajoutons à ballY
et ballSpeedVert
pour que la balle aille plus haut et plus vite. Ainsi, plus la raquette frappe la balle rapidement, plus elle montera haut et vite.
Étape 4 du didacticiel de traitement : mouvement horizontal et contrôle du ballon
Dans cette section, nous allons ajouter un mouvement horizontal à la balle. Ensuite, nous rendrons possible le contrôle de la balle horizontalement avec notre raquette. Nous y voilà:
... // we will start with 0, but for we give 10 just for testing float ballSpeedHorizon = 10; ... void gameScreen() { ... applyHorizontalSpeed(); ... } ... void applyHorizontalSpeed(){ ballX += ballSpeedHorizon; ballSpeedHorizon -= (ballSpeedHorizon * airfriction); } void makeBounceLeft(float surface){ ballX = surface+(ballSize/2); ballSpeedHorizon*=-1; ballSpeedHorizon -= (ballSpeedHorizon * friction); } void makeBounceRight(float surface){ ballX = surface-(ballSize/2); ballSpeedHorizon*=-1; ballSpeedHorizon -= (ballSpeedHorizon * friction); } ... void keepInScreen() { ... if (ballX-(ballSize/2) < 0){ makeBounceLeft(0); } if (ballX+(ballSize/2) > width){ makeBounceRight(width); } }
Et le résultat est :

L'idée ici est la même que ce que nous avons fait pour le mouvement vertical. Nous avons créé une variable de vitesse horizontale, ballSpeedHorizon
. Nous avons créé une méthode pour appliquer une vitesse horizontale à ballX
et éliminer le frottement de l'air. Nous avons ajouté deux autres instructions if à la méthode keepInScreen()
qui surveillera la balle pour frapper les bords gauche et droit de l'écran. Enfin, nous avons créé makeBounceLeft()
et makeBounceRight()
pour gérer les rebonds de gauche et de droite.
Maintenant que nous avons ajouté de la vitesse horizontale au jeu, nous voulons contrôler la balle avec la raquette. Comme dans le célèbre jeu Atari Breakout et dans tous les autres jeux de casse-briques, la balle doit aller à gauche ou à droite selon le point de la raquette qu'elle touche. Les bords de la raquette devraient donner plus de vitesse horizontale à la balle alors que le milieu ne devrait avoir aucun effet. Coder d'abord :
void watchRacketBounce() { ... if ((ballX+(ballSize/2) > mouseX-(racketWidth/2)) && (ballX-(ballSize/2) < mouseX+(racketWidth/2))) { if (dist(ballX, ballY, ballX, mouseY)<=(ballSize/2)+abs(overhead)) { ... ballSpeedHorizon = (ballX - mouseX)/5; ... } } }
Le résultat est:
L'ajout de cette simple ligne à watchRacketBounce()
a fait l'affaire. Ce que nous avons fait, c'est que nous avons déterminé la distance entre le point que la balle frappe et le centre de la raquette avec ballX - mouseX
. Ensuite, on en fait la vitesse horizontale. La différence réelle était trop importante, alors j'ai fait quelques essais et j'ai pensé qu'un dixième de la valeur semblait le plus naturel.
Étape 5 du didacticiel de traitement : création des murs
Notre croquis commence à ressembler davantage à un jeu à chaque étape. Dans cette étape, nous allons ajouter des murs se déplaçant vers la gauche, comme dans Flappy Bird :
... int wallSpeed = 5; int wallInterval = 1000; float lastAddTime = 0; int minGapHeight = 200; int maxGapHeight = 300; int wallWidth = 80; color wallColors = color(0); // This arraylist stores data of the gaps between the walls. Actuals walls are drawn accordingly. // [gapWallX, gapWallY, gapWallWidth, gapWallHeight] ArrayList<int[]> walls = new ArrayList<int[]>(); ... void gameScreen() { ... wallAdder(); wallHandler(); } ... void wallAdder() { if (millis()-lastAddTime > wallInterval) { int randHeight = round(random(minGapHeight, maxGapHeight)); int randY = round(random(0, height-randHeight)); // {gapWallX, gapWallY, gapWallWidth, gapWallHeight} int[] randWall = {width, randY, wallWidth, randHeight}; walls.add(randWall); lastAddTime = millis(); } } void wallHandler() { for (int i = 0; i < walls.size(); i++) { wallRemover(i); wallMover(i); wallDrawer(i); } } void wallDrawer(int index) { int[] wall = walls.get(index); // get gap wall settings int gapWallX = wall[0]; int gapWallY = wall[1]; int gapWallWidth = wall[2]; int gapWallHeight = wall[3]; // draw actual walls rectMode(CORNER); fill(wallColors); rect(gapWallX, 0, gapWallWidth, gapWallY); rect(gapWallX, gapWallY+gapWallHeight, gapWallWidth, height-(gapWallY+gapWallHeight)); } void wallMover(int index) { int[] wall = walls.get(index); wall[0] -= wallSpeed; } void wallRemover(int index) { int[] wall = walls.get(index); if (wall[0]+wall[2] <= 0) { walls.remove(index); } }
Et cela s'est traduit par :
Même si le code semble long et intimidant, je promets qu'il n'y a rien de difficile à comprendre. La première chose à remarquer est ArrayList
. Pour ceux d'entre vous qui ne savent pas ce qu'est une ArrayList
, c'est juste une implémentation de list qui agit comme un Array, mais elle a quelques avantages par rapport à elle. Il est redimensionnable, il a des méthodes utiles comme list.add(index)
, list.get(index)
et list.remove(index)
. Nous conservons les données du mur sous forme de tableaux d'entiers dans l'arraylist. Les données que nous conservons dans les tableaux concernent l'espace entre deux murs. Les tableaux contiennent les valeurs suivantes :
[gap wall X, gap wall Y, gap wall width, gap wall height]
Les murs réels sont dessinés en fonction des valeurs des murs d'espacement. Notez que tout cela pourrait être mieux géré et plus propre en utilisant des classes, mais puisque l'utilisation de la programmation orientée objet (POO) n'est pas dans le cadre de ce tutoriel de traitement, c'est ainsi que nous allons le gérer. Nous avons deux méthodes de base pour gérer les murs, wallAdder()
et wallHandler
.
La méthode wallAdder()
ajoute simplement de nouveaux murs dans chaque milliseconde wallInterval
à l'arraylist. Nous avons une variable globale lastAddTime
qui stocke l'heure à laquelle le dernier mur a été ajouté (en millisecondes) . Si la milliseconde actuelle millis()
moins la dernière milliseconde ajoutée lastAddTime
est supérieure à notre valeur d'intervalle wallInterval
, cela signifie qu'il est maintenant temps d'ajouter un nouveau mur. Des variables d'écart aléatoires sont ensuite générées sur la base des variables globales définies tout en haut. Ensuite, un nouveau mur (tableau d'entiers qui stocke les données du mur d'espacement) est ajouté dans la liste de tableaux et le lastAddTime
est défini sur la milliseconde actuelle millis()
.
wallHandler()
parcourt les murs actuels qui se trouvent dans l'arraylist. Et pour chaque élément à chaque boucle, il appelle wallRemover(i)
, wallMover(i)
et wallDrawer(i)
par la valeur d'index de l'arraylist. Ces méthodes font ce que leur nom l'indique. wallDrawer()
dessine les murs réels en fonction des données de mur d'espacement. Il récupère le tableau de données du mur dans l'arraylist et appelle la méthode rect()
pour dessiner les murs là où ils devraient être. La méthode wallMover()
récupère l'élément de l'arraylist, change son emplacement X en fonction de la variable globale wallSpeed
. Enfin, wallRemover()
supprime les murs de l'arraylist qui sont hors de l'écran. Si nous n'avions pas fait cela, Processing les aurait traités comme ils sont toujours à l'écran. Et cela aurait été une énorme perte de performance. Ainsi, lorsqu'un mur est supprimé de l'arraylist, il n'est pas dessiné sur les boucles suivantes.
La dernière chose difficile à faire est de détecter les collisions entre la balle et les murs.
void wallHandler() { for (int i = 0; i < walls.size(); i++) { ... watchWallCollision(i); } } ... void watchWallCollision(int index) { int[] wall = walls.get(index); // get gap wall settings int gapWallX = wall[0]; int gapWallY = wall[1]; int gapWallWidth = wall[2]; int gapWallHeight = wall[3]; int wallTopX = gapWallX; int wallTopY = 0; int wallTopWidth = gapWallWidth; int wallTopHeight = gapWallY; int wallBottomX = gapWallX; int wallBottomY = gapWallY+gapWallHeight; int wallBottomWidth = gapWallWidth; int wallBottomHeight = height-(gapWallY+gapWallHeight); if ( (ballX+(ballSize/2)>wallTopX) && (ballX-(ballSize/2)<wallTopX+wallTopWidth) && (ballY+(ballSize/2)>wallTopY) && (ballY-(ballSize/2)<wallTopY+wallTopHeight) ) { // collides with upper wall } if ( (ballX+(ballSize/2)>wallBottomX) && (ballX-(ballSize/2)<wallBottomX+wallBottomWidth) && (ballY+(ballSize/2)>wallBottomY) && (ballY-(ballSize/2)<wallBottomY+wallBottomHeight) ) { // collides with lower wall } }
La méthode watchWallCollision()
est appelée pour chaque mur de chaque boucle. Nous saisissons les coordonnées du mur de l'espace, calculons les coordonnées des murs réels (haut et bas) et nous vérifions si les coordonnées de la balle entrent en collision avec les murs.
Étape 6 du didacticiel de traitement : Santé et score
Maintenant que nous pouvons détecter les collisions de la balle et des murs, nous pouvons décider de la mécanique du jeu. Après quelques ajustements au jeu, j'ai réussi à rendre le jeu quelque peu jouable. Mais quand même, c'était très dur. Ma première pensée sur le jeu a été de le faire comme Flappy Bird, lorsque la balle touche les murs, le jeu se termine. Mais ensuite j'ai réalisé qu'il serait impossible de jouer. Alors voici ce que j'en ai pensé :
Il devrait y avoir une barre de santé au-dessus de la balle. La balle devrait perdre de la santé lorsqu'elle touche les murs. Avec cette logique, cela n'a aucun sens de faire rebondir la balle sur les murs. Ainsi, lorsque la santé est à 0, le jeu devrait se terminer et nous devrions passer au jeu sur écran. Alors on y va :
int maxHealth = 100; float health = 100; float healthDecrease = 1; int healthBarWidth = 60; ... void gameScreen() { ... drawHealthBar(); ... } ... void drawHealthBar() { // Make it borderless: noStroke(); fill(236, 240, 241); rectMode(CORNER); rect(ballX-(healthBarWidth/2), ballY - 30, healthBarWidth, 5); if (health > 60) { fill(46, 204, 113); } else if (health > 30) { fill(230, 126, 34); } else { fill(231, 76, 60); } rectMode(CORNER); rect(ballX-(healthBarWidth/2), ballY - 30, healthBarWidth*(health/maxHealth), 5); } void decreaseHealth(){ health -= healthDecrease; if (health <= 0){ gameOver(); } }
Et voici une course simple:
Nous avons créé une health
variable globale pour conserver la santé de la balle. Et puis créé une méthode drawHealthBar()
qui dessine deux rectangles au-dessus de la balle. La première est la barre de santé de base, l'autre est la barre active qui affiche la santé actuelle. La largeur de la seconde est dynamique et est calculée avec healthBarWidth*(health/maxHealth)
, le rapport de notre santé actuelle par rapport à la largeur de la barre de santé. Enfin, les couleurs de remplissage sont définies en fonction de la valeur de la santé. Dernier point mais non le moindre, scores :
... void gameOverScreen() { background(0); textAlign(CENTER); fill(255); textSize(30); text("Game Over", height/2, width/2 - 20); textSize(15); text("Click to Restart", height/2, width/2 + 10); } ... void wallAdder() { if (millis()-lastAddTime > wallInterval) { ... // added another value at the end of the array int[] randWall = {width, randY, wallWidth, randHeight, 0}; ... } } void watchWallCollision(int index) { ... int wallScored = wall[4]; ... if (ballX > gapWallX+(gapWallWidth/2) && wallScored==0) { wallScored=1; wall[4]=1; score(); } } void score() { score++; } void printScore(){ textAlign(CENTER); fill(0); textSize(30); text(score, height/2, 50); }
Il fallait marquer quand le ballon passe un mur. Mais nous devons ajouter au maximum 1 score par mur. Cela signifie que si le ballon passe un mur, puis revient et le passe à nouveau, un autre score ne doit pas être ajouté. Pour ce faire, nous avons ajouté une autre variable au tableau de murs d'espacement dans la liste de tableaux. La nouvelle variable stocke 0
si la balle n'a pas encore franchi ce mur et 1
si elle l'a fait. Ensuite, nous avons modifié la méthode watchWallCollision()
. Nous avons ajouté une condition qui déclenche la méthode score()
et marque le mur comme passé lorsque la balle franchit un mur qu'elle n'a pas franchi auparavant.
Nous sommes maintenant très proches de la fin. La dernière chose à faire est d'implémenter le click to restart
sur l'écran du jeu. Nous devons définir toutes les variables que nous avons utilisées à leur valeur initiale et redémarrer le jeu. C'est ici:
... public void mousePressed() { ... if (gameScreen==2){ restart(); } } ... void restart() { score = 0; health = maxHealth; ballX=width/4; ballY=height/5; lastAddTime = 0; walls.clear(); gameScreen = 0; }
Ajoutons quelques couleurs supplémentaires.
Voila ! Nous avons Flappy Pong!
Le code complet du jeu Processing peut être trouvé ici.
Portage du code de jeu de traitement sur le Web à l'aide de p5.js
p5.js est une bibliothèque JavaScript avec une syntaxe très similaire à celle du langage de programmation Processing. Ce n'est pas une bibliothèque capable d'exécuter simplement du code de traitement existant ; au lieu de cela, p5.js nécessite l'écriture de code JavaScript réel, similaire au port JavaScript de Processing connu sous le nom de Processing.js. Notre tâche consiste à convertir le code de traitement en JavaScript à l'aide de l'API p5.js. La bibliothèque a un ensemble de fonctions et une syntaxe similaire à Processing, et nous devons apporter certaines modifications à notre code pour les faire fonctionner en JavaScript, mais puisque Processing et JavaScript partagent des similitudes avec Java, c'est moins un saut qu'il n'y paraît . Même si vous n'êtes pas un développeur JavaScript, les modifications sont très insignifiantes et vous devriez pouvoir suivre très bien.
Tout d'abord, nous devons créer un simple index.html
et ajouter p5.min.js
à notre en-tête. Nous devons également créer un autre fichier appelé flappy_pong.js
qui hébergera notre code converti.
<html> <head> <title>Flappy Pong</title> <script tyle="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.19/p5.min.js"></script> <script tyle="text/javascript" src="flappy_pong.js"></script> <style> canvas { box-shadow: 0 0 20px lightgray; } </style> </head> <body> </body> </html>
Notre stratégie lors de la conversion du code devrait consister à copier et coller tout notre code dans flappy_pong.js
, puis à apporter toutes les modifications. Et c'est ce que j'ai fait. Et voici les étapes que j'ai suivies pour mettre à jour le code :
Javascript est un langage non typé (il n'y a pas de déclarations de type comme
int
etfloat
). Nous devons donc changer toutes les déclarations de type envar
.Il n'y a pas de
void
en Javascript. Nous devrions tout changer pourfunction
.Nous devons supprimer les déclarations de type des arguments des signatures de fonction. (ie.
void wallMover(var index) {
tofunction wallMover(index) {
)Il n'y a pas de
ArrayList
en JavaScript. Mais nous pouvons obtenir la même chose en utilisant des tableaux JavaScript. Nous apportons les modifications suivantes :-
ArrayList<int[]> walls = new ArrayList<int[]>();
tovar walls = [];
-
walls.clear();
auxwalls = [];
-
walls.add(randWall);
auxwalls.push(randWall);
-
walls.remove(index);
auxwalls.splice(index,1);
-
walls.get(index);
auxwalls[index]
-
walls.size()
àwalls.length
-
Changer la déclaration du tableau
var randWall = {width, randY, wallWidth, randHeight, 0};
àvar randWall = [width, randY, wallWidth, randHeight, 0];
Supprimez tous les mots clés
public
.Déplacez toutes les déclarations
color(0)
dansfunction setup()
carcolor()
ne sera pas définie avant l'appelsetup()
.Modifier la
size(500, 500);
pourcreateCanvas(500, 500);
Renommez
function gameScreen(){
en quelque chose d'autre commefunction gamePlayScreen(){
car nous avons déjà une variable globale nomméegameScreen
. Lorsque nous travaillions avec Processing, l'une était une fonction et l'autre était une variableint
. Mais JavaScript les confond car ils ne sont pas typés.Même chose pour
score()
. Je l'ai renommé enaddScore()
.
Le code JavaScript complet couvrant tout dans ce tutoriel de traitement peut être trouvé ici.
Traiter le code du jeu : vous pouvez le faire aussi
Dans ce tutoriel Processing, j'ai essayé d'expliquer comment construire un jeu très simple. Cependant, ce que nous avons fait dans cet article n'est que la pointe de l'iceberg. Avec le langage de programmation Processing, à peu près tout peut être réalisé. À mon avis, c'est le meilleur outil pour programmer ce que vous imaginez. Mon intention réelle avec ce tutoriel sur le traitement était plutôt que d'enseigner le traitement et de créer un jeu, pour prouver que la programmation n'est pas si difficile. Construire son propre jeu n'est pas qu'un rêve. Je voulais vous montrer qu'avec un peu d'effort et d'enthousiasme, vous pouvez y arriver. J'espère vraiment que ces deux articles inspireront tout le monde à essayer la programmation.