În această serie de tutoriale, vă vom arăta cum să faceți un shooter twin-stick inspirat de Geometry Wars, cu grafică neon, efecte particulare nebune și muzică minunată, pentru iOS folosind C ++ și OpenGL ES 2.0. În această parte, vom adăuga controalele jocului virtuale și dușmanii "gaura neagră".
În seria de până acum am creat gameplay-ul de bază pentru shooter-ul nostru de gemeni, Shape Blaster. Apoi, vom adăuga două "gamepad-uri virtuale" pe ecran pentru a controla nava cu.
Intrarea este o necesitate pentru orice joc video, iar iOS ne oferă o provocare interesantă și ambiguă cu intrarea multi-touch. Vă voi arăta o abordare bazată pe conceptul de jocuri virtuale virtuale, unde vom simula gamepad-urile hardware folosind doar o atingere și o logică complexă pentru a afla lucrurile. După adăugarea gamepad-urilor virtuale pentru intrarea multi-touch, vom adăuga, de asemenea, găuri negre la joc.
Pe ecran, comenzile bazate pe atingere reprezintă principalul mijloc de intrare pentru majoritatea aplicațiilor și jocurilor bazate pe iPhone și iPad. De fapt, iOS permite utilizarea unei interfețe multi-touch, adică citirea mai multor puncte de atingere în același timp. Frumusețea interfețelor bazate pe atingere este că puteți defini interfața ca fiind orice doriți, indiferent dacă este vorba de un buton, de un stick de control virtual sau de un control alunecător. Ce vom implementa este o interfață de atingere pe care o voi numi "gamepad-uri virtuale".
A gamepad descrie în mod obișnuit un control fizic standard, în formă de plus, asemănător interfeței plus pe un sistem Game Boy sau un controler PlayStation (cunoscut și ca un pad direcțional sau un D-pad). Un gamepad permite mișcarea atât pe axa în sus, cât și în jos, și pe axa stângă și dreaptă. Rezultatul este că sunteți capabil să semnați opt direcții distincte, cu adăugarea "fără direcție". În Shape Blaster, interfața gamepad nu va fi fizică, ci pe ecran, deci a virtual gamepad.
Un joc tipic fizic; panoul direcțional în acest caz este în formă de plus.
Deși există doar patru intrări, există opt direcții (plus neutru) disponibile.
Pentru a avea un gamepad virtual în jocul nostru, trebuie să recunoaștem intrarea touch atunci când se întâmplă și să o convertim într-o formă pe care jocul o înțelege deja.
Gamepad-ul virtual implementat aici funcționează în trei etape:
În fiecare pas, ne vom concentra doar pe atingerea pe care o avem și vom urmări ultimul eveniment tactil pe care trebuie să îl comparăm. De asemenea, vom ține evidența touch ID, care determină ce deget atinge ce gamepad.
Imaginea de mai jos arată modul în care jocpadurile vor apărea pe ecran:
Captură de ecran a gamepad-urilor finale în poziție.
În Utilitate
bibliotecă, să examinăm clasa evenimentului pe care o vom folosi în primul rând. tTouchEvent
încapsulează tot ceea ce avem nevoie pentru a trata evenimentele touch la un nivel de bază.
clasa tTouchEvent public: enum EventType kTouchBegin, kTouchEnd, kTouchMove,; public: EventType mEvent; tPoint2f mLocation; uint8_t mID; public: tTouchEvent (const Tipul EventType & newEvent, const tPoint2f & newLocation, const uint8_t & newID): mEvent (newEvent), mLocation (newLocation), mID (newID) ;
Tip de eveniment
ne permite să definim tipul de evenimente pe care le vom permite fără a deveni prea complicate. mLocation
va fi punctul real de atingere și la mijlocul
va fi ID-ul degetului, care începe de la zero și adaugă unul pentru fiecare deget atins pe ecran. Dacă definim constructorul să ia doar const
, vom putea să instanțializăm clasele de evenimente fără a trebui să creăm în mod explicit variabile numite pentru ele.
Vom folosi tTouchEvent
exclusiv pentru a trimite evenimente touch de pe sistemul de operare la noi Intrare
clasă. De asemenea, vom folosi ulterior pentru a actualiza reprezentarea grafică a gamepad-urilor în VirtualGamepad
clasă.
Versiunea originală XNA și C # a Intrare
clasa poate manipula mouse-ul, tastatura și intrările reale ale gamepad-ului fizic. Mouse-ul este folosit pentru a trage la un punct arbitrar pe ecran din orice poziție; tastatura poate fi folosită atât pentru mutare cât și pentru fotografiere în direcțiile date. Din moment ce am ales să emulați intrarea originală (pentru a rămâne adevărat la un "port direct"), vom păstra majoritatea codului original același, folosind numele tastatură
și șoarece
, chiar dacă nu avem nici pe dispozitive iOS.
Iată cum ne Intrare
clasa va arata. Pentru fiecare dispozitiv, va trebui să păstrăm o "instantanee curentă" și "instantanee anterioare", astfel încât să putem spune ce se schimbă între ultimul eveniment de intrare și evenimentul de intrare curent. În cazul nostru, mMouseState
și mKeyboardState
sunt "instantanee actuale", și mLastMouseState
și mLastKeyboardState
reprezintă "fotografia anterioară".
Clasa de intrare: public tSingleton protejat: tPoint2f mMouseState; tPoint2f mLastMouseState; tPoint2f mFreshMouseState; std :: vectormKeyboardState; std :: vector mLastKeyboardState; std :: vector mFreshKeyboardState; bool mIsAimingWithMouse; uint8_t mLeftEngaged; uint8_t mRightEngaged; public: enum KeyType kUp = 0, kLeft, kDown, kRight, kW, kA, kS, kD,; protejate: tVector2f GetMouseAimDirection () const; protejate: Intrare (); publică: tPoint2f getMousePosition () const; void update (); // Verifică dacă o tastă a fost doar apăsată bool wasKeyPressed (KeyType) const; tVector2f getMovementDirection () const; tVector2f getAimDirection () const; void onTouch (const tTouchEvent & msg); clasa prietenului tSingleton; clasa prietenului VirtualGamepad; ; Intrare :: Intrare (): mMouseState (-1, -1), mLastMouseState (-1, -1), mIsAimingWithMouse (false), mLeftEngaged (255), mRightEngaged (255) mKeyboardState.resize (8); mLastKeyboardState.resize (8); mFreshKeyboardState.resize (8); pentru (size_t i = 0; i < 8; i++) mKeyboardState[i] = false; mLastKeyboardState[i] = false; mFreshKeyboardState[i] = false; tPoint2f Input::getMousePosition() const return mMouseState;
Pe un PC, orice eveniment pe care îl primim este "distinct", ceea ce înseamnă că o mișcare a mouse-ului este diferită de împingerea literei A, și chiar scrisoarea A este destul de diferit de litera S că putem spune că nu este exact același eveniment.
Cu iOS, noi numai vreodată obțineți intrări de atingere și o singură atingere nu este suficient de distinctă de la alta pentru a ne spune dacă este vorba de o mișcare a mouse-ului sau de o apăsare-cheie sau chiar de o cheie. Toate evenimentele arată exact din punctul nostru de vedere.
Pentru a ne da seama de această ambiguitate, vom introduce doi noi membri, mFreshMouseState
și mFreshKeyboardState
. Scopul lor este de a agrega, sau "captura toate", evenimentele într-un anumit cadru, fără a modifica alte variabile de stare altfel. Odată ce suntem mulțumiți de faptul că a trecut un cadru, putem actualiza starea actuală cu membrii "proaspeți" sunând Intrare :: actualizare
. Intrare :: actualizare
ne spune starea de intrare pentru a avansa.
void Input :: update () mLastKeyboardState = mKeyboardState; mLastMouseState = mMouseState; mKeyboardState = mFreshKeyboardState; mMouseState = mFreshMouseState; dacă [mKeyboardState [kLeft] || mKeyboardState [kRight] || mKeyboardState [kUp] || mKeyboardState [kDown]) mIsAimingWithMouse = false; altfel dacă (mMouseState! = mLastMouseState) mIsAimingWithMouse = true;
De când o vom face o dată pe cadru, vom apela Intrare :: actualizare ()
din cadrul GameRoot :: onRedrawView ()
:
// În GameRoot :: onRedrawView () Intrare :: getInstance () -> update ();
Acum, să ne uităm la modul în care transformăm intrarea în atingere fie într-un mouse sau tastatură simulată. În primul rând, vom planifica să avem două zone rectangulare diferite care reprezintă gamepad-urile virtuale. Orice afară din aceste zone vom considera "cu siguranță un eveniment de șoarece"; ceva înăuntru, vom considera "cu siguranță un eveniment de tastatură".
Orice în interiorul cutiilor roșii pe care le vom cartografia la intrarea simulată a tastaturii; altceva pe care îl vom trata ca intrarea mouse-ului.
Să ne uităm la Intrare :: onTouch ()
, care are toate evenimentele tactile. Vom face o imagine de ansamblu asupra metodei și vom nota zonele A FACE
unde un cod mai specific ar trebui să fie:
void Input :: onTouch (const tTouchEvent & msg) tPoint2f leftPoint = VirtualGamepad :: getInstance () -> mLeftPoint - tPoint2f (18, 18); tPoint2f rightPoint = VirtualGamepad :: getInstance () -> mRightPoint - tPoint2f (18, 18); tPoint2f intPoint ((int) msg.mLocation.x, (int) msg.mLocation.y); bool mouseDown = (msg.mEvent == tTouchEvent :: kTouchBegin) || (msg.mEvent ==TTouchEvent :: kTouchMove); if (msg_ID): if (msg.mID == mLeftEngaged) // TODO: Setați toate tastele de mișcare ca "key up" altceva (msg.mID == mRightEngaged) // TODO: "key up" dacă (mouseDown && tRectf (leftPoint, 164, 164) .contains (intPoint)) mLeftEngaged = msg.mID; // TODO: Setați toate tastele de mișcare ca "tasta sus" // TODO: Determinați ce taste de mișcare să setați dacă (mouseDown && tRectf (rightPoint, 164, 164) .contains (intPoint)) mRightEngaged = msg.mID; // TODO: Setați toate tastele de declanșare ca "tastă sus" // TODO: Determinați ce taste de tragere să setați dacă (! TRectf (leftPoint, 164, 164) .contains (intPoint) &&! TRectf (rightPoint, 164, .contains (intPoint)) // Dacă o facem aici, touch a fost cu siguranță un eveniment "mouse" mFreshMouseState = tPoint2f ((int32_t) msg.mLocation.x, (int32_t) msg.mLocation.y);
Codul este destul de simplu, dar se întâmplă o logică puternică pe care o voi sublinia:
leftPoint
și rightPoint
variabilele locale.mouseDown
stat: dacă suntem "presați" cu un deget, trebuie să știm dacă este înăuntru leftPoint
sau rect rightPoint
și, dacă este cazul, să ia măsuri pentru actualizarea acestuia proaspăt pentru tastatură. Dacă nu este în nici unul din rect, vom presupune că este un eveniment de șoarece și actualizăm proaspăt de stat pentru șoarece.Acum, când vedem imaginea de ansamblu, hai să aruncăm un pic mai departe.
Când un deget este ridicat de pe suprafața iPhone-ului sau a iPad-ului, verificăm dacă este un deget pe care îl știm că se află pe un jocpad și, dacă da, reinițializăm toate "cheile simulate" pentru acel joc:
dacă (! mouseDown) dacă (msg.mID == mLeftEngaged) mFreshKeyboardState [kA] = false; mFreshKeyboardState [kD] = false; mFreshKeyboardState [kW] = fals; mFreshKeyboardState [kS] = fals; altfel dacă (msg.mID == mRightEngaged) mFreshKeyboardState [kUp] = false; mFreshKeyboardState [kDown] = false; mFreshKeyboardState [kLeft] = false; mFreshKeyboardState [kRight] = false;
Situația este oarecum diferită atunci când există o atingere care începe pe suprafață sau se mișcă; verificăm dacă atingerea este în cadrul fiecărui gamepad. Deoarece codul pentru ambele gamepad-uri este similar, vom arunca o privire doar la jocul din stânga (care se ocupă cu mișcarea).
Ori de câte ori primim un eveniment tactil, vom șterge complet starea tastaturii pentru acel gamepad și vom verifica în zona noastră de rect pentru a determina ce tastă sau taste să apăsați. Așadar, deși avem un total de opt direcții (plus neutru), vom verifica doar patru dreptunghiuri: una pentru sus, una pentru jos, una pentru stânga și una pentru dreapta.
Cele nouă zone de interes din gamepadul nostru.
dacă (mouseDown && tRectf (leftPoint, 164, 164) .contains (intPoint)) mLeftEngaged = msg.mID; mFreshKeyboardState [kA] = falsă; mFreshKeyboardState [kD] = false; mFreshKeyboardState [kW] = fals; mFreshKeyboardState [kS] = fals; dacă (tRectf (leftPoint, 72, 164) .contains (intPoint)) mFreshKeyboardState [kA] = adevărat; mFreshKeyboardState [kD] = false; altfel dacă (tRectf (leftPoint + tPoint2f (128, 0), 72, 164) .contains (intPoint)) mFreshKeyboardState [kA] = false; mFreshKeyboardState [kD] = adevărat; dacă (tRectf (leftPoint, 164, 72) .contains (intPoint)) mFreshKeyboardState [kW] = adevărat; mFreshKeyboardState [kS] = fals; altfel dacă (tRectf (leftPoint + tPoint2f (0, 128), 164, 72) .contains (intPoint)) mFreshKeyboardState [kW] = false; mFreshKeyboardState [kS] = adevărat;
Dacă rulați jocul acum, veți avea suport virtual pentru gamepad, însă nu veți putea vedea de unde să înceapă sau să se termine jocurile virtuale.
Aici este locul unde VirtualGamepad
clasa intră în joc. VirtualGamepad
Scopul principal este de a desena gamepad-urile pe ecran. Modul în care vom afișa gamepad-ul va fi modul în care alte jocuri au tendința de a face acest lucru dacă au gamepad-uri: ca un cerc mai mare "de bază" și un cerc mai mic "control stick" putem muta. Acest lucru arată similar unui joystick arcadă de sus în jos și mai ușor de tras decât alte alternative.
Mai întâi, observați că fișierele imagine vpad_top.png
și vpad_bot.png
au fost adăugate la proiect. Să modificăm Artă
pentru a le încărca:
clasa Art: public tSingletonprotejat: ... tTexture * mVPadBottom; tTexture * mVPadTop; ... public: ... tTexture * getVPadBottom () const; tTexture * getVPadTop () const; ... clasa prietenului tSingleton ; ; Artă :: Artă () ... mVPadTop = TText nou (tSurface ("vpad_top.png")); mVPadBottom = nou tTexture (tSurface ("vpad_bot.png")); tTexture * Art :: getVPadBottom () const retur mVPadBottom; tTexture * Art :: getVPadTop () const retur mVPadTop;
VirtualGamepad
clasa va desena ambele gamepads pe ecran, și să păstreze Stat
informații în cadrul membrilor mLeftStick
și mRightStick
pe unde să desenezi "bețele de control" ale gamepad-urilor.
Am ales câteva poziții ușor arbitrare pentru jocpads, care sunt inițializate în mLeftPoint
și mRightPoint
membri - calculele le plasează la aproximativ 3,75% din marginea stângă sau dreaptă a ecranului și aproximativ 13% în partea de jos a ecranului. Am bazat aceste măsurători pe un joc comercial cu gamepad-uri virtuale similare, dar cu un gameplay diferit.
clasa VirtualGamepad: public tSingletonpublic: statul enum kCenter = 0x00, kTop = 0x01, kBottom = 0x02, kLeft = 0x04, kRight = 0x08, kTopLeft = 0x05, kTopRight = 0x09, kBottomLeft = 0x06, kBottomRight = 0x0a,; protejate: tPoint2f mLeftPoint; tPoint2f mRightPoint; int mLeftStick; int mRightStick; protejate: VirtualGamepad (); void DrawStickAtPoint (tSpriteBatch * spriteBatch, const tPoint2f & punct, starea de stat); void UpdateBasedOnKeys (); public: void draw (tSpriteBatch * spriteBatch); void update (const tTouchEvent & msg); clasa prietenului tSingleton ; prietenul clasa de intrare; ; VirtualGamepad :: VirtualGamepad (): mLeftStick (kCenter), mRightStick (kCenter) mLeftPoint = tPoint2f (int (3.0f / 80.0f * 800.0f), 600 - int (21.0f / 160.0f * 600.0f) - 128); mRightPoint = tPoint2f (800 - int (3.0f / 80.0f * 800.0f) - 128, 600 - int (21.0f / 160.0f * 600.0f) - 128);
Așa cum am menționat anterior, mLeftStick
și mRightStick
sunt bitmascuri, iar utilizarea lor este de a determina unde să atragă cercul interior al gamepad-ului. Vom calcula masca bit în metoda VirtualGamepad :: UpdateBasedOnKeys ()
.
Această metodă este chemată imediat după Intrare :: onTouch
, astfel încât să putem citi membrii de stat "proaspeți" și să știm că sunt actualizați:
void VirtualGamepad :: UpdateBasedOnKeys () Intrare * inp = Intrare :: getInstance (); mLeftStick = kCenter; dacă (inp-> mFreshKeyboardState [Input :: kA]) mLeftStick | = kLeft; altfel dacă (inp-> mFreshKeyboardState [Input :: kD]) mLeftStick | = kRight; dacă (inp-> mFreshKeyboardState [Input :: kW]) mLeftStick | = kTop; altfel dacă (inp-> mFreshKeyboardState [Input :: kS]) mLeftStick | = kBottom; mRightStick = kCenter; dacă (inp-> mFreshKeyboardState [Input :: kLeft]) mRightStick | = kLeft; altfel dacă (inp-> mFreshKeyboardState [Input :: kRight]) mRightStick | = kRight; dacă (inp-> mFreshKeyboardState [Input :: kUp]) mRightStick | = kTop; altfel dacă (inp-> mFreshKeyboardState [Input :: kDown]) mRightStick | = kBottom;
Pentru a desena un gamepad individual, sunăm VirtualGamepad :: DrawStickAtPoint ()
; această metodă nu știe și nici nu-i pasă ce gamepad te desenezi, știe doar unde vrei să fie desenată și statul să o tragă. Pentru că am folosit mască biți și am calculat înainte, metoda noastră devine mai mică și mai ușoară citit:
void VirtualGamepad :: DrawStickAtPoint (tSpriteBatch * spriteBatch, const tPoint2f & punct, starea de stat) tPoint2f offset = tPoint2f (18, 18); spriteBatch-> draw (10, Art :: getInstance () -> getVPadBottom (), punct, tOptional()); comutator (stare) caz kCenter: offset + = tPoint2f (0, 0); pauză; caz kTopLeft: offset + = tPoint2f (-13, -13); pauză; caz kTop: offset + = tPoint2f (0, -18); pauză; cazul kTopRight: offset + = tPoint2f (13, -13); pauză; cazul kRight: offset + = tPoint2f (18, 0); pauză; caz kBottomRight: offset + = tPoint2f (13, 13); pauză; caz kBottom: offset + = tPoint2f (0, 18); pauză; caz kBottomLeft: offset + = tPoint2f (-13, 13); pauză; cazul kLeft: offset + = tPoint2f (-18, 0); pauză; spriteBatch-> draw (11, Art :: getInstance () -> getVPadTop (), punct + offset, tOptional ());
Desenarea a două gamepad-uri devine mult mai ușoară, deoarece este doar un apel la metoda de mai sus de două ori. Să ne uităm la VirtualGamepad :: remiză ()
:
void VirtualGamepad :: desenează (tSpriteBatch * spriteBatch) DrawStickAtPoint (spriteBatch, mLeftPoint, (State) mLeftStick); DrawStickAtPoint (spriteBatch, mRightPoint, (Stat) mRightStick);
În cele din urmă, trebuie să tragem de fapt jocul virtual, așa că GameRoot :: onRedrawView ()
, adăugați următoarea linie:
VirtualGamepad :: getInstance () -> tragere (mSpriteBatch);
Asta e. Dacă executați jocul acum, ar trebui să vedeți gamepad-urile virtuale în întregime. Când atingi în interiorul jocului din stânga, trebuie să te miști. Când atingeți în interiorul gamepad-ului corect, direcția de tragere ar trebui să se schimbe. De fapt, puteți utiliza simultan ambele gamepad-uri și chiar mutați-le folosind gamepad-ul stâng și atingeți-l în afara gamepad-ului potrivit pentru a obține mișcarea mouse-ului. Iar când renunți, nu mai mișcați și (opțional) opriți fotografierea.
Am implementat pe deplin suportul gamepad virtual și funcționează, dar este posibil să-l găsiți un pic cam nebun sau greu de folosit. De ce este cazul? Acesta este locul în care provocarea reală a controalelor touch-based pe iOS vine cu jocuri tradiționale care nu au fost inițial concepute pentru ei.
Nu sunteți singuri, totuși. Multe jocuri fie suferă de aceste probleme, fie și le-au depășit.
Iată câteva lucruri pe care le-am observat cu ajutorul intrării pe ecranul tactil; ați putea avea unele observații similare:
În primul rând, controlorii jocului au o senzație diferită de ecran tactil plat; știți unde este degetul pe un joc real și cum să nu vă alunecați degetele. Cu toate acestea, pe un ecran tactil, degetele dvs. pot să se deplaseze puțin prea mult din zona touch, astfel încât este posibil ca intrarea dvs. să nu fie corect recunoscută și este posibil să nu realizați acest lucru până când este prea târziu.
În al doilea rând, ați observat, de asemenea, atunci când jucați cu comenzi tactile, că mâna dvs. vă ascunde viziunea, astfel că nava vă poate fi lovită de un inamic sub mâna pe care nu l-ați văzut pentru început!
În cele din urmă, puteți găsi că zonele de atingere sunt mai ușor de utilizat pe un iPad decât pe un iPhone sau invers. Deci, avem probleme cu o altă dimensiune a ecranului care afectează dimensiunea zonei de intrare, ceea ce este cu siguranță ceva ce nu trăim atât de mult pe un computer desktop. (Cele mai multe tastaturi și șoareci au aceeași dimensiune și acționează în același mod sau pot fi ajustate.)
Iată câteva modificări pe care le puteți face la sistemul de introducere descris în acest articol:
Din nou, depinde de tine ceea ce vrei să faci și cum vrei să o faci. În plus, există multe moduri de a face intrarea atingerii. Partea dificilă este să o faci bine și să faci jucători fericiți.
Unul dintre cei mai interesați dușmani în Geometry Wars este gaură neagră. Să examinăm cum putem face ceva similar în Shape Blaster. Vom crea funcționalitatea de bază acum și vom revizui inamicul în tutorialul următor pentru a adăuga efectele particulelor și interacțiunile particulelor.
O gaură neagră cu particule care orbitează
Găurile negre vor trage în nava jucătorului, inamicii din apropiere și (după următorul tutorial) particule, dar vor respinge gloanțele.
Există multe funcții posibile pe care le putem folosi pentru atracție sau repulsie. Cel mai simplu este să folosiți forța constantă astfel încât gaura neagră să tragă cu aceeași forță indiferent de distanța obiectului. O altă opțiune este ca forța să crească liniar, de la zero la o anumită distanță maximă până la o forță completă pentru obiectele direct deasupra găurii negre. Dacă vrem să modelăm gravitatea mai realist, putem folosi pătratul invers al distanței, ceea ce înseamnă că forța gravitației este proporțională cu 1 / (distanța ^ 2)
.
De fapt, vom folosi fiecare dintre aceste trei funcții pentru a gestiona diferite obiecte. Gloanțele vor fi respinse cu o forță constantă; dușmanii și nava jucătorului vor fi atrași de o forță liniară; iar particulele vor folosi o funcție pătrată inversă.
Vom face o nouă clasă pentru găurile negre. Să începem cu funcționalitatea de bază:
clasa BlackHole: entitate publică protejat: int mHitPoints; public: BlackHole (const tVector2f & poziție); void update (); redactarea void (tSpriteBatch * spriteBatch); void wasShot (); void kill (); ; BlackHole :: BlackHole (const tVector2f & poziție): mHitPoints (10) mImage = Art :: getInstance () -> getBlackHole (); mPoziția = poziție; mRadius = mImage-> getSurfaceSize () width / 2.0f; mKind = kBlackHole; void BlackHole :: wasShot () mHitPoints--; dacă (mHitPoints <= 0) mIsExpired = true; void BlackHole::kill() mHitPoints = 0; wasShot(); void BlackHole::draw(tSpriteBatch* spriteBatch) // make the size of the black hole pulsate float scale = 1.0f + 0.1f * sinf(tTimer::getTimeMS() * 10.0f / 1000.0f); spriteBatch->trageți (1, mImage, tPoint2f ((int32_t) mPosition.x, (int32_t) mPosition.y), tOptional(), mColor, mOrientation, getSize () / 2.0f, tVector2f (scară));
Găurile negre au zece fotografii pentru a ucide. Reglează ușor scala spritei pentru a face să se pulseze. Dacă decideți că distrugerea găurilor negre ar trebui să acorde și puncte, trebuie să efectuați ajustări similare la Gaură neagră
așa cum am făcut cu Dusman
clasă.
Apoi vom face ca găurile negre să aplice efectiv o forță asupra altor entități. Vom avea nevoie de o mică metodă de ajutor de la noi EntityManager
:
std :: ListaEntityManager :: getNearbyEntities (const tPoint2f & pos, raza float) std :: lista rezultat; pentru (std :: lista :: iterator iter = mEntities.begin (); iter! = mEntities.end (); iter ++) if (* iter) dacă (pos.distanceSquared ((* iter) -> getPosition ()) < radius * radius) result.push_back(*iter); return result;
Această metodă ar putea deveni mai eficientă utilizând o schemă de partiționare spațială mai complicată, dar pentru numărul de entități pe care le vom avea, este bine, deoarece este.
Acum putem face ca găurile negre să aplice forța lor Blackhole :: actualizare ()
metodă:
void BlackHole :: actualizare () std :: listăentități = EntityManager :: getInstance () -> getNearbyEntities (mPoziție, 250); pentru (std :: lista :: iterator iter = entities.begin (); iter! = entities.end (); iter ++) if ((* iter) -> getKind () == kEnemy &&! ((Enemy *) (* iter)) -> getIsActive getKind () == kBullet) tVector2f temp = ((* iter) -> getPosition () - mPozi); (* iter) -> setVelocitate ((* iter) -> getVelocity () + temp.normalize () * 0.3f); altceva tVector2f dPos = mPoziție - (* iter) -> getPosition (); lungimea plutitorului = dPos.length (); (* iter) -> setVelocitate ((* iter) -> getVelocity () + dPos.normalize () * tMath :: mix (2.0f, 0.0f, lungime / 250.0f));
Găurile negre afectează numai entitățile dintr-o rază aleasă (250 pixeli). Gloanțele din această rază au o forță constantă respingătoare aplicată, în timp ce orice altceva are o forță liniară atrăgătoare aplicată.
Va trebui să adăugăm o manipulare a coliziunilor pentru găurile negre la EntityManager
. Adăugați un std :: Lista
pentru găurile negre ca și în cazul celorlalte tipuri de entități și adăugați următorul cod EntityManager :: handleCollisions ()
:
// manipulați coliziuni cu găuri negre pentru (std :: list:: iteratorul i = mBlackHoles.begin (); i! = mBlackHoles.end (); i ++) pentru (std :: lista :: iterator j = mEnemies.begin (); j! = mEnemies.end (); j ++) dacă ((* j) -> getIsActive () && esteColliding (* i, * j)) (* j) -> wasShot (); pentru (std :: list :: iterator j = mBullets.begin (); j! = mBullets.end (); j ++) dacă (isColliding (* i, * j)) (* j) -> setExpired (); (* I) -> wasShot (); dacă (isColliding (PlayerShip :: getInstance (), * i)) KillPlayer (); pauză;
În cele din urmă, deschideți EnemySpawner
clasă și să creeze niște găuri negre. Am limitat numărul maxim de găuri negre la două și mi-a dat o șansă de 600 de găuri negre care au dat naștere fiecărui cadru.
dacă (EntityManager :: getInstance () -> getBlackHoleCount () < 2 && int32_t(tMath::random() * mInverseBlackHoleChance) == 0) EntityManager::getInstance()->adăugați (BlackHole nou (GetSpawnPosition ()));
Am discutat și am adăugat gamepad-uri virtuale și am adăugat găuri negre folosind diferite formule de forță. Shape Blaster începe să arate destul de bine. În următoarea parte, vom adăuga câteva efecte de particule nebun, deasupra vârfului.