Le fait de découvrir des collisions sur les limites parfaites du pixel est le Saint-Graal saint de détection de collision dans de 2èmes jeux. En tant que tel, cela a l'air de l'idéal, sinon dire parfait, la solution de la détection de collision en général. Pourtant, il est aussi tout à fait compliqué et les solutions franches ne jouent pas très bien jusqu'à ce que vous commenciez à optimiser le code. Yet, it’s also quite complicated and the straightforward solutions don’t perform very well until you start optimizing the code.
Ce premier poste se concentre à créer un masque de pixel en analysant les données d'image crues, comme proposé il y a plus de 3 ans par Ernesto Corvi. C'est la solution la plus rapide si vous voulez évaluer si un point heurte un pixel sur une image, qui travaille aussi pour les lutins tournés et écailleux. Cependant il faut vraiment certains optimisant pour accélérer des collisions découvrantes entre une boîte bondissante d'un noeud et le masque de pixel, ou deux masques de 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 solution alternative est de rendre les deux objets de collision sur un CCRenderTexture, comme développé par Dani et d'autres sur le forum Cocos2D. Il est capable de découvrir des collisions de lutins arbitrairement tournés et écailleux, mais peut être attendu être sensiblement plus lent qu'un masque de pixel. Je discuterai cette solution dans un avenir iDevBlogADay le poste. I will discuss this solution in a future iDevBlogADay post.
Les résultats trouveront leur voie dans Kobold2D, pour rendre les solutions sans hésiter disponibles pour tous les promoteurs.
Ernesto Corvi Stratégie CollisionMap parfaite du Pixel
L'approche pseudo-codée postée par Ernesto implique de créer un UIImage de 32 morceaux d'un dossier et allouer un pixelMask (il appelle cela collisionMap) tamponnent pour tenir les états de collision de collision/pas pour chaque pixel dans l'image. Alors son juste une affaire du fait de réitérer sur les pixels d'image, en démasquant la partie alpha pour déterminer si le pixel est complètement opaque et si c'est, le drapeau de collision dans le pixelMask est mis.
Cette solution a quelques besoins pour les améliorations. C'est le code rudimentaire qui ne représente pas d'étalages de Rétine et-hd reflète et quitte le pixelMask comme à l'envers l'image comme l'UIImage. Il ne permet pas à l'utilisateur de spécifier un seuil pour la valeur alpha (0 à 255 gamme), être capable de considérer des pixels qui ne sont pas complètement opaques. Et le code ne fournit pas de solution à évaluer des intersections de rectangle du masque de pixel, sans parler de l'intersection de deux masques de pixel. C'est de quoi ce poste est. 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.
Présentation KKPixelMaskSprite
KKPixelMaskSprite est une classe réutilisable héritant de CCSprite qui ajoute le pixelMask et plusieurs méthodes de détection de collision. En montrant la macro USE_BITARRAY à 1 vous pouvez changer la classe pour utiliser un BitArray plutôt. Au lieu de conserver l'omble chevalier sans signature (BOOL) le tape le BitArray conserve des morceaux individuellement, en réduisant ainsi la consommation de mémoire à 1/8ème (ou 12.5 %). Par exemple, un 512×512 le masque de pixel en utilisant BOOL (l'omble chevalier sans signature) exigera 256 Ko de mémoire alors que le même masque de pixel qu'un peu de gamme exigerait seulement 32 Ko de mémoire. 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.
J'ai voulu utiliser la Bibliothèque de Manipulation de bits de Michael Dipperstein parce qu'elle est libérée selon la licence de LGPL – la même licence Cocos2D avait utilisé avant qu'elle a adopté la Licence de MIT – et parce qu'elle fournit la mise en oeuvre tant à C (utilisé) qu'à C ++.
Marchons par le code qui charge l'image et alloue la gamme de pixelMask :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//cela garantit que nous chargeons l'actif-hd sur les appareils de Rétine NSString* fullpath = [CCFileUtils fullPathFromRelativePath:filename];
= [CCFileUtils fullPathFromRelativePath:filename];
UIImage* reflètent = [[UIImage alloc] initWithContentsOfFile:fullpath];
= [[UIImage alloc] initWithContentsOfFile:fullpath];
//obtenez toute l'information d'image dont nous avons besoin pixelMaskWidth = image.size.width;
image.size.width;
pixelMaskHeight = image.size.height;
image.size.height;
pixelMaskSize = pixelMaskWidth * pixelMaskHeight;
pixelMaskWidth * pixelMaskHeight;
//allouez et dégagez le tampon de pixelMask #if USE_BITARRAY pixelMask = BitArrayCreate (pixelMaskSize);
BitArrayCreate(pixelMaskSize);
BitArrayClearAll (pixelMask);
);
#else pixelMask = malloc (pixelMaskSize * sizeof (BOOL));
malloc(pixelMaskSize * sizeof(BOOL));
memset (pixelMask, 0, pixelMaskSize * sizeof (BOOL));
, 0, pixelMaskSize * sizeof(BOOL));
#endif |
La ligne 2 garantit que le suffixe-hd est ajouté au nom de fichier, s'il y a une version HD de l'image disponible et l'étalage de Rétine est permis. Les lignes 6-8 obtiennent l'information d'image et la conséquence pixelMaskSize, qui est simplement le nombre de pixels dans l'image. Selon la macro USE_BITARRAY le pixelMask est ou déclaré comme bit_array_t* pixelMask; (BitArray) ou BOOL* pixelMask; (gamme de BOOL). Depending on the USE_BITARRAY macro the pixelMask is either declared as bit_array_t* pixelMask; (BitArray) or BOOL* pixelMask; (BOOL array).
De la note voici que j'ai voulu utiliser le type de BOOL simplement parce que c'est le type de données booléen natal dans l'Objectif-C. BOOL est un typedef à l'omble chevalier sans signature donc il utilise le même type de données sous-jacent que le code d'Ernesto. Soyez conscients que BOOL et bool (notent les minuscules) sont deux types différents! Les minuscules bool sont un nombre entier de 32 morceaux. Be aware that BOOL and bool (note the lowercase) are two different types! The lowercase bool is a 32-Bit integer.
Avec l'organisation de tampon de pixelMask et dégagé, nous pouvons lire maintenant les pixels de l'image rapidement et déterminer si la valeur alpha de chaque pixel y permet d'être ajoutée au pixelMask ou non. Pour la clarté, les parties de mise en oeuvre macro et BitArray USE_BITARRAY ne sont pas montrées d'ici sur.
|
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 |
//recevez les données de pixel (plus correctement : texels) comme les nombres entiers sans signature de 32 morceaux CFDataRef imageData =
=
CGDataProviderCopyData (CGImageGetDataProvider (l'image. CGImage));
(image.CGImage));
const UInt32* imagePixels = (const UInt32 *) CFDataGetBytePtr (imageData);
imagePixels = (const UInt32*)CFDataGetBytePtr(imageData);
UInt32 alphaValue = 0, x = 0, y = pixelMaskHeight - 1;
= 0, x = 0, y = pixelMaskHeight - 1;
Alpha d'UInt8 = 0;
= 0;
pour (NSUInteger i = 0; je <pixelMaskSize; je ++) NSUInteger i = 0; i < pixelMaskSize; i++) { //garantissez que le pixelMask est créé dans le haut à l'orientation de fond L'index de NSUInteger = y * pixelMaskWidth + x; x ++; si (x == pixelMaskWidth) { x = 0; y-; } //démasquez les couleurs pour que seulement la valeur alpha reste (8 morceaux supérieurs) alphaValue = imagePixels [je] & 0xff000000; si (alphaValue> 0) { //recevez la valeur alpha, comparez ensuite l'alpha avec le seuil alpha l'alpha = (UInt8) (alphaValue>> 24); si (l'alpha> alphaThreshold)
)
{
pixelMask [l'index] = OUI;
] = YES;
}
}
}
CFRelease (imageData);
);
imageData = zéro;
nil;
[reflétez la libération];
];
reflétez = le zéro; nil; |
Les lignes 2-4 accès d'augmentation aux renseignements de pixel crus dans l'image. Cela implique d'utiliser plusieurs fonctions de Fondation de Base. L'UIImage vous donne l'accès au Quartz sous-jacent CGImage par la propriété CGImage. Du CGImage un CGDataProvider est créé qui permet aux données brutes de l'image d'être copiées à 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 est simplement une flèche sur une Fondation de Base intérieure struct appelé __ CFData. Les détails de ce struct ne sont pas exposés aux promoteurs mais il peut être cherché dans CFData.c sur opensource.apple.com. La fonction de CFDataGetBytePtr rend une flèche sur les octets conservés dans le __ CFData struct. Ce qui reste doit jeter simplement la flèche sur un UInt32 (un typedef pour l'intervalle sans signature) parce que l'UIImage est une image avec 32 morceaux par 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).
Ce qui suit dans les lignes 8-30 est une boucle qui inspecte chaque pixel dans l'image pour déterminer si ce pixel devrait être ajouté puisqu'une collision a mordu au pixelMask ou non. La première particularité commence avec les lignes 11-17 qui calcule l'index dans le pixelMask. En faisant donc il fixe l'édition que les images d'UIImage sont à l'envers en ajoutant des morceaux au tampon de pixelMask dans cul sec la manière en réitérant sur le tampon d'imagePixels du haut en bas. 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.
Dans la ligne 20 la partie alpha (leftmost 8 morceaux) de chaque valeur de pixel de 32 morceaux est démasquée. Cela met tous les morceaux en couleur (0-23) à 0 et quitte seulement les plus hauts morceaux qui contiennent la valeur alpha de 8 morceaux. Si alphaValue est 0 le pixel est complètement transparent et nous pouvons aller juste avec l'itération suivante. Autrement les lignes 24 à 28 créent l'UInt8 (typedef pour l'omble chevalier sans signature) la valeur contenant l'alpha du pixel (0 à 255 gamme) qui est alors comparé avec l'alphaThreshold. Si l'épreuve réussit, le morceau correspondant dans le pixelMask est montré à OUI. Finalement les lignes 32-35 exécutent le nettoyage nécessaire et libèrent la mémoire d'image. 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 peut être initialisé avec spriteWithFile et une valeur d'alphaThreshold optionnelle :
|
1 |
spriteB = [KKPixelMaskSprite spriteWithFile:@ "imageB.png" alphaThreshold:100]; [KKPixelMaskSprite spriteWithFile:@"imageB.png" alphaThreshold:100]; |
L'essai pour la collision d'un point dans le masque de pixel
En évaluant si un peu à x indiqué, y l'endroit dans le masque de pixel est mis est peu coûteux quantificativement, sinon banal. Il exige simplement le calcul de l'index dans la gamme, plus quelques vérifications de limites :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (BOOL) pixelMaskBitAt : (CGPoint) point
) pixelMaskBitAt:(CGPoint)point
{
//autour du point coordonne au nombre entier le plus proche l'intervalle x = (l'intervalle) (point.x + 0.5f);
= (int)(point.x + 0.5f);
l'intervalle y = (l'intervalle) (point.y + 0.5f);
= (int)(point.y + 0.5f);
si (x <0 || y <0 || x> = pixelMaskWidth || y> = pixelMaskHeight)
x < 0 || y < 0 || x >= pixelMaskWidth || y >= pixelMaskHeight)
{
revenez NON;
;
}
L'index de NSUInteger = y * pixelMaskWidth + x;
= y * pixelMaskWidth + x;
rendez pixelMask [l'index];
[index];
} |
La seule considération spéciale pour faire consiste en ce que les points peuvent avoir des parties infimes, mais nous devons calculer l'index basé sur les valeurs de nombre entier (les lignes 4-5). Tenir compte de l'arrondissage naturel d'une virgule flottante au nombre entier évalue le truc simple d'ajouter que 0.5f est utilisé. Cela garantit que chaque valeur de fraction plus grande que 0.5f est augmentée au plus haut nombre entier suivant et depuis la fonte au nombre entier enlève simplement la partie infime, nous finissons par devenir avec les valeurs de 1.0 à 1.4999 pour être arrondis à la virgule inférieure à 1 alors que les valeurs de 1.5 à 1.9999 seront rassemblées à 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.
L'enregistrement de limites de la ligne 7 évite de calculer un index qui est hors du terrain, qui pourrait faire l'app s'écraser. Dans la ligne 12 on calcule l'index pixelMask basé sur la formule fiable qui fait la carte d'une 2ème position sur une gamme de 1 dimension. Finalement le morceau à cet index est rendu. Dans l'ensemble cette épreuve utilise arithmetics simple et est blazingly vite. Finally the bit at that index is returned. Overall this test uses simple arithmetics and is blazingly fast.
Mais c'est seulement une partie de l'histoire. La méthode pixelMaskBitAt est en fait une méthode privée pour la classe KKPixelMaskSprite. Comme un utilisateur de la classe vous enverrez le message pixelMaskContainsPoint plutôt. J'expliquerai pourquoi après l'échantillon codé : As a user of the class you will be sending the pixelMaskContainsPoint message instead. I’ll explain why after the code sample:
|
1 2 3 4 5 6 7 8 9 |
- (BOOL) pixelMaskContainsPoint : (CGPoint) point
) pixelMaskContainsPoint:(CGPoint)point
{
//l'ensemble de point a besoin d'être par rapport à l'espace du noeud montrez = [moi convertToNodeSpace:point];
[self convertToNodeSpace:point];
//le point classe aux pixels de Rétine au besoin montrez = ccpMult (le point, CC_CONTENT_SCALE_FACTOR ());
ccpMult(point, CC_CONTENT_SCALE_FACTOR());
revenez [moi pixelMaskBitAt:point];
self pixelMaskBitAt:point];
} |
Comme nous travaillons avec Cocos2D, nous devons considérer la possibilité que le lutin avec le masque de pixel est tourné ou escaladé, ou tous les deux. La méthode convertToNodeSpace dans la ligne 4 s'en occupe pour nous. Et il exécute une autre tâche importante en garantissant que la coordonnée de point est par rapport à la coordonnée d'origine de texture de lutin (le coin inférieur gauche). Le pixelMask a la largeur et la hauteur de l'image, en l'indexant avec l'ensemble d'écran (dont l'origine est au coin inférieur gauche de l'écran) rendrait des résultats incorrects. 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 ligne 6 s'assure aussi que le point est converti en coordonnée de pixel en la multipliant avec CC_CONTENT_SCALE_FACTOR (). Le facteur d'échelle content est 2 sur les appareils de Rétine avec l'étalage de Rétine permis, autrement c'est 1. Cela suppose que si vous permettez le mode de Rétine et dirigez l'app sur un appareil de Rétine, les images de-hd correspondantes seront fournies à tous les lutins de masque de 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.
Continuez à Lire la Partie 2 …
Je devais fendre ce poste dans deux, en raison d'une restriction PHP.
Continuez s'il vous plaît avec le deuxième poste de cet article à apprendre comment l'intersection de rectangle et pixelMask avec la détection de collision pixelMask sont faits.
| Suivez @gaminghorror | Suivez @kobold2d |
|









[...] la Détection de Collision parfaite du Pixel Rapide pour Cocos2D avec le Code (1/2) d'Exemple [...]
grand … et parfait
[...] KKPixelMaskSprite comme décrit dans la Détection de Collision parfaite du Pixel Rapide pour Cocos2D [...]
L'outil agréable, merci du fait de partager! Comment l'utiliseriez-vous pour exécuter une limite que les lutins ne peuvent pas toucher ? J'ai le projet qui est le haut en bas 2ème et le lutin de joueur déplacera une carte. J'ai un PNG qui a les murs coloriés rouges et j'utilise votre outil pour exactement découvrir la collision entre le joueur et les murs. Mais quand je découvre une collision, j'arrête la forme de joueur bougeant plus. Comment devrais-je permettre au joueur de partir de la limite dès qu'il la frappe ? 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?
Bien, aussitôt que j'ai posé la question j'ai pensé à une idée et je vous voudrais l'opinion sur elle … son travail pour moi maintenant
CGPoint commencent = CGPointMake (worldMap.position.x, worldMap.position.y);
CGPoint pnt = CGPointMake (worldMap.position.x +-velocity.x * le delta, worldMap.position.y +-velocity.y * le delta);
worldMap.position = pnt;
la collision d'omble chevalier = [coll pixelMaskIntersectsNode:boy];
si (collision) {
worldMap.position = début;
}
Mon problème suivant consiste en ce que je construis ma carte de collision ou masque de pixel d'une couche CCLayerPanZoom qui tient un CCBigImage fait de 256×256 les tuiles de pixel. Des suggestions là ? Puis-je créer un masque de pixel de CCBigImage ? Can I create a pixel mask from CCBigImage?