In mijn vorige Tetris-zelfstudie heb ik je laten zien hoe botsingsdetectie in Tetris moet worden afgehandeld. Laten we nu eens kijken naar het andere belangrijke aspect van het spel: lijn wist.
Notitie: Hoewel de code in deze tutorial met AS3 is geschreven, zou je in bijna elke game-ontwikkelomgeving dezelfde technieken en concepten moeten kunnen gebruiken.
Het detecteren dat een regel is ingevuld, is eigenlijk heel eenvoudig; gewoon kijken naar de array van arrays maakt het heel duidelijk wat te doen:
De gevulde lijn bevat geen nullen, dus we kunnen een bepaalde rij als volgt controleren:
rij = 4; // Controleer de vierde rij isFilled = true; voor (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
Hiermee kunnen we natuurlijk elke rij doorlopen en uitzoeken welke van hen zijn gevuld:
voor (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é, we kunnen dit optimaliseren door uit te vinden welke lijnen dat zijn waarschijnlijk worden ingevuld, op basis van welke rijen het laatste blok in beslag neemt, maar waarom zou u zich zorgen maken? Elk afzonderlijk element in het 10x16-raster doorlopen is geen procesintensieve taak.
De vraag is nu: hoe kunnen we duidelijk de lijnen?
Op het eerste gezicht lijkt dit eenvoudig: we voegen gewoon de gevulde rij (en) van de array en voegen nieuwe lege regels toe aan de bovenkant.
voor (var row = 0; rij < 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]);
Als we dit op de bovenstaande array proberen (en vervolgens alles weergeven), krijgen we:
... wat is wat we verwachten, toch? Er zijn nog 16 rijen, maar de gevulde is verwijderd; de nieuwe lege regel heeft alles naar beneden gedrukt om te compenseren.
Hier is een eenvoudiger voorbeeld, met de voor en na foto's naast elkaar:
Een ander te verwachten resultaat. En - hoewel ik het hier niet zal laten zien - behandelt dezelfde code ook situaties waarin meer dan één regel tegelijk wordt gevuld (zelfs als die regels niet naast elkaar liggen).
Er zijn echter gevallen waarin dit niet doet wat u zou verwachten. Kijk hier eens even naar:
Het is raar om te zien dat het blauwe blok daar zweeft, vastzit aan niets. Het is niet fout, precies - de meeste versies van Tetris doen dit, inclusief de klassieke Game Boy-editie - dus u kunt het daarbij laten.
Er zijn echter een aantal andere populaire manieren om dit aan te pakken ...
Wat als we dat eenzame blauwe blokje nog steeds laten vallen nadat de lijn was vrijgemaakt?
De grote moeilijkheid hierbij is eigenlijk precies uitvinden wat we proberen te doen. Het is moeilijker dan het klinkt!
Mijn eerste instinct hier zou zijn om elk individueel blok te laten vallen totdat het geland was. Dat zou tot dergelijke situaties leiden:
... maar ik vermoed dat dit geen plezier zou zijn, omdat alle hiaten snel zouden worden opgevuld. (Voel je vrij om hiermee te experimenteren, er kan iets in zitten!)
Ik wil dat die oranje blokken verbonden blijven, maar dat blauwe blok valt. Misschien kunnen we blokken laten vallen als ze geen andere blokken links of rechts van hen hebben? Ah, maar kijk eens naar deze situatie:
Hier wil ik dat de blauwe blokken allemaal in hun respectievelijke "gaten" vallen nadat de regel is gewist - maar de middelste set blauwe blokken hebben allemaal andere blokken naast elkaar: andere blauwe blokken!
('Controleer dus alleen of de blokken zich naast rode blokken bevinden', zou je denken, maar onthoud dat ik ze alleen in blauw en rood gekleurd heb om het gemakkelijker te maken naar verschillende blokken te verwijzen, ze kunnen elke kleur hebben en ze had op elk moment kunnen worden gelegd.)
We kunnen één ding identificeren dat de blauwe blokken in het rechterbeeld - en het enige zwevende blauwe blok van voor - allemaal gemeen hebben: ze zijn bovenstaande de regel die is gewist. Dus, wat als we, in plaats van te proberen de individuele blokken te laten vallen, we al deze blauwe blokken groeperen en ze laten vallen als één?
We kunnen zelfs dezelfde code hergebruiken die een individuele tetromino doet vallen. Hier is een herinnering aan de vorige tutorial:
// set tetromino.potentialTopLeft een rij te zijn onder 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) // dit blok zou onder het speelveld liggen else if (landed [row + tetromino.potentialTopLeft.row]! = 0 && landed [col + tetromino.potentialTopLeft.col]! = 0) / / de spatie is genomen
Maar in plaats van een tetromino
object maken we een nieuw object waarvan vorm
bevat alleen de blauwe blokken - laten we dit object noemen klomp
.
Het overbrengen van de blokken is gewoon een kwestie van het doorlopen van de geland
array, het vinden van elk niet-nul element, het invullen van de dezelfde element in de clump.shape
array, en het instellen van het element van de geland
array naar nul.
Zoals gewoonlijk is dit gemakkelijker te begrijpen met een foto:
Aan de linkerkant is de clump.shape
array, en aan de rechterkant is de geland
matrix. Hier hoef ik geen lege rijen in te vullen clump.shape
om dingen netter te houden, maar je zou dit zonder problemen kunnen doen.
Zo onze klomp
object ziet er zo uit:
clump.shape = [[1,0, 1,0,0,0 ,,0,0,0], [1,0,1,1,0,0,0,0,0], [ 1,0,0,1,1,0,0,0,0,1]]; clump.topLeft = row: 10, col: 0;
... en nu voeren we gewoon dezelfde code uit die we gebruiken om een tetromino te laten vallen, totdat de klomp landt:
// stel clump.potentialTopLeft in als één rij onder clump.topLeft en dan: voor (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) // dit blok zou onder het speelveld liggen else if (landed [row + clump.potentialTopLeft.row]! = 0 && landed [col + clump.potentialTopLeft.col]! = 0) / / de spatie is genomen
Zodra de klomp is geland, kopiëren we de afzonderlijke elementen terug naar de geland
array - nogmaals, net als wanneer een tetromino landt. In plaats van dit elke halve seconde te doen en alles tussen elke val opnieuw weer te geven, stel ik voor om het zo vaak mogelijk opnieuw te laten lopen totdat de klont zo snel mogelijk komt, en dan alles weergeven, zodat het lijkt alsof het onmiddellijk daalt.
Volg dit door als je wilt; hier is het resultaat:
Het is mogelijk dat hier een andere regel wordt gevormd, zonder dat de speler nog een blok hoeft te gooien - mogelijke spelersstrategieën openen die niet beschikbaar zijn met de methode Naive - dus je moet onmiddellijk opnieuw controleren op gevulde lijnen. In dit geval zijn er geen gevulde lijnen, dus het spel kan doorgaan en je kunt nog een blok spawnen.
Alles lijkt goed voor de Clump-methode, maar helaas is er een probleem, zoals te zien in dit voor- en na-voorbeeld:
Hier is het blauwe blok in het midden geland - en aangezien het is samengeklonterd met het blauwe blok aan de rechterkant, wordt die ook als "geland" beschouwd. Het volgende blok spawt, en opnieuw hebben we een blauw blok zwevend in de lucht.
De Big Clump-methode is eigenlijk geen effectieve methode, vanwege dit niet-intuïtieve probleem, maar wel is halverwege een goede methode ...
Kijk nog eens naar deze twee voorbeelden:
In beide gevallen is er een voor de hand liggende manier om de blauwe blokken in afzonderlijke groepen te scheiden - twee blokken (elk van één blok) in de eerste en drie bosjes (van drie, vier en één blokken) in de tweede.
Als we de blokken op die manier klonteren en dan elke klomp onafhankelijk laten vallen, dan zouden we het gewenste resultaat moeten krijgen! Bovendien zal "klonteren" niet langer een woord lijken te zijn.
Dit is wat ik bedoel:
We beginnen met deze situatie. Het is duidelijk dat de tweede line-up wordt gewist.
We splitsten de blokken boven de vrijgemaakte lijn in drie verschillende klompen. (Ik heb verschillende kleuren gebruikt om te bepalen welke blokken samenkomen.)
De bosjes vallen onafhankelijk - merk op hoe de groene groep twee rijen valt, terwijl de blauwe en paarse bosjes landen na slechts één te zijn gevallen. De onderste regel is nu gevuld, dus dit wordt ook gewist en de drie klompen vallen.
Hoe komen we tot de vorm van de bosjes? Welnu, zoals je kunt zien aan de afbeelding, is het eigenlijk vrij eenvoudig: we groeperen alle blokken in aaneengesloten vormen - dat wil zeggen, voor elk blok groeperen we het met al zijn buren en buren van buren, en dus aan, totdat elk blok deel uitmaakt van een groep.
In plaats van uit te leggen hoe deze groepering precies moet worden gedaan, zal ik u wijzen op de Wikipedia-pagina voor opvulling, wat verschillende manieren om dit te bereiken uitlegt, samen met de voor- en nadelen van elk.
Zodra je de vormen van je bosjes hebt, kun je ze in een array plakken:
klompen = []; klontjes [0] .shape = [[3], [3]]; klontjes [0] .topLeft = rij: 11, col: 0; klonten [1] .shape = [[0,1,0], [0,1,1], [0,1,1], [1,1,1]]; klontjes [1] .topLeft = rij: 9, col: 3; klonten [2] .shape = [[1,1,1], [1,1,1], [0,1,1]]; klompen [2] .topLeft = row: 10, col: 7;
Herhaal vervolgens elke klomp in de array en vergeet niet om nieuwe gevulde lijnen te controleren zodra ze zijn geland.
Dit wordt de Sticky-methode genoemd en wordt in een paar games gebruikt, zoals Tetris Blast. Ik vind het leuk; het is een goede draai aan Tetris, waardoor nieuwe strategieën mogelijk zijn. Er is nog een andere populaire methode die behoorlijk anders is ...
Als je de concepten tot nu toe hebt gevolgd, denk ik dat het de moeite waard is om de Cascade-methode zelf te proberen als een oefening.
In principe onthoudt elk blok van welke tetromino het deel uitmaakte, zelfs als een segment van die tetromino door een regel wordt vernietigd. De tetromino's - of rare, gehakte delen van tetromino's - vallen als bosjes.
Zoals altijd helpen foto's:
Een T-tetromino valt, een lijn vervolledigend. Merk op hoe elk blok verbonden blijft met zijn originele tetromino? (We nemen hier aan dat tot nu toe geen regels zijn gewist.)
De voltooide lijn wordt leeggemaakt, die het groene Z-tetromino in twee afzonderlijke stukken splitst en stukken van andere tetromino's hakt.
De T-tetromino (of wat er van over is) blijft vallen, omdat het niet wordt opgehouden door andere blokken.
De T-tetromino landt en voltooit een andere regel. Deze lijn wordt gewist, hakt stukken af en nog meer tetromino's.
Zoals je kunt zien, speelt de Cascade-methode een beetje anders af dan de andere twee hoofdmethoden. Als je nog steeds onduidelijk bent over hoe het werkt, kijk dan of je een kopie van Quadra of Tetris 2 kunt vinden (of video's op YouTube opzoeken), omdat ze beide deze methode gebruiken.
Succes!
Bedankt voor het lezen van deze tutorial! Ik hoop dat je iets hebt geleerd (en niet alleen over Tetris), en dat je de uitdaging aan gaat. Als je spellen maakt met deze technieken, zou ik ze graag willen zien! Plaats ze alsjeblieft in de comments hieronder, of tweet me op @MichaelJW.
.