In dit artikel zullen we het gebruik van fysica onderzoeken om projectieleffecten in games zoals Angry Birds te simuleren. We zullen kijken naar de basis van het gebruik van 2D-fysica in de speelwereld, zoals het maken van lichamen en het toepassen van impulsen en krachten.
Waarom een physics-engine gebruiken? Wat doet het eigenlijk??
Een physics-engine helpt ons om twee zeer belangrijke dingen voor onze game te doen:
Botsingsdetectie: Games zouden niet echt leuk zijn als je personage door de vloer viel voordat je kon springen, of als je een vijand met je voet sloeg, je er gewoon doorheen viel. Botsingsdetectie met behulp van een physics-engine zorgt voor zeer precieze contactgeluiden en maakt het mogelijk om interacties tussen objecten te simuleren met behulp van krachten.
Forceer simulatie:Wat moet er na een aanrijding gebeuren? Spellogica zou kunnen worden genoemd, je zou kunnen stuiteren, het andere spelobject zou kunnen stuiteren, of je zou eenvoudigweg niet verder kunnen bewegen. Dit wordt allemaal achter de schermen afgehandeld met behulp van de berekende krachten van de motor. Maar krachten zijn niet beperkt tot contact; andere krachten, zoals zwaartekracht en impulsen, kunnen optreden, zonder dat objecten elkaar raken. Krachten beïnvloeden in-game acties en de beweging van objecten, personages en zelfs de wereld-ruimte zelf.
We zullen kijken hoe physics-engines werken kort, maar laten we eerst eens kijken wat engines die u mogelijk wilt gebruiken, en waarom u kunt besluiten om ze te gebruiken, op basis van uw specifieke behoeften.
Wanneer je voor het eerst gaat nadenken over het gebruik van natuurkunde in je spel, zul je moeten beslissen hoe je het probleem wilt aanpakken, en wat je spel nodig heeft in termen van simulatie. Je hebt twee opties om natuurkunde te gebruiken:
Er zijn verschillende uitstekende opties voor vooraf gedefinieerde en gebruiksklare physics-engines. Een van de meest populaire keuzes voor 2D-spellen is Box2D; het is een native C ++ geschreven engine, maar heeft wrappers, ports en extensies waarmee het in bijna elk 2D-platform kan worden gebruikt. Een andere populaire keuze is Chipmunk 2D, dat wordt gebruikt in verschillende reguliere game-engines, zoals Cocos2D.
In sommige games is het gebruik van een vooraf gedefinieerde engine niet noodzakelijk de optimale keuze. Het gebruik van een physics engine kan onnodige overhead veroorzaken wanneer de volledige functie niet vereist is. In gevallen zoals eenvoudige platformspelers of spellen van het type met baksteenbreker, waarbij je geen pixelvolmaakte botsingsdetectie of een aantal andere mogelijkheden van een engine nodig hebt, kan het onnodig hulpbronnen gebruiken die elders beter kunnen worden uitgegeven.
Het bouwen van je eigen engine kan je meer flexibiliteit geven over het eindproduct, maar het kan ook de dingen ingewikkelder maken als je te maken hebt met meer dan een paar instanties van personages en objecten.
Voordat we de eigenschappen en details van een fysica-simulatie bespreken, laten we kijken hoe het wordt genoemd in de loop van je gamescène.
De typische gamelus doorloopt het volgende voor elk frame, in de volgende volgorde:
Dit betekent dat het berekenen van de resulterende fysica de laatste taak is die in de lus wordt uitgevoerd voordat het scherm wordt bijgewerkt. Dit is logisch, omdat het punt van de simulatie is om te reageren op wat er in de wereldruimte van het spel is gebeurd.
Merk op dat deze afbeelding laat zien dat fysica wordt gesimuleerd tijdens elk frame van je gamelus. Dit kan resulteren in een grote overhead als je simulatie te groot of gecompliceerd wordt. Om deze reden is het het beste om het spelbeheer en de oproepen naar de simulatie en de luisteraars beperkt te houden.
Het is nu logisch om twee verschillende methoden voor polling van de fysica-simulatie te bespreken: vaste versus frameafhankelijke snelheden. Houd rekening met de (Void) update:
methode consistent binnen de meeste gamelussen. Deze lus wordt eenmaal per frame van de gamescène genoemd. Als uw "simuleer fysica" methode wordt aangeroepen (Void) update:
, de fysica van je spelwereld zal afhangen van je framesnelheid en dit kan leiden tot enkele schokkerige en onrealistische simulaties. In iOS wordt dit effect verzacht door het gebruik van de usesPreciseCollisionDetection
Booleaanse eigenschap, maar hoe zit het met andere engines?
Overweeg het volgende codesegment:
CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval; self.lastUpdateTimeInterval = currentTime; if (timeSinceLast> 1) timeSinceLast = 1.0 / 60.0;
Deze code is ontworpen om problemen met de delta-waarde voor tijd te compenseren. Overweeg een situatie waarin je het spel op je telefoon speelde en werd gebeld: het zou je spel helpen om je delta terug te zetten naar de verwachte 1/60 (voor een 60 fps-spel).
Dit is eigenlijk de eerste stap in een discussie over het ontkoppelen van de fysica-simulatie uit de tijdstap van de (Void) update:
methode. Hoewel een aangepast tijdsinterval zeker zou helpen bij een meer stabiele natuursimulatiereceptie, corrigeert het niet voor alle situaties. Om dit te doen, zouden we eigenlijk moeten verwijderen de fysica-simulatie uit de spelweergave-lus en maak een vaste cyclus waarin deze zou kunnen lopen. Bijvoorbeeld; als je game op 60 fps moet draaien, stel je de fysica in om 60 keer per seconde te simuleren. Deze ontkoppeling verwijdert alle problemen met rendering-problemen die veranderlijke feedback in uw physics-simulatie veroorzaken.
Kortom, wees bewust in de uitvoering van de natuurkunde. Als u merkt dat u een engine gebruikt in een omgeving waar u systeembronnen belast, overweeg dan een simulatie van een gefaseerde natuursimulatie om rechtvaardigheid en betrouwbaarheid te behouden.
EEN sprite is een afbeelding die wordt weergegeven op het scherm van je spel. Een sprite heeft standaard geen eigenschappen binnen een fysische simulatie. Je kunt een deel van het gedrag van een fysieke wereld 'faken' door eigenschappen van een sprite te gebruiken, zoals een selectiekader en een kruispuntoproep, maar dan moet je alle resulterende logica zelf schrijven. Zou het niet beter zijn als de game dit allemaal aankan voor ons?
In deze fragmenten maken we een sprite:
SKSpriteNode * sprite = [SKSpriteNode spriteNodeWithImageNamed: @ "image"]; sprite.position = locatie; [self addChild: sprite];
... en bel een botsing tussen twee sprites:
-(ongeldige) update: (CFTimeInterval) currentTime / * Bel vóór elk frame wordt weergegeven * / if (CGRectIntersectsRect (sprite1.frame, sprite2.frame)) // do something
Fysica lichamen zijn "eenvoudige" vormen die de ruwe grootte en vorm van uw sprite bepalen, of misschien een actief gebied van uw sprite definiëren. Stel je de volgende situatie voor:
Een fysiek lichaam is niet vooraf gedefinieerd door de afbeelding van je sprite en is meestal onzichtbaar in het spel. Je maakt de vorm dynamisch, vaak door een methode te gebruiken om de vorm te tekenen die het lichaam vormt, of door een programma te gebruiken om je lichaam te tekenen en te definiëren. Vervolgens bevestig je het lichaam aan de sprite en krijg je toegang tot de gesimuleerde effecten en eigenschappen die aan dat lichaam zijn toegewezen.
Je kunt meerdere physics-lichamen aan één enkele sprite koppelen. Neem bijvoorbeeld een sprite van een held die een zwaard draagt. Het zou logisch zijn om één lichaam te maken voor het heldenkarakter, en een ander voor het zwaard dat hij draagt. Hiermee kunt u gamlogica maken op basis van botsingen tussen verschillende instanties.
In pseudocode zou de logica er ongeveer zo uitzien:
// physics logic - (void) physicsCollisionDidOccur switch (collision bitmask) case (Player || Sword): // doe niets; breken; case (Player || Enemy): // ouch !!; breken; case (Sword || Enemy): // do damage !!; breken; standaard: // niets doen; breken;
Beschouw de situatie van een van een ruimtegame, waar je een heldenschip en een vijandelijk schip hebt:
Je zou waarschijnlijk het physics-lichaam van de speler een beetje kleiner willen maken dan het basissprite-beeld om twee redenen:
Verbeterde visuele botsing: Wanneer een speler in botsing komt met een object in je spel, door dit kleinere physics-lichaam te maken, overlappen de sprite-afbeeldingen tijdelijk op het contactpunt, dat er visueel goed uitziet. (Verderop dit punt: houd bij het tekenen van z-waarden het personage van je speler vooraan in de scènehiërarchie.)
Door de gebruiker waargenomen eerlijkheid: Om te proberen je spel "eerlijk" te laten voelen voor de speler, moet je het botte lichaam beperkt houden tot het grootste deel van het object en weg van externe uitsteeksels zoals de achterste vin van de afbeelding hierboven. Op deze manier zijn er geen "goedkope treffers" om de spelers van je spel te ergeren. Omgekeerd wil je meestal dat het lichaam van de vijandelijke fysica op zijn minst de grootte van het basisbeeld heeft; als we onze ruimteheld een laser geven om zijn vijand neer te schieten, maakt een iets te groot vijandelijk lichaam het voor onze speler redelijker om een hit te krijgen. Overweeg ook dezelfde aanpak voor tegels in een platformgame of puzzelspel waarbij je speler van platform naar platform moet springen. Spelers zijn gewend aan een beetje "gratie" in dit soort spellen; het uitbreiden van het fysieke lichaam een beetje zal helpen om je spel redelijk "redelijk" te houden.
Er zijn twee hoofdtypen fysica-instanties:
Een op de rand gebaseerd lichaam is een statische, onbeweeglijke lijn die een grens vormt voor andere lichamen om tegenaan te botsen. Het heeft een negatieve ruimte daarbinnen dat geen effect heeft op een lichaam. Een geweldige toepassing hiervan zou zijn om een grens rondom je scherm te maken die alle lichamen erin bevat.
Een volume-gebaseerde body heeft volume en massa en kan dynamisch of statisch zijn. Omdat deze lichamen massa hebben, stuiteren voorwerpen ervan en kunnen ze worden beïnvloed door krachtcontacten. Op volumes gebaseerde bodies kunnen uit vier hoofdvormen bestaan:
Er zijn enkele beperkingen aan het gebruik van instanties in uw typische 2D-physics-engine. Dit zijn de twee belangrijkste beperkingen:
Als een vorm is convex, het betekent dat geen binnenhoek kleiner is dan 180 graden.
Voor de duidelijkheid, het kan mogelijk zijn om natuurkundige simulaties uit te voeren op concave vormen, maar de verwerkingskosten zijn zo hoog dat het simpelweg niet realistisch is voor 2D, vooral wanneer het op een handheld of minder krachtig apparaat wordt uitgevoerd. Concaaf-net zoals vormen kunnen worden geconstrueerd door twee convexe vormen aan elkaar te koppelen met iets dat a heet Statische verbinding. Verbindingen zijn een andere geweldige functie beschikbaar met 2D-engines, maar vallen buiten het bereik van deze discussie.
Wanneer een bal een muur raakt, zal er in de 'echte' wereld zoiets als dit gebeuren:
Je karakter is sprite kan dit type transformatie ondergaan, maar het lichaam van de natuurkunde kan niet. Je kunt bepaalde eigenschappen van het lichaam regelen om zijn 'bounciness' te beïnvloeden, maar het kan eigenlijk geen veranderlijke vorm hebben. Dit staat bekend als a Stijf lichaam, wat betekent dat het lichaam zelf niet kan worden vervormd of geplet.
Laten we even kijken naar enkele van de meest bruikbare eigenschappen die beschikbaar zijn op een typisch physics-lichaam:
De meeste engines hebben meer eigenschappen beschikbaar dan dit, maar voor de doeleinden van deze discussie zijn deze voldoende om aan de slag te gaan.
In een gesimuleerde fysicawereld worden lichamen verplaatst door de toepassing van krachten en impulsen.
krachten: Algemene krachten tasten lichamen geleidelijker aan dan impulsen. Ze zijn een constante kracht die wordt uitgeoefend over een tijdseenheid (zoals zwaartekracht of een motor).
Impulsen (Impulse Forces): Impulsen zijn onmiddellijk aangebrachte aanpassingen aan het momentum van een lichaam. Impulsen worden meestal toegepast op een simulatie op basis van gebruikersinvoer.
Nu je de theorie begrijpt, is de beste manier om je kennis van projectiel-fysica-engines te verbeteren, door er zelf een te bouwen. Vervolgens zal ik de code voor een eenvoudige, op fysica gebaseerde game die ik heb geschreven, opsplitsen, zodat je precies kunt zien hoe het werkt!