game maker
Gebruikersnaam:
Wachtwoord:
Home Info Forums Help
Welkom, Gast. Alsjeblieft inloggen of registreren.
De activerings e-mail gemist?
+  Forums
|-+  Werken met Game Maker
| |-+  Tutorials en Uitbreidingen (Moderator: Maarten Baert)
| | |-+  [Tut+Exa] Physics programmeren
Pagina's: [1] 2 3 ... 5
« vorige volgende »
Print
Advertenties

Maarten Baert
Forumbeheerder


Offline Offline

Berichten: 4942

Gelieve quote te gebruiken als je PMs beantwoordt.


WWW
« Gepost op: 1 Mei 2010, 23:13:31 »

Physics programmeren

GM-versie: Game Maker 7 en GameMaker 8 maar de code is misschien ook bruikbaar in oudere versies
Registratie vereist: Nee
Niveau: Gevorderd/Expert

Inleiding
Veel Game Maker-gebruikers zullen wel weten dat het niet makkelijk is om realistische physics te programmeren met de beperkte rekenkracht van GML. Er bestaan dan ook een aantal DLL's die bedoeld zijn voor dit soort simulaties (zoals mijn DLL ExtremePhysics), maar het gebruiken van die DLL's heeft natuurlijk ook nadelen:
  • Je moet een geregistreerde versie van Game Maker hebben (hoewel dat niet echt het grootste probleem is).
  • Je kan enkel de dingen simuleren die al ingebouwd zijn in de DLL, want je kan geen aanpassingen maken aan de engine zelf.
  • Voor veel eenvoudigere dingen is een DLL gebruiken gewoon veel ingewikkelder dan nodig.
In deze tutorial zal ik uitleggen hoe je realistische physics kan programmeren. De technieken die ik zal bespreken zijn dezelfde als die ik gebruik in ExtremePhysics, maar om het niet te moeilijk te maken zal ik geen rekening houden met roterende objecten (zoals ExtremePhysics wel doet). Dit zal de gebruikte technieken een heel stuk simpeler (en sneller) maken, en zonder roterende objecten kan je veel dingen nog steeds goed simuleren. Ook wie al weet hoe je dingen kan simuleren kan aan de latere hoofdstukken nog veel hebben.

Kennis
Ik verwacht dat je al beschikt over redelijk wat kennis van wiskunde (je moet o.a. kunnen rekenen met vectoren en best ook weten wat afgeleiden zijn) en ook al wat van fysica (meer specifiek mechanica), anders zal je deze tutorial niet goed kunnen begrijpen. Als je een wiskundige of wetenschappelijke richting volgt zal dit waarschijnlijk geen probleem zijn.

Examples
Ik heb voor deze tutorial een aantal examples gemaakt die de verschillende technieken toepassen. In de examples staat de belangrijkste uitleg ook als commentaar, dus je moet niet altijd terug naar deze tutorial om ze te begrijpen. Om te vermijden dat jullie 10 verschillende examples moeten downloaden heb ik alles in één GMK gezet, in verschillende rooms.
Download de examples

Integratie
De eerste techniek die je moet kennen is integratie. Het is eigenlijk iets heel simpel dat je waarschijnlijk al lang gebruikt. Ik zal maar meteen de bijbehorende code geven in GML, dan zie je meteen wat ik bedoel:
GML:
// integreren van de snelheid:
xspeed += xversnelling;
yspeed += yversnelling;
// integreren van de positie:
x += xspeed;
y += yspeed;
Deze code is gebaseerd op wat de mechanica zegt: de versnelling is de afgeleide van de snelheid en de snelheid is de afgeleide van de positie. Of in symbolen:
Citaat
a = dv/dt
v = dx/dt
Of voor wie integralen kent:
Citaat
v = int(a)dt
x = int(v)dt
Bij dit laatste zie je al heel duidelijk het verband met de bovenstaande code. Dit is ook waarom deze techniek integratie heet: het gaat om het (benaderd) uitrekenen van integralen.

Je ziet dat ik in mijn code hspeed en vspeed niet meer gebruik. Dat doe ik omdat je met hspeed en vspeed te weinig controle hebt over de volgorde waarin alles uitgevoerd wordt.

Je moet goed beseffen dat deze integratietechniek alleen maar een benadering is. Het resultaat is niet 100% correct, maar ligt meestal wel heel dicht in de buurt. Het gaat er eigenlijk vooral om dat de simulatie er realistisch uitziet, en daarvoor is het meestal geen probleem als er wat afwijkingen zijn van de werkelijkheid. Dit soort afwijkingen kan je meestal toch niet zien. Maar soms kunnen ze wel voor problemen zorgen, namelijk wanneer die afwijkingen zich opstapelen en zo iets veroorzaken wat je wel duidelijk ziet. Een manier om die fouten te verkleinen is om een kleinere tijdsstap te nemen (met andere woorden door de room speed te verhogen), maar dit vraagt ook meer rekenkracht.


Example: Botsende bal
Een simpel voorbeeld is een balletje dat steeds maar hoger gaat botsen:
GML:
// step event
yspeed += 0.5;

if y>room_height-30 {
yspeed = -abs(yspeed);
}

x += xspeed;
y += yspeed;
Als je dit test zal je merken dat de bal steeds hoger gaat botsen. Dat komt door integratiefouten. Als je de code in een andere volgorde schrijft, krijg je een balletje dat steeds lager gaat botsen:
GML:
// step event
if y>room_height-30 {
yspeed = -abs(yspeed);
}

yspeed += 0.5;

x += xspeed;
y += yspeed;
Uiteindelijk zakt het balletje zelfs weg in de grond. Dit komt alweer door integratiefouten. In het eerste geval werd er energie toegevoegd aan het systeem (wat in werkelijkheid niet kan), in het tweede geval verdween er energie uit het systeem (wat in werkelijkheid ook niet kan want er werd nergens energie geabsorbeerd). Hoe los je dit nu op? Je kan de tijdsstap proberen te verkleinen, maar dit zal het probleem nooit echt helemaal oplossen, en het kost meer rekentijd. Een veel handigere methode is om een correctie toe te voegen aan de code:
GML:
yspeed += 0.5;

if y>room_height-30 {
yspeed = -max(0,abs(yspeed)-0.5);
}

x += xspeed;
y += yspeed;
Wat ik hier gebruik is iets dat ik een 'snelheidsdrempel' noem (velocity threshold in ExtremePhysics). Bij elke botsing gaat een deeltje van de snelheid verloren om te compenseren voor de integratiefout die de botsing veroorzaakt. Als de snelheid lager is dan de snelheidsdrempel wordt de snelheid gewoon 0 (daarvoor dient die max(0,...)). Hier gebruik ik als snelheidsdrempel 0.5. Als je de snelheidsdrempel dezelfde waarde geeft als de zwaartekracht zal (in dit voorbeeld) er geen energie meer worden toegevoegd of weggenomen, waardoor de bal altijd mooi even hoog blijft botsen. In complexere simulaties is het vaak lastiger om de snelheidsdrempel exact te kiezen, daarom wordt vaak een snelheidsdrempel gebruikt die te hoog is, zodat er energie verloren gaat. Het balletje zal daardoor steeds lager gaan botsen, maar het zal in ieder geval niet onder de grond zakken (vanwege die max(0,...)).

Zoals je bij het voorbeeldje al zag, is de volgorde waarin alles uitgevoerd wordt redelijk belangrijk. Daarom gebruik ik ook geen hspeed en vspeed meer. Meestal doe ik eerst de integratie van de snelheid, en daarna de integratie van de positie. Dit komt overeen met de eerste en derde code uit het voorbeeld. Dit is ook wat ik gebruik in ExtremePhysics, en in de rest van de examples in deze tutorial.

Krachten
Krachten simuleren is iets wat je niet zo vaak ziet in games (behalve gewone zwaartekracht), maar het is heel simpel en het helpt om de volgende stappen beter te begrijpen. Daarom bespreek ik dit eerst.


Example: Planeten
In dit example maak ik een simpele simulatie van de zon en twee planeten. Ik simuleer enkel de zwaartekracht tussen de zon en een planeet, want de zwaartekracht tussen twee planeten is relatief klein vergeleken met die tussen de zon en een planeet, en het maakt het alleen maar ingewikkelder. Eventueel kan je dit redelijk gemakkelijk zelf toevoegen.

De formule voor zwaartekracht is:
Citaat
F = G*m1*m2/sqr(r)
Hierbij is F de kracht, G de zwaartekrachtsconstante, m1 de massa van het eerste voorwerp, m2 de massa van het tweede voorwerp, en r de afstand tussen de twee voorwerpen. G is normaal gezien 6.67e-11 Nm2/kg2, maar die eenheden betekenen in deze simulatie niet veel. Daarom neem ik gewoon zomaar een getal totdat de simulatie er goed uit ziet.

Hiermee kunnen we een kracht berekenen, maar wat we echt nodig hebben is de versnelling. De tweede wet van Newton zegt dat:
F = m*a
Of omgekeerd:
a = F/m;
Met deze formule kunnen we de berekende kracht omzetten in de bijbehorende versnelling.

Zoals ik in het vorige hoofdstuk al zei bestaat de simulatie uit twee stappen: eerst het integreren van de snelheid, dan het integreren van de positie. Om die twee in de juiste volgorde uit te kunnen voeren zet ik de eerste stap in de step event, en de tweede stap in de end step event. De code wordt dan:
Step event:
GML:
var xx,yy,d,f;

// STAP 1: integreer de snelheid

// Bereken de vector die van de zon naar de planeet wijst.
xx = x-obj_zon.x;
yy = y-obj_zon.y;

// Bereken de lengte van die vector.
d = sqrt(xx*xx+yy*yy);

// Normaliseer de vector.
xx /= d;
yy /= d;

// Bereken de kracht (volgens de wet van Newton).
// De constante is natuurlijk wel anders, deze is gewoon
// met wat testen gekozen. Om te vermijden dat de krachten te
// groot (of oneindig groot) worden, kan de afstand niet kleiner
// worden dan 50 (de grootte van de zon).
f = 0.4*massa*obj_zon.massa/sqr(max(50,d));

// Volgens de wet van Newton (een andere wet deze keer) is F = m*a,
// of omgekeerd a = F/m. Die laatste zal je altijd nodig hebben
// bij het uitoefenen van krachten.

// De vector wijst van de zon naar de planeet, maar de kracht die uitgeoefend
// wordt op de planeet is juist in de omgekeerde richting. Vandaar het minteken
// bij de code voor de planeet.
obj_zon.xspeed += xx*f/obj_zon.massa;
obj_zon.yspeed += yy*f/obj_zon.massa;
xspeed -= xx*f/massa;
yspeed -= yy*f/massa;
End step event:
GML:
// STAP 2: integreer de positie

x += xspeed;
y += yspeed;
Als je dit test zal je zien dat de planeten inderdaad in ellipsvormige banen bewegen, zoals ook in de werkelijkheid. Er zijn integratiefouten, maar die zijn zo klein dat ze weinig verschil maken.

Constraints
Nu wordt het ingewikkelder. Alles wat je tot nu toe gezien hebt kan je leren op school, maar wat er nu komt niet meer. Alles wat ik hier uitleg heb ik ofwel zelf uitgezocht ofwel gevonden op het internet.

Het woord 'constraint' is Engels voor 'beperking'. Een constraint is een beperking die je oplegt aan het systeem (het systeem is de verzameling van alle voorwerpen in de simulatie). Een mogelijke constraint is "de afstand tussen voorwerp A en voorwerp B moet altijd gelijk zijn aan 200". Dit soort constraints maakt de simulatie natuurlijk een stuk lastiger. Ik zal daarom tonen hoe je deze constraint omzet in code in het volgende example.


Example: Slinger
In dit example toon ik hoe je een slinger kan simuleren. De slinger bestaat uit twee bollen die met elkaar verbonden zijn door een staaf met lengte 200. De constraint is dus "de afstand tussen de twee bollen moet altijd gelijk zijn aan 200". De eerste bol wordt bestuurd met de muis, de tweede (grotere) bol kan vrij bewegen. Er is ook zwaartekracht.

Omdat de eerste bol wordt bestuurd door de muis is het niet de bedoeling dat we ook die bol vrij simuleren, want dan zou de bol de muis niet kunnen volgen. Om dat op te lossen geef ik de eerste bol een oneindig grote massa. Dit zorgt ervoor dat de bol niet beinvloed wordt door krachten en zich dus gedraagt als een niet-beweegbaar object. Omdat Game Maker het getal 'oneindig' niet kan voorstellen, werk ik in plaats van met de massa met de inverse massa. De inverse massa is gewoon gelijk aan 1/massa. Als de massa oneindig is, is de inverse massa dus 0. Hiermee kunnen we oneindig dus wel voorstellen. Je zal ook merken dat alle formules die we nodig zullen hebben ook werken met de inverse massa, dus dat is geen probleem.

Ik begin met de create event. Die is nog altijd zeer simpel:
GML:
x = mouse_x;
y = mouse_y;

lengte = 200; // de lengte van de staaf

// beginwaarden voor de slinger
slinger_x = x;
slinger_y = y+lengte;
slinger_xspeed = 0;
slinger_yspeed = 0;

// massa's
invmassa = 0;
invmassa_slinger = 1;
Ik gebruik x en y als coordinaten van de eerste bol, en slinger_x en slinger_y als coordinaten van de tweede bol. Ik denk niet dat hier veel uitleg bij nodig is.

Nu de step event. Dit is waar al het ingewikkelde simuleerwerk gebeurt. Het ziet er nogal lang uit, maar dat komt vooral omdat ik alles expliciet uitschrijf en heel veel commentaar geef.

Het eerste dat je moet weten is dat je bij dit soort simulaties nooit (of in ieder geval bijna nooit) rechtstreeks de positie mag aanpassen. Als je dit wel doet, lijkt het voor de engine alsof het object 'teleporteert' of 'verspringt' naar de nieuwe positie, in plaats van dat het er geleidelijk naar toe beweegt door de tijd. Hierdoor zal de simulatie niet goed werken. Om de eerste bol met de muis te kunnen besturen, pas ik daarom gewoon de snelheid aan zodat de bol beweegt in de richting van de muis. Die snelheid deel ik dan nog eens door vier om het wat minder schokkerig te maken, want anders zal de bol zeer schokkerig bewegen als de muis traag beweegt (met een snelheid van minder dan 1 pixel per step). Ik simuleer ook een beetje luchtwrijving omdat de slinger anders nooit stopt met slingeren, en dat is niet erg handig om te testen.

GML:
var xx,yy,d,deltaxspeed,deltayspeed,snelheidsfout,f;

// STAP 1: integreer de snelheid

// Je kan niet zomaar de positie veranderen in die van de muis,
// je moet werken met de snelheid. Ik deel het verschil door 4 om
// het wat minder schokkerig te maken (als de muis traag beweegt
// maakt dit veel verschil).

xspeed = (mouse_x-x)/4;
yspeed = (mouse_y-y)/4;

// Simuleer de zwaartekracht van de slinger.
slinger_yspeed += 0.2;

// Simuleer ook nog een beetje luchtwrijving, anders stopt de slinger nooit met slingeren.
slinger_xspeed *= 1-0.003;
slinger_yspeed *= 1-0.003;

Ik heb nog niet gezegd hoe je constraints moet oplossen. We weten nu wat de beperking is, maar hoe zorgen we nu dat die gerespecteerd wordt? Dit zal moeten gebeuren met krachten, want dat is de enige fysisch correcte manier om het systeem te beinvloeden. De moeilijkheid is nu om te berekenen welke krachten je moet gebruiken. In dit geval is de kracht de drukkracht of trekkracht in de verbindingsstaaf. Die kracht moeten we eerst berekenen en daarna uitoefenen op het voorwerp. Dit gebeurt weer via de tweede wet van Newton: We berekenen welke versnelling nodig is om de beperking in stand te houden, zetten dat om in een kracht, en oefenen die kracht dan uit op de voorwerpen. Nu komen we bij het volgende probleem: De constraint zegt enkel iets over de positie, niet over de snelheid. Met krachten (en dus versnellingen) veranderen we in de eerste plaats de snelheid, niet de positie. Hoe los je daarmee dan een constraint op? Eigenlijk is het simpeler dan het lijkt: Je zet de constraint gewoon om in iets wat wel over krachten gaat. In dit geval is de constraint dat de afstand constant moet blijven. Dit betekent dat de afgeleide (de snelheid dus!) 0 moet zijn, want de afgeleide van een constante is altijd 0. De snelheid waar het over gaat is het verschil in snelheid tussen de twee voorwerpen volgens de verbindingsas. En die snelheid kunnen we berekenen via vectorrekenen.

Het resultaat is dit:
GML:
// STAP 2: los constraints op

// Bereken een vector die van de muis naar de slinger wijst, en normaliseer die.
xx = slinger_x-x;
yy = slinger_y-y;
d = sqrt(xx*xx+yy*yy);
if d>0.001 {
    xx /= d;
    yy /= d;
} else {
    // De twee voorwerpen liggen te dicht bij elkaar om een goede
    // richtingsvector te kunnen berekenen, daarom kies ik gewoon gewoon
    // eender welke richting. Het is niet echt belangrijk dat dit niet juist is.
    xx = 1;
    yy = 0;
}

// Bereken het verschil in snelheid.
deltaxspeed = slinger_xspeed-xspeed;
deltayspeed = slinger_yspeed-yspeed;

// Bereken de snelheidsfout. De 'constraint' voor een verbinding met vaste lengte
// tussen twee voorwerpen is dat de afstand constant is. Dat betekent dat de afgeleide
// van die afstand (de snelheid dus) 0 moet zijn, want anders bewegen de voorwerpen van
// elkaar weg of naar elkaar toe, en dat kan niet als de afstand constant moet blijven.
// Daarom moet je berekenen hoe veel de snelheid afwijkt van de ideale snelheid (0 dus).
snelheidsfout = deltaxspeed*xx+deltayspeed*yy;

// Bereken de kracht die nodig is om deze snelheidsfout te corrigeren, en de snelheid
// te veranderen in de ideale snelheid (0 dus).
// Wat je hier ziet is eigenlijk de oplossing van een wiskundig probleem:
//   Gegeven:
//       deltaxspeed = (slinger_xspeed-xx*f*invmassa_slinger)-(xspeed+xx*f*invmassa_muis)
//       deltayspeed = (slinger_yspeed-yy*f*invmassa_slinger)-(yspeed+yy*f*invmassa_muis)
//       xx*xx+yy*yy = 1
//       deltaxspeed*xx+deltayspeed*yy = 0
//   Gevraagd: f
//   Oplossing: f = (deltaxspeed*xx+deltayspeed*yy)/(invmassa_muis+invmassa_slinger)
// Deze oplossing moet je maar één keer zoeken, daarna kan je het gewoon onthouden
// zodat je het later opnieuw kan gebruiken. Dit is een van de meest voorkomende
// constraints, en voorlopig is dit de enige oplossing die je moet onthouden.
f = snelheidsfout/(invmassa+invmassa_slinger);

// Oefen deze kracht uit op de muis en de slinger.
xspeed += xx*f*invmassa; // dit doet eigenlijk niets want we weten dat invmassa 0 is
yspeed += yy*f*invmassa; // dit doet ook niets
slinger_xspeed -= xx*f*invmassa_slinger;
slinger_yspeed -= yy*f*invmassa_slinger;
Je kan deze code testen, en dan zal je merken dat het inderdaad werkt. Maar als je wat langer test en de slinger snel laat draaien, zal je zien dat de slinger na verloop van tijd langer wordt. Hoe kan dit? Dit is weeral het gevolg van integratiefouten. Net zoals bij het balletje van het eerste voorbeeld moeten we een correctie toevoegen om te compenseren voor die integratiefout, anders kan die zich opstapelen en wordt de slinger steeds langer. Technieken zoals dit worden stabilisatietechnieken genoemd.

De rest van deze tutorial gaat voor een groot deel over verschillende stabilisatietechnieken. De stabilisatietechniek die ik nu zal uitleggen heet baumgarte-stabilisatie. Dit is de techniek die ik ook gebruik in ExtremePhysics. Het is veruit de simpelste techniek, en bovendien vraagt het nauwelijks rekenkracht. Het nadeel is dat het in sommige zeer complexe situaties geen heel goede techniek is. Maar voorlopig is dit meer dan genoeg.

Het idee van baumgarte-stabilisatie is heel simpel: Tel een fractie van de positiefout op bij de snelheidsfout. Dat ziet er zo uit:
GML:
// zet dit net achter het berekenen van de snelheidsfout
snelheidsfout += (d-lengte)*0.2;
De 0.2 is de baumgarte-factor. Deze is gewoon gekozen door goed te testen. Meestal werkt 0.2 redelijk goed. Als de factor te laag is werkt de stabilisatie niet goed, als de factor te hoog is dan zal alles beginnen te trillen. Als je het nu opnieuw test zal je zien dat het werkt zoals het hoort, ook als je de slinger snel laat ronddraaien. De lengte wordt dan misschien wel iets langer dan 200, maar het herstelt zich automatisch.

Collisions
Nu je weet hoe constraints werken zijn collisions helemaal niet meer zo moeilijk om te programmeren. Eigenlijk zijn het gewoon constraints die enkel actief zijn als de twee voorwerpen dicht genoeg bij elkaar zijn. Er is nog maar één ding waar je rekening mee moet houden: Als je als voorwaarde gebruikt dat de twee voorwerpen overlappen, zullen de contacten tussen de voorwerpen voortdurend ingeschakeld en uitgeschakeld worden (door afrondingsfouten). Dit zorgt ervoor dat alles gaat trillen. Om dit te vermijden moet je de constraints al inschakelen voor de voorwerpen elkaar echt raken, bijvoorbeeld wanneer de afstand tussen de twee voorwerpen kleiner is dan 0.01. Dit is wat ik zal doen in het volgende example.


Example: Bal v1
In dit example toon ik hoe je collisions kan simuleren tussen een bal en een muur, en tussen twee ballen.

Vanaf nu wordt de hele simulatie in de step event van één object gezet. Dit is nodig omdat de volgorde waarin alles uitgevoerd wordt zeer belangrijk is. Door with-loops te gebruiken kan alles uitgevoerd worden in de juiste volgorde.

Je weet ondertussen waarschijnlijk al wel ongeveer wat er gaat komen:
GML:
// STAP 1: integreer de snelheid

with obj_bal {
    // zwaartekracht
    yspeed += 0.2;
}
Hiervoor is geen uitleg meer nodig. Het enige belangrijke verschil is dat we nu with-loops gebruiken, maar voor de rest verandert er niets. Nu los ik de constraints op:
GML:
// STAP 2: los constraints op

with obj_bal {
    
    // De 0.01 is de contactdrempel. Dit wordt gebruikt om te
    // vermijden dat contacten voortdurend ingeschakeld en uitgeschakeld
    // worden als twee objecten elkaar raken.
Collisions tussen bal en muur zijn redelijk simpel te programmeren. De methode is dezelfde als vorige keer maar ik heb het wat verkort om plaats te sparen. Volledig uitgeschreven krijg je dit:
GML:
   overlapping = r-x;
    if overlapping>-0.01 {
        snelheidsfout = -xspeed+overlapping*0.2;
        f = snelheidsfout/invmassa;
        // Het verschil is dat de normaalkracht enkel positief kan zijn
        // (de muur kan tegen de bal duwen maar niet trekken).
        if f>0 {
            xspeed += f*invmassa;
        }
    }
Dit kan je verkorten tot dit:
GML:
   if r-x>-0.01 {
        xspeed += max(0,-xspeed+(r-x)*0.2);
    }
of dit:
GML:
   if r-x>-0.01 {
        xspeed = max((r-x)*0.2,xspeed);
    }
Voor alle vier de muren geeft dat dus:
GML:
   // Controleer of de bal de muren raakt.
    if r-x>-0.01 {
        // De voorwaarde is nu redelijk simpel: De x-snelheid moet groter of gelijk zijn aan 0.
        // (of (r-x)*0.2 met baumgarte-stabilisatie)
        xspeed = max((r-x)*0.2,xspeed);
    }
    if r-y>-0.01 {
        // De y-snelheid moet groter of gelijk zijn aan 0.
        yspeed = max((r-y)*0.2,yspeed);
    }
    if r-(room_width-x)>-0.01 {
        // De x-snelheid moet kleiner of gelijk zijn aan 0.
        xspeed = min(-(r-(room_width-x))*0.2,xspeed);
    }
    if r-(room_height-y)>-0.01 {
        // De y-snelheid moet kleiner of gelijk zijn aan 0.
        yspeed = min(-(r-(room_height-y))*0.2,yspeed);
    }
Oke, nu nog de collisions met de andere ballen. Hier komt de code van de slinger goed van pas.
GML:
   // Controleer of de bal andere ballen raakt (met een hoger id om te voorkomen
    // dat collisions twee keer gedetecteerd worden).
    with obj_bal {
        if id>other.id {
            
            // Dit is bijna hetzelfde als wat ik bij de slinger gedaan heb: De snelheid volgens de verbindingslijn moet groter of gelijk zijn aan 0.
            xx = x-other.x;
            yy = y-other.y;
            d = sqrt(xx*xx+yy*yy);
            if d<r+other.r+0.01 { // er is een collision
                if d>0.001 {
                    xx /= d;
                    yy /= d;
                } else {
                    xx = 1;
                    yy = 0;
                }
                deltaxspeed = xspeed-other.xspeed;
                deltayspeed = yspeed-other.yspeed;
                snelheidsfout = -deltaxspeed*xx-deltayspeed*yy+(r+other.r-d)*0.2;
                f = snelheidsfout/(invmassa+other.invmassa);
                // Dezelfde redenering als vorige keer: De normaalkracht kan nooit negatief zijn.
                if f>0 {
                    xspeed += xx*f*invmassa;
                    yspeed += yy*f*invmassa;
                    other.xspeed -= xx*f*other.invmassa;
                    other.yspeed -= yy*f*other.invmassa;
                }
            }
            
        }
    }
    
} // einde van de with-loop
Dat was het voor constraints. Nu nog de positie integreren:
GML:
// STAP 3: integreer de positie

with obj_bal {
    x += xspeed;
    y += yspeed;
}
Test het maar, je zal zien dat het werkt.

Iteraties
Bij alle vorige examples waren er relatief weinig constraints (vaak zelfs maar één). Hierdoor was het redelijk makkelijk om constraints op te lossen. Maar als je met complexere systemen werkt, gebeurt het heel vaak dat je verschillende constraints hebt die elkaar tegenwerken. Als je gewoon alle constraints één voor één oplost zoals we tot nu toe gedaan hebben, heb je redelijk veel kans dat de eerste constraint al lang niet meer geldig is nadat alle andere constraints opgelost zijn, en bijgevolg is de simulatie niet meer erg realistisch. Er zijn twee dingen die je daartegen kan doen:
  • Los alle constraints tegelijk op. Je krijgt dan een groot stelsel van constraints. Dat stelsel is gelukkig wel altijd lineair. Je kan het dus omzetten in een matrix en die matrix oplossen (als je matrixrekenen kent tenminste). Maar dit kost zeer veel rekentijd, en niet elk stelsel is éénduidig oplosbaar. Deze methode wordt gebruikt door programma's zoals Interactive Physics.
  • Gebruik iteraties. Dit betekent dat je simpelweg meerdere keren alle constraints oplost. Op die manier krijg je natuurlijk nooit alle constraints perfect opgelost, maar na elke iteratie ligt de situatie al iets dichter bij de juiste oplossing. Dit is de methode die gebruikt wordt door veel real-time physics engines, en ook door ExtremePhysics. Deze methode zal ik hier ook gebruiken.


Example: Bal v2
Iteraties toevoegen aan je engine is ongelofelijk simpel: Zet gewoon een repeat-loop rond het stuk code dat constraints oplost. Ik ga niet alle code van het vorige example kopiëren voor één lijn extra, als je de code wilt zien moet je maar kijken in het example.

Zoals je merkt kan je nu ook simulaties met veel meer balletjes tegelijk simuleren. Als je dit had geprobeerd bij het vorige example zouden de balletjes gewoon in elkaar gezakt zijn. Hoe complexer de simulatie, hoe meer iteraties je nodig hebt. In ExtremePhysics gebruik ik meestal 20 tot 30 iteraties. Het nadeel is natuurlijk dat dit ook veel keer meer rekenkracht kost. Je ziet in de screenshot van dit example ook al dat de fps onder de 60 zakt, met maar 5 iteraties. Dat komt doordat ik om collisions te vinden een with-loop in een with-loop moet gebruiken. Als er veel objecten zijn zorgt dat natuurlijk voor heel veel collisionberekeningen. Dit kan eventueel nog verbeterd worden door maar één keer collisions te berekenen en ze dan op te slaan in een lijst. Dit is ook wat ExtremePhysics doet (zeker omdat de collisionberekeningen daar nog een stuk ingewikkelder zijn). Nu merk je ook waarom ik ExtremePhysics in C++ heb geschreven Gemoedelijk.

Betekent dit dat echte physics niet mogelijk zijn in Game Maker, omdat het te veel rekenkracht kost? Eigenlijk niet, zolang je het aantal constraints beperkt. Game Maker is duidelijk niet geschikt voor simulaties met honderden objecten die allemaal kunnen botsen, maar als je vooraf weet welke constraints er zijn is het niet zo'n probleem. Dit zal ik dan ook doen in de volgende examples.


Example: Ketting v1
In dit example simuleer ik een ketting van kleine balletjes met op het einde een grote bal. Het eerste balletje kan je besturen met de muis, de rest kan vrij bewegen. Deze keer kunnen de balletjes ook botsen met de muren.

De constraints zijn dezelfde als die voor de slinger en de balletjes die tegen de muren botsen. Het enige verschil is dat nu alles in een array wordt opgeslagen en ik iteraties gebruik.

De create event ziet er zo uit:
GML:
var i;

x = mouse_x;
y = mouse_y;

segmenten = 10;
segmentlengte = 20;

baumgartefactor = 0.2;
iteraties = 30;

knoopmassa = 1;
eindmassa = 5;
zwaartekracht = 0.2;

for(i=0;i<=segmenten;i+=1) {
    knopen_x[i] = x;
    knopen_y[i] = y+i*segmentlengte;
    knopen_xspeed[i] = 0;
    knopen_yspeed[i] = 0;
    knopen_invmassa[i] = 1/knoopmassa;
    knopen_r[i] = 3;
}
knopen_invmassa[0] = 0;
knopen_invmassa[segmenten] = 1/eindmassa;
knopen_r[segmenten] = 10;
Eigenlijk verschilt dit niet veel van die van de slinger, behalve dat een aantal instellingen nu opgeslagen zijn in variables zodat je ze makkelijker kan aanpassen bij het testen.

De step event:
GML:
var i,xx,yy,d,deltaxspeed,deltayspeed,snelheidsfout,f;

x = mouse_x;
y = mouse_y;

knopen_xspeed[0] = (x-knopen_x[0])*0.3;
knopen_yspeed[0] = (y-knopen_y[0])*0.3;

// STAP 1: integreer de snelheid

for(i=0;i<=segmenten;i+=1) {
    knopen_yspeed[i] += zwaartekracht;
}

// STAP 2: los snelheidsconstraints op

// Los de constraints op.
repeat iteraties {
    for(i=0;i<segmenten;i+=1) {
        
        // Dit kan in principe ook vooraf berekend worden.
        xx = knopen_x[i+1]-knopen_x[i];
        yy = knopen_y[i+1]-knopen_y[i];
        d = sqrt(xx*xx+yy*yy);
        if d>0.001 {
            xx /= d;
            yy /= d;
        } else {
            xx = 1;
            yy = 0;
        }
        
        deltaxspeed = knopen_xspeed[i+1]-knopen_xspeed[i];
        deltayspeed = knopen_yspeed[i+1]-knopen_yspeed[i];
        snelheidsfout = deltaxspeed*xx+deltayspeed*yy+(d-segmentlengte)*baumgartefactor;
        f = snelheidsfout/(knopen_invmassa[i]+knopen_invmassa[i+1]);
        knopen_xspeed[i] += xx*f*knopen_invmassa[i];
        knopen_yspeed[i] += yy*f*knopen_invmassa[i];
        knopen_xspeed[i+1] -= xx*f*knopen_invmassa[i+1];
        knopen_yspeed[i+1] -= yy*f*knopen_invmassa[i+1];
        
    }
    for(i=0;i<=segmenten;i+=1) {
        if knopen_r[i]-(knopen_x[i]-0)>-0.01 then knopen_xspeed[i] += max(0,-knopen_xspeed[i]+(knopen_r[i]-(knopen_x[i]-0))*baumgartefactor);
        if knopen_r[i]-(knopen_y[i]-0)>-0.01 then knopen_yspeed[i] += max(0,-knopen_yspeed[i]+(knopen_r[i]-(knopen_y[i]-0))*baumgartefactor);
        if knopen_r[i]-(room_width-knopen_x[i])>-0.01 then knopen_xspeed[i] -= max(0,+knopen_xspeed[i]+(knopen_r[i]-(room_width-knopen_x[i]))*baumgartefactor);
        if knopen_r[i]-(room_height-knopen_y[i])>-0.01 then knopen_yspeed[i] -= max(0,+knopen_yspeed[i]+(knopen_r[i]-(room_height-knopen_y[i]))*baumgartefactor);
    }
}

// STAP 3: integreer positie

for(i=0;i<=segmenten;i+=1) {
    knopen_x[i] += knopen_xspeed[i];
    knopen_y[i] += knopen_yspeed[i];
}
Veel uitleg is niet nodig, want al deze code heb ik gewoon uit de vorige examples gehaald.

Als je dit test zal je zien dat je redelijk veel iteraties nodig hebt voor het redelijk goed werkt (in mijn geval 30). En zelfs met al die iteraties zal de ketting nog veel uitrekken en trillen als je de slinger te snel rond laat draaien. Als je het aantal segmenten verhoogt wordt dit alleen maar erger. Hier kom je bij de belangrijkste beperking van Baumgarte-stabilisatie: Het werkt niet erg goed als je veel constraints hebt die elkaar allemaal tegenwerken, zoals hier duidelijk het geval is. In de volgende examples bespreek ik andere stabilisatiemethodes die de beperkingen van Baumgarte-stabilisatie kunnen opvangen.


Example: Ketting v2
In dit example gebruik ik een andere stabilisatietechniek. De maker van de physics engine Box2D noemde dit Nonlinear Gauss-Seidel. Gauss-Seidel is een iteratief algoritme dat gebruikt kan worden om oplossingen te vinden voor stelsels. Waarom deze methode zo heet is mij niet echt duidelijk (ik zie in ieder geval geen verband met het algoritme), en er is op het internet niet veel over te vinden (zeker niet in de context van physics simulatie). Maar goed, ik weet wel hoe de methode werkt. Het komt erop neer dat je een vierde stap toevoegt aan de simulatie: het oplossen van positieconstraints. Deze stap komt als laatste, na het integreren van de positie. De methode heeft als doel om de afwijkingen in positie meteen na het integreren te corrigeren, door rechtstreeks de positie aan te passen. De code hiervoor is eigenlijk bijna identiek aan die van stap 2, je moet maar een paar aanpassingen maken:
  • De snelheidsfout werkt niet meer met de snelheid maar alleen met de positie. Het komt er dus op neer dat enkel de baumgarte-stabilisatie overblijft. Het is dus eerder een positiefout dan een snelheidsfout.
  • In plaats van een kracht uit te oefenen op het voorwerp wordt het voorwerp meteen verplaatst.
  • De baumgarte-factor kan meestal veel hoger ingesteld worden voor er problemen komen. Meestal werkt 1 heel goed.
  • Er worden opnieuw iteraties gebruikt, maar meestal zijn er minder iteraties nodig dan voor de snelheid.
De code ziet er dan zo uit:
GML:
// STAP 4: los positieconstraints op

repeat positieiteraties {
    for(i=0;i<segmenten;i+=1) {
        
        xx = knopen_x[i+1]-knopen_x[i];
        yy = knopen_y[i+1]-knopen_y[i];
        d = sqrt(xx*xx+yy*yy);
        if d>0.001 {
            xx /= d;
            yy /= d;
        } else {
            xx = 1;
            yy = 0;
        }
        
        snelheidsfout = (d-segmentlengte)*positiebaumgartefactor;
        f = snelheidsfout/(knopen_invmassa[i]+knopen_invmassa[i+1]);
        knopen_x[i] += xx*f*knopen_invmassa[i];
        knopen_y[i] += yy*f*knopen_invmassa[i];
        knopen_x[i+1] -= xx*f*knopen_invmassa[i+1];
        knopen_y[i+1] -= yy*f*knopen_invmassa[i+1];
        
    }
    for(i=0;i<=segmenten;i+=1) {
        if knopen_r[i]-(knopen_x[i]-0)>0 then knopen_x[i] += (knopen_r[i]-(knopen_x[i]-0))*positiebaumgartefactor;
        if knopen_r[i]-(knopen_y[i]-0)>0 then knopen_y[i] += (knopen_r[i]-(knopen_y[i]-0))*positiebaumgartefactor;
        if knopen_r[i]-(room_width-knopen_x[i])>0 then knopen_x[i] -= (knopen_r[i]-(room_width-knopen_x[i]))*positiebaumgartefactor;
        if knopen_r[i]-(room_height-knopen_y[i])>0 then knopen_y[i] -= (knopen_r[i]-(room_height-knopen_y[i]))*positiebaumgartefactor;
    }
}
Als je dit test zal je zien dat de ketting al een stuk realistischer is dan bij het vorige example. Met evenveel iteraties in totaal is het resultaat nu een heel stuk beter.

Deze methode werkt meestal zeer goed, maar is fysisch niet helemaal correct. Vaak gaat er hierbij energie verloren, waardoor voorwerpen sneller 'stilvallen' dan ze in werkelijkheid zouden doen. Dit probleem heb je bij Baumgarte-stabilisatie niet, maar daar worden positieconstraints dan weer minder goed gerespecteerd. Het is dus een compromis tussen de juiste posities en de juiste snelheden.

Krachten opslaan
Dit is de laatste methode die ik ken. Het idee is hier heel simpel: De krachten die nodig waren om een constraint op te lossen tijdens de vorige step komen vaak grotendeels overeen met die die in de huidige step nodig zullen zijn. De oude krachten kunnen daarom gebruikt worden als een goede gok voor de echte oplossing. Op die manier kan er verdergewerkt worden aan de oplossing van de vorige step om die preciezer te maken, in plaats van telkens opnieuw van 0 te beginnen. Hierdoor worden de oplossingen steeds preciezer. Deze methode wordt ook gebruikt in ExtremePhysics.

De moeilijkheid is nu om die krachten op te slaan. Als de constraints vastliggen (zoals bij de ketting) is dit makkelijk (voor de ketting is het gewoon een array), maar als de constraints veranderen, zoals bij collisions, is het veel lastiger. Een redelijk groot deel van ExtremePhysics doet niets anders dan het bewaren van contacten tussen verschillende steps door, omdat het redelijk lastig is om dit efficiënt te doen.


Example: Ketting v3
De aanpassingen aan het example zijn redelijk klein. Er is één extra array nodig om de krachten in op te slaan, en voor de constraints opgelost worden, worden eerst de krachten van de vorige step uitgeoefend:
GML:
// Pas de opgeslagen krachten toe.
for(i=0;i<segmenten;i+=1) {
    
    xx = knopen_x[i+1]-knopen_x[i];
    yy = knopen_y[i+1]-knopen_y[i];
    d = sqrt(xx*xx+yy*yy);
    if d>0.001 {
        xx /= d;
        yy /= d;
    } else {
        xx = 1;
        yy = 0;
    }
    
    knopen_xspeed[i] += xx*knopen_f[i]*knopen_invmassa[i];
    knopen_yspeed[i] += yy*knopen_f[i]*knopen_invmassa[i];
    knopen_xspeed[i+1] -= xx*knopen_f[i]*knopen_invmassa[i+1];
    knopen_yspeed[i+1] -= yy*knopen_f[i]*knopen_invmassa[i+1];
    
}
Bij het oplossen van snelheidsconstraints wordt elke kracht die uitgeoefend wordt ook opgeslagen:
GML:
       knopen_f[i] += f;
Voor de volledige code moet je het example bekijken.

Als je dit test, zal je merken dat er veel minder energie verloren gaat dan in het vorige example. Als je de slinger snel ronddraait zal die niet meer na een paar keer ronddraaien stilvallen, zoals bij het vorige example.

Slot
Dat was het voorlopig. Besef wel dat dit een heel lastig onderwerp is, dus verwacht niet dat je het na één keer lezen al volledig zal begrijpen. De enige manier om het echt te begrijpen is om het zelf toe te passen. Ik hoop dat jullie er wat aan hebben.

« Laatste verandering: 8 Maart 2011, 00:21:23 door Matrebatre »

Naar boven Gelogd

poptup
Gebruiker


Offline Offline

Berichten: 972

kijk niet naar postcount, maar naar de antwoorden.


WWW
« Antwoord #1 Gepost op: 2 Mei 2010, 08:34:16 »

goede (grote) tut. zelf snap ik er niets van. maar later zal ik er veel aan hebben

Naar boven Gelogd

goodboy
Gebruiker


Offline Offline

Berichten: 2766


« Antwoord #2 Gepost op: 2 Mei 2010, 10:14:05 »

heey... ik zocht dit al... ik ga kijken of ik het snap...
edit ]
hoory het dat kettein v1 op de kop kan staan

« Laatste verandering: 2 Mei 2010, 10:24:18 door goodboy »
Naar boven Gelogd

Maarten Baert
Forumbeheerder


Offline Offline

Berichten: 4942

Gelieve quote te gebruiken als je PMs beantwoordt.


WWW
« Antwoord #3 Gepost op: 2 Mei 2010, 11:17:32 »

hoory het dat kettein v1 op de kop kan staan
Wat bedoel je?


Naar boven Gelogd

goodboy
Gebruiker


Offline Offline

Berichten: 2766


« Antwoord #4 Gepost op: 2 Mei 2010, 12:25:38 »

Wat bedoel je?
nou bij de ketting V1 staat hij soms op de kop is dat de bedoeling???

en de kettingen die springen als je ze onder aan je scherm houd of ze gaan heel raar staan..

verde spupper mooie exa/ tut ik denk dat ik het ooit in me game ga gebruiken en de credits voor de Physic aan jou geef

Naar boven Gelogd

Maarten Baert
Forumbeheerder


Offline Offline

Berichten: 4942

Gelieve quote te gebruiken als je PMs beantwoordt.


WWW
« Antwoord #5 Gepost op: 2 Mei 2010, 13:22:34 »

nou bij de ketting V1 staat hij soms op de kop is dat de bedoeling???
Als je snel genoeg met de muis beweegt dan kan het natuurlijk dat de slinger volledig ronddraait. Dat is ook de bedoeling. Of bedoel je iets anders?

en de kettingen die springen als je ze onder aan je scherm houd of ze gaan heel raar staan..
De bollen kunnen niet verder dan de onderkant van het scherm, dat is ook de bedoeling ...


Naar boven Gelogd

goodboy
Gebruiker


Offline Offline

Berichten: 2766


« Antwoord #6 Gepost op: 2 Mei 2010, 16:55:18 »

Als je snel genoeg met de muis beweegt dan kan het natuurlijk dat de slinger volledig ronddraait. Dat is ook de bedoeling. Of bedoel je iets anders?
De bollen kunnen niet verder dan de onderkant van het scherm, dat is ook de bedoeling ...
nee ik draai hem niet rond...

Naar boven Gelogd

Maarten Baert
Forumbeheerder


Offline Offline

Berichten: 4942

Gelieve quote te gebruiken als je PMs beantwoordt.


WWW
« Antwoord #7 Gepost op: 2 Mei 2010, 18:09:40 »

nee ik draai hem niet rond...
Dan begrijp ik echt niet wat je bedoelt. Kan je een screenshot sturen?


Naar boven Gelogd

p v loon
Gebruiker


Offline Offline

Berichten: 384


« Antwoord #8 Gepost op: 3 Mei 2010, 09:23:21 »

prachtige tutorial! ook handig als je alleen maar een klein deel physics hoeft, en geen hele engine. heel mooi!  Tong


Naar boven Gelogd

lucb1e
Gebruiker


Offline Offline

Berichten: 4546


WWW
« Antwoord #9 Gepost op: 4 Mei 2010, 21:00:29 »

Ik snap er maar de helft van (heb ook niet echt de tijd om me erin te verdiepen), maar het is zoals al gezegd een fantastische tut. Erg leerzaam en bruikbaar Gemoedelijk


Naar boven Gelogd

Martin Beentjes
Gebruiker


Offline Offline

Berichten: 2332

Gelieve quotes gebruiken bij PB's.


« Antwoord #10 Gepost op: 26 Juni 2010, 21:32:23 »

Zeer goede tutorial, ik heb hem wel doorgelezen, maar snap er op het moment (wat je al zei) geen bal van. Tong
Ik ben ook nog niet helemaal toe aan Physics, maar als ik het eenmaal is nodig heb kijk ik nog 's hier naar.

Martin

Naar boven Gelogd

dennisvdz
Gebruiker


Offline Offline

Berichten: 1154

Oh, here it goes again!


« Antwoord #11 Gepost op: 26 Juni 2010, 23:18:35 »

Als ik het zo lees (Nog niet alles gelezen, komt nog wel eens Tong), zie ik dat alle rekenbelasting op GM gedrukt word.
Geeft dat niet enorme lag en kan het niet beter extern gebeuren ofzo?
Gewoon een kleine rekenexternsie ofzo wat al het werk doet en resultaten terugzend. Of is dit totaal niet mogelijk ivm. allerlei vage dingen die GM met zich meebrengt? Ja, ik ben nogal nieuw en geinterreseerd in het systeem achter goedwerkende physics.


Naar boven Gelogd

Maarten Baert
Forumbeheerder


Offline Offline

Berichten: 4942

Gelieve quote te gebruiken als je PMs beantwoordt.


WWW
« Antwoord #12 Gepost op: 26 Juni 2010, 23:54:05 »

Als ik het zo lees (Nog niet alles gelezen, komt nog wel eens Tong), zie ik dat alle rekenbelasting op GM gedrukt word.
Geeft dat niet enorme lag en kan het niet beter extern gebeuren ofzo?
Gewoon een kleine rekenexternsie ofzo wat al het werk doet en resultaten terugzend. Of is dit totaal niet mogelijk ivm. allerlei vage dingen die GM met zich meebrengt? Ja, ik ben nogal nieuw en geinterreseerd in het systeem achter goedwerkende physics.
Klopt ongeveer. Daarom heb ik ook ExtremePhysics gemaakt Gemoedelijk.

Veel van deze dingen vragen inderdaad redelijk wat rekentijd van GM. Vooral situaties waar collisions berekend moeten worden, zoals in Bal v2, zijn in GM zeer traag. In gecompileerde talen zoals C++ kan je dat grotendeels oplossen door een 'broad-phase' in te voeren, waarmee je de complexiteit van het berekenen van collisions kan terugbrengen van O(n2) naar O(n3/2) of vaak nog minder. Dat doe ik in ExtremePhysics, en dat is ook de reden waarom het mogelijk is om met duizenden kleine objecten tegelijk te werken. Als ik in ExtremePhysics op dezelfde manier collisions zou berekenen als ik dat hier doe (zoals ik deed in de eerste versies), is het meteen een heel stuk trager (gemakkelijk een factor 6). Maar een broad-phase is jammer genoeg niet haalbaar in GM omdat daarvoor redelijk complexe data structures nodig zijn, en dat kan gewoon niet in GM. In C++ kan je gewoon je eigen data structures programmeren.

Ik heb deze tutorial vooral geschreven voor situaties waarbij rekenkracht geen groot probleem is. Er zijn genoeg games waarin er veel minder collisions nodig zijn (bv. een top-down race spel) en daar is dit al meer dan genoeg. Ook situaties waarbij je werkt met andere soorten constraints (zoals de ketting) zijn best doenbaar in GM, de rekentijd die je daarvoor nodig hebt is niet zo veel. In zo'n geval zal een DLL het niet veel beter doen omdat je nog steeds alle data terug uit de DLL moet halen elke step (in ExtremePhysics betekent dat minimum 3 functies per step voor elk object).

Wat ik dus eigenlijk wilde zeggen: Ik heb het niet getest, maar ik vermoed dat je niet veel verschil zal merken als je de berekeningen in een DLL steekt. Je zou meerdere functies moeten aanroepen om alle resultaten weer terug uit de DLL te halen. Het zou zelfs kunnen dat het trager is, want GM verliest redelijk wat tijd bij het aanroepen van DLL-functies.


Naar boven Gelogd

Martin Beentjes
Gebruiker


Offline Offline

Berichten: 2332

Gelieve quotes gebruiken bij PB's.


« Antwoord #13 Gepost op: 27 Juni 2010, 00:24:04 »

Dus als je physics wilt gaan maken, en dan bedoel ik bijvoorbeeld deze hele tutorial. Kun je dan dus beter het in C++ doen?
En dan een .dll bestand van maken, en die weer bij je spel voegen?

Naar boven Gelogd

Maarten Baert
Forumbeheerder


Offline Offline

Berichten: 4942

Gelieve quote te gebruiken als je PMs beantwoordt.


WWW
« Antwoord #14 Gepost op: 27 Juni 2010, 10:59:24 »

Dus als je physics wilt gaan maken, en dan bedoel ik bijvoorbeeld deze hele tutorial. Kun je dan dus beter het in C++ doen?
En dan een .dll bestand van maken, en die weer bij je spel voegen?
Het snelste is om je hele spel gewoon in C++ te maken, als je dat wilt Gemoedelijk.

Nee, het idee van deze tutorial is dat je simpele physics ook zelf kan programmeren in GML. Enkel als je echt complexe dingen nodig hebt, is een DLL nuttig.


Naar boven Gelogd

Advertenties
« vorige volgende »
Pagina's: [1] 2 3 ... 5
Print


Topic Informatie
0 geregistreerde leden en 1 gast bekijken dit topic.

Ga naar:  

Powered by SMF 1.1.21 | SMF © 2006-2007, Simple Machines
www.game-maker.nl © 2003-2019 Nederlandse Game Maker Community