El descubrimiento de colisiones en límites perfectos por el pixel es el grial santo del descubrimiento de colisión en 2dos juegos. Como tal, parece al ideal, si no decir perfecto, solución del descubrimiento de colisión en general. Aún, también es completamente complicado y las soluciones francas no funcionan muy bien hasta que usted comience a optimizar el código. Yet, it’s also quite complicated and the straightforward solutions don’t perform very well until you start optimizing the code.
Este primer correo se concentra en crear una máscara de pixel analizando los datos de imagen crudos, como propuesto hace más de 3 años por Ernesto Corvi. Es la solución más rápida si usted quiere probar si un punto choca con un pixel en una imagen, que también trabaja para elfos hechos girar y escalados. Sin embargo realmente se necesita un poco de optimización para acelerar colisiones de descubrimiento entre un cuadro delimitador de un nodo y la máscara de pixel, o dos máscaras 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 solución alternativa es dar los dos objetos que chocan en CCRenderTexture, como desarrollado por Dani y otros en el foro Cocos2D. Es capaz de descubrir colisiones de elfos arbitrariamente hechos girar y escalados, pero puede ser esperado ser perceptiblemente más lento que una máscara de pixel. Hablaré de esta solución en un futuro iDevBlogADay correo. I will discuss this solution in a future iDevBlogADay post.
Los resultados encontrarán su camino en Kobold2D, para hacer las soluciones disponibles en el acto a todos los reveladores.
Ernesto Corvi Estrategia de CollisionMap perfecta por el Pixel
El enfoque de pseudocódigo fijado por Ernesto implica crear UIImage de 32 bites de un archivo y asignar un pixelMask (él llama esto collisionMap) almacenan en un buffer para sostener chocar/no que choca estados para cada pixel a la imagen. Entonces su sólo un asunto de iteración sobre los pixeles de imagen, enmascarando la parte alfa para determinar si el pixel es totalmente opaco, y si es, la bandera de colisión en el pixelMask es puesta.
Esta solución tiene unas necesidades de mejoras. Es el código rudimentario que no explica demostraciones de Retina e imágenes-hd y deja el pixelMask como al revés imagen como el UIImage. Esto no permite que el usuario especifique un umbral para el valor alfa (0 a 255 variedad), sea capaz de considerar pixeles que no son totalmente opacos. Y el código no proporciona una solución a probar intersecciones de rectángulo de la máscara de pixel, sin mencionar la intersección de dos máscaras de pixel. Esto es sobre qué este correo es. 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.
Introducción de KKPixelMaskSprite
KKPixelMaskSprite es un heredamiento de clase reutilizable de CCSprite que añade el pixelMask y varios métodos de descubrimiento de colisión. Poniendo el macro USE_BITARRAY a 1 usted puede cambiar la clase para usar BitArray en cambio. En vez de almacenar el trabajo por horas no firmado (BOOL) lo escribe a máquina BitArray almacena trozos individualmente, así reduciendo el consumo de memoria al 1/8vo (o el 12.5 %). Por ejemplo, un 512×512 la máscara de pixel usando BOOL (trabajo por horas no firmado) requerirá 256 KILOBYTES de la memoria mientras que la misma máscara de pixel que un poco serie sólo requeriría 32 KILOBYTES de la 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.
Decidí usar la Biblioteca de Manipulación de Trozo de Michael Dipperstein porque es soltada según la licencia de LGPL – la misma licencia Cocos2D había usado antes de que ella adoptara la Licencia de MIT – y porque proporciona realizaciones tanto a C (usado) como a C ++.
Vaya al paseo por el código que carga la imagen y asigna la serie de pixelMask:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//esto asegura que cargamos el activo-hd en dispositivos de Retina NSString* fullpath = [CCFileUtils fullPathFromRelativePath:filename];
= [CCFileUtils fullPathFromRelativePath:filename];
Imagen de UIImage* = [[UIImage alloc] initWithContentsOfFile:fullpath];
= [[UIImage alloc] initWithContentsOfFile:fullpath];
//consiga toda la información de imagen que necesitamos pixelMaskWidth = image.size.width;
image.size.width;
pixelMaskHeight = image.size.height;
image.size.height;
pixelMaskSize = pixelMaskWidth * pixelMaskHeight;
pixelMaskWidth * pixelMaskHeight;
//asigne y limpie el parachoques 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 línea 2 asegura que el sufijo-hd es añadido al nombre del archivo, si hay una versión HD de la imagen disponible y la demostración de Retina es permitida. Las líneas 6-8 consiguen la información de imagen y pixelMaskSize que resulta, que es simplemente el número de pixeles a la imagen. Según el macro USE_BITARRAY el pixelMask es o declarado como bit_array_t* pixelMask; (BitArray) o BOOL* pixelMask; (serie 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 nota aquí está que decidí usar el tipo de BOOL simplemente porque es el tipo de datos booleano natal en el Objetivo-C. BOOL es un typedef al trabajo por horas no firmado entonces esto usa el mismo tipo de datos subyacente como el código de Ernesto. ¡Esté consciente que BOOL y bool (notan la minúscula) son dos tipos diferentes! La minúscula bool es un número entero de 32 bites. Be aware that BOOL and bool (note the lowercase) are two different types! The lowercase bool is a 32-Bit integer.
Con el sistema de parachoques de pixelMask y limpiado, podemos explorar ahora los pixeles de la imagen y determinar si el valor alfa de cada pixel permite que él sea añadido al pixelMask o no. Para la claridad, el macro USE_BITARRAY y las partes de realización de BitArray no son mostrados de aquí en.
|
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 |
//consiga los datos de pixel (más correctamente: texels) como números enteros no firmados de 32 bites CFDataRef imageData =
=
CGDataProviderCopyData (CGImageGetDataProvider (imagen. 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;
Alfa de UInt8 = 0;
= 0;
para (NSUInteger i = 0; yo <pixelMaskSize; yo ++) NSUInteger i = 0; i < pixelMaskSize; i++) { //asegure que el pixelMask es creado en la cumbre a la orientación de fondo Índice de NSUInteger = y * pixelMaskWidth + x; x ++; si (x == pixelMaskWidth) { x = 0; y-; } //la máscara los colores de modo que sólo el valor alfa permanezca (8 trozos superiores) alphaValue = imagePixels [yo] & 0xff000000; si (alphaValue> 0) { //consiga el valor alfa, luego compare la alfa con el umbral alfa alfa = (UInt8) (alphaValue>> 24); si (alfa> alphaThreshold)
)
{
pixelMask [índice] = SÍ;
] = YES;
}
}
}
CFRelease (imageData);
);
imageData = nada;
nil;
[liberación de imagen];
];
imagen = nada; nil; |
Líneas 2-4 acceso de ganancia a la información de pixel cruda a la imagen. Esto implica usar varias funciones de Fundación Principales. El UIImage le da el acceso al Cuarzo subyacente CGImage por la propiedad CGImage. Del CGImage CGDataProvider es creado que permite que los datos iniciales de la imagen sean copiados a 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 es simplemente una aguja de una Fundación Principal interna struct llamado __ CFData. Los detalles de este struct no son expuestos a reveladores pero puede ser buscado en CFData.c en opensource.apple.com. La función de CFDataGetBytePtr devuelve una aguja de los bytes almacenados en el __ CFData struct. Lo que permanece debe echar simplemente la aguja de un UInt32 (un typedef para el intervalo no firmado) porque el UIImage es una imagen con 32 trozos por 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).
Lo que sigue en líneas 8-30 es un lazo que inspecciona cada pixel a la imagen para determinar si aquel pixel debería ser añadido ya que una colisión mordía al pixelMask o no. La primera particularidad comienza con líneas 11-17 que calcula el índice en el pixelMask. Haciendo entonces esto fija la cuestión que las imágenes UIImage son al revés añadiendo trozos al parachoques de pixelMask en un fondo manera iterando sobre el parachoques de imagePixels de arriba abajo. 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.
En la línea 20 la parte alfa (8 trozos extremos izquierdos) de cada valor de pixel de 32 bites es enmascarada. Esto pone todos los trozos en color (0-23) a 0 y sólo deja los trozos más altos que contienen el valor alfa de 8 bites. Si alphaValue es 0 el pixel es totalmente transparente y podemos circular sólo con la siguiente iteración. Por otra parte las líneas 24 a 28 crean el UInt8 (typedef para el trabajo por horas no firmado) valor que contiene la alfa del pixel (0 a 255 variedad) que es entonces comparado con el alphaThreshold. Si la prueba tiene éxito, el trozo correspondiente en el pixelMask es puesto a SÍ. Finalmente las líneas 32-35 realizan la limpieza necesaria y sueltan la memoria de imagen. 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.
KKPixelMaskSprite puede ser inicializado con spriteWithFile y un valor de alphaThreshold opcional:
|
1 |
spriteB = [KKPixelMaskSprite spriteWithFile:@ "imageB.png" alphaThreshold:100]; [KKPixelMaskSprite spriteWithFile:@"imageB.png" alphaThreshold:100]; |
Las pruebas para colisión de un punto en la máscara de pixel
Probando si un poco en x especificado, y posición en la máscara de pixel es puesto es computacionalmente barato, si no trivial. Esto simplemente requiere el cálculo del índice en la serie, más algunos controles de límites:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (BOOL) pixelMaskBitAt: (CGPoint) punto
) pixelMaskBitAt:(CGPoint)point
{
//alrededor del punto coordina al número entero más cercano intervalo x = (intervalo) (point.x + 0.5f);
= (int)(point.x + 0.5f);
intervalo y = (intervalo) (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)
{
vuelva NO;
;
}
Índice de NSUInteger = y * pixelMaskWidth + x;
= y * pixelMaskWidth + x;
devuelva el pixelMask [índice];
[index];
} |
La única consideración especial para hacer consiste en que los puntos pueden tener partes fraccionarias, pero debemos calcular el índice basado en valores enteros (líneas 4-5). Para tener el doblamiento natural en cuenta de un punto flotante a valores enteros la broma simple de añadir 0.5f es usada. Esto asegura que cada valor de fracción mayor que 0.5f es aumentado al siguiente número entero más alto, y desde el reparto al número entero simplemente quita la parte fraccionaria, terminamos con valores de 1.0 a 1.4999 para ser redondeados a 1 mientras que los valores de 1.5 a 1.9999 serán acorralados 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.
El registro de límites de línea 7 evita calcular un índice que es fuera de límites, que podrían hacer que el app se estrellara. En la línea 12 el índice pixelMask es calculado basado en la fórmula confiada que traza un mapa de una 2da posición en una serie de 1 dimensión. Finalmente el trozo en aquel índice es devuelto. En general esta prueba usa arithmetics simple y es abrasadoramente rápida. Finally the bit at that index is returned. Overall this test uses simple arithmetics and is blazingly fast.
Pero esto es sólo una parte de la historia. El método pixelMaskBitAt es realmente un método privado de la clase de KKPixelMaskSprite. Como un usuario de la clase usted enviará el mensaje pixelMaskContainsPoint en cambio. Explicaré por qué después de la muestra de código: 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) punto
) pixelMaskContainsPoint:(CGPoint)point
{
//las coordenadas de punto tienen que ser con relación al espacio del nodo señale = [mí convertToNodeSpace:point];
[self convertToNodeSpace:point];
//punto de alta calidad a pixeles de Retina si es necesario señale = ccpMult (punto, CC_CONTENT_SCALE_FACTOR ());
ccpMult(point, CC_CONTENT_SCALE_FACTOR());
vuelva [mí pixelMaskBitAt:point];
self pixelMaskBitAt:point];
} |
Ya que trabajamos con Cocos2D, debemos considerar la posibilidad que el elfo con la máscara de pixel sea hecho girar o escalado, o ambos. El método convertToNodeSpace en la línea 4 tiene cuidado de esto para nosotros. Y esto realiza otra tarea importante asegurando que la coordenada de punto es con relación a la coordenada de origen de la textura de elfo (esquina más abajo dejada). El pixelMask tiene la anchura y la altura de la imagen, poniendo índice a ello con coordenadas de pantalla (cuyo origen está en la esquina izquierda inferior de la pantalla) devolvería resultados incorrectos. 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 línea 6 también se asegura que el punto es convertido a una coordenada de pixel multiplicándola con CC_CONTENT_SCALE_FACTOR (). El factor de escala contento es 2 en dispositivos de Retina con la demostración de Retina permitida, por otra parte es 1. Esto supone que si usted permite el modo de Retina y dirige el app en un dispositivo de Retina, las imágenes-hd correspondientes sean proporcionadas a todos los elfos de máscara 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.
Siga Leyendo la Parte 2 …
Tuve que partir este correo en dos, debido a una limitación PHP.
Por favor siga con el segundo correo de este artículo aprendiendo como la intersección de rectángulo y pixelMask con el descubrimiento de colisión pixelMask son hechos.
| Siga @gaminghorror | Siga @kobold2d |
|









[...] Descubrimiento de Colisión perfecto por el Pixel Rápido para Cocos2D con Ejemplo de código (1/2) [...]
gran … y perfecto
[...] KKPixelMaskSprite como descrito en Descubrimiento de Colisión perfecto por el Pixel Rápido para Cocos2D [...]
¡Instrumento agradable, gracias por compartimiento! ¿Cómo usaría usted esto para poner en práctica un límite qué los elfos no pueden tocar? Tengo el proyecto que es la cumbre abajo 2da y el elfo de jugador trasladará un mapa. Tengo un PNG que tiene las paredes coloreadas rojas y uso su instrumento para descubrir exactamente la colisión entre el jugador y las paredes. Pero cuando descubro una colisión, paro la forma de jugador que me mueve más. ¿Cómo debería yo permitir que el jugador aleje del límite una vez que él lo golpea? 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, tan pronto como hice la pregunta pensé en una idea, y me gustaría usted opinión sobre ella … su trabajo para mí ahora
CGPoint comienzan = CGPointMake (worldMap.position.x, worldMap.position.y);
CGPoint pnt = CGPointMake (worldMap.position.x +-velocity.x * delta, worldMap.position.y +-velocity.y * delta);
worldMap.position = pnt;
colisión de trabajo por horas = [coll pixelMaskIntersectsNode:boy];
si (colisión) {
worldMap.position = principio;
}
Mi siguiente problema consiste en que construyo mi mapa de colisión o máscara de pixel de una capa de CCLayerPanZoom que sostiene CCBigImage hecho de 256×256 azulejos de pixel. ¿Alguna suposición allí? ¿Puedo crear una máscara de pixel de CCBigImage? Can I create a pixel mask from CCBigImage?