La rivelazione di collisioni su confini perfetti il pixel è il Gral santo di scoperta di collisione in 2i giochi. Come tale, questo sembra l'ideale, se non dire perfetto, la soluzione di scoperta di collisione in generale. Tuttavia, è anche abbastanza complicato e le soluzioni diritte non si comportano molto bene finché Lei inizi a ottimizzare il codice. Yet, it’s also quite complicated and the straightforward solutions don’t perform very well until you start optimizing the code.

Questo primo posto si concentra su creare una maschera di pixel analizzando i dati d'immagine crudi, come proposto più di 3 anni fa da Ernesto Corvi. È la soluzione più rapida se Lei vuole fare sondaggi alla ricerca se un punto si scontra con un pixel su un'immagine, che anche lavora per folletti fatti girare e scagliosi. Comunque proprio vuole un po' d'ottimizzazione per accelerare collisioni di rivelazione tra una scatola di limitazione di un nodo e la maschera di pixel, o due maschere di pixel. However it does take some optimizing to speed up detecting collisions between a bounding box of a node and the pixel mask, or two pixel masks.

La soluzione alternativa è quella di rendere i due oggetti scontranti su un CCRenderTexture, come sviluppato da Dani e altri sul foro Cocos2D. Questo è capace di scoprire collisioni di folletti arbitrariamente fatti girare e scagliosi, ma può esser aspettato essere notevolmente più lento che una maschera di pixel. Discuterò questa soluzione in un futuro iDevBlogADay il posto. I will discuss this solution in a future iDevBlogADay post.

I risultati troveranno la loro strada in Kobold2D, per fare le soluzioni prontamente disponibili a tutti i progettisti.

Ernesto Corvi Strategia CollisionMap perfetta il Pixel

L'approccio di pseudocodice annunciato da Ernesto coinvolge la creazione di UIImage di 32 bit da un file e un'assegnazione di un pixelMask (lui telefona questo collisionMap) bufferizzano per tenere la collisione/non che si scontra stati per ogni pixel nell'immagine. Allora il suo soltanto una questione d'iterazione sopra i pixel d'immagine, mascherando la parte d'alfa per determinare se il pixel è completamente opaco, e se è, la bandiera di collisione nel pixelMask è messa.

Questa soluzione ha alcune necessità di miglioramenti. È il codice rudimentale che non rende conto d'esposizioni di Retina e immagini-hd e lascia il pixelMask come sottosopra l'immagine come l'UIImage. Questo non permette all'utente di specificare una soglia per il valore d'alfa (0 a 255 gamma), essere capace di considerare pixel che non sono completamente opachi. E il codice non provvede una soluzione per mettere alla prova intersezioni di rettangolo della maschera di pixel, per tacere dell'intersezione di due maschere di pixel. Questo è su che questo posto è. It doesn’t allow the user to specify a threshold for the alpha value (0 to 255 range), to be able to consider pixels which aren’t fully opaque. And the code doesn’t provide a solution for testing rectangle intersections of the pixel mask, let alone the intersection of two pixel masks. That’s what this post is about.

Introduzione KKPixelMaskSprite

KKPixelMaskSprite è una classe riutilizzabile che riceve un'eredità di CCSprite che aggiunge il pixelMask e parecchi metodi di scoperta di collisione. Mettendo la macroistruzione USE_BITARRAY a 1 Lei può cambiare la classe per usare un BitArray invece. Invece fare provvista di lavoro a giornata non firmato (BOOL) lo batte a macchina il BitArray fa provvista di pezzettini singolarmente, così riducendo il consumo di memoria a 1/8o (o il 12.5 %). Ad esempio, 512×512 la maschera di pixel usando BOOL (il lavoro a giornata non firmato) richiederà 256 CHILOBIT di memoria mentre la stessa maschera di pixel che poco assortimento solo richiederebbe 32 CHILOBIT di memoria. Instead of storing unsigned char (BOOL) types it the BitArray stores bits individually, thus reducing memory consumption to 1/8th (or 12.5%). For example, a 512×512 pixel mask using BOOL (unsigned char) will require 256 KB of memory whereas the same pixel mask as a bit array would only require 32 KB of memory.

Preferii usare la Biblioteca di Manipolazione di bit da Michael Dipperstein perché è rilasciata secondo la licenza di LGPL – la stessa licenza Cocos2D aveva usato prima che adottò la Licenza di MIT – e perché provvede esecuzioni sia per C (usato) sia per C ++.

La passeggiata attraverso il codice che carica l'immagine e assegna l'assortimento di pixelMask:

l'immagine di carico e assegna l'assortimento di pixelMask
Obiettivo-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//questo garantice che stiamo caricando il bene-hd su dispositivi di Retina
Il NSString* fullpath = [CCFileUtils fullPathFromRelativePath:filename]; = [CCFileUtils fullPathFromRelativePath:filename];
L'immagine di UIImage* = [[UIImage alloc] initWithContentsOfFile:fullpath]; = [[UIImage alloc] initWithContentsOfFile:fullpath];
      
//ottenga tutta l'informazione d'immagine di cui abbiamo bisogno
pixelMaskWidth = image.size.width; image.size.width;
pixelMaskHeight = image.size.height; image.size.height;
il pixelMaskSize = pixelMaskWidth * pixelMaskHeight; pixelMaskWidth * pixelMaskHeight;
      
//assegni e sgomberi il respingente di pixelMask
#if USE_BITARRAY
il pixelMask = BitArrayCreate (pixelMaskSize); BitArrayCreate(pixelMaskSize);
BitArrayClearAll (pixelMask); );
#else
il pixelMask = malloc (pixelMaskSize * sizeof (BOOL)); malloc(pixelMaskSize * sizeof(BOOL));
il memset (pixelMask, 0, pixelMaskSize * sizeof (BOOL)); , 0, pixelMaskSize * sizeof(BOOL));
#endif

La linea 2 garantice che il suffisso-hd è aggiunto al nome di file, se c'è una versione HD dell'immagine disponibile e l'esposizione di Retina è permessa. Le linee 6-8 ottengono l'informazione d'immagine e pixelMaskSize risultante, che è semplicemente il numero di pixel nell'immagine. Secondo la macroistruzione USE_BITARRAY il pixelMask è o dichiarato come bit_array_t* pixelMask; (BitArray) o BOOL* pixelMask; (assortimento di BOOL). Depending on the USE_BITARRAY macro the pixelMask is either declared as bit_array_t* pixelMask; (BitArray) or BOOL* pixelMask; (BOOL array).

Di nota qui è che preferii usare il tipo di BOOL semplicemente perché è il tipo di dati boolean nativo in Obiettivo-C. BOOL è un typedef a lavoro a giornata non firmato allora questo sta usando lo stesso tipo di dati sottostante che codice di Ernesto. Sia conscio che BOOL e bool (annotano la lettera minuscola) sono due tipi diversi! La lettera minuscola bool è un intero di 32 bit. Be aware that BOOL and bool (note the lowercase) are two different types! The lowercase bool is a 32-Bit integer.

Con la situazione di respingente di pixelMask e sgomberato, noi possiamo adesso scrutare pixel dell'immagine e determinare se il valore d'alfa di ogni pixel permette lei di esser aggiunto al pixelMask o no. Per chiarezza, le parti d'esecuzione macro e BitArray USE_BITARRAY non sono mostrate da qui su.

l'immagine di rapida lettura e i pezzettini di collisione di serie in pixelMask
Obiettivo-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//riceva i dati di pixel (più correttamente: il texels) come interi non firmati di 32 bit
CFDataRef imageData = =
            CGDataProviderCopyData (CGImageGetDataProvider (l'immagine. CGImage)); (image.CGImage));
il const UInt32* imagePixels = (const UInt32 *) CFDataGetBytePtr (imageData); imagePixels = (const UInt32*)CFDataGetBytePtr(imageData);
L'UInt32 alphaValue = 0, x = 0, y = pixelMaskHeight - 1; = 0, x = 0, y = pixelMaskHeight - 1;
Alfa di UInt8 = 0; = 0;
per (NSUInteger i = 0; io <pixelMaskSize; io ++) NSUInteger i = 0; i < pixelMaskSize; i++)
{
   //garantica che il pixelMask è creato in cima per basare l'orientamento
   L'indice di NSUInteger = y * pixelMaskWidth + x;
   x ++;
   se (x == pixelMaskWidth)
   {
      x = 0;
      y-;
   }
   //mascheri i colori in modo che solo il valore d'alfa rimanga (8 pezzettini superiori)
   l'alphaValue = imagePixels [io] & 0xff000000;
   se (alphaValue> 0)
   {
      //riceva il valore d'alfa, poi confronti l'alfa con la soglia d'alfa
      l'alfa = (UInt8) (alphaValue>> 24);
      se (l'alfa> alphaThreshold) )
      {
         il pixelMask [l'indice] = ; ] = YES;
      }
   }
}
CFRelease (imageData); );
imageData = zero; nil;
[rilascio d'immagine]; ];
immagine = zero; nil;

Le linee 2-4 accesso d'aumento all'informazione di pixel cruda nell'immagine. Questo coinvolge l'utilizzazione di parecchie funzioni di Fondazione Principali. L'UIImage Le dà l'accesso al Quarzo sottostante CGImage attraverso la proprietà CGImage. Dal CGImage un CGDataProvider è creato che permette ai dati crudi dell'immagine di esser copiati a un CFDataRef. The UIImage gives you access to the underlying Quartz CGImage through the CGImage property. From the CGImage a CGDataProvider is created which allows the image’s raw data to be copied to a CFDataRef.

CFDataRef è puramente una lancetta a una Fondazione Principale interna struct chiamato __ CFData. I dettagli di questo struct non sono esposti a progettisti ma può esser cercato in CFData.c su opensource.apple.com. La funzione di CFDataGetBytePtr restituisce una lancetta ai byte fatti provvista in __ CFData struct. Quello che rimane deve semplicemente gettare la lancetta a un UInt32 (un typedef per intervallo non firmato) perché l'UIImage è un'immagine con 32 pezzettini a pixel (RGBA8888). The CFDataGetBytePtr function returns a pointer to the bytes stored in the __CFData struct. What remains is simply to cast the pointer to an UInt32 (a typedef for unsigned int) because the UIImage is an image with 32-Bits per pixel (RGBA8888).

Quello che segue in linee 8-30 è un cappio che ispeziona ogni pixel nell'immagine per determinare se quel pixel deve esser aggiunto siccome una collisione ha morso al pixelMask o no. La prima peculiarità comincia da linee 11-17 che calcola l'indice nel pixelMask. Facendo allora questo fissa il problema che le immagini UIImage sono sottosopra aggiungendo pezzettini al respingente di pixelMask in sottosopra il modo reiterando sopra il respingente di imagePixels da cima a fondo. By doing so it fixes the issue that UIImage images are upside down by adding bits to the pixelMask buffer in a bottom up manner while iterating over the imagePixels buffer from top to bottom.

In linea 20 la parte d'alfa (leftmost 8 pezzettini) di ogni valore di pixel di 32 bit è mascherata. Questo mette tutti i pezzettini a colori (0-23) a 0 e lascia solo i pezzettini più alti che contengono il valore d'alfa di 8 bit. Se alphaValue è 0 il pixel è completamente trasparente e possiamo soltanto passare con la vicina iterazione. Altrimenti le linee 24 a 28 creano l'UInt8 (typedef per lavoro a giornata non firmato) il valore che contiene l'alfa del pixel (0 a 255 gamma) che è allora rispetto all'alphaThreshold. Se la prova ha successo, il pezzettino corrispondente nel pixelMask è messo a SÌ. Finalmente le linee 32-35 compiono la ripulita necessaria e rilasciano la memoria d'immagine. If alphaValue is 0 the pixel is fully transparent and we can just move on with the next iteration. Otherwise lines 24 to 28 create the UInt8 (typedef for unsigned char) value containing the pixel’s alpha (0 to 255 range) which is then compared with the alphaThreshold. If the test succeeds, the corresponding bit in the pixelMask is set to YES. Finally lines 32-35 perform the necessary cleanup and release the image memory.

Un KKPixelMaskSprite può esser inizializzato con spriteWithFile e un valore di alphaThreshold facoltativo:

1
lo spriteB = [KKPixelMaskSprite spriteWithFile:@ "imageB.png" alphaThreshold:100]; [KKPixelMaskSprite spriteWithFile:@"imageB.png" alphaThreshold:100];

Prova di collisione di un punto nella maschera di pixel

Facendo sondaggi alla ricerca se un po' a x specificato, y la posizione nella maschera di pixel è messo è computazionalemente non costoso, se non banale. Questo semplicemente richiede il calcolo dell'indice nell'assortimento, più alcuni conti di limiti:

prova se un punto si sta scontrando
Obiettivo-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (BOOL) pixelMaskBitAt: (CGPoint) punto ) pixelMaskBitAt:(CGPoint)point
{
   //intorno al punto coordina all'intero più vicino
   l'intervallo x = (l'intervallo) (point.x + 0.5f); = (int)(point.x + 0.5f);
   l'intervallo y = (l'intervallo) (point.y + 0.5f); = (int)(point.y + 0.5f);
   se (x <0 || y <0 || x> = pixelMaskWidth || y> = pixelMaskHeight) x < 0 || y < 0 || x >= pixelMaskWidth || y >= pixelMaskHeight)
   {
      ritorni NO; ;
   }
   L'indice di NSUInteger = y * pixelMaskWidth + x; = y * pixelMaskWidth + x;
   restituisca il pixelMask [l'indice]; [index];
}

L'unica considerazione speciale per fare è quella che i punti possono avere parti frazionarie, ma dobbiamo calcolare l'indice basato su valori d'intero (le linee 4-5). Tenere conto d'arrotondamento naturale di una virgola mobile a intero valuta il trucco semplice di aggiungere che 0.5f è usato. Questo garantice che ogni valore di frazione più grande che 0.5f è aumentato al vicino intero più alto, e da gettare a intero semplicemente toglie la parte frazionaria, finiamo con valori di 1.0 a 1.4999 per esser arrotondati giù a 1 mentre i valori da 1.5 a 1.9999 saranno riuniti a 2. This ensures that every fraction value greater than 0.5f is increased to the next higher integer, and since casting to integer simply removes the fractional part, we end up with values of 1.0 to 1.4999 to be rounded down to 1 whereas values from 1.5 to 1.9999 will be rounded up to 2.

La registrazione di limiti della linea 7 evita di calcolare un indice che è fuori dai limiti, che potrebbe indurre l'app ad andare in rovina. In linea 12 l'indice pixelMask è calcolato basato sulla formula fidata che fa la carta di una 2a posizione su un assortimento 1-dimensionale. Finalmente il pezzettino a quell'indice è restituito. Nell'insieme questa prova usa l'aritmetica semplice e è in fiammemente rapida. Finally the bit at that index is returned. Overall this test uses simple arithmetics and is blazingly fast.

Ma questo è solo una parte della storia. Il metodo pixelMaskBitAt è veramente un metodo privato della classe KKPixelMaskSprite. Come un utente della classe Lei starà mandando il messaggio pixelMaskContainsPoint invece. Spiegherò perché dopo il campione di codice: As a user of the class you will be sending the pixelMaskContainsPoint message instead. I’ll explain why after the code sample:

converta il punto in spazio di nodo & pixel di retina
Obiettivo-C
1
2
3
4
5
6
7
8
9
- (BOOL) pixelMaskContainsPoint: (CGPoint) punto ) pixelMaskContainsPoint:(CGPoint)point
{
   //le coordinate di punto hanno bisogno di essere relativo a spazio del nodo
   indichi = [stesso convertToNodeSpace:point]; [self convertToNodeSpace:point];
   //il punto dell'alta società a pixel di Retina in caso di necessità
   indichi = ccpMult (il punto, CC_CONTENT_SCALE_FACTOR ()); ccpMult(point, CC_CONTENT_SCALE_FACTOR());
   ritorni [stesso pixelMaskBitAt:point]; self pixelMaskBitAt:point];
}

Poiché stiamo lavorando con Cocos2D, dobbiamo considerare la possibilità che il folletto con la maschera di pixel è fatto girare o scalato, o entrambi. Il metodo convertToNodeSpace in linea 4 ha cura di questo per noi. E questo compie altro compito importante garanticendo che la coordinata di punto è relativo a coordinata d'origine della struttura di folletto (l'angolo più in basso lasciato). Il pixelMask ha la larghezza e l'altezza dell'immagine, includendolo in un indice con coordinate di schermo (la cui origine è all'angolo sinistro più basso dello schermo) restituirebbe risultati scorretti. And it performs another important task by ensuring that the point coordinate is relative to the sprite texture’s origin coordinate (lower left corner). The pixelMask has the width and height of the image, indexing it with screen coordinates (whose origin is at the lower left corner of the screen) would return incorrect results.

La linea 6 anche si assicura che il punto è convertito in una coordinata di pixel moltiplicandola con CC_CONTENT_SCALE_FACTOR (). Il fattore di scala contento è 2 su dispositivi di Retina con esposizione di Retina permessa, altrimenti è 1. Questo presume che se Lei permette il modo di Retina e dirige l'app su un dispositivo di Retina, le immagini-hd corrispondenti saranno provviste per tutti i folletti di maschera di pixel. This assumes that if you enable Retina mode and run the app on a Retina device, corresponding -hd images will be provided for all pixel mask sprites.

Continui di Leggere la Parte 2 …

Dovevo spaccare questo posto in due, a causa di una restrizione PHP.

Per favore continui con il secondo posto di quest'articolo di imparare come l'intersezione di rettangolo e pixelMask con scoperta di collisione pixelMask sono fatti.


Mi aiuti ad aiutarLa!

Passo il mio tempo intero raggiungendo nuove idee che La aiuteranno a diventare un progettista app migliore. Molto godo il processo d'istruzione, la spinta di confini (mio e vostro e quella di tecnologia), avendo la libertà di perseguire qualsiasi cosa che è sulla mia mente o vostra, ad audacemente programma quello che nessuno ha programmato prima, e scrivere su quello che ho imparato. Mi aiuti ad aiutarLa scartabellando i prodotti nell'Imparare di Deposito di Cocos2D. Help me help you by browsing the products in the Learn Cocos2D Store.


Ogni vendita mi permette di continuare a scrivere il codice migliore, più seminari universitari sotto la guida di un tutor e le risposte utili!

5 Risposte a “Scoperta di Collisione perfetta il Pixel rapida per Cocos2D con il Codice (1/2) d'Esempio”

  1. il thozzz dice:

    gran … e perfetto

  2. [...] KKPixelMaskSprite come descritto in Scoperta di Collisione perfetta il Pixel Rapida per Cocos2D [...]

  3. il kevin dice:

    L'attrezzo simpatico, grazie per divisione! Come userebbe Lei questo per attuare un confine che i folletti non possono toccare? Ho il progetto che è la cima giù 2a e il folletto di giocatore si moverà intorno a una carta geografica. Ho un PNG che ha le pareti colorate rosso e sto usando il Suo attrezzo per scoprire esattamente la collisione tra il giocatore e le pareti. Ma quando scopro una collisione, fermo la forma di giocatore che mi muove più. Come devo permettere al giocatore di allontanarsi dal confine appena lui lo colpisce? I have project that is top-down 2D and the player sprite will move around a map. I’ve got a PNG that has the walls colored red and I’m using your tool to accurately detect collision between the player and the walls. But when I detect a collision, I stop the player form moving any more. How should I allow the player to move away from the boundary once he hits it?

    • il kevin dice:

      Bene, appena che feci la domanda pensai a un'idea, e mi piacerebbe Lei l'opinione su lei … il suo lavorare per me adesso

      CGPoint cominciano = CGPointMake (worldMap.position.x, worldMap.position.y);
      Il CGPoint pnt = CGPointMake (worldMap.position.x +-velocity.x * il delta, worldMap.position.y +-velocity.y * il delta);
      worldMap.position = pnt;
      la collisione di lavoro a giornata = [coll pixelMaskIntersectsNode:boy];
      se (collisione) {
      worldMap.position = inizio;
      }

      Il mio vicino problema è quello che sto costruendo la mia carta geografica di collisione o maschera di pixel di uno strato CCLayerPanZoom che tiene un CCBigImage fatto di 256×256 le tegole di pixel. Qualche suggerimenti là? Posso creare una maschera di pixel di CCBigImage? Can I create a pixel mask from CCBigImage?

Lasci una Risposta