Gör ett match-3-spel i Construct 2 Match Detection

Hittills har denna serie täckt grunderna för att skapa ett Match-3-spel och implementera de ursprungliga spelelementen, som block-swapping. I denna handledning ska vi bygga på allt detta och börja upptäcka när spelaren har gjort en match.


Final Game Demo

Här är en demonstration av spelet vi arbetar mot genom hela serien:




1. Upptäcka en match

För närvarande ska vi bara implementera en grundläggande version av det matchande systemet, med fokus på att hitta när matchningar existerar och förstöra matchade block. I senare artiklar fortsätter vi att utveckla och utveckla systemet.

Tips: Du bör läsa in hur en rekursiv funktion fungerar, om du inte redan vet det; i huvudsak är det en funktion som kallar sig själv. Rekursiva funktioner kan fungera på samma sätt som loopar, men eftersom de också kan ta in och returnera variabler, har de många fler användningsområden än loopar gör.

Precis som med föregående handledning vill jag först diskutera hur systemet ska fungera, och försök sedan bygga det.

  • Matchningssystemet kommer att iterera genom varje instans av Blockera objekt.
  • För varje Blockera, det kommer att passera färgen och positionen av blocket som man tittar på i en rekursiv funktion som kommer att se på den horisontella eller vertikala grannan och bestämma om de har samma färg.
  • Om en matchning hittas kommer den att ringa funktionen igen med positionen och färgen på det nya blocket, snarare än den ursprungliga.
  • Detta fortsätter tills det inte finns någon matchning. Då kontrollerar den hur många matchningar den hittat.
  • Om det hittades tre eller flera matchningar markeras alla block som det bara såg ut som matchade med IsMatched instansvariabel som vi gjorde i en av de föregående handledningarna; annars gör det ingenting.
  • Slutligen, när alla block har blivit kontrollerade, kommer funktionen att förstöra varje block som är markerat som en matchning.

Först behöver vi en händelse som kan iterera genom var och en Blockera. Det sätt jag byggt upp systemet, det är det faktiskt iterates genom blocken dubbelt: en gång för att söka efter vertikala träffar och en gång för att kontrollera om horisontella matchningar. Beroende på vilken kontroll det gör gör det en annan funktion att faktiskt leta efter matchen.

Det allra första vi behöver göra är att göra en Global variabel för att hålla reda på hur många matchande block vi har hittat vid en viss iteration:

Global Variable Name: "NumMatchesFound" Typ = Nummervärde = 0

Nu, låt oss göra Händelse som kommer att iterera genom blocken:

Händelse: Funktion> På funktion Namn: "FindMatches" Sub-Event: System> För varje objekt: Block Action: System> Ange värde NumMatchesFound = 1 Åtgärd: Funktion> Samtalsfunktion Namn: "CheckMatchesX" Parameter 0: Block.X Parameter 1 : Block.Y Parameter 2: Block.Color Sub-Event: System> Jämför Variabel NumMatchesFound> = 3 Åtgärd: Block> Ange Boolean IsMatched = Sann Sub-Event: System> För Varje Objekt: Block Action: System> Ange värde NumMatchesFound = 1 Funktion: Funktion> Samtalsfunktion Namn: "CheckMatchesY" Parameter 0: Block.X Parameter 1: Block.Y Parameter 2: Block.Color Sub-Event: System> Jämför Variable NumMatchesFound> = 3 Åtgärd: Block> Ställ in Boolean IsMatched = Sann Sub-Event: Block> Är Boolean Instansvariabel Set System> Vänta Second = 0.1 Block> Destroy

Din kod ska se så här ut:


I detta fall tar vi igenom varje block och skickar in dem CheckMatchesX eller CheckMatchesY, de funktioner som kommer att kontrollera om grannblocket är en matchning.

För att skicka blocket till funktionen överför vi funktionerna tre olika parametrar:

  • Parameter 0 är blockets X-position
  • Parameter 1 är blockets Y-position
  • Parameter 2 är färgen.

Efter varje block skickas till en av funktionerna och funktionen slutar köra, kontrollerar den NumMatchesFound för att se om det hittat tre eller flera matchande block och märker sedan blocken som matchade om det gjorde det.

Slutligen, varje block som är markerat som matchade blir förstörd efter .1 sekunder passerar. Detta vänta uttalande är där för att låta spelet växla bilderna för blocken till bilden som indikerar att de matchas och att ge spelaren ett ögonblick att märka denna förändring.

(Medan du kunde ta bort vänta uttalande utan att negativt påverka gameplayen, det gör matchningen lättare för spelaren att förstå och saktar spelet tillräckligt så att spelaren enkelt kan hålla reda på vad som händer.)


2. De två kontrollfunktionerna

Nästa måste vi göra CheckMatchesX och CheckMatchesY funktioner. Dessa funktioner fungerar på samma sätt som iteratorerna ovan, eftersom det kommer att finnas en version för kontroll av horisontella matchningar, CheckMatchesX, och en för vertikala matcher, CheckMatchesY.

Horisontella kontroller

Låt oss först bygga den horisontella kontrollfunktionen:

Händelse: Funktion> På funktion Namn: "CheckMatchesX" Sub-Event: Skick: Blockera> Jämför XX = Funktion.Param (0) + (Block.Width + 2) Skick: Blockera> Jämför YY = Function.Param (1) Skick : Block> Jämför instansvariabel Färg = Funktion.Param (2) Åtgärd: System> Lägg till Variabel = NumBlock Värde = 1 Åtgärd: Funktion> Samtalsfunktion Namn: "CheckMatchesX" Parameter 0: Funktion.Param (0) + (Block. Bredd + 2) Parameter 1: Funktion.Param (1) Parameter 2: Funktion.Param (2) Underhändelse: System> Jämför Variabel NumMatchesFound> = 3 Åtgärd: Block> Ställ in Boolean IsMatched = True

Din kod ska se så här ut:


Så vad gör den här funktionen??

  • Först testar den för att se om ett grannblock även existerar till vänster om blocket vi passerade in.
  • När funktionen bekräftar att det finns ett block i närliggande plats kontrollerar det om det är samma färg som blocket vi passerade in.
  • Om det är så ökar det NumMatchesFound av en, och skickar det nyfunna Blocket till funktionen precis som det gjorde för originalet.
  • Detta fortsätter tills det hittar ett block som inte är samma färg som originalet. På den tiden kontrollerar den att det finns tillräckligt med matchande block för att skapa en grupp och märker blocken som matchningar om det gjorde det.

Vertikala kontroller

Låt oss nu göra en annan version av den här funktionen, som gör samma sak för vertikala träffar. Detta kommer att bli vårt CheckMatchesY fungera. Du kan antingen kopiera den ursprungliga funktionen och göra alla lämpliga ändringar, eller bygga bara den igen från början. i båda fallen är här hur din funktion ska se ut när den är klar:

Händelse: Funktion> På funktion Namn: "CheckMatchesY" Sub-Event: Skick: Blockera> Jämför XX = Funktion.Param (0) Skick: Block> Jämför YY = Funktion.Param (1) + (Block.Width + 2) Skick : Block> Jämför instansvariabel Färg = Funktion.Param (2) Åtgärd: System> Lägg till Variabel = NumBlock Värde = 1 Åtgärd: Funktion> Samtalsfunktion Namn: "CheckMatchesY" Parameter 0: Funktion.Param (0) Parameter 1: Funktion .Param (1) + (Block.Width + 2) Parameter 2: Funktion.Param (2) Underhändelse: System> Jämför Variabel NumMatchesFound> = 3 Åtgärd: Block> Ställ in Boolean IsMatched = True

Din kod ska se så här ut:



3. Faktiskt letar efter kontroller

Slutligen måste vi faktiskt kalla FindMatches fungera. Gå till SwapBlocks funktion och lägg till en ny delhändelse till slutet av funktionen:

Händelse: Funktion> Sub-Event: Åtgärd: Funktion> Samtalsfunktion Namn: "FindMatches"

Du kommer märka att denna delhändelse faktiskt inte har några villkor. Om du aldrig har gjort en underhändelse som den här innan, gör bara en delhändelse med något villkor alls, eftersom det kräver att du ger ett villkor när du gör en underhändelse och sedan raderar villkoret, men lämna den sub-händelse. På så sätt kontrollerar du att underevenemanget alltid körs.

Din SwapBlocks händelse ska nu se ut så här:

Om du kör spelet vid denna tidpunkt kommer du att se att blocken blir förstörda när matcher uppstår. Du kommer också att märka att alla matcher som finns där när spelet börjar, försvinner inte förrän du gör ett byte av något slag. Detta beror på att vi aldrig ringer till FindMatches funktion efter att vi skapat rutnätet.

Anledningen till att vi inte har lagt till den här koden är att i den slutliga versionen kommer det att finnas en annan funktion som förhindrar att matchningar genereras automatiskt så här, så det finns verkligen ingen anledning att oroa sig för detta problem alls. (Men gärna ringa FindMatches funktion tidigare, om du vill.)


4. Konsolidering av kontrollerna

Vid denna tidpunkt har vi ett ganska starkt matchande system, men problemet är att vår kod är överflödig. För närvarande har vi två olika funktioner som kontrollerar om det finns en matchande granne, och den enda skillnaden mellan dem är den som kontrollerar vertikalt och den andra kontrollerar horisontellt.

Eftersom den fria versionen av Construct 2 begränsar hur många händelser vi kan ha är det definitivt ett slöseri. För att lösa detta ska vi göra en ny version av funktionen som kan göra både kontroller.

Om du tittar på funktionen kommer du att se den enda skillnaden mellan de två versionerna är den som läggs till Block.Width + 2 till blockens x-position, och den andra lägger till den i Bocks y-position. Det hinder som vi måste komma förbi för att göra detta till en enda funktion, ger funktionen ett sätt att lägga till Block.Width + 2 till endast X, eller bara Y, utan använder en Om uttalande eller flera funktioner, eftersom de kräver fler händelser som ska utföras.

Min lösning på detta är inte så komplicerad, men det blir lättare att förstå om vi kan se att den kommer ihop, så vi kommer att implementera det, och jag kommer att förklara hur det fungerar när vi kan se allting i aktion.

  1. Ta bort CheckMatchesY händelse.
  2. Byt namn på CheckMatchesX händelse till, helt enkelt, CheckMatches.
  3. I funktionssamtalet för CheckMatchesX under FindMatches händelse:
    1. Ändra funktionssamtalet som ska vara för CheckMatches istället för CheckMatchesX.
    2. Lägg till Parameter 3.
      1. Värde = 1.
    3. Lägg till Parameter 4.
      1. Värde = 0.
  4. I funktionssamtalet för CheckMatchesY under FindMatches händelse:
    1. Ändra funktionssamtalet som ska vara för CheckMatches istället för CheckMatchesY.
    2. Lägg till Parameter 3.
      1. Värde = 0.
    3. Lägg till Parameter 4.
      1. Värde = 1.

Som jag kommer att förklara snart kommer dessa parametrar att berätta CheckMatches om det gör en horisontell kontroll eller en vertikal kontroll. När vi skickar in 1 för Parameter 3, och 0 för Parameter 4, Det är en horisontell kontroll, och när vi skickar in 0 för Parameter 3, och 1 för Parameter 4, Det är en vertikal kontroll.

Gå nu tillbaka till CheckMatches funktionen och ändra villkoren och åtgärderna så att den ser ut så här:

Händelse: Funktion> På funktion Namn: "CheckMatches" Sub-Event: Skick: Block> Jämför XX = Function.Param (0) + ((Block.Width + 2) * Function.Param (3)) Skick: Block> Jämför YY = Function.Param (1) + ((Block.Width + 2) * Function.Param (4)) Skick: Block> Jämför instansvariabel Färg = Funktion.Param (2) Åtgärd: Block> Ställ in Boolean IsMatched = True Action : Funktion> Samtalsfunktion Namn: "CheckMatches" Parameter 0: Funktion.Param (0) + ((Block.Width + 2) * Function.Param (3)) Parameter 1: Funktion.Param (1) + ((Block. Bredd + 2) * Funktion.Param (4)) Parameter 2: Funktion.Param (2) Parameter 3: Funktion.Param (3) Parameter 4: Funktion.Param (4) Underhändelse: System> Jämför Variabel NumMatchesFound> = 3 Åtgärd: Block> Ställ in Boolean IsMatched = True

Detta är vad din FindMatches och CheckMatches kod ska nu se ut som:


Hur fungerar detta?

Så, vad är den här nya versionen av funktionen som faktiskt gör?

Tja, när du ringer CheckMatches Du skickar nu ytterligare två parametrar, och snarare än att lägga till Block.Width + 2 till antingen x- eller y-läget, lägger den till (Block.Width + 2) * Function.Param (3) till x-positionen och (Block.Width + 2) * Function.Param (4) till y-positionen.

Eftersom en av dessa två parametrar alltid kommer att vara 1, och den andra kommer alltid att vara 0, det betyder att antingen x- eller y-positionen kommer att ändras - aldrig båda!

Till exempel, om vi passerar in 1 för Parameter 3, och 0 för Parameter 4, då lägger det till (Block.Width + 2) * 1, vilket är helt enkelt Block.Width + 2, till x-positionen och (Block.Width + 2) * 0, vilket är 0, till y-positionen.

Här är ett snabbt exempel för att visa vad jag menar och hur det beräknar placeringen av blocket där den kommer att kontrollera matchen. Låt oss säga att i det här exemplet är det ursprungliga Blocket på (200, 200), och blocken har en bredd av 40. Så, om vi vill få positionen i det närliggande vertikala blocket, skulle formlerna fungera så här:

  • X = 200 + ((Block.Width + 2) * 0) = 200 + (40 + 2) * 0 = 200 + 0 = 200
  • Y = 200 + ((Block.Width + 2) * 1) = 200 + (40 + 2) * 1 = 200 + 42 = 242

Om vi ​​ville få läget för det närliggande horisontella blocket skulle formlerna fungera så här:

  • X = 200 + ((Block.Width + 2) * 1) = 200 + (40 + 2) * 1 = 200 + 42 = 242
  • Y = 200 + ((Block.Width + 2) * 0) = 200 + (40 + 2) * 0 = 200 + 0 = 200

Om du kör spelet nu bör du se att matchningssystemet fortfarande fungerar som det ursprungligen gjorde, men ur vårt perspektiv är det faktiskt ett bättre system.


Slutsats

Vid denna tidpunkt är vår matchningsdetekteringsfunktion fortfarande ofullständig, men vi har redan gjort mycket i denna handledning och jag tycker att det är viktigt att låta allt detta sjunka innan vi lägger till något annat. Med det i åtanke kommer jag att avsluta denna artikel här. Kolla in demoen i sin nuvarande form.

I nästa artikel lägger vi till ett poängsystem, vi kommer att förbättra matchningssystemet, och vi lägger till "gravitation" så att blocken kommer att falla när block under dem elimineras.

Om du vill ha ett försprång i nästa artikel, ta en stund att överväga hur du skulle upptäcka när det finns ett tomt utrymme under ett block. Försök att titta på Block> Överlappar vid förskjutning funktion för inspiration!