Een videogame maken. Flappy Bird
In deze les zullen we alles wat we geleerd hebben toepassen om ons eerste spel te maken, en er is niets beter dan te beginnen met een van de meest succesvolle spellen van het laatste decennium: Flappy Bird.
De gameplay van Flappy Bird is eenvoudig: Beweeg je personage verticaal om obstakels te vermijden die willekeurig op het podium verschijnen. Naarmate het personage vordert, stijgt zijn score, en hij verliest als hij een obstakel raakt, zoals te zien is in figuur 1:
Figuur 1. Origineel Flappy Bird spel.
In deze les ontwikkelen we onze eigen versie van het spel, waarbij we werken aan de volgende concepten:
- Ontwikkeling van geanimeerde sprites (houdingen) en decors
- Ontwikkeling van spelschermen
- Procedurele hindernis generatie
- Geluidseffecten en scoring
- Implementatie van de spellogica
- Beheer van gebeurtenissen en controles
ONTWIKKELING VAN GEANIMEERDE SPRITES EN SCENARIO’S
De eerste stap in de ontwikkeling van ons spel is het verzamelen van alle elementen die in de interface zullen worden getoond, d.w.z. het hoofdpersonage en de hindernissen en scenario’s.
We beginnen met de achtergrond van het spel. Op dit punt hebben we twee opties:
1) GEBRUIK VAN DE STANDAARD AFBEELDINGEN van SCRATCH
Zoals beschreven in de vorige lessen heeft Scratch een grote galerij met voorgedefinieerde afbeeldingen die we in onze projecten kunnen gebruiken.
Er moet een onderscheid worden gemaakt tussen twee hoofdgroepen:
Personages en obstakels: Ze overlappen met de achtergrond en zijn elementen waar de speler direct op reageert.
Achtergronden: Over het algemeen zijn het statische beelden, waarmee de speler geen interactie heeft. Veel spellen kiezen er echter voor animaties en effecten aan de achtergrond toe te voegen om de spelervaring te verbeteren. Een van deze effecten is de zogenaamde “parallax scroll”, die wordt gebruikt om de relatieve beweging van de personages te simuleren op basis van de verplaatsing van de achtergrond van het scherm. Zoals te zien is in figuur 2, is er een gevoel van beweging van het schip, ook al blijft het in feite statisch op hetzelfde punt op het scherm, terwijl de achtergrond beweegt.
Figuur 2. Parallax scroll effect.
Op dit punt beginnen we met het toevoegen van een van de voorgedefinieerde achtergronden in Scratch, zoals getoond in Figuur 3.
Figuur 3. Standaard achtergrond.
Hoewel de door Scratch geleverde afbeeldingen standaard zijn, belet dit ons niet om ze aan te passen aan onze behoeften.
Zoals getoond in Figuur 4, kunnen we elk element verplaatsen, verplaatsen en de grootte ervan aanpassen, zelfs dupliceren en de kleur ervan veranderen.
Figuur 4. Standaard achtergronden bewerken.
2) IMPORTEREN UIT EEN EXTERN BESTAND
Met Scratch kun je ze ook tekenen vanuit de eigen interface, maar het is beperkt tot het tekenen van eenvoudige veelhoekige vormen, dus het is niet de beste optie als je een spel wilt maken met een professionele afwerking.
In een spel is het gebruikelijk dat er verschillende achtergronden zijn, afhankelijk van het scenario of het niveau waarin ons personage zich bevindt, evenals specifieke achtergronden voor onder andere het spelmenu.
In dit project gaan we dezelfde achtergrond gebruiken voor alle menu’s en scenario’s van het spel, maar we gaan ze een beetje aanpassen.
Omdat we zowel de spelachtergrond en de achtergrond van het hoofdmenu moeten definiëren, als een specifieke GAME OVER achtergrond, zullen we de reeds gemaakte achtergronden dupliceren en ze een naam geven, zoals getoond in Figuur 5.
Figuur 5. Dupliceer achtergronden en hernoem ze.
We zullen nu wat tekst toevoegen om onze schermen te personaliseren (Figuur 6).
Figuur 6. Tekst toevoegen aan achtergronden.
Nu we de achtergronden klaar hebben, is het tijd om de rest van de elementen toe te voegen, te beginnen met onze protagonist. Hiervoor selecteren we een van de meegeleverde Sprites, zoals getoond in Figuur 7.
Figuur 7. Selectie van het personage.
Zoals we in de vorige lessen hebben gezien, is het personage samengesteld uit verschillende afbeeldingen om zijn beweging te simuleren. Deze afbeeldingen kunnen naar behoefte worden bewerkt met de eigen interface van Scratch, zoals getoond in figuur 8, waar we kunnen bewegen, van kleur veranderen en de omtrek van de verschillende onderdelen waaruit het personage bestaat opnieuw kunnen bepalen.
Videospeler
Figuur 8. Een Sprite bewerken.
Tenslotte voegen we het obstakel van het spel toe via een “pipe” (buis). Daartoe kunnen we een gegenereerd exemplaar uit de interface zelf importeren (Figuur 9) of importeren (Figuur 10).
Videospeler
Figuur 9. Teken een buislijn.
Figuur 10. Gebruik van geïmporteerde buizen (pipes).
HET BEHEERSEN VAN ONS PERSONAGE EN DE LOGICA VAN HET SPEL
Zodra we de grafische elementen waaruit ons spel zal bestaan hebben gedefinieerd, is het tijd om het te gaan programmeren.
Zoals we weten wordt het spel gestart wanneer de gebruiker de groene vlag activeert (Figuur 11), maar we moeten bij het begin van het spel aangeven wat we willen laten uitvoeren, anders wordt het laatste scenario en personage dat we hebben geselecteerd weergegeven.
Figuur 11. Voorbeeld van spelinitialisatie door groene vlag.
In ons spel willen we dat het hoofdmenu dat we eerder hebben gemaakt wordt weergegeven bij het opstarten. Voorlopig zal ons menu geen knoppen hebben, die zullen we later toevoegen, dus voorlopig willen we gewoon dat het spel na een paar seconden start.
We moeten onthouden dat elk element op het scherm (personages, obstakels, landschap…) zijn eigen code kan bevatten, dus afhankelijk van het element dat we willen besturen, zullen we het in een of ander blok moeten programmeren.
Omdat we in dit geval de logica van de schermen willen definiëren, klikken we op een van de achtergronden die we hebben gemaakt om ze te gaan programmeren.
Daartoe definiëren we de blokken van figuur 12, waarin we het scherm tonen dat we MAIN_MENU hebben genoemd, en we geven aan dat we gedurende een bepaalde tijd op dit scherm willen blijven, bijvoorbeeld 5 seconden.
Een goede gewoonte bij het ontwikkelen van een spel is om de start te controleren door middel van een flat of event, dat wil zeggen door middel van een activeringsvariabele waarvan de waarde verandert om aan de rest van de elementen van het programma aan te geven dat ze moeten beginnen. Dit gebeurt via het blok BROADCAST, waaraan we een naam toevoegen, die de naam zal zijn van de gebeurtenis die het startsein geeft. In ons geval noemen we het START.
Figuur 12. Startblok van het spel.
Nu we het een beetje over gebeurtenissen hebben gehad, gaan we de gebeurtenis implementeren die wordt geactiveerd wanneer we het spel verliezen. Daartoe definiëren we de blokken in Figuur 13, waar we de
gebeurtenis de gewenste naam geven, bijvoorbeeld GAME OVER. Wanneer deze gebeurtenis optreedt, willen we dat het spelscherm verandert en de uitvoering van het spel stopt, wat we doen door het commando STOP ALL te gebruiken.
Figuur 13. Game Over gebeurtenis.
Dit hele proces is te zien in figuur 14.
Videospeler
Figuur 14. Samenvatting van stappen 12 en 13 voor het programmeren van de menulogica
Nu gaan we werken met ons personage door op zijn icoontje te klikken. Aangezien het standaardmodel erg groot is, zullen we de afmetingen ervan wijzigen zoals getoond in Figuur 15.
Figura 15. Cambiar escala del personaje.
Figuur 15. Verander de schaal van het personage.
Het personage moet starten op specifieke coördinaten op het scherm om de speler voldoende tijd te geven om de verschillende obstakels te visualiseren. Dit kan op twee manieren: door de speler handmatig te verplaatsen (figuur 16A) of door code (figuur 16B).
Figuur 16. Tekenlocatie. (A) handmatig (B) programmatisch.
Ons personage is een vogel die obstakels moet ontwijken terwijl hij vliegt, dus we moeten hem met een bepaalde snelheid met zijn vleugels laten slaan zodra het spel begint.
Dit effect wordt bereikt door het voortdurend verwisselen van de houding (sprites). Hiervoor is de component NEXT COSTUME beschikbaar. Aangezien het flapperen zo natuurlijk mogelijk moet zijn, introduceren we een zekere vertraging tussen elke animatie, zoals getoond in Figuur 17.
Videospeler
Figuur 17. Animatie van het personage
Het personage flapt correct, maar voorlopig zweeft hij in de lucht, dus gaan
we hem laten beïnvloeden door de zwaartekracht. Daartoe implementeren we een nieuw blok (Figuur 18) dat wordt uitgevoerd wanneer de START gebeurtenis wordt ontvangen (hoewel het ook kan binnen het blok dat we hebben gemaakt in Figuur 16A, maar op deze manier is het intuïtiever).
Videospeler
Figure 18. Voeg zwaartekracht toe
Het personage beweegt alleen verticaal. Om de werking van de zwaartekracht te simuleren, moeten we onze protagonist een bepaald aantal pixels verticaal naar beneden laten bewegen, d.w.z. op de Y-as.
Zoals we in figuur 18 kunnen zien, klimt het personage bij een waarde van
+10 zeer snel totdat het uit het raam gaat. Om dit op te lossen veranderen we de waarde in -2. Omdat we willen dat de zwaartekracht altijd op ons personage inwerkt, zullen we een lus gebruiken.
We hebben het personage al laten vallen door de werking van de zwaartekracht, maar nu gaan we het personage een paar pixels laten stijgen als de speler op de SPATIEtoets drukt. Om dit te doen, zullen we het vorige blok zoals in Figuur 19 aanpassen om voortdurend te controleren of de speler de toets indrukt, in welk geval we +5 pixels stijgen, terwijl in het geval dat de toets niet wordt ingedrukt, we vallen door de werking van de zwaartekracht.
Figuur 19. Vluchtcontrole van het personage
We hebben de basisbesturing van het spel al gedefinieerd, maar we hebben niet gedefinieerd wat er gebeurt als ons personage van het scherm valt. Net als in het oorspronkelijke spel moeten we bij een dergelijke gebeurtenis het spel uitschrijven en het spel stoppen.
Om het te implementeren, definiëren we een nieuw blok (hoewel we het ook binnen het vorige blok zouden kunnen definiëren), waarin we voortdurend controleren of het geselecteerde personage een van de randen van het scherm raakt en zo ja, dan laten we het personage verdwijnen en veranderen we het scherm in Game Over, terwijl we het spel stoppen, zoals getoond in Figuur 20.
Videospeler
Figuur 20. Detectie van botsingen met schermgrenzen
Zoals we kunnen zien, hebben we gebruik gemaakt van de GAME OVER
gebeurtenis die we eerder hebben gecreëerd. Deze gebeurtenis activeert het codeblok van figuur 13, dat verantwoordelijk is voor het weergeven van het GAME OVER scherm en het stoppen van het spel.
Voor testdoeleinden is het raadzaam een pauze van 1 seconde in te lassen net voordat wordt gecontroleerd of het personage het obstakel raakt of niet, want soms plaatst het spel de hoofdrolspeler buiten beeld, waardoor hij automatisch verliest.
GENEREREN VAN OBSTAKELS
Nu we alle basiselementen van het spel hebben gedefinieerd, moeten we de obstakels toevoegen die de speler moet vermijden om vooruit te komen. De
De uitdaging bestaat erin deze hindernissen op verschillende hoogten te laten ontstaan om de speler meer moeilijkheden te bezorgen.
Hiervoor moeten we de hindernissen zo programmeren dat ze aan het begin van het spel verschijnen op een plaats die ver van de speler verwijderd is om te voorkomen dat de speler per ongeluk crasht, maar dichtbij genoeg om nog gedeeltelijk zichtbaar te zijn op het scherm, zodat de speler het toekomstige obstakel kan visualiseren.
Daartoe voegen we in de buisvorm (pipe, sprite) een blok toe waarmee we ons obstakel binnen specifieke coördinaten kunnen plaatsen, die we willen laten uitvoeren vanaf het moment dat het spel begint.
Figuur 21. Begin van het genereren van obstakels.
Een enkele buis die niet beweegt lijkt niet echt een uitdaging voor de speler, dus moeten we een oneindig aantal buizen op verschillende hoogtes genereren, die de speler met een bepaalde snelheid moeten benaderen.
Maar de buizen (pipes) komen niet alleen uit de basis, ze moeten ook van boven komen om een smalle doorgangsruimte voor de speler te creëren. Net als bij ons personage heeft de obstakel sprite (vorm/houding) twee afbeeldingen of vermommingen, één met de buis bovenaan en één met de buis onder.
Omdat we elke keer dat we een obstakel maken beide wil hebben, zullen we, om het programmeren te vereenvoudigen, gebruik maken van Custom Blocks. In wezen is het een blok dat complexere functies implementeert dan Scratch standaard heeft, in ons geval maakt het een obstakel onderaan en kloont het, maar met behoud van de Sprite van het bovenste obstakel.
Eerst definiëren we het aangepaste blok zoals in Figuur 22, waar we een naam toevoegen en een enkele variabele genaamd DISTANCE definiëren die we later zullen gebruiken.
Figuur 22. Creatie van een aangepast blok.
Zodra het blok is gecreëerd, definiëren we de uit te voeren instructies. Allereerst tonen we een obstakel dat we dwingen een van de vermommingen te worden, bijvoorbeeld die op de buis hierboven. Daarna klonen we het object en aan dat nieuwe gekloonde object wijzen we de andere sprite toe om de buis eronder te hebben.
Dit blok moet worden uitgevoerd op het moment dat de speler van het menu naar het spel gaat, dus als we de START gebeurtenis ontvangen, verbergen we het obstakel en roepen we het blok CREËER OBSTAKEL op dat we zojuist hebben gemaakt. Aangezien we geïnteresseerd zijn in het feit dat het spel voortdurend obstakels genereert, zullen we dit blok binnen een oneindige lus introduceren, zoals in Figuur 23, waarbij de afstandswaarde voorlopig op 0 wordt gezet.
Videospeler
Figuur 23. Implementatie van het aangepaste blok voor het maken van pijpleidingen
We kunnen zien dat wanneer we het spel uitvoeren, de buizen aan elkaar vastzitten, waardoor ons personage er niet doorheen kan. Om dit op te lossen, zullen we de vorige code aanpassen om de hoogte van elke buis willekeurig aan te passen, maar een ruimte van ongeveer 120 pixels ertussen te laten, zodat ons personage er zonder probleem doorheen kan.
Om dit te doen, hoeven we alleen een wijziging aan te brengen in de Y- coördinaat van beide objecten (het origineel en de kloon), waarbij we als Y- coördinaat de willekeurige afstandswaarde tussen 1 en 120 zetten die we doorgeven als parameter van het aangepaste blok. Om een afstand van 120 pixels tussen de obstakels te krijgen, zal het tweede blok een Y-coördinaat hebben = afstand -120, zoals getoond in figuur 24.
Figuur 24. Genereren van obstakels met willekeurige hoogte.
Laten we, voordat we deze verandering testen, een vertraging van bijvoorbeeld 10 seconden invoeren in de hoofdlus, zodat we het effect kunnen visualiseren. Als we het spel meerdere keren draaien, zullen we zien hoe bij elke run de hoogte tussen de hindernissen anders is, maar steeds die afstand behoudt, zoals getoond in Figuur 25.
Figuur 25. Weergave van de verandering in hoogte van obstakels.
Nu de hindernissen zijn gemaakt en hun hoogte willekeurig is gegenereerd, moeten we ze verplaatsen naar de positie van de speler, zodat hij ze kan ontwijken. Het probleem ligt in het feit dat de hindernissen procedureel worden gegenereerd, en we zullen ze allemaal afzonderlijk moeten beheren. Gelukkig zorgt Scratch ervoor dat dit beheer op een manier gebeurt die transparant is voor de gebruiker.
De te implementeren code wordt getoond in figuur 26, die zal worden uitgevoerd telkens wanneer een obstakel-instantie wordt gecreëerd. Aangezien we willen dat het obstakel voortdurend op de X-as beweegt, voegen we het verplaatsingsblok in in een ongedefinieerde lus, waarin we het obstakel zullen verplaatsen.
Om de afstand tussen obstakels aan te passen, moeten we de pauzetijd tussen het creëren van elk obstakel wijzigen. Om de moeilijkheidsgraad te verhogen, wijzigen wij deze tijd met een willekeurig getal tussen twee vooraf bepaalde waarden.
Videospeler
Figuur 26. Verplaatsing van obstakels.
Maar als we het spel uitvoeren, zullen we zien dat alle obstakels zich ophopen aan de linkerkant van het scherm (Figuur 27).
Figuur 27. Probleem van opeenhoping van obstakels.
Om dit op te lossen hoeven we alleen maar te detecteren wanneer het obstakel de linkerrand van het scherm bereikt, om het automatisch te laten verdwijnen. Daartoe implementeren we de code in figuur 28.
Videospeler
Figuur 28. Oplossing voor de opeenhoping van obstakels aan de linkerkant van het scherm.
Op dit punt kunnen we bepalen dat als ons personage tegen een van de obstakels botst, het Game Over scherm verschijnt en het spel voorbij is. Op dit punt, kan het nodig zijn om de schaal van ons personage of de hoogte van obstakels aan te passen om te voorkomen dat onze protagonist voortdurend crasht.
Daartoe moeten we het botsingsblok dat we eerder hebben gemaakt zodanig wijzigen dat het ook rekening houdt met botsingen met obstakels, zoals getoond in Figuur 29.
Videospeler
Figuur 29. Botsing met obstakels.
EINDGEGEVENS
We hebben nu een volledig operationeel spel, maar we kunnen nieuwe functies toevoegen om de spelerservaring te verbeteren.
We beginnen met het toevoegen van een geluidseffect telkens als ons personage een obstakel overwint. Hiervoor gebruiken we een van de standaardgeluiden van Scratch (Figuur 30) en voegen die toe aan ons project.
Figuur 30. Selectie van een audiobestand.
Om het geluid weer te geven wanneer de speler het obstakel overwint, moeten we controleren of het obstakel zich een beetje links van ons personage bevindt, dat altijd dezelfde positie op het scherm inneemt, d.w.z. op de coördinaten X=-110 Y=0. Dit wordt bereikt door het blok van figuur 31 te implementeren, waarbij we, rekening houdend met het feit dat het geluid slechts eenmaal moet worden afgespeeld en dat het obstakel in sprongen van 3 pixels naar links beweegt, een waarde definiëren waarover we zeker weten dat het obstakel zal passeren, om de vergelijking te maken, bijvoorbeeld een waarde van -99.
Figuur 31. Geluid afspelen bij het overwinnen van een obstakel.
Als laatste detail gaan we de score van de speler toevoegen, die toeneemt telkens als hij hindernissen overwint. Daartoe moeten we een variabele SCORE aanmaken, die we initialiseren op nul wanneer het spel begint (Figuur 32). Zoals we zien, wordt deze variabele automatisch linksboven in het scherm toegevoegd.
Figuur 32. Creëren en initialiseren van variabele score.
Deze variabele moet worden verhoogd telkens als de speler een obstakel overwint, of met andere woorden, telkens als het eerder gedefinieerde geluid wordt afgespeeld (Figuur 33).
Figuur 33. Score verhogen door obstakels te overwinnen.
En daarmee zijn we klaar met ons Flappy Birds-achtig spel, waarbij naarmate de speler vordert zijn score toeneemt, zoals getoond in Figuur 34.
Figuur 34. Eindresultaat van de wedstrijd