Dans mon précédent tutoriel sur Tetris, je vous ai montré comment gérer la détection de collision dans Tetris. Voyons maintenant l’autre aspect important du jeu: line clears.
Remarque: Bien que le code de ce tutoriel soit écrit en AS3, vous devriez pouvoir utiliser les mêmes techniques et concepts dans presque tous les environnements de développement de jeux..
Détecter qu'une ligne a été remplie est en réalité très simple; Il suffit de regarder le tableau de tableaux pour que vous sachiez quoi faire:
La ligne remplie est celle qui ne contient pas de zéros - nous pouvons donc vérifier une ligne donnée comme ceci:
rangée = 4; // vérifie la quatrième ligne isFilled = true; pour (var col = 0; col < landed[row].length; col++) if (landed[row][col] == 0) isFilled = false; //if isFilled is still true than row 4 is filled
Avec cela, bien sûr, nous pouvons parcourir chaque rangée et déterminer lesquels sont remplis:
pour (var row = 0; col < landed.length; row++) isFilled = true; for (var col = 0; col < landed[row].length; col++) if (landed[row][col] == 0) isFilled = false; //if isFilled is still true then current row is filled
Ok, nous pourrions optimiser cela en déterminant quelles lignes sont probable à remplir, en fonction des rangées occupées par le dernier bloc, mais pourquoi s'en préoccuper? Faire une boucle à travers chaque élément de la grille 10x16 n'est pas une tâche gourmande en ressources processeur..
La question est maintenant: comment pouvons-nous clair les lignes?
À première vue, cela semble simple: nous séparons simplement les lignes remplies du tableau et ajoutons de nouvelles lignes vides en haut..
pour (var rangée = 0; rangée < landed.length; row++) isFilled = true; for (var col = 0; col < landed[row].length; col++) if (landed[row][col] == 0) isFilled = false; //remove the filled line sub-array from the array landed.splice(row, 1); //add a new empty line sub-array to the start of the array landed.unshift([0,0,0,0,0,0,0,0,0,0]);
Si nous essayons ceci sur le tableau ci-dessus (et ensuite rendons tout), nous obtenons:
… Ce à quoi nous nous attendions, non? Il y a encore 16 rangées, mais celle remplie a été supprimée. la nouvelle ligne vide a tout poussé vers le bas pour compenser.
Voici un exemple plus simple, avec les images avant et après côte à côte:
Un autre résultat attendu. Et - bien que je ne le montre pas ici - le même code traite également des situations dans lesquelles plusieurs lignes sont remplies à la fois (même si ces lignes ne sont pas adjacentes).
Cependant, dans certains cas, cela ne répond pas à vos attentes. Regarde ça:
C'est étrange de voir ce bloc bleu qui flotte, attaché à rien. Ce n'est pas faux, exactement - la plupart des versions de Tetris le font, y compris la version classique de Game Boy - pour que vous puissiez en rester là..
Cependant, il existe deux autres moyens populaires de gérer cela…
Et si nous faisions en sorte que ce bloc bleu solitaire continue de tomber après le nettoyage de la ligne?
La grande difficulté avec cela consiste à déterminer exactement ce que nous essayons de faire. C'est plus dur que ça en a l'air!
Mon premier réflexe serait de faire tomber chaque bloc jusqu'à son atterrissage. Cela conduirait à des situations comme celle-ci:
… Mais je soupçonne que ce ne serait pas amusant, car toutes les lacunes seraient rapidement comblées. (N'hésitez pas à expérimenter avec cela, cependant - il pourrait y avoir quelque chose dedans!)
Je veux que ces blocs orange restent connectés, mais que ce bloc bleu tombe. Peut-être pourrions-nous faire tomber des blocs s'ils n'ont pas d'autres blocs à leur gauche ou à leur droite? Ah, mais regardez cette situation:
Ici, je veux que les blocs bleus tombent tous dans leurs "trous" respectifs après que la ligne ait été effacée - mais l'ensemble de blocs bleus du milieu a tous d'autres blocs à côté: d'autres blocs bleus!
("Alors, vérifiez seulement si les blocs sont à côté des blocs rouges", pourriez-vous penser, mais souvenez-vous que je ne les ai colorés que de bleu et de rouge pour faciliter la référence à différents blocs; ils peuvent être n'importe quelle couleur, et ils aurait pu être étendu à tout moment.)
Nous pouvons identifier une chose que les blocs bleus dans l'image de droite - et le seul bloc bleu flottant d'avant - sont tous en commun: ils sont au dessus de la ligne qui a été effacée. Alors, si au lieu d'essayer de faire tomber les blocs individuels, nous regroupons tous ces blocs bleus et les faisons tomber comme un seul?
Nous pourrions même réutiliser le même code qui fait chuter un tétromino individuel. Voici un rappel du tutoriel précédent:
// définit tetromino.potentialTopLeft sur une ligne sous tetromino.topLeft, then: for (var row = 0; row < tetromino.shape.length; row++) for (var col = 0; col < tetromino.shape[row].length; col++) if (tetromino.shape[row][col] != 0) if (row + tetromino.potentialTopLeft.row >= landed.length) // ce bloc serait en dessous du terrain de jeu else if (atterri [rangée + tetromino.potentialTopLeft.row]! = 0 && atterri [col + tetromino.potentialTopLeft.col]! = 0) / / l'espace est pris
Mais plutôt que d'utiliser un tetromino
objet, nous allons créer un nouvel objet dont forme
ne contient que les blocs bleus - appelons cet objet touffe
.
Transférer les blocs est juste une question de boucle à travers le a atterri
tableau, trouver tous les éléments non nuls, remplir le même élément dans le clump.shape
tableau, et définissant l'élément de la a atterri
tableau à zéro.
Comme d'habitude, c'est plus facile à comprendre avec une image:
À gauche se trouve le clump.shape
tableau, et à droite est le a atterri
tableau. Ici, je ne me soucie pas de remplir les lignes vides clump.shape
garder les choses ordonnées, mais vous pouvez le faire sans aucun problème.
Donc notre touffe
l'objet ressemble à ceci:
clump.shape = [[1,0,0,0,0,0,0,0,0,0], [1,0,0,1,1,0,0,0,0,0,0], [ 1,0,0,1,1,0,0,0,0,1]]; clump.topLeft = rangée: 10, col: 0;
… Et maintenant, nous répétons le même code que celui utilisé pour faire tomber un tétromino, jusqu'à ce que la bosse se pose:
// définit clump.potentialTopLeft sur une ligne sous clump.topLeft, alors: for (var row = 0; row < clump.shape.length; row++) for (var col = 0; col < clump.shape[row].length; col++) if (clump.shape[row][col] != 0) if (row + clump.potentialTopLeft.row >= landed.length) // ce bloc serait sous le terrain de jeu sinon si (landed [rangée + clump.potentialTopLeft.row]! = 0 && landed [col + clump.potentialTopLeft.col]! = 0) / / l'espace est pris
Une fois que la masse a atterri, nous copions les éléments individuels vers le a atterri
array - encore une fois, comme quand un tetromino atterrit. Cependant, plutôt que de courir cela toutes les demi-secondes et de tout refaire entre chaque automne, je suggère de le courir encore et encore jusqu'à ce que la bosse se pose, le plus rapidement possible, et puis tout rendre, de sorte qu'il semble qu'il tombe instantanément.
Suivez ceci à travers si vous aimez; voici le résultat:
Il est possible qu'une autre ligne soit formée ici, sans que le joueur ait à supprimer un autre bloc - ouvrant des stratégies de joueur possibles non disponibles avec la méthode Naive - vous devez donc immédiatement vérifier à nouveau les lignes pleines. Dans ce cas, il n’ya pas de lignes remplies, le jeu peut continuer et vous pouvez générer un autre bloc..
Tout semble bien pour la méthode Clump, mais malheureusement, il y a un problème, comme le montre cet exemple avant-après:
Ici, le bloc bleu au milieu a atterri - et comme il est groupé avec le bloc bleu à droite, celui-ci est également considéré comme ayant "atterri". Le prochain bloc apparaîtrait, et encore nous avons un bloc bleu flottant dans les airs.
La méthode Big Clump n’est pas réellement une méthode efficace, en raison de ce problème peu intuitif, mais elle est à mi-chemin d'une bonne méthode…
Regardez à nouveau ces deux exemples:
Dans les deux cas, il existe un moyen évident de séparer les blocs bleus en plusieurs blocs distincts: deux blocs (chacun d’un bloc) dans le premier et trois blocs (de trois, quatre et un blocs) dans le second..
Si nous agglutinons les blocs de cette manière, puis que nous faisons tomber chaque agglomérat indépendamment, nous devrions obtenir le résultat souhaité! De plus, "groupe" n'apparaîtra plus comme un mot.
Voici ce que je veux dire:
Nous commençons par cette situation. De toute évidence, la deuxième ligne va être effacée.
Nous avons divisé les blocs situés au-dessus de la ligne de démarcation en trois groupes distincts. (J'ai utilisé différentes couleurs pour identifier les blocs qui s'agglutinent.)
Les touffes tombent indépendamment - remarquez comment la touffe verte tombe deux rangées, tandis que les touffes bleues et violettes atterrissent après une seule chute. La ligne de fond est maintenant remplie, elle est donc effacée également et les trois touffes tombent..
Comment pouvons-nous déterminer la forme des touffes? Eh bien, comme vous pouvez le voir sur l’image, c’est en fait assez simple: nous regroupons tous les blocs en formes contiguës, c’est-à-dire que, pour chaque bloc, nous le regroupons avec tous ses voisins et voisins, et ainsi de suite. allumé, jusqu'à ce que chaque bloc soit dans un groupe.
Plutôt que d’expliquer exactement comment faire ce regroupement, je vous dirigerai vers la page Wikipedia consacrée aux zones inondables, qui explique plusieurs manières d’y parvenir, ainsi que les avantages et les inconvénients de chacun..
Une fois que vous avez les formes de vos bouquets, vous pouvez les coller dans un tableau:
touffes = []; amas [0] .shape = [[3], [3]]; agrégats [0] .topLeft = rangée: 11, col: 0; amas [1] .shape = [[0,1,0], [0,1,1], [0,1,1], [1,1,1]]; amas [1] .topLeft = rangée: 9, col: 3; amas [2] .shape = [[1,1,1], [1,1,1], [0,1,1]]; amas [2] .topLeft = rangée: 10, col: 7;
Ensuite, il suffit d’itérer chaque bloc dans le tableau qui tombe, en se rappelant de vérifier les nouvelles lignes remplies une fois qu’ils ont atterri.
C'est ce qu'on appelle la méthode Sticky, et elle est utilisée dans quelques jeux, tels que Tetris Blast. Je l'aime; c'est une tournure décente sur Tetris, permettant de nouvelles stratégies. Il existe une autre méthode populaire qui est un peu différente…
Si vous avez suivi les concepts jusqu'à présent, je pense que cela vaut la peine d'essayer de mettre en œuvre vous-même la méthode Cascade..
Fondamentalement, chaque bloc se souvient de quel tétromino il faisait partie, même lorsqu'un segment de ce tétromino est détruit par une ligne vide. Les tétrominoes - ou d’étranges parties de tétrominoes hachées - tombent en touffes.
Comme toujours, les images aident:
Un T-tetromino tombe, complétant une ligne. Notez comment chaque bloc reste connecté à son tétromino d'origine? (Nous supposerons ici qu'aucune ligne n'a été effacée jusqu'à présent.)
La ligne terminée est effacée, ce qui divise le Z-tétromino vert en deux parties séparées et coupe les morceaux des autres tétrominos..
Le T-Tetromino (ou ce qu'il en reste) continue de tomber, parce qu'il n'est pas retenu par d'autres blocs.
Le T-tetromino atterrit, complétant une autre ligne. Cette ligne est effacée, coupant des morceaux de tétromino.
Comme vous pouvez le constater, la méthode Cascade se déroule assez différemment des deux autres méthodes principales. Si vous ne savez toujours pas comment cela fonctionne, voyez si vous pouvez trouver une copie de Quadra ou de Tetris 2 (ou rechercher des vidéos sur YouTube), car ils utilisent tous les deux cette méthode..
Bonne chance!
Merci d'avoir lu ce tutoriel! J'espère que vous avez appris quelque chose (et pas seulement sur Tetris) et que vous pourrez relever le défi. Si vous faites des jeux en utilisant ces techniques, j'aimerais les voir! S'il vous plaît postez-les dans les commentaires ci-dessous, ou tweetez-moi à @MichaelJW.
.