Prosjektfysikkmotorer Bygg en spillverden

I Hva er det i en Projectile Physics Engine, dekket vi teorien og essensielle elementene i fysikkmotorer som kan brukes til å simulere prosjektil effekter i spill som Angry Birds. Nå sementer vi den kunnskapen med et reelt eksempel. I denne opplæringen bryter jeg koden for et simpelt fysikkbasert spill som jeg har skrevet, slik at du kan se nøyaktig hvordan det fungerer.

For de som er interessert, bruker eksempelkoden som tilbys gjennom denne opplæringen, Sprite Kit API som er gitt for innfødte iOS-spill. Denne API bruker en Objective-C innpakket Box2D som fysikk simuleringsmotor, men konseptene og deres søknad kan brukes i en hvilken som helst 2D fysikk motor eller verden.

Å bygge en spillverden

Her er prøvespillet i aksjon:

Det overordnede konseptet av spillet tar følgende form:

  1. En struktur av plattformer med fysikk kropper legges til nivået, bygge et tårn.
  2. Et eller flere objektive gjenstander er plassert i tårnet, hver med en fysikkdel som er tildelt den.
  3. En avfyringsmekanisme skyter et prosjektillegeme med en momentan impuls; Når prosjektilens kropp kolliderer med plattformene, tar simuleringen over og beregner resultatene for oss.
  4. Hvis et projektil eller en plattform berører målet, blir det vekk fra scenen, og spilleren vinner! Denne kollisjonen oppdages ved hjelp av fysikkorganene, slik at simuleringen opprettholder sin realisme ved kollisjonspunktet.

Vår første bruk av fysikk vil være å skape en kantsløyfe rundt vår skjerms ramme. Følgende legges til i en initialiserer eller -(Void) loadLevel metode:

// lage en kanten-loop fysikk kropp for skjermen, i utgangspunktet lage en "grenser" self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect: self.frame];

Dette vil holde alle våre gjenstander innenfor rammen, slik at tyngdekraften ikke trekker hele vårt spill av skjermen!

Legge til objekter

La oss se på å legge til noen fysikkaktiverte sprites til vår scene. Først vil vi se på koden for å legge til tre typer plattformer. Vi vil bruke firkantede, rektangulære og trekantede plattformer for å jobbe med i denne simuleringen.

-(void) createPlatformStructures: (NSArray *) plattformer for (NSDictionary * plattform i plattformer) // Grab Info From Dictionay og utarbeide variabler int type = [plattform [@ "platformType"] intValue]; CGPoint posisjon = CGPointFromString (plattform [@ "platformPosition"]); SKSpriteNode * platSprite; platSprite.zPosition = 10; // Logic å fylle ut nivå basert på plattform typen hvis (type == 1) // Square platSprite = [SKSpriteNode spriteNodeWithImageNamed: @ "SquarePlatform"]; // lage sprite platSprite.position = posisjon; // posisjon sprite platSprite.name = @ "Square"; CGRect physicsBodyRect = platSprite.frame; // bygge et rektangel variabel basert på størrelse platSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: physicsBodyRect.size]; // bygge fysikk kropp platSprite.physicsBody.categoryBitMask = otherMask; // tilordne en kategori maske til fysikk kroppen platSprite.physicsBody.contactTestBitMask = objectiveMask; // lage en kontakt test maske for fysikk kroppskontakt callbacks platSprite.physicsBody.usesPreciseCollisionDetection = YES;  annet hvis (type == 2) // rektangel platSprite = [SKSpriteNode spriteNodeWithImageNamed: @ "RectanglePlatform"]; // lage sprite platSprite.position = posisjon; // posisjon sprite platSprite.name = @ "Rectangle"; CGRect physicsBodyRect = platSprite.frame; // bygge et rektangel variabel basert på størrelse platSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: physicsBodyRect.size]; // bygge fysikk kropp platSprite.physicsBody.categoryBitMask = otherMask; // tilordne en kategori maske til fysikk kroppen platSprite.physicsBody.contactTestBitMask = objectiveMask; // lage en kontakt test maske for fysikk kroppskontakt callbacks platSprite.physicsBody.usesPreciseCollisionDetection = YES;  annet hvis (type == 3) // Triangle platSprite = [SKSpriteNode spriteNodeWithImageNamed: @ "TrianglePlatform"]; // lage sprite platSprite.position = posisjon; // posisjon sprite platSprite.name = @ "Triangle"; // Lag en mutable bane i form av en trekant, ved hjelp av sprite-grensene som en retningslinje CGMutablePathRef physicsPath = CGPathCreateMutable (); CGPathMoveToPoint (fysikkPath, null, -platSprite.size.width / 2, -platSprite.size.height / 2); CGPathAddLineToPoint (fysikkPath, null, platSprite.size.width / 2, -platSprite.size.height / 2); CGPathAddLineToPoint (fysikkPath, null, 0, platSprite.size.height / 2); CGPathAddLineToPoint (fysikkPath, null, -platSprite.size.width / 2, -platSprite.size.height / 2); platSprite.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath: physicsPath]; // bygge fysikk kropp platSprite.physicsBody.categoryBitMask = otherMask; // tilordne en kategori maske til fysikk kroppen platSprite.physicsBody.contactTestBitMask = objectiveMask; // lage en kontakt test maske for fysikk kroppskontakt callbacks platSprite.physicsBody.usesPreciseCollisionDetection = YES; CGPathRelease (physicsPath); // slipp banen nå som vi er ferdige med det [selvtillitChild: platSprite];  

Vi får vite hva alle eiendomserklæringene betyr litt. For nå, fokus på opprettelsen av hver kropp. Torget og de rektangulære plattformene lager hver sin kropp i en enlinjedeklarasjon, og bruker spriteens avgrensingsboks som kroppsstørrelse. Trekantplattformens kropp krever å tegne en bane; dette bruker også spriteens avgrensningsboks, men beregner en trekant ved hjørnene og halvveispunktene av rammen.

Det objektive objektet, en stjerne, er også opprettet, men vi vil bruke en sirkulær fysikk kropp.

-(void) addObjectives: (NSArray *) mål for (NSDictionary * mål i mål) // Ta tak i posisjonsinformasjonen fra ordlisten gitt fra plist CGPoint posisjon = CGPointFromString (objektiv [@ "objectivePosition"]); // lage en sprite basert på info fra ordboken over SKSpriteNode * objSprite = [SKSpriteNode spriteNodeWithImageNamed: @ "star"]; objSprite.position = posisjon; objSprite.name = @ "objective"; // Tilordne en fysikk kropp og fysiske egenskaper til sprite objSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius: objSprite.size.width / 2]; objSprite.physicsBody.categoryBitMask = objectiveMask; objSprite.physicsBody.contactTestBitMask = otherMask; objSprite.physicsBody.usesPreciseCollisionDetection = JA; objSprite.physicsBody.affectedByGravity = NEI; objSprite.physicsBody.allowsRotation = NO; // legg barnet til scenen [self addChild: objSprite]; // Opprett en handling for å gjøre målet mer interessant SKAction * turn = [SKAction rotateByAngle: 1 varighet: 1]; SKAction * repeat = [SKAction repeatActionForever: sving]; [objSprite runAction: repeat]; 

Klar, sett, brann!

Kanonen i seg selv trenger ikke noen kropper festet, da det ikke har behov for kollisjonsdeteksjon. Vi vil bare bruke det som utgangspunkt for vårt prosjektil. 

Her er metoden for å lage et prosjektil:

-(void) addProjectile // Lag et sprite basert på bildet vårt, gi det en posisjon og navn prosjektil = [SKSpriteNode spriteNodeWithImageNamed: @ "ball"]; projectile.position = cannon.position; projectile.zPosition = 20; projectile.name = @ "Projectile"; // Tilordne en fysikk kropp til sprite projectile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius: projectile.size.width / 2]; // Tilordne egenskaper til fysikklegemet (disse er alle og har standardverdier ved opprettelsen av kroppen) projectile.physicsBody.restitution = 0.5; projectile.physicsBody.density = 5; projectile.physicsBody.friction = 1; projectile.physicsBody.dynamic = YES; projectile.physicsBody.allowsRotation = JA; projectile.physicsBody.categoryBitMask = otherMask; projectile.physicsBody.contactTestBitMask = objectiveMask; projectile.physicsBody.usesPreciseCollisionDetection = YES; // Legg sprite til scenen, med fysikk kroppen festet [selvtillitChild: prosjektil]; 

Her ser vi en mer fullstendig erklæring om noen egenskaper som kan tildeles fysikk. Når du spiller med prøveprosjektet senere, kan du prøve å endre restitusjon, friksjon, og tetthet av prosjektilet for å se hvilke effekter de har på den generelle spillingen. (Du finner definisjoner for hver eiendom i Hva er i en projektil fysikkmotor?)

Det neste trinnet er å lage koden for å faktisk skyte denne ballen på målet. For dette vil vi bruke en impuls til et prosjektil basert på en berøringshendelse:

-(void) berørerBegan: (NSSet *) berører medEvent: (UIEvent *) hendelse / * Kalt når en berøring begynner * / for (UITouch * berøre) CGPoint location = [touch locationInNode: self]; NSLog (@ "Touched x:% f, y:% f", location.x, location.y); // Sjekk om det allerede er et prosjektil i scenen hvis (! ErThereAProjectile) // Hvis ikke, legg det til, erThereAProjectile = YES; [self addProjectile]; // Lag en vektor som skal brukes som en 2D-kraftverdi projectileForce = CGVectorMake (18, 18); for (SKSpriteNode * node i self.children) if ([node.name isEqualToString: @ "Projectile"]) // Bruk en impuls til prosjektilet, midlertidig overvinne tyngdekraften og friksjonen [node.physicsBody applyImpulse: projectileForce]; 

En annen morsom endring i prosjektet kan være å spille med impulsvektorverdien. Krafter - og dermed impulser - blir brukt ved hjelp av vektorer, og gir storhet og retning til enhver kraftverdi.

Nå har vi vår struktur og vårt mål, og vi kan skyte på dem, men hvordan ser vi om vi scoret en hit?

Kollisjonskurs

Først et raskt par definisjoner:

  • EN ta kontakt med brukes når to kropper berører.
  • EN kollisjon brukes til å hindre at to kropper krysser.

Kontaktlytter

Hittil har fysikkmotoren vært å håndtere kontakter og kollisjoner for oss. Hva om vi ønsket å gjøre noe spesielt når to spesielle objekter berører? Til å begynne med må vi fortelle spillet vårt som vi vil lytte til kontakten. Vi vil bruke en delegat og en erklæring for å oppnå dette. 

Vi legger til følgende kode øverst i filen:

@interface MyScene () @slutt

... og legg til denne utsagnet til initialisatoren:

self.physicsWorld.contactDelegate = self

Dette tillater oss å bruke metoden stub vist nedenfor for å lytte etter kontakt:

-(void) didBeginContact: (SKPhysicsContact *) kontakt // kode

Før vi kan bruke denne metoden, skjønt, må vi diskutere kategorier.

kategorier

Vi kan tildele kategorier til våre ulike fysikkorganer, som en eiendom, for å sortere dem i grupper. 

Sprite Kit bruker spesielt bitvise kategorier, noe som betyr at vi er begrenset til 32 kategorier i en hvilken som helst scene. Jeg liker å definere mine kategorier ved hjelp av en statisk konstant erklæring som denne:

// Opprett fysikk Kategori Bit-Mask er statisk const uint32_t objectiveMask = 1 << 0; static const uint32_t otherMask = 1 << 1;

Legg merke til bruken av bitvise operatører i erklæringen (en diskusjon om bitvise operatører og bitvariabler ligger utenfor omfanget av denne opplæringen, bare vet at de egentlig bare er tall lagret i en veldig raskt tilgjengelig variabel, og at du kan ha 32 maksimum).

Vi tilordner kategoriene ved å bruke følgende egenskaper:

platSprite.physicsBody.categoryBitMask = otherMask; // tilordne en kategori maske til fysikk kroppen platSprite.physicsBody.contactTestBitMask = objectiveMask; // opprette en kontakt test maske for fysikk kroppen kontakt tilbakeringinger

Gjør det samme for de andre variablene i prosjektet, la oss nå fullføre vår kontaktlytter metode stub fra tidligere, og også denne diskusjonen!

-(void) didBeginContact: (SKPhysicsContact *) kontakt // dette er kontaktlyttermetoden, vi gir det kontaktoppdragene vi bryr oss om, og utfører handlinger basert på kollisjonen uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB .categoryBitMask); // definere en kollisjon mellom to kategorimaskere hvis (kollisjon == (andreMask | objektiveMask)) // håndtere kollisjonen fra ovennevnte hvis setningen, kan du opprette flere hvis / annet setninger for flere kategorier hvis (! isGameReseting) NSLog (@"Du vinner!"); isGameReseting = YES; // Sett opp en liten handling / animasjon for når et mål er truffet SKAction * scaleUp = [SKAction scaleTo: 1,25 duration: 0.5]; SKAction * tint = [SKAction colorizeWithColor: [UIColor redColor] colorBlendFactor: 1 varighet: 0,5]; SKAction * blowUp = [SKAction group: @ [scaleUp, tint]]; SKAction * scaleDown = [SKAction scaleTo: 0.2 varighet: 0.75]; SKAction * fadeOut = [SKAction fadeAlphaTo: 0 varighet: 0,75]; SKAction * blowDown = [SKAction group: @ [scaleDown, fadeOut]]; SKAction * remove = [SKAction removeFromParent]; SKAction * sekvens = [SKAction sekvens: @ [blowUp, blowDown, remove]]; // Finn ut hvilken kontaktgruppe som er et mål ved å sjekke navnet sitt, og kjør deretter handlingen på den hvis ([contact.bodyA.node.name isEqualToString: @ "objective"]) [contact.bodyA.node runAction :sekvens];  annet hvis ([contact.bodyB.node.name isEqualToString: @ "objective"]) [contact.bodyB.node runAction: sekvens];  / / Etter noen få sekunder, start nivået på nytt [selv utfør Selector: @selector (gameOver) withObject: nil afterDelay: 3.0f]; 

Konklusjon

Jeg håper at du har hatt denne opplæringen! Vi har lært alt om 2D fysikk og hvordan de kan brukes på et 2D prosjektilspill. Jeg håper du nå har en bedre forståelse av hva du kan gjøre for å begynne å bruke fysikk i dine egne spill, og hvordan fysikk kan føre til noe nytt og morsomt gameplay. Gi meg beskjed i kommentarene nedenfor, hva du synes, og hvis du bruker noe du har lært her i dag for å lage egne prosjekter, vil jeg gjerne høre om det. 

En kommentar til eksempelprosjektet

Jeg har tatt med et fungerende eksempel på koden som er oppgitt i dette prosjektet som en GitHub repo. Den fullt kommenterte kildekoden er der for alle å bruke. 

Noen mindre deler av arbeidsprosjektet, som ikke var relatert til fysikk, ble ikke diskutert i denne opplæringen. For eksempel er prosjektet bygget for å være utvidbart, slik at koden tillater lasting av flere nivåer ved hjelp av en eiendomslistefil for å skape forskjellige plattformarrangementer og flere mål å treffe. Spillet over seksjonen, og koden for å fjerne objekter og tidtakere, ble heller ikke diskutert, men er fullt kommentert og tilgjengelig i prosjektfilene.

Noen ideer til funksjoner du kan legge til for å utvide på prosjektet:

  1. Ulike typer ammunisjon
  2. Bevegelig og skalerbar skuddretning og -styrke
  3. Flere typer og størrelser på plattformer
  4. Terreng
  5. Animasjon og lydeffekter 

Ha det gøy! Takk for at du leste!