I min tidigare Tetris-handledning visade jag dig hur man hanterar kollisionsdetektering i Tetris. Låt oss nu ta en titt på den andra viktiga aspekten av spelet: linjen rensar.
Notera: Även om koden i denna handledning skrivs med AS3, borde du kunna använda samma tekniker och begrepp i nästan vilken spelutvecklingsmiljö som helst.
Det är faktiskt mycket enkelt att upptäcka att en rad har fyllts. bara titta på arrayen av arrays gör det ganska uppenbart vad man ska göra:
Den fyllda linjen är den som inte har några nollor i det - så vi kan kolla en given rad så här:
rad = 4; // kolla fjärde raden isFilled = true; för (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
Med detta kan vi naturligtvis gå igenom varje rad och ta reda på vilka av dem är fyllda:
för (var rad = 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
Okej, vi kan optimera detta genom att ta reda på vilka linjer som är troligt att fyllas, baserat på vilka rader det senaste blocket upptar, men varför stör? Looping genom varje enskilt element i 10x16-nätet är inte en processorintensiv uppgift.
Frågan är nu: hur gör vi klar linjerna?
Vid första anblicken verkar detta enkelt: vi splittar bara den fyllda raden från arrayen och lägger till nya tomma linjer överst.
för (var rad = 0; rad < 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]);
Om vi försöker detta på ovanstående matris (och sedan gör allt) får vi:
... vilket är vad vi skulle förvänta oss, eller hur? Det finns fortfarande 16 rader, men den fyllda har tagits bort. Den nya tomma linjen har tryckt ner allt för att kompensera.
Här är ett enklare exempel, med före och efter bilder sida vid sida:
Ett annat förväntat resultat. Och även om jag inte kommer att visa det här - gäller samma kod också situationer där mer än en rad fylls på en gång (även om de inte är angränsande).
Det finns emellertid fall där det inte gör vad du kan förvänta dig. Titta på det här:
Det är konstigt att se det blå blocket flyter där, knutet till ingenting. Det är inte fel, exakt - de flesta versioner av Tetris gör det här, inklusive den klassiska Game Boy-utgåvan - så du kan lämna den där.
Det finns dock ett par andra populära sätt att hantera detta ...
Vad händer om vi gjorde det ensamma blå blocket fortsätter att falla efter att linjen rensades?
Den stora svårigheten med detta är att faktiskt utse exakt vad vi försöker göra. Det är svårare än det låter!
Min första instinkt här skulle vara att få varje enskilt block att falla ner tills det landat. Det skulle leda till situationer som följande:
... men jag misstänker att det inte skulle vara kul, eftersom alla luckor snabbt skulle bli fyllda. (Gör det gärna med att experimentera med det här - det kan vara något i det!)
Jag vill att de orange blocken ska hålla kontakten, men det blåa blocket ska falla. Kanske kan vi få block att falla om de inte har några andra block till vänster eller höger om dem? Ah, men titta på den här situationen:
Här vill jag att de blå blocken alla faller i sina respektive "hål" efter att raden har rensats - men den mellersta uppsättningen blå block har alla andra block bredvid dem: andra blå block!
("Kontrollera så bara om blocken ligger bredvid röda block", kanske du tror, men kom ihåg att jag bara har färgat dem i blått och rött för att göra det enklare att hänvisa till olika block, de kan vara några färger och de kunde ha legat när som helst.)
Vi kan identifiera en sak som blått blockerar i högerbilden - och det ensamma flytande blå kvarteret från tidigare - alla är gemensamma: de är ovan linjen som blev klar. Så, om i stället för att försöka göra de enskilda blocken faller, grupperar vi alla dessa blå block tillsammans och får dem att falla som en?
Vi kan till och med återanvända samma kod som gör att en enskild tetromino faller. Här är en påminnelse, från föregående handledning:
// set tetromino.potentialTopLeft att vara en rad under tetromino.topLever då: för (var rad = 0; rad < 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) // det här blocket skulle ligga under spelfältet annars om (landade [rad + tetromino.potentialTopLeft.row]! = 0 && landade [col + tetromino.potentialTopLeft.col]! = 0) / utrymmet är taget
Men snarare än att använda a tetromino
objekt, vi skapar ett nytt objekt vars form
innehåller bara de blå blocken - låt oss ringa det här objektet klump
.
Överföring av blocken är bara en fråga om looping genom landat
array, hitta varje icke-noll element, fylla i samma element i clump.shape
array och ställa in elementet i landat
array till noll.
Som vanligt är det lättare att förstå med en bild:
Till vänster är clump.shape
array, och till höger är landat
array. Här stör jag inte några tomma rader i clump.shape
för att hålla saker snyggare, men du kan göra det utan några problem.
Så, vår klump
objekt ser så här ut:
clump.shape = [[1,0,0,0,0,0,0,0,0], [1,0,0,1,1,0,0,0,0,0], [ 1,0,0,1,1,0,0,0,0,1]]; clump.topLeft = rad: 10, col: 0;
... och nu kör vi bara upprepade gånger samma kod som vi använder för att göra en tetromino falla tills klumpen landar:
// set clump.potentialTopLeft att vara en rad under clump.topLev då: för (var rad = 0; rad < 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) // det här blocket skulle ligga under spelplanen annars om (landade [rad + clump.potentialTopLeft.row]! = 0 && landade [col + clump.potentialTopLeft.col]! = 0) / / utrymmet är taget
När klumpen har landat kopierar vi de enskilda elementen tillbaka till landat
array - igen, precis som när en tetromino landar. Men snarare än att köra det varje halv sekund och återföra allt mellan varje fall, föreslår jag att det körs om och om igen tills klumpen landar så fort som möjligt och sedan gör allt så att det ser ut som det sjunker direkt.
Följ detta igenom om du vill; här är resultatet:
Det är möjligt att en annan linje kommer att bildas här, utan att spelaren måste släppa ett annat block - öppna möjliga spelarstrategier som inte är tillgängliga med Naive-metoden - så måste du omedelbart kolla på fyllda linjer igen. I det här fallet finns inga fina linjer, så spelet kan fortsätta, och du kan koka ett annat block.
Allt verkar bra för Clump-metoden, men tyvärr finns det ett problem, som visas i detta före och efter exempel:
Här har det blå blocket i mitten landat - och eftersom det klumpas ihop med det blå blocket till höger, anses den som "landat" också. Nästa block skulle gissa, och igen har vi ett blått block som flyter i mitten av luften.
Big Clump-metoden är inte en effektiv metod, på grund av detta ointuitiva problem, men det är halvvägs till en bra metod ...
Titta igen på dessa två exempel:
I båda fallen finns det ett uppenbart sätt att separera de blå blocken i separata klumpar - två klumpar (var och en av ett block) i de första och tre klumparna (i tre, fyra och ett block) i andra.
Om vi klumpar blocken så, och gör sedan varje klump faller självständigt, så borde vi få det önskade resultatet! Dessutom kommer "klump" inte längre att vara ett ord.
Här är vad jag menar:
Vi börjar med denna situation. Självklart kommer den andra raden upp att bli rensad.
Vi delar blocken ovanför den rensade linjen i tre distinkta klumpar. (Jag har använt olika färger för att identifiera vilka block som samlas ihop.)
Klumparna faller självständigt - märka hur den gröna klumpen faller två rader, medan de blåa och lila klumparna landar efter att ha fallit bara en. Bottenlinjen är nu fylld, så det blir också klart och de tre klumparna faller.
Hur räknar vi ut klumparnas form? Tja, som du kan se från bilden är det faktiskt ganska enkelt: vi grupperar alla block upp i sammanhängande former - det vill säga för varje block samlar vi upp det med alla dess grannar och grannarnas grannar och så tills varje block är i en grupp.
Istället för att förklara exakt hur man gör denna gruppering, pekar jag dig på Wikipedia-sidan för översvämningsfyllning, vilket förklarar flera sätt att uppnå detta, tillsammans med fördelar och nackdelar med varje.
När du har klumparnas former kan du hålla dem i en grupp:
klumpar = []; klumpar [0] .shape = [[3], [3]]; klumpar [0]. topLeft = rad: 11, kol: 0; klumpar [1] .shape = [[0,1,0], [0,1,1], [0,1,1], [1,1,1]]; klumpar [1]. topLeft = rad: 9, kol: 3; klumpar [2] .shape = [[1,1,1], [1,1,1], [0,1,1]]; klumpar [2]. topLeft = rad: 10, kol: 7;
Därefter, repetera bara varje klump i arrayen som faller, kom ihåg att kontrollera efter nya fyllda linjer när de landat.
Detta kallas den klibbiga metoden, och den används i några spel, till exempel Tetris Blast. Jag gillar det; Det är en anständig vridning på Tetris, vilket möjliggör nya strategier. Det finns en annan populär metod som är ganska annorlunda ...
Om du har följt begreppen hittills tycker jag att det är värt att försöka implementera Cascade-metoden själv som en övning.
I grund och botten kommer varje block ihåg vilket tetromino det var en del av, även när ett segment av den tetrominoen förstörs av en linjeklar. Tetrominoerna - eller konstiga, huggade delar av tetrominoer - faller som klumpar.
Som alltid hjälper bilderna:
En T-tetromino fall, kompletterande en linje. Observera hur varje block förbinds med sin ursprungliga tetromino? (Vi antar här att inga rader har rensats hittills.)
Den färdiga linjen rensas, vilken delar upp den gröna z-tetromino i två separata bitar och huggar bitar av andra tetrominoer.
T-tetromino (eller vad är kvar av det) fortsätter att falla, eftersom det inte hålls uppe vid några andra block.
T-tetromino landar, kompletterar en annan linje. Denna linje rensas, huggar bitar av ännu mer tetrominoer.
Som du kan se spelar Cascade-metoden lite annorlunda än de andra två huvudmetoderna. Om du fortfarande är oklart om hur det fungerar, se om du kan hitta en kopia av Quadra eller Tetris 2 (eller titta upp videor på YouTube), eftersom de båda använder den här metoden.
Lycka till!
Tack för att du läste denna handledning! Jag hoppas att du lärde dig något (och inte bara om Tetris), och att du kommer att gå till utmaningen. Om du gör några spel med hjälp av dessa tekniker, skulle jag gärna se dem! Vänligen skicka in dem i kommentarerna nedan, eller tweet mig på @MichaelJW.
.