title | date | draft | toc | headercolor | onderwerp |
---|---|---|---|---|---|
Web - Ontbijtkoekclicker |
2022-07-28 20:56:17 +0200 |
false |
true |
teal-background |
JavaScript |
We gaan onze eigen versie van het spel Cookie Clicker maken met JavaScript en HTML!
In deze instructie gaan we voornamelijk aan de slag met JavaScript. Het doel van de instructie is het maken van een spel dat je in je browser kan spelen. Het spel is gebaseerd op het bekende spel Cookie Clicker. Naast het coderen van de functionaliteit, kan je aan verschillende onderdelen van het spel een eigen draai geven!
Voor het maken van de Ontbijtkoekclicker is het belangrijk om bekend te zijn met de volgende talen:
- HTML
- CSS
- JavaScript
Ben je hier nog niet mee bekend? Ga dan eerst aan de slag met de instructie Web - Development.
Voor deze instructie gebruiken we dezelfde editor als bij de Web - Development instructie: Visual Studio Code.
Om meer tijd te kunnen besteden aan JavaScript en het personaliseren van het spel,
beginnen we met een vooropgezette pagina.
Deze bestaat uit de volgende bestanden:
- HTML
- CSS
- JavaScript
Deze zijn te downloaden via: Download bestanden. Zorg voordat je begint met de instructie dat je de pagina kan weergeven met je editor/plugin(s).
{{< voorbeeld kop="Extra: afbeeldingen lokaal opslaan" >}}
De afbeeldingen die we gebruiken als voorbeeld maken geen deel uit van de download.
Dit zorgt dat je af en toe lange URLs in de code voorbij ziet komen.
Deze URLs kan je eventueel vervangen door de afbeeldingen te downloaden.
Zet gedownloade afbeeldingen in dezelfde directory als waar pagina.html zich bevindt.
Deze kan je vervolgens als volgt gebruiken:
{{< highlight html >}}
{{< /highlight >}}
{{< /voorbeeld >}}
We beginnen met een samenvatting van het spel, zodat het duidelijk is waar we naartoe werken. De leukste manier om hier een idee over te krijgen is natuurlijk door het origineel te spelen.
Het doel van het spel is om cookies te genereren. Dit kun je doen door op het koekje te klikken, maar je kunt niet eeuwig blijven klikken. Uiteindelijk wil je dat er zoveel mogelijk koekjes automatisch worden gegenereerd. Om het spel automatisch koekjes te laten genereren kun je Clickers kopen, deze klikken automatisch op het koekje. Je koopt Clickers door met koekjes te betalen. Bij iedere aankoop worden de Clickers duurder. Daarnaast kun je Powerups kopen met koekjes. Deze Powerups verbeteren je Clickers zodat zij bijvoorbeeld sneller cookies genereren.
Het spel bestaat uit pakweg vier verschillende onderdelen:
- Een koekje waar je als speler op kan klikken. Hier kan je ook je aantal koekjes (score) en het aantal koekjes dat je per seconde verdient zien.
- Een overzicht waarin je alle actieve Clickers kan zien.
- Een winkel waar je Clickers kan kopen.
- Een winkel waar je Powerups kan kopen.
In deze instructie focussen we voornamelijk op het schrijven van JavaScript-code. Maar als je Cookie Clicker speelt, kom je niet enkel in aanraking met JavaScript. Het spel gebruikt HTML-code om een "skelet" van de pagina weer te geven. Het spel kleedt het skelet vervolgens aan met behulp van CSS. Met JavaScript maken we de pagina uiteindelijk interactief, zodat je er dus een spel op kan spelen.
Om de drie talen op een georganiseerde manier samen te laten werken, hebben we een aantal keuzes in de opzet van het HTML-bestand gemaakt. Die keuzes lichten we in dit deel toe, zodat je de individuele onderdelen op de HTML-pagina straks gemakkelijk kan beïnvloeden via JavaScript-code.
We beginnen met het HTMl bestand. Het bestand pagina.html bevat een section
tag
met daarin vier verschillende div
tags:
{{< highlight html>}}
Deze div
s komen overeen met de onderdelen van het originele spel
en bepalen dus op welke plek van de pagina de onderdelen komen te staan.
Het belangrijkste onderdeel van de div
s zijn de id
s.
Met behulp van de id
s kunnen we deze div
s bereiken en gebruiken in JavaScript.
Stel bijvoorbeeld dat we in het overzicht met actieve Clickers een Clicker toe willen voegen.
Om te zorgen dat we vanuit de div
met actieve Clickers opereren, bereiken we deze als volgt:
{{< highlight javascript>}} const actieveClickerDiv = document.getElementById("actieveClickers") {{< /highlight >}}
Kortom, elk onderdeel van het spel komt overeen met een div
in de HTML-code!
Als we bijvoorbeeld in JavaScript iets aan de Powerups willen veranderen,
zoeken we de Powerup div
eerst op met de correcte id
!
De id
is in dit voorbeeld powerupWinkel
.
We weten nu waar de onderdelen van het spel komen te staan,
maar hoe zien die onderdelen er precies uit?
En wat staat er in de bijbehorende div
s?
De meeste onderdelen bestaan uit een lijst, met een onderdeel (of: element) voor elke rang (Engels: "tier") Clicker. Als voorbeeld kunnen we naar de Powerups kijken:
{{< highlight html>}}
<!-- Via deze div kan je de Deegroller Powerup kopen. Deze kan je als voorbeeld gebruiken. -->
<div class="tier0" onclick="koopPowerup(0)">
<img class="icon" src="https://gartic.com.br/imgs/mural/__/__fera__/rolling-pin.png">
<text>10$</text>
</div>
<div class="tier1" onClick="koopPowerup(1)">
<img class="icon" src="???">
<text>???$</text>
</div>
<div class="tier2" onClick="koopPowerup(2)">...</div>
<div class="tier3" onClick="koopPowerup(3)">...</div>
<div class="tier4" onClick="koopPowerup(4)">...</div>
De Deegroller Powerup (tier 0) is hier volledig uitgewerkt.
Het belangrijkste onderdeel van dit voorbeeld is de class
van de elementen in de lijst.
De class
werkt op een soortgelijke manier als de id
, maar een class is niet uniek.
Hierdoor kunnen we het class
systeem voor alle drie de lijsten gebruiken die we gaan maken.
Als iets met de goedkoopste Clicker te maken heeft, heeft het class="tier0"
.
De een na goedkoopste Clicker heeft class="tier1"
, etc.
Als je een element zoekt, moet je nu echter wel zorgen dat je in de juiste lijst zoekt.
De volgende code verkrijgt resultaten met class="tier0"
uit
alle lijsten (actieve Clickers, Clickers winkel, Powerup winkel).
{{< highlight html>}} const tierDiv = document.getElementsByClassName("tier0") {{< /highlight >}}
Dat is niet de bedoeling als we bijvoorbeeld iets aan de tier 0 Powerup willen veranderen. In plaats daarvan zorgen we dat we eerst naar de Powerups kijken, en vervolgens naar die van tier 0:
{{< highlight html>}} const actieveClickerDiv = document.getElementById("powerup") const tierDiv = actieveClickerDiv.getElementsByClassName("tier0")[0] {{< /highlight >}}
Let op: omdat getElementsByClassName
meerdere resultaten kan geven
(in dit voorbeeld niet), kiezen we de eerste via [0]
.
Aan het begin van het spel heb je nog geen Clickers en moet je handmatig je koekjes verdienen. Dit doe je door op de grote afbeelding van het koekje te klikken. Hier komen enkele dingen bij kijken:
- elke klik op de afbeelding word geregistreerd via een
onClick="onClickCookie()"
HTML-attribuut; - we houden de hoeveelheid koekjes bij in een variabele
geld
in JavaScript, deze begint op0
; - de functie
onClickCookie
verhoogtgeld
met1
; - een functie
updateGeld
zorgt dat de nieuwe waarde vangeld
in de HTML-code update; onClickCookie
roeptupdateGeld
aan na het verhogen vangeld
.
Om dit te implementeren, moet je goed letten op de id
s van de div
- en h2
tags:
{{< highlight html>}}
{{< /highlight >}}Voor updateGeld
heb je de h2
die het geld weergeeft nodig.
Als je deze eenmaal gevonden hebt, kan je de inhoud van de h2
via innerText
als volgt updaten:
{{< highlight javascript>}} geldH2.innerText = "Geld: " + hoeveelheid + "$" {{< /highlight >}}
{{< voorbeeld kop="Voorbeeldcode klikbaar koekje" >}} {{< highlight javascript>}} let geld = 0
/* Voegt 1$ toe en update de pagina */ function onClickCookie() { geld = geld + 1
updateGeld(geld) /* Update de pagina! */
}
/* Verander het geld bedrag dat op de pagina word weergegeven naar hoeveelheid. */ function updateGeld(hoeveelheid) { const geldH2 = document.getElementById("geld")
geldH2.innerText = "Geld: " + hoeveelheid + "$"
} {{< /highlight >}}
Eventueel kan je updateGeld
ook direct gebruik laten maken van de globale variabele geld
.
{{< /voorbeeld >}}
Als we eenmaal wat koekjes hebben, willen we het spel natuurlijk voor ons laten werken!
Hiervoor gebruiken we Clickers, die je kan kopen bij de Clickers winkel.
In pagina.html zien we een div
met id=clickerWinkel
;
dit is waar we de Clickers te koop zullen zetten.
In deze div
vinden we de volgende HTML-code:
{{< highlight html>}}
{{< /highlight >}}Hier kan je je eigen Clickers in zetten, met een plaatje en een prijs!
Dit doe je door de ???
te vervangen.
Als voorbeeld hebben we dit al gedaan voor tier0
in het HTML-bestand;
hier is een chefkok met zijn cloche te zien.
Je hoeft ze natuurlijk niet per se alle vijf toe te voegen.
Als je een paar Clickers hebt, kunnen we beginnen met het toevoegen van de functionaliteit. Laten we een paar doelen vaststellen:
- elke klik op een Clicker
div
word geregistreerd via eenonClick="koopClicker(n)"
HTML-attribuut; - we maken een constante variabele met het maximum aantal Clickers van een tier, om te zorgen dat ze later op de pagina passen 1;
- we houden per tier Clicker bij hoeveel ze kosten;
- de functie
koopClicker
checkt of je genoeg geld hebt en of er nog niet teveel Clickers zijn; - als dit het geval is, registreer dan de nieuwe Clicker en haal het geld weg (aankoop voltooid);
- update naderhand de pagina op basis van de veranderingen.
Vooralsnog verdienen de Clickers dus nog geen koekjes voor ons, we houden enkel bij hoeveel we er van welke tier gekocht hebben.
Tip: omdat we het onderdeel Actieve Clickers nog niet hebben, is het lastig te zien of de winkel werkt.
Het kan handig zijn om in clickerWinkel
console.log(aantalClickers[tier])
te gebruiken,
om te zien hoeveel je er hebt na aanschaf.
{{< voorbeeld kop="Voorbeeldcode Clicker winkel" >}} {{< highlight javascript>}} const aantalClickers = [0, 0, 0, 0, 0] const clickerKosten = [1, 10, 50, 100, 500]
let geld = 0 let maxAantalClickers = 14 /* Voor de basisfunctie ligt dit aan de grootte van je scherm. */
/* Koopt een Clicker, mits hier geld voor is en er nog ruimte is. Update de pagina. */ function koopClicker(tier) { if (aantalClickers[tier] >= maxAantalClickers) { return } if (geld < clickerKosten[tier]) { return }
geld = geld - clickerKosten[tier]
aantalClickers[tier] = aantalClickers[tier] + 1
/* Update de pagina! */
updateGeld(geld)
}
{{< /highlight >}} {{< /voorbeeld >}}
In deze sectie zorgen we dat de actieve Clickers geld genereren. Dit doen we door elke seconde het inkomen van alle actieve Clickers bij elkaar op te tellen. Het totaal voegen we toe aan de hoeveelheid koekjes die we al hebben. Een voorbeeld:
Clicker | Inkomen per seconde | Aantal | Totaal |
---|---|---|---|
Kok | 1 | 4 | 4 |
Clicker tier 1 | 2 | 1 | 2 |
Clicker tier 2 | 4 | 3 | 12 |
Clicker tier 3 | 0 | 0 | 0 |
Clicker tier 4 | 0 | 0 | 0 |
Som: | 18 |
We hebben drie nieuwe functies nodig:
berekenGeldPerSeconde
voert de berekening uit het voorbeeld uit;updateGeldPerSeconde
update dediv
metid="geldPerSeconde"
;koopClicker
(en laterkoopPowerup
) voerenupdateGeldPerSeconde
uit bij aankoop van een nieuwe Clicker.updateClickerGeld
voegt de inkomsten van de Clickers toe aan het totaal, met behulp vanberekenGeldPerSeconde
.
Om de functie updateClickerGeld
elke seconde uit te voeren kan je de volgende code gebruiken:
{{< highlight javascript >}} /* Zorgt dat updateClickerGeld elke seconde activeert. */ setInterval(updateClickerGeld, 1000) {{< /highlight >}}
{{< voorbeeld kop="Voorbeeldcode actieve Clickers inkomsten toevoegen" >}} {{< highlight javascript >}} const aantalClickerTiers = 5
const clickerInkomsten = [1, 2, 4, 8, 32] const aantalClickers = [0, 0, 0, 0, 0]
function berekenGeldPerSeconde() { let totaal = 0
for (let tier = 0; tier < aantalClickerTiers; tier++) {
const inkomsten = clickerInkomsten[tier]
const aantal = aantalClickers[tier]
totaal += inkomsten * aantal
}
return totaal
}
/* Voegt het geld van de Clickers toe aan het geld. Update de pagina. */ function updateClickerGeld() { geld = geld + berekenGeldPerSeconde()
/* Update de pagina! */
updateGeld(geld)
}
/* Zorgt dat updateClickerGeld elke seconde activeert. */ setInterval(updateClickerGeld, 1000)
/* Verander het geld per seconde bedrag dat op de pagina word weergegeven naar hoeveelheid. */ function updateGeldPerSeconde(hoeveelheid) { const geldPerSecondeH2 = document.getElementById("geldPerSeconde")
geldPerSecondeH2.innerText = "Geld per seconde: " + hoeveelheid + "$"
} {{< /highlight >}}
Vergeet niet dat koopClicker
(en later koopPowerup
) updateGeldPerSeconde
uitvoeren bij aankoop van een nieuwe Clicker!
{{< /voorbeeld >}}
We kunnen eindelijk werkende Clickers toevoegen, maar de pagina voelt nog erg leeg.
Het zou leuk zijn om te zien welke Clickers we in dienst hebben!
Hiervoor gebruiken we de div
met id="actieveClickers"
.
Dit is hoe de div
er uit zou moeten zien na het kopen van twee Chefs:
{{< highlight html >}}
{{< /highlight >}}We gebruiken hier weer hetzelfde systeem als voorheen, waarin elk onderdeel van het spel een div
is,
welke een lijst met class="tier"
elementen bevat.
Die elementen kunnen we dus vinden zoals voorheen!
De vraag is nu: hoe voegen we hier afbeeldingen aan toe zoals in het bovenstaande voorbeeld?
Het antwoord: via document.createElement
.
Via deze functie kunnen we in de JavaScript-code HTML-elementen maken,
en deze later in een ander HTML-element toevoegen.
Dit is te zien in het volgende voorbeeld:
{{< highlight javascript >}} const actieveClickerDiv = document.getElementById("actieveClickers") const tierDiv = actieveClickerDiv.getElementsByClassName("tier0")[0]
const afbeelding = document.createElement("img") afbeelding.src = "" /* jouw afbeelding bron */ afbeelding.className = "icon"
tierDiv.appendChild(afbeelding) {{< /highlight >}}
Het voorbeeld laat alleen niet zien hoe je dit voor een willekeurige tier
doet.
Als je dit lastig vindt, vraag dan gerust om hulp!
{{< voorbeeld kop="Voorbeeldcode actieve Clickers" >}} {{< highlight javascript >}} /* Een lijst met de icoontjes van de Clickers. / clickerIcons = [ "https://pngimg.com/uploads/chef/chef_PNG54.png", / tier 1 / / tier 2 / / tier 3 / / tier 4 */ ]
/* Voeg een nieuw icoontje toe aan de lijst met Actieve Clickers van tier tier */ function voegActieveClickerToe(tier) { const actieveClickerDiv = document.getElementById("actieveClickers") const tierDiv = actieveClickerDiv.getElementsByClassName("tier" + tier)[0]
const afbeelding = document.createElement("img")
afbeelding.src = clickerIcons[tier]
afbeelding.className = "icon"
tierDiv.appendChild(afbeelding)
} {{< /highlight >}}
Van belang: gebruik voegActieveClickerToe(tier)
aan het einde van de functie koopClicker
,
zodat het kopen van een Clicker van tier tier
een nieuwe aan de lijst voor tier
Clickers toevoegt.
{{< /voorbeeld >}}
We kunnen nu handmatig koekjes verdienen en deze investeren in Clickers. De Clickers zijn te zien in het overzicht met actieve Clickers. Het spel voelt echter een beetje eenzijdig: klikken > kopen > klikken > ... > einde(?).
Een manier om extra strategie toe te voegen is het introduceren van Powerups. Een Powerup zorgt dat de actieve Clickers van een bepaalde tier beter worden. In het voorbeeld kan je voor de Chef een deegroller kopen: deze zorgt dat de Chef twee keer zoveel koekjes per seconde kan produceren.
In deze instructie zullen we één Powerup per tier Clicker maken. Dit is net iets simpeler dan in het originele spel, maar je kan het later uitbreiden! De HTML-code voor Powerups lijkt erg op die van de Clicker winkel:
{{< highlight html >}}
<div class="tier1" onClick="koopPowerup(1)">
<img class="icon" src="???">
<text>???$</text>
</div>
...
Ook hier komt een kort plan van aanpak van pas:
- elke klik op een Clicker
div
word geregistreerd via eenonClick="koopPowerup(n)"
HTML-attribuut; - we houden per tier Clicker bij of de Powerup al gekocht is;
- de functie
koopPowerup
check of je genoeg geld hebt en of de Powerup nog niet gekocht is; - als dit het geval is, registreer dan de nieuwe Powerup en haal het geld weg (aankoop voltooid);
- update naderhand de pagina op basis van de veranderingen.
Ook moeten we zorgen dat we ergens een lijstje bijhouden met de inkomsten per seconde van elke Clicker. Als de Powerup is gekocht, vermenigvuldigen we die inkomsten met twee.
Tip: als de aanschaf van een Powerup succesvol is, moeten we zorgen dat men deze niet nogmaals kan kopen. Je kan de Powerup uit de lijst halen via:
{{< highlight javascript >}} const tierDiv = ... tierDiv.remove() {{< /highlight >}}
{{< voorbeeld kop="Voorbeeldcode Powerup winkel" >}} {{< highlight javascript >}} const powerupKosten = [10, 100, 500, 1000, 5000]
let geld = 0 const beschikbarePowerups = [true, true, true, true, true] const clickerInkomsten = [1, 2, 4, 8, 32]
/* Koopt een Powerup, mits hier geld voor is en de powerup nog niet gekocht is. Update de pagina. */ function koopPowerup(tier) { if (beschikbarePowerups[tier] == false) { return } if (geld < powerupKosten[tier]) { return }
beschikbarePowerups[tier] = false;
geld = geld - powerupKosten[tier]
clickerInkomsten[tier] = clickerInkomsten[tier] * 2
/* Update de pagina! */
verwijderPowerup(tier)
updateGeldPerSeconde(berekenGeldPerSeconde())
updateGeld(geld)
}
/* Verwijder de Powerup voor tier tier uit de lijst. */ function verwijderPowerup(tier) { const powerupDiv = document.getElementById("powerupWinkel") const tierDiv = powerupDiv.getElementsByClassName("tier" + tier)[0]
tierDiv.remove()
} {{< /highlight >}} {{< /voorbeeld >}}
Allereerst: gefeliciteerd met het halen van het einde van deze instructie!
Er zijn een hoop uitbreidingen mogelijk op het basisconcept:
- de kosten van Clickers verhogen na elke aankoop;
- meerde Powerups per Clicker;
- meer Clickers toevoegen;
Hier kan je zelf mee aan de slag gaan en natuurlijk extra hulp bij vragen.
Laat ons eventueel weten of er nog andere uitbreidingen zijn die je in deze instructie zou willen zien!
{{< voorbeeld kop="De volledige voorbeeldcode" >}}
{{< highlight javascript >}}
/* Een lijst met de icoontjes van de Clickers. */
clickerIcons = [
"https://pngimg.com/uploads/chef/chef_PNG54.png",
/* tier 1 */
/* tier 2 */
/* tier 3 */
/* tier 4 */
]
/* De volgende functies beïnvloeden het uiterlijk van de pagina. */
/* Voeg een nieuw icoontje toe aan de lijst met Actieve Clickers van tier tier */
function voegActieveClickerToe(tier) {
const actieveClickerDiv = document.getElementById("actieveClickers")
const tierDiv = actieveClickerDiv.getElementsByClassName("tier" + tier)[0]
const afbeelding = document.createElement("img")
afbeelding.src = clickerIcons[tier]
afbeelding.className = "icon"
tierDiv.appendChild(afbeelding)
}
/* Verander het geld bedrag dat op de pagina word weergegeven naar hoeveelheid. */
function updateGeld(hoeveelheid) {
const geldH2 = document.getElementById("geld")
geldH2.innerText = "Geld: " + hoeveelheid + "$"
}
/* Verander het geld per seconde bedrag dat op de pagina word weergegeven naar hoeveelheid. */
function updateGeldPerSeconde(hoeveelheid) {
const geldPerSecondeH2 = document.getElementById("geldPerSeconde")
geldPerSecondeH2.innerText = "Geld per seconde: " + hoeveelheid + "$"
}
/* Verwijder de Powerup voor tier tier uit de lijst. */
function verwijderPowerup(tier) {
const powerupDiv = document.getElementById("powerupWinkel")
const tierDiv = powerupDiv.getElementsByClassName("tier" + tier)[0]
tierDiv.remove()
}
/* Vanaf hier focussen we op de functionaliteit! */
/* De volgende gegevens veranderen niet. */
const aantalClickerTiers = 5
const clickerKosten = [1, 10, 50, 100, 500]
const powerupKosten = [10, 100, 500, 1000, 5000]
/* De volgende gevevens kunnen veranderen. */
let geld = 0
let maxAantalClickers = 14 /* Voor de basisfunctie ligt dit aan de grootte van je scherm. */
const clickerInkomsten = [1, 2, 4, 8, 32]
const aantalClickers = [0, 0, 0, 0, 0]
const beschikbarePowerups = [true, true, true, true, true]
/* Voegt 1$ toe en update de pagina */
function onClickCookie() {
geld = geld + 1
updateGeld(geld) /* Update de pagina! */
}
/* Berekent het geld dat de Clickers genereren per seconde. */
function berekenGeldPerSeconde() {
let totaal = 0
for (let tier = 0; tier < aantalClickerTiers; tier++) {
const inkomsten = clickerInkomsten[tier]
const aantal = aantalClickers[tier]
totaal += inkomsten * aantal
}
return totaal
}
/* Koopt een Powerup, mits hier geld voor is en de Powerup nog niet gekocht is. Update de pagina. */
function koopPowerup(tier) {
if (beschikbarePowerups[tier] == false) {
return
}
if (geld < powerupKosten[tier]) {
return
}
beschikbarePowerups[tier] = false;
geld = geld - powerupKosten[tier]
clickerInkomsten[tier] = clickerInkomsten[tier] * 2
/* Update de pagina! */
verwijderPowerup(tier)
updateGeldPerSeconde(berekenGeldPerSeconde())
updateGeld(geld)
}
/* Koopt een Clicker, mits hier geld voor is en er nog ruimte is. Update de pagina. */
function koopClicker(tier) {
if (aantalClickers[tier] >= maxAantalClickers) {
return
}
if (geld < clickerKosten[tier]) {
return
}
geld = geld - clickerKosten[tier]
aantalClickers[tier] = aantalClickers[tier] + 1
/* Update de pagina! */
voegActieveClickerToe(tier)
updateGeldPerSeconde(berekenGeldPerSeconde())
updateGeld(geld)
}
/* Voegt het geld van de Clickers toe aan het geld. Update de pagina. */
function updateClickerGeld() {
geld = geld + berekenGeldPerSeconde()
/* Update de pagina! */
updateGeld(geld)
}
/* Zorgt dat updateClickerGeld elke seconde activeert. */
setInterval(updateClickerGeld, 1000)
{{< /highlight >}}
{{< /voorbeeld >}}
{{< licentie rel="http://creativecommons.org/licenses/by-nc-sa/4.0/">}}
Footnotes
-
Er is immers geen ruimte op het scherm voor 100 kok icoontjes! ↩