¿Por qué se llama a doBeginContact varias veces?

En un juego de iOS que usa Sprite Kit junto con la detección de contacto en el motor de física incorporado de Sprite Kit, disminuyo el número de vidas del héroe en una cada vez que entra en contacto con un enemigo. Esto se hace desde el método didBeginContact . Sin embargo, parece que ese método no solo se llama una vez, cuando el contacto comienza, sino que se llama continuamente siempre que el Héroe y el enemigo se superpongan: cuando establezco un punto de interrupción en ese método, puedo ver que es exactamente el mismo instancias corporales físicas que existen como contact.bodyA y contact.bodyB . El resultado es que el héroe perderá múltiples vidas, aunque solo pase un solo enemigo.

Si el héroe se encuentra con el mismo enemigo más tarde, debería obtener una vida más sustraída, y por lo tanto no puedo simplemente mantener un hash seenEnemies para resolver el problema anterior.

La pregunta ahora es: ¿cómo te asegurarías de que solo se restara un resultado por cada contacto entre héroe y enemigo?

Tuve el mismo problema (la puntuación aumentaba varias veces para un solo enemigo destruido y se perdían múltiples puntos de vida por una sola instancia de daño.) Un usuario en los foros de Apple piensa que es un error en [SKPhysicsBody bodyWithTexture: size:] pero yo No creo que ese sea el caso, porque también estaba sucediendo con otros constructores.

En primer lugar, la categoryBitMask y contactTestBitMask son muy importantes, obviamente. Eche un vistazo al código de muestra SpriteKit Physics Collisions de Apple:

// Los contactos suelen ser un problema de doble envío; el efecto que desea se basa en el tipo de ambos cuerpos en el contacto. Esto muestra esto de una manera de fuerza bruta, al verificar los tipos de cada uno. Un ejemplo más complicado podría usar métodos en objetos para realizar la verificación de tipos.

// Los contactos pueden aparecer en cualquier orden , por lo que normalmente deberías verificarlos uno contra el otro. En este ejemplo, los tipos de categoría están bien ordenados, por lo que el código intercambia los dos cuerpos si están desordenados. Esto permite que el código solo pruebe colisiones una vez.

Lo que hice para resolverlo fue establecer una bandera después de manejar cada condición. En mi caso, estaba probando si bodyA.node.parent era nulo en didBeginContact , porque llamé a removeFromParent() sobre los nodos misiles / enemigos para destruirlos.

Creo que deberías esperar que el evento se active varias veces y tu código debe asegurarse de que se procese solo una vez.

La razón por la que se hace disparar el método didBeginContact varias veces se debe a que tiene múltiples puntos de contacto en formas cóncavas.

Si miras la imagen de abajo, verás que tengo 2 sprites, una estrella negra y un rectángulo rojo. Cuando la estrella negra golpea el rectángulo rojo, lo golpea en varios puntos, rodeados de un círculo azul. Sprite Kit hará una llamada para cada intersección de línea, para que el desarrollador pueda usar la variable contactPoint para cada uno de estos contactos.

enter image description here

Me encontré con el mismo problema. En mi caso, el didBeginContact() fue llamado muchas veces (conté hasta 5 veces) por un contacto de una bala con el enemigo. Como la viñeta es un formato de círculo simple, estoy de acuerdo con @SFX en que no puede ser un error solo en Texture-Bodies. Las pruebas han demostrado que no hubo una llamada a update() entre las llamadas a didBeginContact() . Entonces la solución es simple (Swift):

 var updatesCalled = 0 ... internal update() { updatesCalled ++ } ... internal func didBeginContact(contact: SKPhysicsContact) { NSLog("didBeginContact: (\(contact.contactPoint.x), \(contact.contactPoint.y)), \(updatesCalled)") if(updatesCalled == 0) {return} // No real change since last call updatesCalled = 0 ... your code here ... } 

Intenté didEndContact() pero eso no fue llamado en absoluto. No investigué más sobre esto.

Por cierto: acabo de cambiar de Android, y estoy impresionado por la facilidad y la estabilidad de este sistema 🙂

Descubrí una solución fácil:

Simplemente cambie el valor de categoryBitMask de cualquiera de los dos cuerpos a 0 o valor no utilizado inmediatamente después de detectar el contacto.

Por ejemplo:

 if (firstBody.categoryBitMask == padCategory && secondBody.categoryBitMask == colorBallCategory) { secondBody.categoryBitMask = 0; // DO OTHER THING HERE } 

Aquí hay una opción que hace que el jugador sea invulnerable después de ser golpeado por un tiempo determinado:

A. Crea una variable que hace que el jugador sea invulnerable a perder una vida después de ser golpeado por unos segundos.

  1. Cree una variable booleana global llamada isInvuln (establecida en FALSE) y una NSTimeInterval llamada invulnTime.
  2. En el método que maneja al jugador y al enemigo que establecen contacto, verifica si Invuln es Falso antes de quitarse la vida. (si isInvuln es cierto … no hagas nada)
  3. Si isInvuln es falso, toma una vida y establece isInvuln en verdadero.

      if(self.isInvuln == FALSE){ self.player.lives-=1; self.isInvuln = True;} 
  4. Agregue a su updateWithCurrentTime:

      if(self.isInvuln==True){ self.invulnTime += timeSinceLast;} if (self.invulnTime > 3) { self.isInvuln = FALSE:} self.invulnTime= 0; 

Esto hará que cuando un enemigo y un jugador colisionen, el jugador pierda una vida y se vuelva invulnerable por 3 segundos. Después de esos 3 segundos, el jugador puede recibir daño nuevamente. Si el enemigo contacta al jugador dentro de los 3 segundos invulnerables, el método de contacto no hace nada. Espero que esto ayude a generar ideas para abordar su problema.

En mi experiencia, didEndContact y didBeginContact son llamados varias veces mientras los objetos se superponen. Esto también está sucediendo en SceneKit usando iOS 9, así que tengo que asumir que es un comportamiento intencionado.