În ceea ce se află într-un motor de fizică a proiectilului, am abordat teoria și elementele esențiale ale motoarelor de fizică care pot fi folosite pentru a simula efectele proiectilului în jocuri ca Păsările Angry. Acum, vom cimenta aceste cunoștințe cu un exemplu real. În acest tutorial, voi rupe codul pentru un joc bazat pe fizică, pe care l-am scris, pentru a vedea exact cum funcționează.
Pentru cei interesați, codul exemplu furnizat în acest tutorial folosește API-ul Sprite Kit furnizat pentru jocuri native iOS. Acest API folosește un Box2D învelit în obiectivul C ca motor de simulare a fizicii, dar conceptele și aplicarea lor pot fi folosite în orice motor de fizică 2D sau în lume.
Iată exemplul jocului în acțiune:
Conceptul general al jocului are următoarea formă:
Prima noastră utilizare a fizicii va fi crearea unui corp de buclă de margine în jurul cadrului ecranului nostru. Următoarele sunt adăugate la un inițializator sau -(Void) loadLevel
metodă:
// creați un corp de fizică de buclă de margine pentru ecran, practic creând o "limită" de auto.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect: self.frame];
Acest lucru va păstra toate obiectele noastre în cadru, astfel încât gravitatea nu va trage întregul nostru joc de pe ecran!
Să ne uităm la adăugarea unor scripturi pe care le-am permis fizicii la scena noastră. În primul rând, vom examina codul pentru adăugarea a trei tipuri de platforme. Vom folosi platforme pătrate, dreptunghiulare și triunghiulare pentru a lucra cu această simulare.
-(void) createPlatformStructures: (NSArray *) platforme pentru (NSDictionary * platforma in platforme) // Grab Info From Dictionay si pregati variabile int type = [platform [@ "platformType"] intValue]; Poziția CGPoint = CGPointFromString (platforma [@ "platformPosition"]); SKSpriteNode * platSprite; platSprite.zPoziție = 10; // Logic la nivel de populație în funcție de tipul de platformă dacă (type == 1) // Square platSprite = [SKSpriteNode spriteNodeWithImageNamed: @ "SquarePlatform"]; // crea sprite platSprite.position = pozitie; // poziția sprite platSprite.name = @ "Square"; CGRact fizicBodyRect = platSprite.frame; // construiți o variabilă dreptunghiulară pe baza dimensiunii platSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: physicsBodyRect.size]; // construi corpul fizicii platSprite.physicsBody.categoryBitMask = altMask; // atribuiți o mască de categorie corpului fizicii platSprite.physicsBody.contactTestBitMask = objectiveMask; // crea o mască de testare a contactelor pentru apelurile de contact fizice ale corpului platSprite.physicsBody.usesPreciseCollisionDetection = DA; altfel dacă (type == 2) // Rectangle platSprite = [SKSpriteNode spriteNodeWithImageNamed: @ "RectanglePlatform"]; // crea sprite platSprite.position = pozitie; // poziția sprite platSprite.name = @ "dreptunghi"; CGRact fizicBodyRect = platSprite.frame; // construiți o variabilă dreptunghiulară pe baza dimensiunii platSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: physicsBodyRect.size]; // construi corpul fizicii platSprite.physicsBody.categoryBitMask = altMask; // atribuiți o mască de categorie corpului fizicii platSprite.physicsBody.contactTestBitMask = objectiveMask; // crea o mască de testare a contactelor pentru apelurile de contact fizice ale corpului platSprite.physicsBody.usesPreciseCollisionDetection = DA; altfel dacă (type == 3) // Triunghi platSprite = [SKSpriteNode spriteNodeWithImageNamed: @ "TrianglePlatform"]; // crea sprite platSprite.position = pozitie; // poziția sprite platSprite.name = @ "Triunghiul"; // Crearea unei căi mutabile în forma unui triunghi, folosind limitele sprite ca orientare CGMutablePathRef physicsPath = CGPathCreateMutable (); CGPathMoveToPoint (fizicăPath, nil, -platSprite.size.width / 2, -platSprite.size.height / 2); CGPathAddLineToPoint (fizicăPath, nil, platSprite.size.width / 2, -platSprite.size.height / 2); CGPathAddLineToPoint (fizicăPath, zero, 0, platSprite.size.height / 2); CGPathAddLineToPoint (fizicăPath, nil, -platSprite.size.width / 2, -platSprite.size.height / 2); platSprite.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath: physicsPath]; // construi corpul fizicii platSprite.physicsBody.categoryBitMask = altMask; // atribuiți o mască de categorie corpului fizicii platSprite.physicsBody.contactTestBitMask = objectiveMask; // crea o mască de testare a contactelor pentru apelurile de contact fizice ale corpului platSprite.physicsBody.usesPreciseCollisionDetection = DA; CGPathRelease (physicsPath); // eliberați calea acum când am terminat cu ea [self addChild: platSprite];
Vom ajunge la ceea ce înseamnă toate declarațiile de proprietate într-un pic. Pentru moment, concentrați-vă asupra creării fiecărui corp. Pătratul și platformele dreptunghiulare își creează fiecare corpul într-o declarație cu o singură linie, folosind caseta delimitată a spritei ca dimensiune corporală. Corpul platformei triunghiulare necesită traseu; aceasta folosește de asemenea caseta de legare a spritelui, dar calculează un triunghi în colțuri și jumătăți de drum ale cadrului.
Obiectul obiectiv, o stea, este creat în mod similar, dar vom folosi un corp fizic circular.
-(void) addObjective: (NSArray *) obiective pentru (NSDictionary * obiectiv în obiective) // Prindeți informațiile de poziție din dicționarul furnizat din poziția CGPoint plist = CGPointFromString (obiectiv [@ "objectivePosition"]); // creați un sprite pe baza informațiilor din dicționarul de mai sus SKSpriteNode * objSprite = [SKSpriteNode spriteNodeWithImageNamed: @ "star"]; objSprite.position = poziție; objSprite.name = @ "obiectiv"; // Atribuiți corpului fizicii și proprietăților fizice spriteului objSprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius: objSprite.size.width / 2]; objSprite.physicsBody.categoryBitMask = objectiveMask; objSprite.physicsBody.contactTestBitMask = altMask; objSprite.physicsBody.usesPreciseCollisionDetection = DA; objSprite.physicsBody.affectedByGravity = NO; objSprite.physicsBody.allowsRotation = NO; // adăugați copilul la scenă [self addChild: objSprite]; // Creați o acțiune pentru a face obiectivul mai interesant SKAction * turn = [SKAction rotateByAngle: 1 duration: 1]; SKAction * repeat = [repetActionFactor cu repetiție: întoarcere]; [objSprite runAction: repetați];
Tunul în sine nu are nevoie de corpuri atașate, deoarece nu are nevoie de detectarea coliziunilor. Vom folosi pur și simplu ca punct de pornire al proiectilului nostru.
Iată metoda de creare a unui proiectil:
-(void) addProjectile // Creați un sprite bazat pe imaginea noastră, dați-i o poziție și numele proiectilului = [SKSpriteNode spriteNodeWithImageNamed: @ "ball"]; proiectil.position = cannon.position; projectile.zPosition = 20; projectile.name = @ "Projectile"; // Alocați un corp fizic spicului proiectil.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius: projectile.size.width / 2]; // Alocați proprietățile corpului fizicii (toate acestea există și au valori implicite la crearea corpului) projectile.physicsBody.restitution = 0.5; proiectil.physicsBody.density = 5; proiectil.physicsBody.friction = 1; proiectil.physicsBody.dynamic = DA; proiectil.physicsBody.allowsRotation = DA; projectile.physicsBody.categoryBitMask = altMask; proiectil.physicsBody.contactTestBitMask = objectiveMask; proiectil.physicsBody.usesPreciseCollisionDetection = DA; // Adăugați sprite la scenă, cu corpul fizicii atașat [self addChild: projectile];
Aici vedem o declarație mai completă a unor proprietăți atribuite corpului de fizică. Când jucați mai târziu cu proiectul de probă, încercați să modificați restituire
, frecare
, și densitate
din proiectil pentru a vedea ce efecte au asupra gameplay-ului general. (Puteți găsi definiții pentru fiecare proprietate în Ce este într-un motor de proiecție Fizică?)
Următorul pas este să creați codul pentru a trage de fapt această minge la țintă. Pentru aceasta, vom aplica un impuls unui proiectil bazat pe un eveniment tactil:
-(void) atingeBegan: (NSSet *) atinge cu EventEvent: (UIEvent *) eveniment / * Numit atunci când începe o atingere * / pentru (UITouch * atinge în atinge) CGPoint location = [touch locationInNode: self]; NSLog (@ "Tusit x:% f, y:% f", location.x, location.y); // Verificați dacă există deja un proiectil în scenă dacă (! IsThereAProjectile) // Dacă nu, adăugați itThereAProjectile = YES; [self addProjectile]; // Creați un Vector pentru a utiliza ca forță 2D forța de proiecțieForce = CGVectorMake (18, 18); pentru (SKSpriteNode * nod în self.children) if ([node.name esteEqualToString: @ "Projectile"]) // Aplicați un impuls proiectilului, depășind temporar gravitatea și fricțiunea [node.physicsBody applyImpulse: projectileForce];
O altă modificare amuzantă a proiectului ar putea fi jucarea cu valoarea vectorului de impuls. Forțele - și, prin urmare, impulsurile - sunt aplicate folosind vectori, dând magnitudine și direcție la orice valoare de forță.
Acum avem structura și obiectivul nostru și putem trage la ei, dar cum putem vedea dacă am obținut un rezultat?
În primul rând, o pereche rapidă de definiții:
Până acum, motorul de fizică a manipulat contacte și coliziuni pentru noi. Dacă am vrea să facem ceva deosebit atunci când ating două obiecte particulare? Pentru a începe, trebuie să ne spuneți jocului nostru că dorim să ascultați contactul. Vom folosi un delegat și o declarație pentru a realiza acest lucru.
Adăugăm următorul cod în partea de sus a fișierului:
@interface MyScene ()@Sfârșit
... și adăugați această declarație la inițializator:
auto.physicsWorld.contactDelegate = auto
Acest lucru ne permite să folosim metoda stub descrisă mai jos pentru a asculta contactul:
-(void) didBeginContact: (SKPhysicsContact *) contactați // code
Înainte de a putea folosi această metodă, trebuie să discutăm categorii.
Putem desemna categorii la diferite corpuri fizice, ca proprietate, pentru a le sorta în grupuri.
Sprite Kit utilizează în special categorii de biți, ceea ce înseamnă că suntem limitați la 32 de categorii în orice scenă dată. Îmi place să defini categoriile mele folosind o declarație constantă statică, cum ar fi:
// Crearea categoriei de fizică Bit-Mask static const uint32_t objectiveMask = 1 << 0; static const uint32_t otherMask = 1 << 1;
Notați utilizarea de operatori bit-declarați în declarație (o discuție despre operatorii de biți și variabilele de biți este dincolo de scopul acestui tutorial, știu doar că ele sunt în esență doar numere stocate într-o variabilă foarte rapid accesată și că puteți avea 32 maxim).
Atribuiți categoriile utilizând următoarele proprietăți:
platSprite.physicsBody.categoryBitMask = altMask; // atribuiți o mască de categorie corpului fizicii platSprite.physicsBody.contactTestBitMask = objectiveMask; // creați o mască de testare a contactului pentru apelurile telefonice de contact fizice
Făcând același lucru și pentru celelalte variabile din proiect, permiteți-ne să finalizăm acum modul de abordare a ascultătorilor de contact din mai devreme, precum și această discuție!
-(void) didBeginContact: (SKPhysicsContact *) contact // aceasta este metoda ascultătorului de contact, îi oferim sarcini de contact de care ne interesează și apoi efectuăm acțiuni bazate pe coliziune uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB .categoryBitMask); // definește o coliziune între două măști de categorii dacă (collision == (otherMask | objectiveMask)) // manipulează coliziunea din instrucțiunea if mai sus, puteți crea mai multe dacă / else pentru mai multe categorii dacă (! isGameReseting) NSLog (@"Ai castigat!"); isGameReseting = DA; // Creați o mică acțiune / animație atunci când un obiectiv este lovit SKAction * scaleUp = [Scala SKActionTo: 1,25 durată: 0,5]; SKAction * nuanță = [SKAction colorizeWithColor: [UICcolor redColor] colorBlendFactor: 1 durată: 0,5]; SKAction * blowUp = [Grupul de acțiune: @ [scaleUp, tint]]; SKAction * scaleDown = [scala SKActionTo: 0.2 durată: 0.75]; SKAction * fadeOut = [SKAction fadeAlphaTo: 0 durata: 0.75]; SKAction * blowDown = [Grupul SKAC: @ [scaleDown, fadeOut]]; SKAction * remove = [SKAction removeFromParent]; Secțiunea SKAction * = [secvența SKAction: @ [blowUp, blowDown, remove]]; // Aflați care dintre corpurile de contact este un obiectiv prin verificarea numelui său, apoi executați acțiunea pe el dacă ([contact.bodyA.node.name esteEqualToString: @ "obiectiv"]) [contact.bodyA.node runAction :secvenţă]; altfel dacă ([contact.bodyB.node.name esteEqualToString: @ "obiectiv"]) [contact.bodyB.node runAction: secvență]; // după câteva secunde, reporniți nivelul [self performSelector: @selector (gameOver) withObject: null afterDelay: 3.0f];
Sper că te-ai bucurat de acest tutorial! Am învățat totul despre fizica 2D și despre modul în care pot fi aplicate la un joc de proiectile 2D. Sper că acum aveți o mai bună înțelegere a ceea ce puteți face pentru a începe să utilizați fizica în propriile jocuri și cum fizica poate duce la un joc nou și distractiv. Anunță-mă în comentariile de mai jos ce crezi și dacă folosești ceva pe care l-ai învățat astăzi aici pentru a crea proiecte de-ale tale, mi-ar plăcea să aud despre asta.
Am inclus un exemplu de lucru al codului furnizat în acest proiect ca un repo GitHub. Codul sursă complet comentat este disponibil pentru toți utilizatorii.
Unele porțiuni minore ale proiectului de lucru care nu au legătură cu fizica nu au fost discutate în acest tutorial. De exemplu, proiectul este construit pentru a fi extensibil, astfel încât codul permite încărcarea mai multor nivele utilizând un fișier listă de proprietăți pentru a crea diferite aranjamente de platformă și obiective multiple care trebuie lovite. Secțiunea de jocuri și codul de eliminare a obiectelor și a cronometrelor nu au fost, de asemenea, discutate, dar sunt complet comentate și disponibile în fișierele proiectului.
Unele idei pentru funcțiile pe care le puteți adăuga pentru a vă extinde proiectul:
A se distra! Vă mulțumim pentru lectură!