O descobrimento de choques em limites perfeitos pelo pixel é o Gral sagrado da detecção de choque em 2os jogos. Como tal, ele parece o ideal, não se dizer perfeito, solução para a detecção de choque em geral. Ainda, também é bastante complicado e as soluções francas não executam muito bem até que você comece a otimizar o código. Yet, it’s also quite complicated and the straightforward solutions don’t perform very well until you start optimizing the code.

Este primeiro correio concentra-se em criar uma máscara de pixel analisando os dados de imagem crus, como proposto há mais de 3 anos por Ernesto Corvi. É a solução mais rápida se você quiser testar se um ponto colidir com um pixel em uma imagem, que também trabalha para duendes feitos girar e escalados. Contudo realmente precisa-se de um pouco de otimização para acelerar choques de descobrimento entre uma caixa delimitadora de um nó e a máscara de pixel, ou duas 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.

A solução alternativa é dar dois objetos colidem para um CCRenderTexture, como desenvolvido por Dani e outros no fórum Cocos2D. É capaz de descobrir choques de duendes arbitralmente feitos girar e escalados mas pode ser esperado ser visivelmente mais lento do que uma máscara de pixel. Discutirei esta solução no futuro iDevBlogADay correio. I will discuss this solution in a future iDevBlogADay post.

Os resultados encontrarão o seu caminho em Kobold2D, para fazer as soluções prontamente disponíveis para todos os reveladores.

Ernesto Corvi Estratégia CollisionMap perfeita pelo Pixel

A aproximação de pseudocódigo posta no correio por Ernesto implica a criação de um UIImage de 32 bits de um arquivo e alocação de um pixelMask (ele chama ele collisionMap) armazenam em buffer para manter colidir/não colidindo estados de cada pixel na imagem. Então o seu somente uma matéria da repetição por cima dos pixéis de imagem, mascarando fora a parte alfabética para determinar se o pixel é totalmente opaco, e se for, a bandeira de choque no pixelMask é estabelecida.

Esta solução tem algumas necessidades de melhoras. É o código rudimentar que não presta contas de exposições de Retina e imagens-hd e deixa o pixelMask como de pernas para o ar imagem como o UIImage. Ele não permite ao usuário especificar um limiar do valor alfabético (0 a 255 variedade), ser capaz de considerar pixéis que não são totalmente opacos. E o código não fornece uma solução de testar intersecções de retângulo da máscara de pixel, sem falar na intersecção de duas máscaras de pixel. Isto é sobre que este correio é. 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.

Introdução KKPixelMaskSprite

KKPixelMaskSprite é uma classe reutilizável que herda de CCSprite que acrescenta o pixelMask e vários métodos de detecção de choque. Estabelecendo a macro USE_BITARRAY em 1 você pode modificar a classe para usar um BitArray em vez disso. Em vez de guardar o trabalho a dias não assinado (BOOL) datilografa-o o BitArray guarda bits individualmente, assim reduzindo consumo de memória a 1/8o (ou 12.5 %). Por exemplo, um 512×512 a máscara de pixel usando BOOL (trabalho a dias não assinado) necessitará 256 KBS da memória ao passo que a mesma máscara de pixel que um bocado tabela só necessitaria 32 KBS da memória. 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.

Decidi usar a Biblioteca de Manipulação de Bit de Michael Dipperstein porque é lançada de acordo com a licença de LGPL – a mesma licença Cocos2D tinha usado antes que ela adotasse a Licença de MIT – e porque fornece implementações tanto de C (usado) como de C ++.

Vai o passeio pelo código que carrega a imagem e aloca a tabela de pixelMask:

a imagem de carga e aloca a tabela de pixelMask
Objetivo-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//isto assegura que estamos carregando o bem-hd em dispositivos de Retina
NSString* fullpath = [CCFileUtils fullPathFromRelativePath:filename]; = [CCFileUtils fullPathFromRelativePath:filename];
Imagem de UIImage* = [[UIImage alloc] initWithContentsOfFile:fullpath]; = [[UIImage alloc] initWithContentsOfFile:fullpath];
      
//adquira toda a informação sobre imagem da qual precisamos
pixelMaskWidth = image.size.width; image.size.width;
pixelMaskHeight = image.size.height; image.size.height;
pixelMaskSize = pixelMaskWidth * pixelMaskHeight; pixelMaskWidth * pixelMaskHeight;
      
//aloque e compense o buffer 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

A linha 2 assegura que o sufixo-hd é acrescentado ao nome de arquivo, se houver uma versão HD da imagem disponível e a exposição de Retina é permitida. As linhas 6-8 adquirem a informação sobre imagem e o pixelMaskSize resultante, que é simplesmente o número de pixéis na imagem. Dependendo da macro USE_BITARRAY o pixelMask é declarado como bit_array_t* pixelMask; (BitArray) ou BOOL* pixelMask; (tabela de BOOL). Depending on the USE_BITARRAY macro the pixelMask is either declared as bit_array_t* pixelMask; (BitArray) or BOOL* pixelMask; (BOOL array).

Da nota aqui está que decidi usar o tipo de BOOL simplesmente porque é o tipo de dados booleano nativo no Objetivo-C. BOOL é um typedef ao trabalho a dias não assinado portanto ele está usando o mesmo tipo de dados subjacente que o código de Ernesto. Esteja sabendo que BOOL e bool (observam a letra minúscula) são dois tipos diferentes! A letra minúscula bool é um número inteiro de 32 bits. Be aware that BOOL and bool (note the lowercase) are two different types! The lowercase bool is a 32-Bit integer.

Com a organização de buffer de pixelMask e compensado, podemos esquadrinhar agora os pixéis da imagem e determinar se o valor alfabético de cada pixel lhe permite ser acrescentado ao pixelMask ou não. Para a claridade, as partes de implementação macro e BitArray USE_BITARRAY não são mostradas daqui em.

imagem de exame e bits de choque de jogo em pixelMask
Objetivo-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
//adquira os dados de pixel (mais corretamente: texels) como números inteiros não assinados de 32 bits
CFDataRef imageData = =
            CGDataProviderCopyData (CGImageGetDataProvider (imagem. 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; eu <pixelMaskSize; eu ++) NSUInteger i = 0; i < pixelMaskSize; i++)
{
   //assegure que o pixelMask é criado no topo à orientação de fundo
   Índice de NSUInteger = y * pixelMaskWidth + x;
   x ++;
   se (x == pixelMaskWidth)
   {
      x = 0;
      y-;
   }
   //a máscara fora as cores para que só o valor alfabético permaneça (8 bits superiores)
   alphaValue = imagePixels [eu] & 0xff000000;
   se (alphaValue> 0)
   {
      //adquira o valor alfabético, logo compare a alfa com o limiar alfabético
      alfa = (UInt8) (alphaValue>> 24);
      se (alfa> alphaThreshold) )
      {
         pixelMask [índice] = SIM; ] = YES;
      }
   }
}
CFRelease (imageData); );
imageData = nada; nil;
[lançamento de imagem]; ];
imagem = nada; nil;

Linhas 2-4 acesso a lucro à informação sobre pixel crua na imagem. Isto implica a utilização de várias funções de Fundação Principais. O UIImage dá-lhe o acesso ao Quartzo subjacente CGImage pela propriedade CGImage. Do CGImage um CGDataProvider é criado que permite aos dados brutos da imagem ser copiados a um 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 é simplesmente um ponteiro para uma Fundação Principal interna struct denominado __ CFData. Os detalhes deste struct não são expostos a reveladores mas pode ser procurado em CFData.c em opensource.apple.com. A função de CFDataGetBytePtr devolve um ponteiro para os bytes guardados no __ CFData struct. O que permanece simplesmente deve lançar o ponteiro para um UInt32 (um typedef do número interno não assinado) porque o UIImage é uma imagem com 32 bits 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).

O que segue em linhas 8-30 é um laço que inspeciona cada pixel na imagem para determinar se aquele pixel deve ser acrescentado como um choque mordeu ao pixelMask ou não. A primeira peculiaridade começa com linhas 11-17 que calcula o índice no pixelMask. Fazendo portanto ele fixa a questão que as imagens UIImage são de pernas para o ar acrescentando bits ao buffer de pixelMask em um fundo maneira repetindo por cima do buffer de imagePixels de cima para baixo. 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.

Na linha 20 a parte alfabética (8 bits mais à esquerda) de cada valor de pixel de 32 bits é mascarada fora. Isto estabelece todos os bits a cores (0-23) em 0 e deixa só os bits mais altos que contêm o valor alfabético de 8 bits. Se alphaValue for 0 o pixel é totalmente transparente e somente podemos mudar com a seguinte iteração. De outra maneira as linhas 24 para 28 criam o UInt8 (typedef para o trabalho a dias não assinado) valor que contém a alfa do pixel (0 a 255 variedade) que então é comparado com o alphaThreshold. Se o teste tiver sucesso, o bit correspondente no pixelMask é estabelecido em SIM. Finalmente as linhas 32-35 executam a limpeza necessária e lançam a memória de imagem. 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.

Um KKPixelMaskSprite pode ser inicializado com spriteWithFile e um valor de alphaThreshold opcional:

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

Testar para choque de um ponto na máscara de pixel

Testando se um bocado em um x especificado, y posição na máscara de pixel for estabelecido é computacionalmente barato, se não trivial. Ele simplesmente necessita o cálculo do índice na tabela, mais alguns controles de limites:

testar se um ponto está colidindo
Objetivo-C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (BOOL) pixelMaskBitAt: (CGPoint) ponto ) pixelMaskBitAt:(CGPoint)point
{
   //ao redor do ponto coordena ao número inteiro mais próximo
   número interno x = (número interno) (point.x + 0.5f); = (int)(point.x + 0.5f);
   número interno y = (número interno) (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)
   {
      volte NÃO; ;
   }
   Índice de NSUInteger = y * pixelMaskWidth + x; = y * pixelMaskWidth + x;
   devolva o pixelMask [índice]; [index];
}

A única consideração especial para fazer consiste em que os pontos podem ter partes fracionárias, mas devemos calcular o índice baseado em valores inteiros (linhas 4-5). Para levar em conta o arredondamento natural de um ponto flutuante a valores inteiros o truque simples de acrescentar 0.5f é usado. Isto assegura que cada valor de fração maior do que 0.5f é aumentado ao seguinte número inteiro mais alto, e desde o arremesso ao número inteiro simplesmente retira a parte fracionária, terminamos com valores de 1.0 a 1.4999 para ser arredondados para baixo a 1 ao passo que os valores de 1.5 a 1.9999 serão reunidos 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.

O controle de limites na linha 7 evita calcular um índice que é fora de limites, que poderiam fazer que ao app caísse. Na linha 12 o índice pixelMask é calculado baseado na fórmula confiada que faz o mapa de uma 2a posição para uma tabela 1 dimensional. Finalmente o bit naquele índice é devolvido. Em geral este teste usa arithmetics simples e é ardentemente rápido. Finally the bit at that index is returned. Overall this test uses simple arithmetics and is blazingly fast.

Mas isto é só uma parte da história. O método pixelMaskBitAt é de fato um método privado da classe KKPixelMaskSprite. Como um usuário da classe você estará enviando a mensagem pixelMaskContainsPoint em vez disso. Explicarei porque depois da amostra 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:

converta o ponto em espaço de nó & pixéis de retina
Objetivo-C
1
2
3
4
5
6
7
8
9
- (BOOL) pixelMaskContainsPoint: (CGPoint) ponto ) pixelMaskContainsPoint:(CGPoint)point
{
   //as coordenadas de ponto têm de ser quanto ao espaço do nó
   aponte = [mesmo convertToNodeSpace:point]; [self convertToNodeSpace:point];
   //ponto de alta qualidade a pixéis de Retina se necessário
   aponte = ccpMult (ponto, CC_CONTENT_SCALE_FACTOR ()); ccpMult(point, CC_CONTENT_SCALE_FACTOR());
   volte [mesmo pixelMaskBitAt:point]; self pixelMaskBitAt:point];
}

Desde que estamos trabalhando com Cocos2D, devemos considerar a possibilidade que o duende com a máscara de pixel seja feito girar ou escalado, ou ambos. O método convertToNodeSpace na linha 4 cuida disto para nós. E ele executa outra tarefa importante assegurando que a coordenada de ponto é quanto à coordenada de origem de textura de duende (esquina mais baixo deixada). O pixelMask tem a largura e a altura da imagem, pondo-o no índex com coordenadas de tela (cuja origem está na esquina esquerda mais baixa da tela) devolveria resultados incorretos. 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.

A linha 6 também se assegura que o ponto é convertido em uma coordenada de pixel multiplicando-a com CC_CONTENT_SCALE_FACTOR (). O fator de escala contente é 2 em dispositivos de Retina com a exposição de Retina permitida, de outra maneira é 1. Isto supõe que se você permitir o modo Retina e dirigir o app em um dispositivo de Retina, as imagens-hd correspondentes sejam fornecidas de todos os duendes 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.

Continue Lendo a Parte 2 …

Tive de partir este correio em dois, devido a uma limitação PHP.

Por favor continue com o segundo correio deste artigo aprendendo como a intersecção de retângulo e pixelMask com a detecção de choque pixelMask são feitos.


Ajude-me a ajudá-lo!

Passo o meu tempo inteiro subindo com novas idéias que o ajudarão a tornar-se um melhor revelador app. Muito gosto do processo de aprendizagem, empurrar de limites (meu e seu e aquela da tecnologia), tendo a liberdade de perseguir independentemente do que está na minha mente ou sua, a corajosamente o programa o que ninguém programou antes, e escrever sobre o que aprendi. Ajude-me a ajudá-lo pesquisando os produtos em Aprender Loja de Cocos2D. Help me help you by browsing the products in the Learn Cocos2D Store.


Cada venda permite-me continuar escrevendo melhor codificar, mais seminários e respostas úteis!

3 Respostas a “Detecção de Choque perfeita pelo Pixel rápida de Cocos2D com Código de exemplo (1/2)”

  1. o thozzz diz:

    grande … e perfeito

  2. [...] KKPixelMaskSprite como descrito em Detecção de Choque perfeita pelo Pixel Rápida de Cocos2D [...]

Deixe uma Resposta