Flyt en figur til den rammer en mur

Få en figur til at bevæge sig, indtil den rammer en mur. Få figuren til at flytte sig hele vejen ind til muren, men ikke længere.

Når du først har lavet en figur, som du gerne vil have til at bevæge sig, vil du nogle gange gerne have, at den stopper, når den rammer en mur. Men du vil gerne have, at den stopper helt inde ved muren, og ikke flere pixels væk derfra. Det er det, man i spiludvikling nogle gange kalder "pixel perfect collisions". Der er mange måder at gøre dette på, og i dette opslag vil jeg vise én af dem.

God læsning inden du starter

  • Assets, hvad er det? En basal oversigt over forskellige asset-typer i GameMaker.
  • Variabler, hvad er det? Variabler og deres brug i GameMaker.
  • Funktioner, hvad er det? Hvordan man definerer og bruger funktioner i GameMaker.
  • Koordinater, hvad er det? Hvordan man placerer og flytter objekter og andet i GameMaker.
  • "Collision Mask", hvad er det? Hvordan man definerer hvor meget en sprite "fylder" og hvornår de rammer andre ting.

Den færdige kode: move_contact_x

Den færdige kode bor inde i et script kaldet move_contact_. Jeg går igennem, hvordan det er sat sammen, længere nede.

Funktion som flytter hen til en væg på x-aksen

Hvordan bruger jeg det?

Når du gerne vil bruge den færdige funktion, skal du "kalde" på funktionen dér, hvor du gerne vil bruge den. Det er programmør-sprog for at sige, at man bruger funktionen. Det kan for eksempel se sådan her ud i en spillers step-event:

Hvordan man bruger funktionen move_contact_x i GameMaker

Det er den mest basale brug: Hvis en tast holdes nede, bevæger vi spilleren indtil den rammer en mur. Men vores funktion kan også lidt mere - det kan være, at vores hastighed bliver sat et andet sted, og er gemt i en variabel (for eksempel hspd (kort for horizontal speed, altså højre-venstre-hastigheden)).

Vores move_contact_x-funktion returnerer én af værdierne true (hvis den kaldende figur er blevet stoppet af en mur) eller false (hvis den ikke er) når den bliver brugt. Hvis vores hastighed er gemt i en variabel, er det vigtigt at vi sætter variablen til 0, hvis vi har ramt en mur. Det kunne for eksempel se sådan her ud:

Alternativt kodeeksempel med stopkode

Her gemmer vi den værdi, som funktionen returnerer, i en midlertidig variabel _collision. Vi stiller så spørgsmålet, om værdien er lig med true, og hvis det er tilfældet, så sætter vi hspd til 0. At gemme hastigheden i en variabel kan være en god idé, hvis man gerne vil have, at ens karakter glider lidt mere og styres lidt blødere.

Hvordan gør jeg så?

For at lave en funktion som denne, skal vi først oprette et nyt script. Det gør du ved at højreklikke ovre i din "Asset Browser", føre musen over på "Create" og så trykke på "Script". Giv scriptet navnet move_contact_. Husk at bruge _ tegn i stedet for mellemrum.

Hvordan man opretter et nyt script i GameMaker

Vi kalder script-filen move_contact_ og ikke move_contact_x, fordi en script-fil kan indeholde flere funktioner, og det kan jo være, at vi gerne vil lave flere move_contact_-et-eller-andet funktioner i fremtiden.

Definér en funktion

Når man opretter et nyt script, starter den med en rød kodeblok, som laver en ny funktion. Den kan man godt slette, hvis man ikke skal bruge den til noget - men vi vil faktisk gerne lave en ny funktion, så vi beholder den. I "Name"-feltet giver du den navnet move_contact_x. Kodeblokken laver ét "Argument"-felt til dig som start. Men hvis du trykker på det lille "+" til venstre for blokken, kan du tilføje et til. Det vil vi gerne.

Når det er gjort, skriver du i det første "Argument"-felt _hspd, markerer det som "optional" og skriver 0 i "Value". Det gør, at når du skal bruge funktionen, kan du indføre en værdi som første "argument" som vil blive gemt i den midlertidige variabel _hspd. Men hvis du ikke indføre en værdi, vil denne variabel bare være 0. Dette vil være det antal pixels, vi gerne vil flytte en figur med, hvis ikke der er noget i vejen (minus-værdier er til venstre, plus-værdier er til højre).

Deklareringsblok til ny funktion

I det andet "Argument"-felt skriver du _object. Det sætter du ligeledes til at være "optional", og du giver det værdien all i "Value"-feltet. Denne variabel vil indeholde det objekt, som du bruger som din mur. Dette vil være én objekttype, så hvis du gerne vil ramme flere forskellige, så er du nødt til at se på nedarvning (se under "God læsning, når du er færdig"). all-værdien, som variablen vil være sat til, hvis ikke andet er indført, betyder "alle objekttyper i hele spillet". Typisk er det ikke det, du vil have.

Et par midlertidige variabler

Det første, vi gør i vores nye funktion, er at opsætte et par midlertidige variabler mere. Det gør vi med den blå "Declare Temp"-blok. Træk én af disse ind i vores røde "Declare a New Function"-blok. Det gør du, ved at trække dem over i højre side af den røde blok.

Vi vil gerne tilføje to midlertidige variabler, så vi trykker på det lille "+" til venstre for blokken.

Blok til deklarering af midlertidige variabler

I det første "Name"-felt skriver du _abs og i "Value"-feltet under skriver du abs(_hspd). I de fleste "Value"-felter i GameMaker kan du nemlig skrive kode og kalde på funktioner. Her bruger vi funktionen abs() som giver dig den absolutte værdi af et tal. Det er noget værre matematiksprog, men groft sagt betyder det bare, at det fjerner minusser, hvis der er nogen. Så abs(4) vil give værdien 4, men abs(-4) vil også give værdien 4. Så i _abs-variablen gemmer vi en kopi af vores _hspd-variabel, men hvor minusser (hvis der er nogen) er blevet fjernet.

I det andet "Name"-felt skriver du i stedet _sign og i det tilhørende "Value"-felt skriver du sign(_hspd). sign() er en anden funktion i GameMaker, som finder fortegnet for den værdi, du putter i. Det vil sige, at den ved minus-tal (for eksempel sign(-32)) vil returnere -1, ved plus-tal (for eksempel sign(27)) vil returnere 1, og ved 0 (sign(0)) vil returnere 0. Så kort sagt altid enten -1, 0 eller 1.

En gentagende løkke

På dette stadie har vi altså i alt 4 midlertidige variabler: _hspd, _object, _abs og _sign. Dem vil vi bruge til at bevæge os og stoppe korrekt. Det næste vi skal sætte op, er et loop - også nogle gange på dansk kaldet en løkke. Et loop er et stykke gentagende kode, og er fundamentalt for spiludvikling og programmering. Hele dit spil kører faktisk i et loop, som ved hver eneste gentagelse både tester og kører alle de forskellige events i alle dine objekter, tegner de baggrunde og figurer du har lavet og meget mere. Den nemmeste måde at se det på, er step-eventet, som altid kører én gang i hver gentagelse.

Det loop vi skal have gang i, er lidt mindre. Der er mange slags loops, og vi skal bruge det der hedder et "Repeat" loop. Et repeat loop tager imod præcis én værdi: Antallet af gentagelser, det skal udføre. Det er her vores _abs-variabel kommer i spil, da det er antallet af gentagelser, vi vil lave (én for hver pixel, vores figur skal flytte sig).

En repeat blok

Vi vil gerne have, at vores funktion returnerer true (sandt, svarende til tallet 1) hvis vi rammer en mur, og false (falsk, svarende til tallet 0) hvis ikke. Så efter loopet (ikke indeni) sætter vi en grå "Return" blok, og sætter denne til at returnere værdien false.

Repeat blok og return blok

Denne retur-værdi fungerer som vores standard: Hvis ikke vi bliver stoppet af noget, så returnerer vi false.

Tid til at flytte os

Det næste på listen, er indholdet af vores loop. Vi starter med at stille et spørgsmål: "Er der en mur én pixel i den retning, vi gerne vil flytte os i". Det gør vi med en grøn "If Object At Place", som vi trækker ind i højre side af vores loop.

Block til a teste for et objekt

I "Object"-feltet - det objekt vi tester for som mur - skriver vi vores _object variabel. Man kan nemlig skrive den slags direkte i tekstfeltet, man behøver ikke trykke på knappen ved siden af og vælge et objekt.

I "X"-feltet indsætter vi vores _sign variabel, da denne jo er tilsvarende præcis 1 pixel i den rigtige retning (-1 eller +1). I "Y"-feltet skriver vi bare 0, da vi ikke tænker at flytte os op eller ned. Ved både "X" og "Y" tikker vi også "Relative" af. Det betyder at vi tester fra det kaldende objekts position (i dette tilfælde ville det svare til, at vi tjekker ved koordinat ["X" = x + _sign, "Y" = y + 0]. Hvis vi ikke tikker "Relative" af, tester vores kode ved koordinat ["X" = _sign, "Y" = 0], altså oppe i øverste venstre hjørne af vores bane.

Resten af mulighederne i blokken behøver vi ikke røre ved - dem skal vi ikke bruge.

Når vores spørgsmål er stillet, er alt, vi trækker ind i den (igen, i højre side), den kode der vil køre, hvis svaret på spørgsmålet er "sandt". I dette tilfælde, hvis spørgsmålet er sandt, betyder det, at der er en mur på den næste pixel, og at vi ikke skal flytte os. Derfor trækker vi en grå "Return" blok ind, og sætter værdien til true. Når en funktion rammer en "Return" blok, kører den ikke mere kode - det vil sige at vores loop også vil stoppe.

Loop blok med kollision test og returnering

På nuværende kan vores funktion altså returnere to værdier: true hvis vi har ramt en mur, og false hvis vi ikke har. Nu mangler vi bare den sidste del: Rent faktisk at flytte vores figur i loopet. Det gør vi ved at "hoppe" én pixel af gangen med en rød "Jump To Point" blok.

Hvis kollision, så stop, ellers flyt med én pixel

Præcis ligesom ved vores kollisionstest, sætter vi vores "X" til _sign og "Y" til 0 med "Relative" slået til; Hvis pladsen er ledig, rykker vi en pixel og loopet får lov at fortsætte. Når det er på plads er vi færdige. Vores funktion er nu klar til brug!

Hvad så nu?

Nu har du en funktion, så du kan flytte din figur oglade den blive stoppet af mure horisontalt. I samme script-fil kunne det måske være en idé at lave en lignende funktion move_contact_y, som gør det samme - bare vertikalt (op og ned)? Og måske kan man lave en funktion move_contact_xy, der tester begge retninger samtidig? Der er mange muligheder for afarter at funktionen, og masser af potentiale for viderebyggelse. Kun fantasien sætter grænser. Jeg håber du har lært noget, og får brugt det til en masse fede spil!

God læsning, når du er færdig


Published