You are on page 1of 19

Game Maker Tutorial

Een First Person Shooter


Geschreven door Mark Overmars

Copyright © 2007 YoYo Games Ltd


Vertaling: Rammstein
Nederlandse Game Maker Community (www.game-maker.nl)
Laatst veranderd: 25 februari 2007
Gebruikt: Game Maker 7.0, Pro versie, Advanced Mode
Niveau: Gevorderd

In deze tutorial gaan we de nieuwe 3D teken functies in Game Maker 6 bekijken.


“Maar ik dacht dat Game Maker was voor 2-dimensionele spellens was?” zal je
misschien wel denken. Nou, ja, het is bedoeld voor 2-dimensionele spellen. Maar er
zijn functies voor 3D graphics. En veel 3-dimensioneel lijkende spellen zijn eigenlijk
2-dimensioneel. In deze tutorial maken we een First Person Shooter. Ook al lijken de
graphics 3-dimensioneel, het spel vindt eigenlijk plaats in een 2-dimensionele wereld.
Dus we zullen de standaard handelingen van Game Maker gebruiken voor dit 2-
dimensionele spel, maar anders dan het tekenen van de 2-dimensionele room gaan we
3-dimensionele graphics maken. Zoals we zullen zien is dit niet erg moeilijk. Maar je
moet GML toch wel goed kennen en niet bang zijn om grote stukken script te
schrijven.
Vandaar dat deze tutorial alleen voor gevorderde gebruikers is. En vergeet niet dat de
3D graphics alleen beschikbaar zijn in de Pro versie van Game Maker.

We zullen de game in een aantal stappen maken, beginnend met een eenvoudige 2-
dimensionale versie en daarna de 3D graphics toevoegen. Alle stukjes games zijn te
vinden in de map Examples die bij deze tutorial zit en kunnen geladen worden in
Game Maker.

Een eerste 2-dimensioneel spel


Zoals hierboven aangegeven, is de eigenlijke game play 2-dimensioneel. Dus moeten
we eerst een 2-dimensioneel spel maken, die we later omzetten naar 3-dimensionele
graphics. Omdat graphics niet echt belangrijk zijn in dit deel, hebben we geen mooie
sprites nodig. Alle objecten (speler, vijanden, kogels, etc.) zullen worden
vertegenwoordigd door simpele gekleurde schijven. En er zijn muren die worden
vertegenwoordigd door horizontale en verticale blokken. In dit hoofdstuk maken we
een simpele 2-dimensionele scène met rooms en een speler. Andere dingen worden
later toegevoegd. Het spel kan worden gevonden in het bestand fps0.gmk in de
Examples map.

We maken 2 muur objecten: een horizontale en een verticale. We maken ook nog een
basis muur object, obj_wall_basic genaamd. Dit object zal de parent van alle
andere muur objecten worden (later zullen we er meer maken). Dit zal aspecten
maken zoals collision detectie en tekent een stuk makkelijker wat we zullen merken.
Muur objecten hebben geen gedrag. Alle muur objecten zullen massief worden. Dus
de horizontale muur zal lijken op zoiets als dit:

1
Nu het is best saai. Alleen de sprite en parent zijn ingevuld.

Het volgende object dat we moeten maken is het speler object. We vertegenwoordigen
het met een kleine schijf (We geven het een rode punt op een kant om te laten zien dat
de directie veranderd. Dit is alleen belangrijk in de 2-dimensionele versie en is niet
belangrijk in de 3D versie. In het end step event plaatsen we de image_angle naar
de direction om de rode punt naar het goede punt te laten wijzen.) We hoeven
alleen nog de richting te definiëren. Om een beetje een lichte richting te krijgen willen
we niet dat de richting meteen start. Dit zal er erg slecht uitzien in de 3D versie. Dus
we laten het geleidelijk starten met bewegen en stoppen met bewegen. Als laatste, typ
in het keyboard event van de <Up> toets zetten we de volgende code:

{
if (speed < 2) speed = min(2,speed+0.4);
}

Zodat het langzaam snelheid krijgt totdat het maximum van 2 is bereikt. In het create
event zetten we de wrijving (friction) naar 0.2 zodat, wanneer de speler de <Up>
toets los laat de snelheid weer terugloopt. (Misschien wil je een beetje spelen met de
maximum snelheid, snelheid krijgen, en wrijving om het effect te krijgen wat je wilt.)
In het <Down> keyboard event doen we hetzelfde maar in de tegenovergestelde
richting. In de <Left> en <Right> keyboard event veranderen we gewoon de directie
van de richting. Uiteindelijk, in het Collision event met het obj_wall_basic object
stoppen we de richting (dit is een beetje lelijk en we zien later hoe we dit veranderen).
Omdat alle andere muren de parent obj_wall_basic hebben hoeven we alleen maar
1 Collision event te definiëren.

Als laatste moeten we een level maken met verschillende gebieden. Voorzichtig level
maken zal belangrijk zijn om interessante game play te creëren. Op dit moment
beperken wij ons tot de rare kijk op de room hieronder.

2
Laad het spel fps0.gmk en speel er een beetje mee. Het ziet er een beetje saai hè?

Het veranderen naar een 3-dimensioneel spel


Dit is waarschijnlijk het meest belangrijke deel. Hier veranderen we ons saaie 2-
dimensionele spel in een veel beter uitziende (maar eigenlijk nog steeds saaie) 3-
dimensioneel spel.

Our player object becomes the most important object. In its creation event we
initialize the 3D mode by using the following piece of code:

d3d_start();
d3d_set_hidden(true);
d3d_set_lighting(false);
d3d_set_culling(false);
texture_set_interpolation(true);

De eerste regel start de 3D modus. De tweede regel bepaald dat verborgen


oppervlakte verwijdering aan staat. Dit betekent dat objecten die achter andere
objecten liggen niet te zien zijn. Normaal gesproken is dit wat je wilt in een 3-
dimensioneel spel. De volgende regel bepaald dat we geen licht bronnen gebruiken.
Als laatste, de vierde regel bepaald dat er niet wordt geselecteerd. Dit is iets
moeilijker om te begrijpen. Wanneer je de ruimte van een veelhoek tekent heft het 2
kanten. Wanneer selecteren aan staat word er alleen maar 1 van de 2 kanten getekend
(bepaald door de orde van de toppen). Dit bespaart tijd met het tekenen als het goed
wordt gebruikt. Maar omdat onze muren zullen worden bekeken van beide kanten, is
3
dit niet wat we willen. Eigenlijk, zijn deze drie regels niet benodigd omdat dit de
standaard instellingen zijn. Uiteindelijk stellen we de textuur interpolatie in. Dit laat
de textuur er beter eruitzien wanneer je er dicht in de buurt komt. (Je kunt dit ook
instellen in de Global Game Settings.)

Om er zeker van te zijn dat de events in het speller object eerder worden ingesteld als
in andere objecten, geven we het een diepte van 100 (je kunt je herinneren dat andere
objecten worden behandeld in aflopende volgorde). We verwijderen ook het End
Step event dat we hadden toegevoegd aan de 2D versie van het spel om de richting
aan te geven. Dit is niet langer nodig en zal later ook tot problemen leiden.

Het volgende om te doen is om de muren mooier eruit gaan zien en op de goede


manier getekend worden. Om ze er mooier eruit te laten zien hebben we wat textures
nodig. In deze tutorial gebruiken we een paar texturen, sommige gemaakt door David
Gurrea die gevonden kunnen worden op de site
http://www.davegh.com/blade/davegh.htm. (Lees alsjeblieft de regels voor het
gebruik ervan door.) We veranderen ze allemaal naar grootte 128x128. Het is erg
belangrijk dat de groottes van de texturen de macht van 2 zijn (bijv. 64, 128, 256)
anders zullen ze niet helemaal mooi passen. Hier is de muur, plafond, en vloer textuur
die we gebruiken:

We voegen dit als een background resource toe aan het spel. Om elke muur object een
muur te laten tekenen met dit plaatje moeten we weten wat de coördinaten van de
muur zullen zijn. We plaatsen dit in het Create event van elk individueel muur object
(omdat het verschillend zal zijn voor elke muur). Bijvoorbeeld, voor de horizontale
muur plaatsen we de volgende code in het Create event:

{
x1 = x-16;
x2 = x+16;
y1 = y;
y2 = y;
z1 = 32;
z2 = 0;
tex = background_get_texture(texture_wall);
}

De laatste regel heeft wat uitleg nodig. De functie background_get_texture()


geeft de index van de textuur van de beoogde achtergrond. We gebruiken dit later
wanneer we de sprite moeten opgeven met behulp van de muur. We zetten eenzelfde
code in het Create even van alle muren.

4
Om de muur echt te tekenen, zetten we in het Draw event van obj_wall_basic de
volgende code met een Code Action:

{
d3d_draw_wall(x1,y1,z1,x2,y2,z2,tex,1,1);
}

Het tekent een muur, gebruikmakend van de warden die we in het Create event
hebben gekregen. De laatste 2 parameters bepalen dat de textuur maar 1 keer moet
worden herhaald in beide richtingen.

We zijn bijna klaar. We moeten nog steeds instellen hoe we kijken naar de wereld en
we moeten ook de vloer en het plafond tekenen. Dit doen we in het Draw event van
het camera object. Hier plaatsen we een Execute Code actie met het volgende stukje
code:

{
// stelt de projectie in
d3d_set_projection(x,y,10, x+cos(direction*pi/180),
y-sin(direction*pi/180),10, 0,0,1);
// stelt de kleur in transparantie in
draw_set_alpha(1);
draw_set_color(c_white);
// tekent de vloer en het plafond
d3d_draw_floor(0,0,0,640,480,0,
background_get_texture(texture_floor),24,18);
d3d_draw_floor(0,0,32,640,480,32,
background_get_texture(texture_ceiling),16,12);
}

Er zijn 3 delen hier. In het eerste deel stellen we de projectie in hoe we in de room
kijken. De functie d3d_set_projection() bepaald de positie van waaruit we kijk.
We kijken vanaf positie (x,y,10). Het punt waar we naartoe kijken lijkt erg
ingewikkeld maar het is gewoon een punt dat ligt op een step in de richting van de
camera. Uiteindelijk bepalen we dat de omhooggaande richting van de camera de z-
richting is (0,0,1).

Het tweede deel stelt de alpha waarde in op 1, dit betekent dat alle objecten
ondoorzichtbaar zijn. Dit is belangrijk. Textures zijn eigenlijk gemixt met de
aanwezige kleur. Dit is een handig gegeven (je kunt bijv. het plafond rood maken).
Maar meestal gebruik je wit. (De standaard kleur is zwart, dus als je het niet
verandert, zal de hele wereld zwart lijken.)

In het derde deel tekenen we een vloer en een plafond (op hoogte 32) gebruikmakend
van de goede textures. Zoals je ziet stellen we in dat de textuur vaak moet worden
herhaald over het plafond in plaats van het uit te rekken.

Dat is alles. Het spel kan worden gevonden in het bestand fps1.gmk in de
Examples map. Je kunt nu het spel spelen en in je wereld rondlopen. Het plaatje zou
er zo uit moeten zien:

5
Verbeter het gevoel
In dit hoofdstuk gaan we het “spel” verbeteren. Allereerst voegen we wat meer
variatie toe, door gebruik te maken van de verschillende typen muren. Ten tweede
zullen we het level design verbeteren. Om het visueel te verbeteren (en wat lugubere
sfeer te creëren) we voegen mist toe aan de wereld. En uiteindelijk verbeteren we de
beweging door de wereld.

Meer verschillende muren


De wereld zoals we hem nu hebben ontworpen lijkt erg saai. Alle muren zien er
hetzelfde uit. Niet alleen is dit saai, het maakt het ook moeilijk voor de speler om
zichzelf in het spel te voelen. Dit is slechte game design (behalve als het de intensie
van het spel is). Het is meer gebruiksvriendelijk als de speler zich makkelijker kan
herinneren waar hij is en vandaan komt.

De oplossing hiervoor is voordehand liggend. We voegen een paar andere textures aan
het spel toe en maken een paar andere muur objecten. Deze worden precies hetzelfde
als de andere, behalve dat we een andere textuur laden in het Create event. Denk er
aan om alle muur objecten de obj_wall_basic muur als parent te geven. Door
verschillend gekleurde sprites voor de verschillende muren te gebruiken, is het
makkelijker om levels te maken. (Bedenk dat deze sprites niet worden gebruikt in het
spel, maar dat deze alleen de levels laten zien.)

6
Betere level design
Het level heeft een paar problemen. Als eerste, er zijn muren die erg plat lijken, omdat
je beide kanten kunt zien. Dit moet worden omzeild. Ten tweede, probeerden we het
hele level op te vullen met regio’s. Ook al is dit normaal voor normale gebouwen, het
is niet erg goed voor spellen. Om interessante verassingen te krijgen kunnen we een
paar doorgangen maken met deuren. Door deuren toe te voegen, maken we het deel
van de wereld die de speler altijd kan zien. Dit vergroot de spanning. Je weet niet wat
er in de hoek kan zitten. (Zoals we hieronder zullen zien is er een andere reden om het
level aan te passen. Het geeft ons de mogelijkheid om de graphics sneller worden
doordat er alleen een deel van het level wordt getekend.)

Om een beter level te creëren hebben we meer ruimte nodig. Daarom vergroten we de
afmetingen. Om te voorkomen dat dit ook meteen de grootte van het programma
vergroot gebruiken we een view. De positie van de view in de room maakt niet uit,
omdat we de projectie zelf maken. Hier is een nieuwe screenshot van het level. De
verschillend gekleurde delen geven de verschillende texturen aan. Je kunt dit nieuwe
spel vinden in het bestand fps2.gmk.

7
Mist toevoegen
Om een meer angstaanjagend effect toe te voegen kan je mist toevoegen aan je spel.
Het effect van mist is dat objecten van afstand donkerder lijken (of lichter, afhangend
van de kleur van de mist). Wanneer een object erg ver weg is wordt het onzichtbaar.
Mist geeft een beter gevoel van diepte en afstand en maakt mogelijk om objecten niet
te tekenen als ze te ver weg zijn, wat dingen sneller maakt. Om mist aan te zetten,
hoef je alleen de volgende regel met een Execute Code actie in het Create event te
zetten van het player object (na het aanzetten van 3D).

d3d_set_fog(true,c_black,10,300);

Als je de mist met aanzet met een zwarte kleur, beginnend op afstand 10, en wordt
helemaal zwart op afstand 300. (Merk op dat op sommige grafische kaarten mist niet
wordt ondersteund. Dus wees er zeker van dat het spel het niet nodig heeft.) Met mist
aan, zal de wereld er zo uitzien:

Betere beweging
Wat nog gedaan moet worden is de verbetering van de beweging. Wanneer je een
object raakt stop je meteen met bewegen. Dit ziet er niet mooi uit en maakt het lopen
door gangen moeilijker. Om dit te verbeteren, wanneer je een muur raakt onder een
bepaalde hoek, glijdt je langs de muur in plaats van stoppen. Om dit voor elkaar te
krijgen moeten we een paar dingen veranderen. Ten eerste, we maken de muren niet
langer solid. Wanneer objecten solid zijn kunnen we niet precies instellen wat er
gebeurd, omdat het systeem dat dan doet. Maar we willen alles kunnen instellen, dus

8
alle objecten worden niet langer solid. In het Collision event plaatsen we de volgende
code met Execute Code:

{
x = xprevious;
y = yprevious;
if (abs(hspeed) >= abs(vspeed) &&
not place_meeting(x+hspeed,y,obj_wall_basic))
{ x += hspeed; exit;}
if (abs(vspeed) >= abs(hspeed) &&
not place_meeting(x,y+vspeed,obj_wall_basic))
{ y += vspeed; exit;}
speed = 0;
}

Je moet de code als volgt lezen. In de eerste twee regels zetten we de speller terug
naar de vorige positie (om de collision ongedaan te maken). Nu kunnen we checken of
we horizontaal kunnen bewegen (glijdend langs de muren). We doen dit alleen als de
horizontale snelheid groter is dan de verticale snelheid. Als de horizontale glijdpositie
is collision vrij met de muren die daar staan. Daarna proberen we hetzelfde met het
verticaal glijden. Als beiden mislukken zetten we de snelheid op 0 om het bewegen te
stoppen.

Een volgende veranderering die we maken is het toelaten van rennen en lopen.
Wanneer de speler op de <Shift> toets drukt laten we hem sneller bewegen. Dit is
voor elkaar gekregen door in de <Up> en <Down> toets checken of <Shift> wordt
ingedrukt en, als het zo is, Het sneller bewegen toelaten, zoals hieronder:

{
var maxspeed;
if keyboard_check(vk_shift) maxspeed = 3 else maxspeed = 1.5;
if (speed < maxspeed ) speed = min(maxspeed ,speed+0.4);
}

Een laatste verandering is dat in de meeste FPS spellen de speler naar de zijkanten kan
lopen. Hiervoor gebruiken we de <z> en <x> toets. De beweging moet haaks zijn op
de richting waar de speller naartoe kijkt. Voor de <z> toets ziet dat er zo uit:

{
var xn,yn;
xn = x - sin(direction*pi/180);
yn = y - cos(direction*pi/180);
if not place_meeting(xn,yn,obj_wall_basic)
{ x = xn; y = yn; }
}

Je kunt het spel uitproberen in het bestand fps2.gmk voor het effect.

Objecten toevoegen aan de wereld


Onze wereld is erg leeg. Er zijn alleen maar een paar muren. In dit hoofdstuk gaan we
wat objecten toevoegen aan de wereld. Deze zijn hoofdzakelijk voor versiering, maar
de speller (en de vijanden) kunnen ook erachter verborgen worden. Er zijn globaal

9
gesproken twee manieren om objecten aan de wereld toe te voegen. De eerste manier
is het creëren van een 3-dimensioneel object, bestaande uit een textuur met
driehoekige plaatjes. Dit geeft het mooiste effect, maar het is erg tijdrovend, om het te
creëren én te tekenen. In plaats daarvan, gebruiken we gewoon sprites om de objecten
te vertegenwoordigen. We zullen deze techniek gebruiken voor alles in onze wereld:
de kogels, planten, wapens, explosies etc. Maar dingen zijn iets moeilijker dan ze
lijken. Sprites zijn plat, als we vanuit het verkeerde oogpunt bekijken, zullen ze niet
zichtbaar zijn. We lossen dit op door de sprites zich naar de speler te laten draaien.

Zien van sprites


We gaan een plant object maken, dat we kunnen plaatsen in een room om de wereld
wat interessanter te maken. Zoals de speler en de muur segmenten, gebruiken we een
simpele sprite. Dit is gemakkelijk om de room te ontwerpen en het wordt gebruikt
voor collision controle, zodat de speler niet door een plant kan lopen. We maken een
plant object en geven het als parent onze basis muur object, Dus voor de speler (en
later voor de kogels) zal de plant zich als een muur gedragen. We herschrijven het
draw event wel, omdat we iets anders moeten tekenen. Om de plant te tekenen hebben
we een mooie sprite nodig. Deze sprite wordt deels transparant en om hem er beter uit
te laten zien, zetten we de optie “smooth the edges” aan. In het draw event gebruiken
we deze sprite om de plant te tekenen op een verticale muur. Omdat de sprite deels
transparant is zie je alleen dat deel van de muur.

Er is nog een belangrijk punt. De sprite is een plat plaatje. Als je het van de zijkant
bekijkt wordt het erg dun, en verdwijnt zelfs. Om dit op te lossen gebruiken we een
makkelijke truc dat in veel spellen wordt gebruikt. We laten de sprite altijd naar de
camera kijken. Dus onafhankelijk van de richting van waar je kijkt, de sprite ziet er
hetzelfde uit. Het draait mee me jou. Het klinkt misschien raar, maar het ziet er best
goed uit. Dus hoe doen we dit? We hebben een stukje rekenkunde nodig. Het
volgende plaatje laat de situatie zien. De pijl stelt de richting van de speler voor,
vermeld met een D. Het zwarte vierkant vertegenwoordigt de sprite. Aangenomen dat
de sprite een grootte heeft van 2L, de posities van de twee hoeken zijn:
• (x-L.sin(D*pi/180),y-L.cos(D*pi/180))
• (x+L.sin(D*pi/180),y+L.cos(D*pi/180))

L
D

Het volgende script kan worden geplaatst in het draw event van het plant object.

10
{
var ss,cc,tex;
tex = sprite_get_texture(spr_plant,0);
ss = sin(obj_player.direction*pi/180);
cc = cos(obj_player.direction*pi/180);
d3d_draw_wall(x-7*ss,y-7*cc,20,x+7*ss,y+7*cc,0,tex,1,1);
}

We bepalen de rechter sprite textuur, berekend door de 2 waarden hierboven, en


daarna tekenen we de sprite op een 14x20 muur dat op de vloer staat, draaiend naar de
correcte richting. De precieze grootte en positie hangen af van het eigenlikje object
dat moet worden getekend.

Als er meerdere sprites zijn die moeten worden getekend op deze manier, is het
makkelijker om de sin en cos in globale variabelen op te slaan, bijgewerkt door het
speler object, beter dan herberekenen voor elke sprite dat moet worden getekend. Dit
is de manier die we gebruiken in ons spel. In elk end step event van de speler plaatsen
we de sin en cos variabelen camsin en camcos.

Er is een belangrijk detail. Omdat de hoeken van de sprite deels transparant zijn (door
de optie “smooth sprite edges”) moeten we voorzichtig zijn met de volgorde waarin
de objecten zijn getekent. Deels transparante (alpha gemixt) sprites zijn alleen gemixt
met objecten die eerder zijn getekend. Om het gewilde effect te willen moet je de
volgende optie aanzetten alpha blended sprites must be drawn after all other
objects are drawn. Dit kan worden bereikt door de plant objecten (en andere) een
negatieve diepte. Het resultaat van het toevoegen van sommige planten lijkt op dit:

Op deze manier kan je veel andere objecten aan je spel toevoegen die je in de rooms
kunt plaatsen. (Maar doe dit niet vaker, dit kan het spel langzaam maken, en de game
play laten haperen.)

11
Animerende objecten
We kunnen ook animerende objecten op dezelfde manier maken. Er zijn alleen 2
belangrijke dingen. Ten eerste, hebben we een sprite nodig die uit meerdere plaatjes
bestaat, We moeten er zeker van zijn dat de eigenlijke sprite die we gebruiken het
zelfde aantal subplaatjes heeft, anders kunnen we niet de image_index gebruiken
voor de animatie. Ten tweede, in het draw event moeten we de textuur nemen dat in
contact staat met de correcte subimage, gebruikmakend van de image_index. Hier is
een typisch stukje code dat kan worden gebruikt in het draw event van een
exploderend object. Zoals je kunt zien gebruikt het ook alpha instellingen.

{
var ss,cc,tex;
tex = sprite_get_texture(spr_explosion,image_index);
ss = sin(obj_player.direction*pi/180);
cc = cos(obj_player.direction*pi/180);
draw_set_alpha(0.7);
draw_set_color(c_white);
d3d_draw_wall(x-8*ss,y-8*cc,2,x+8*ss,y+8*cc,18,ttt,1,1);
draw_set_alpha(1);
}

Het spel dat we hebben gemaakt kan worden gevonden in het bestand fps3.gmk.

Schieten
Omdat dit een shooter moet worden, voegen we de mogelijkheid van het schieten toe.
In dit hoofdstuk zullen we alleen het schieten op vaten toestaan. In het volgende
hoofdstuk voegen we monsters toe om op te schieten.

Vaten toevoegen
Om wat vaten te kunnen schieten, hebben we een plaatje van een vat nodig. We
hebben er eigenlijk twee nodig, een wanneer hij stil staat en een wanneer hij ontploft.
We lenen wat plaatjes hiervoor van Doom (ook voor latere aspecten in het spel). Deze
kunnen worden gevonden op http://www.cslab.ece.ntua.gr/~phib/doom1.htm. Dit zijn
beide geanimeerde sprites. We hebben ook twee sprites nodig om ze te
vertegenwoordigen in de room. Om een goede animatie te krijgen, is het belangrijk
dat deze sprites hetzelfde aantal subimages krijgt als de echte sprite. We maken een
vat object en een exploderend vat object. Beiden geven we de basis muur objecten als
parent, zodat we er niet doorheen kunnen lopen. Het vat object kunnen we op
verschillende plaatsen in de room plaatsen. Omdat het maar twee subimages heeft,
zetten we de variabele image_speed naar 0.1 in het Create event. In het draw event
tekenen we de goede subimage op muren die naar de spellers toe staan, net als
hierboven.

Wanneer het vat wordt vernietigd (dus in het Destroy event) maken we een
exploderend vat object op dezelfde plaats. Voor dit object zetten we een langzamere
animatie snelheid. Omdat het vat niet meteen explodeert, gebruiken we het alarm
event zo dat het geluid van de explosie na een tijdje wordt afgespeeld. We tekenen het
op dezelfde manier zoals hierboven, op het gebruik van een truckje na. We laten de
alpha waarde langzaam zakken zodat de explosie meer doorzichtig wordt. In het draw
event plaatsen we het volgende:

12
draw_set_alpha(1-0.05*image_index);

Uiteindelijk, in het Animation End event vernietigen we het exploderende vat. Het
spel kan worden gevonden in het bestand fps4.gmk.

Je kunt nog eventueel iets extra’s toevoegen, bijv. dat de te dichtbijzijnde vaten
exploderen en dat de gezondheid van de speler er dan minder op wordt, maar dat
hebben we niet gedaan in dit simpele spel.

Het geweer
Voor het geweer gebruiken we ook een animerende sprite. Deze sprite heeft plaatjes
voor een stilstaand geweer (subplaatje 0) en voor het schieten en herladen van het
geweer. Normaal tekenen we alleen subplaatje 0 (dus we zetten image_speed naar
0) maar wanneer er een schot wordt gelost, moeten we een keer door de hele animatie.
We willen het geweer als een voorgrond over het spel. Om dit te bereiken zijn er een
paar dingen nodig. Ten eerste, we moeten er zeker van zijn dat het overlay object als
laatste wordt getekend. Dit doen we door de depth op -100 te zetten. Ten tweede,
moeten we niet langer gebruik maken van de perspectieve projectie, maar deze
veranderen in de normale orthografische projectie. En uiteindelijk moeten we tijdelijk
hidden surface removal uitzetten. De code voor het Draw event ziet er als volgt uit:

{
d3d_set_projection_ortho(0,0,640,480,0);
d3d_set_hidden(false);
draw_sprite_ext(sprite_shotgun,-1,0,224,2,2,0,c_white,1);
d3d_set_hidden(true);
}

Merk op dat we de schaal van de sprite vermenigvuldigen met 2 in beide richtingen.


De volgende tekenfuncties zetten de projectie terug naar een perspectieve projectie
vanaf de speller, we hoeven het niet meer terug te veranderen.

Een gelijkwaardige techniek kan worden gebruikt voor alle voorgronden die je nodig
hebt. Hieronder gebruiken we het ook voor het laten zien van de gezondheid van de
speler maar je kunt bijv. het gebruiken om instructies te geven, statistieken weergeven
e.d. Je kunt ook de voorgrond transparant maken door de alpha waarde te veranderen.

Het schieten
We hebben nu het object om op te schieten, en het geweer, maar we hebben nog
steeds niet het schieten, Omdat dit een jachtgeweer is, gaan de kogels heel snel. Dus
het is geen oplossing om een kogel object te maken. In plaats daarvan, op het moment
dat de speler op de <Space> toets drukt, moeten we het geraakte object achterhalen,
en als het een vat is, laat het exploderen.

Ten eerste introduceren we een variabele can_shoot dat bepaald of the speler een
kogel kan afvuren. We zetten het op true in het Create event van het overlay object.
Wanneer de speler op de <Space> toets drukt checken we het. Als de speler kan
schieten zetten we het op false, en starten de animatie van het geweer door de image

13
speed te veranderen. In het End Animation event, zetten we can_shoot weer op
true en zet both image_index en image_speed op 0. Dit zorgt ervoor dat de
speler niet continu kan schieten.

Om te bepalen wat de kogel raakt doen we het volgende. W nemen kleine stappen van
de positie van de speler in de richting waar de speler naartoe kijkt. Omdat alle
interessante objecten het basic wall object als parent hebben, checken we alleen of
zo’n instantie op de locatie is. Als dat zo is, checken we of het een vat is. Als dat zo is
vernietigen we het vat. Wanneer we een basic wall object raken, stoppen we de loop
omdat dit het einde van het pad van de kogel is. De code die we in het <Space>
Keyboard event zetten ziet er als volgt uit:

{
var xx, yy, dx, dy, ii;
xx = obj_player.x;
yy = obj_player.y;
dx = 4*cos(obj_player.direction*pi/180);
dy = -4*sin(obj_player.direction*pi/180);
repeat (100)
{
xx += dx;
yy += dy;
ii = instance_position(xx,yy,obj_wall_basic);
if (ii == noone) continue;
if (ii.object_index == obj_barrel)
with (ii) instance_destroy();
break;
}
}

In het bestand fps4.gmk kan je het resultaat vinden. (De code is iets anders omdat
we de camera’s positie en sin en cos op hebben geslagen in variabelen, maar het idee
is precies hetzelfde.)

Vijanden toevoegen
Het is mooi dat we wat vaten kapot kunnen schieten, maar het wordt al gauw saai. We
hebben wat tegenstanders nodig. In deze tutorial voegen we maar één monster toe,
maar als je begrijpt hoe het moet, is het gemakkelijk op er meerdere toe te voegen.
Voor het monster hebben we twee animerende sprites nodig, een voor wanneer hij
dood is en een voor wanneer hij leeft. Beiden namen we van de site hierboven. We
hebben ook een sprite nodig die het monster vertegenwoordigd, en dat wordt gebruikt
voor bijv. collision checken.

We maken twee objecten, een voor het monster dat levend is, en een die dood gaat.
Voor beiden stellen we de animatiesnelheid goed in. Wanneer het monster dood gaat
maken we een doodgaand monster op dezelfde plaats. Dit gebeurt allemaal op exact
dezelfde manier zoals voor het vat. Er is maar een verschil. Aan het eind van het
doodgaande plaatje vernietigen we het dode monster niet maar we laten het gewoon
liggen, door gewoon het laatste subplaatje te laten zien. Aan het eind van het
Animation End event voegen we een Change Sprite actie toe en zetten het correcte
subimage (7) en speed 0.

14
Om monsters te kunnen schieten werken we op dezelfde manier als met het vat. Maar
we moeten een paar veranderingen maken. Ten eerste, we willen niet dat de monsters
het basic wall object als parent hebben. Dit geeft namelijk problemen als we
rondlopen. Dus we maken een nieuw object obj_monster_basic. Dit wordt het
basis object voor alle monsters (ook al hebben we er nog maar een nu). Een tweede
verandering is dat we door planten heen willen kunnen schieten. Dus we maken ook
een basis plant object. (Het basis plant object krijgt de basis muur als parent, omdat
we niet door planten heen willen kunnen lopen.) De code voor het schieten ziet er als
volgt uit:

{
var xx, yy, ii;
xx = global.camx;
yy = global.camy;
repeat (50)
{
xx += 4*global.camcos;
yy -= 4*global.camsin;
ii = instance_position(xx,yy,obj_wall_basic);
if (ii == noone)
{
ii = instance_position(xx,yy,obj_monster_basic);
if (ii == noone) continue;
with (ii) instance_destroy();
break;
}
if object_is_ancestor(ii.object_index,obj_plant_basic) continue;
if (ii.object_index == obj_barrel)
with (ii) instance_destroy();
break;
}
}

Het is grotendeels hetzelfde als hierboven. We checken eerst of er een muur wordt
geraakt, als dit niet zo is, checken we of er een monster is geraakt en, als dit zo is, het
vernietigen. Als een muur was geraakt, kijken we of de instantie een plant is, en als
dat zo is doorgaan (zodat de kogel door de plant vliegt). Uiteindelijk, als we een vat
raken laten we het ontploffen. Je kunt het spel vinden in het bestand fps5.gmk.

Het laatste om te doen is het monster de speler aan te laten vallen. Dit monster zal niet
schieten maar simpel nar de speler lopen. Wanneer het de speler raakt moet de speler
wat gezondheid verliezen. Het gezondheidsmechanisme is ingebouwd in Game Maker
zoals je waarschijnlijk al weet. In het create event van de speler zetten we de health
naar 100. In het overlay object tekenen we ook de gezondheidsbalk, licht transparant,
zoals volgt:

draw_set_alpha(0.4);
draw_healthbar(5,460,100,475,health,c_black,c_red,c_lime,
0,true,true);
draw_set_alpha(1);

En in het No More Health event herstarten we gewoon het spel (een beetje saai,
bedenk maar wat beters).

15
Om de monster de speler aan te laten vallen zetten we de volgende code in het Begin
Step event:

{
if (point_distance(x,y,obj_player.x,obj_player.y) > 200)
{ speed = 0; exit; }
if (collision_line(x,y, obj_player.x,obj_player.y,
obj_wall_basic,false,false))
{ speed = 0; exit; }
if (point_distance(x,y, obj_player.x,obj_player.y) < 12)
{
speed = 0; health -= 2;
if not sound_isplaying(snd_ow) sound_play(snd_ow);
exit;
}
move_towards_point(obj_player.x,obj_player.y,1.4);
}

Dit doet het volgende: Als de afstand van het monster naar de speler te ver is doen we
niks en zetten de snelheid op 0. Als het monster de speler niet kan zien (de lijn tussen
hen in wordt geblokkeerd door een muur) doen we ook niets. Alleen wanneer het
monster de speler kan zien begint het naar de speler toe te bewegen. Als de afstand is
minder dan 12 nemen we aan dat het de speler raakt, dus we stoppen hem en laten de
gezondheid naar beneden gaan, en spelen een geluid af. Anders verplaatsen we naar
de speler positie met een opgegeven snelheid. (Merk op dat de eerste test weggelaten
kan worden, maar de functie om te testen voor een botsing met een lijn vergt veel, dus
we doen het alleen als het monster dichtbij genoeg is.) Om muren te omzeilen, kan het
monster hetzelfde glijdmechanisme gebruiken dat we aan de speler gaven. (We
hebben dit al in het basis monster gestopt, omdat het waarschijnlijk voor alle monsters
hetzelfde is.)

16
Nu moeten we wat monsters plaatsen in onze wereld, en dan zijn we klaar. We hebben
onze eerste first person shooter klaar. Het spel kan worden gevonden in het bestand
fps5.gmk. Eigenlijk moet je om het wat meer interessant te maken een uitgang naar
een nieuw level maken, die wordt bewaakt door monsters, en dan meerde levels
maken. Je kunt ook meerdere monsters en wat andere objecten aan de wereld
toevoegen, zoals verbanddozen. Je kunt ook monster een gezondheid geven, zodat er
meerdere kogels nodig zijn, en betere geweren en kogels. Het is aan jou om het te
bepalen. Sommige basis ideeën worden hieronder behandeld.

Andere dingen
In dit hoofdstuk behandelen we een paar andere dingen die je misschien wilt doen.

Verbergen
Wanneer je door onze 3D wereld heen loopt zie je er maar een deel van. Alleen het
systeem weet niet welk deel je ziet. Dus alle objecten (op z’n minst tegenover je)
worden getekend. Dit kost wat tijd, en zelfs op snelle videokaarten kan dit lijden tot
ene langzame frame-rate. Alle 3D spellen moeten dit overwegen en het vermijden van
onnodige objecten zoveel als mogelijk.

Een manier om dit te doen is kleine levels maken. Je kunt dan gebruik maken van
speciale punten zoals teleporters, om van het ene level naar het andere te gaan. Je kan
het ook op een manier doen die de speler niet opmerkt. Bijvoorbeeld, je maakt een
doorgang. Halverwege verplaats je van het ene level naar het andere. Omdat de speler
de rest van het level niet kan zien, merkt hij het niet op. Je moet de room wel
‘persistent’ maken om dit mogelijk te maken. Je kunt eenzelfde effect bereiken met
het gebruik van deuren en liften.

Een andere manier is verbergen. Hier tekenen we alleen de objecten die dichtbij de
speler zijn. Wanneer we iets tekenen checken we of het object dichtbij genoeg is. Om
dit sneller te maken slaan we de positie van de speler (de camera) op in twee globale
variabelen camx en camy. Vervolgens, in het Draw event van elke muur, plant,
monster etc. voegen we het volgende toe:

if (point_distance(x,y,global.camx,global.camy) > 240) exit;

Dus als de afstand naar de camera te groot is, doen we niets. De waarde van 240 hangt
af van je level design. Je kunt het beste er beter van zijn dat er geen plaatsen zijn waar
je door lange doorgangen kunt kijken. Geef ook de achtergrond een zwarte kleur,
zodat als er een plaats is waar je verder kunt kijken dan wordt dat zwart, wat goed
past bij de zwarte mist. Je kunt het spel vonden in het bestand fps6.gmk. Als je een
trage grafische kaart hebt, zou hij het veel beter moeten doen als de vorige. (Als je een
snelle grafische kaart hebt, wil je misschien de room speed veranderen om het effect
te zien. Op mijn eigen computer deed het spel het met 115 frames per seconde, terwijl
deze het doet met 200 frames per seconde.)

Wanneer levels groter worden en meer objecten krijgt. Wordt het effect alleen maar
groter. Je kunt beter zelf bepalen welk object moet worden getekend. Commerciële
spellen gebruiken portal technieken, om te bepalen waar je bent en welke gebieden
moeten worden getekend. Dit kan de snelheid zelfs nog meer verbeteren.

17
Deuren
Het is een mooi als je deuren plaatst tussen kamers en doorgangen. Dit kan de game
play beter maken. Wat zal er achter de deur zitten? Zal er een monster aan de andere
kant zijn? Ook kan je de speler dwingen om het level op een bepaalde manier te
spelen, door knoppen te plaatsen die bepaalde deuren openen.

Hier voegen we wat simpele deuren toe, deuren die wegglijden als ze geraakt worden
door een kogel. Deuren die omhoog of naar beneden glijden zijn ook gemakkelijk.
Draaiende deuren zijn wat moeilijker omdat je rekening moet houden met de situatie
dat de speler of een monster de deur tegenhoudt.

Om de deur toe te voegen, hebben we eerst een textuur nodig. We voegen het toe als
een achtergrond, net als een muur. W hebben ook ene sprite nodig om het te
vertegenwoordigen in onze room design, en voor het checken op botsingen, net als de
andere muren. We maken twee objecten (omdat dat makkelijker is, maar je kan het
ook met een object doen). Het eerste object is de gesloten deur. Het lijkt precies
hetzelfde als het horizontale muur object, behalve dat het in zijn Destroy event een
nieuw object creëert. We geven het ook de basis muur als parent, om er zeker van te
zijn dat je er niet doorheen kunt lopen.

Het tweede object is de glijdende deur. Het lijkt op de gesloten deur, maar in het
Create event geven we het een horizontale snelheid van 1. Ook zetten we een Alarm
op 32 en in het alarm event zetten we de snelheid op 0 (We kunnen ook het object
vernietigen). Uiteindelijk, in het Step event passen we de x1 en x2 posities aan om er
zeker van te zijn dat de textuur op de goede plaats wordt getekend. Dit maakt de
glijdende deur af.

We moeten er ook zeker van zijn dat de deur kan worden geopend. We beslisten dat
we de deur opent als de speler erop schiet. Op zijn einde, in het <Space> event van
het overlay object (waar het schieten gebeurd) veranderen we de code op het eind als
volgt:

if (ii.object_index == obj_barrel) || (ii.object_index == obj_door)


with (ii) instance_destroy();
break;

Dat is alles. Plaats een paar strategische deuren in het level. Wees er zeker van dat ze
zonder gevaar de muur in kunnen glijden, dus plaats ze niet op het eind van muren.
Het resultaat kan worden gevonden in het bestand fps6.gmk. Natuurlijk, om het
interessanter te maken moeten deuren ook verticaal wegglijden, en misschien een deur
dat links of rechts weg klapt.

Vloeren en plafonds
In het spel dat we tot nu toe hebben, maakten we één grote vloer en plafond. Ook al is
dit gemakkelijk, het is ook aardig saai! Dit kan gemakkelijk worden opgelost. Je kunt
een paar verschillende textures voor verschillende vloeren en plafonds maken, en
daarna wat verschillende objecten die dat deel tekenen. Het zijn erg simpele objecten
als ze geen andere functies uitvoeren (of je moet de vloer en het plafond laten
verplaatsen). Nu moet je ze op de gewenste plaatsen in de room plaatsen, en je bent

18
klaar. Je kunt de delen het beste het beste groot maken, anders moet je er veel te veel
ervan gebruiken.

Je kunt ook de plafondobjecten verschillende hoogten geven. Op deze manier kun je


hoge en lage delen maken in je wereld. Let er op dat de muren ook hoog genoeg zijn.
Verschillende hoogtes aan vloeren geven is ook mogelijk, maar wel gecompliceerder.
Je moet dan ook de hoogte aanpassen voor de speler, de monsters en andere objecten,
gebaseerd op de vloer waar ze op staan. Dit vereist wat extra berekeningen. Ook je
moet beslissen wanneer de speler van het ene vlak op het andere vlak kan lopen.

Als je dit hebt is het ook gemakkelijk om verboden delen in de vloer te maken.
Bijvoorbeeld: je kunt de vloer een deel lava textuur geven (Je kan het zelfs
geanimeerd maken). Om te verkomen dat de speler op de lava te lopen is het
makkelijkst om onzichtbare muur delen eromheen te plaatsen.

We hebben deze opties niet toegevoegd in de oefeningen, maar je moet het nu ook
zelf wel kunnen.

Maak er een spel van


Wat we tot nu toe hebben gemaakt is niet echt een spel. Maar het geeft je de basis om
er een te maken. Je zou meer verschillende monster met verschillend gedrag kunnen
maken, andere texturen gebruiken om je wereld er mooier uit te laten zien,
verschillende wapens, verschillend geluid, en variërende objecten. Ook voorzichtig
level design is nodig. Merk op dat het toevoegen van dit alles misschien te veel
texturen moet opslaan. Maar als je niet alles gebruikt, kun je gemakkelijk de functies
gebruiken om bijvoorbeeld sprites en achtergronden te vervangen door een bestand.
Dit heeft het bijkomende voordeel dat bijvoorbeeld het aantal verschillende muur
objecten dat je nodig hebt beperkt is. In het volgende level kun je gemakkelijk
dezelfde gebruiken maar, omdat je de textures vervangt, zien ze er anders uit
Misschien wil je hulp vragen op ons forum of ander hulp op onze website
http://www.yoyogames.com voor meer ideeën en technieken.

19

You might also like