objective c - applyForce(0, 400)-inconsistencia de SpriteKit
objective-c physics (2)
@ godel9 tiene una buena solución sugerida, aunque, en mis propias pruebas, la explicación dada para el comportamiento inesperado no es correcta.
De la referencia de la clase SKPhysicsBody :
La fuerza se aplica para un solo paso de simulación (un cuadro).
Refiriéndonos de nuevo a la sección de referencia de clase SKScene sobre el método -update:
... se llama exactamente una vez por fotograma, siempre que la escena se presente en una vista y no esté en pausa.
Así que podemos suponer que llamar al -applyForce: en el método de actualización de SKScene no debería causar un problema. Pero como se observa, la fuerza no excede la gravedad, a pesar de aplicar una fuerza hacia arriba mucho mayor que la gravedad (400 newtons vs 9.81).
Creé un proyecto de prueba que crearía dos nodos, uno que cae naturalmente, configurando ByGravity afectado en TRUE, y otro que llama a -applyForce con el mismo vector de gravedad esperado (0 newtons en la dirección x, y -9.81 en la dirección y). Luego calculé la diferencia en la velocidad de cada nodo en un paso de tiempo y la duración del paso de tiempo. A partir de esto, luego registré la aceleración (cambio en la velocidad / cambio en el tiempo).
Aquí hay un fragmento de mi subclase SKScene:
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
self.backgroundColor = [UIColor purpleColor];
SKShapeNode *node = [[SKShapeNode alloc] init];
node.path = CGPathCreateWithEllipseInRect(CGRectMake(0, 0, 10, 10), nil);
node.name = @"n";
node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:5];
node.position = CGPointMake(0, 450);
node.physicsBody.linearDamping = 0;
node.physicsBody.affectedByGravity = NO;
[self addChild:node];
node = [[SKShapeNode alloc] init];
node.path = CGPathCreateWithEllipseInRect(CGRectMake(0, 0, 10, 10), nil);
node.name = @"n2";
node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:5];
node.position = CGPointMake(20, 450);
node.physicsBody.linearDamping = 0;
[self addChild:node];
}
return self;
}
- (void)update:(NSTimeInterval)currentTime
{
SKNode *node = [self childNodeWithName:@"n"];
SKNode *node2 = [self childNodeWithName:@"n2"];
CGFloat acc1 = (node.physicsBody.velocity.dy - self.previousVelocity) / (currentTime - self.previousTime);
CGFloat acc2 = (node2.physicsBody.velocity.dy - self.previousVelocity2) / (currentTime - self.previousTime);
[node2.physicsBody applyForce:CGVectorMake(0, node.physicsBody.mass * -150 * self.physicsWorld.gravity.dy)];
NSLog(@"x:%f, y:%f, acc1:%f, acc2:%f", node.position.x, node.position.y, acc1, acc2);
self.previousVelocity = node.physicsBody.velocity.dy;
self.previousTime = currentTime;
self.previousVelocity2 = node2.physicsBody.velocity.dy;
}
Los resultados son inusuales. El nodo que se ve afectado por la gravedad en la simulación tiene una aceleración que se multiplica consistentemente por un factor de 150 en comparación con el nodo cuya fuerza se aplicó manualmente. He intentado esto con nodos de diferente tamaño y densidad, pero existe el mismo multiplicador escalar.
De esto debo deducir que SpriteKit internamente tiene una proporción predeterminada de ''píxel a metro''. Es decir, que cada ''metro'' es igual a exactamente 150 píxeles. Esto a veces es útil, ya que de lo contrario la escena es demasiado grande, lo que significa que las fuerzas reaccionan lentamente (piense en ver un avión desde tierra, está viajando muy rápido pero parece que se mueve muy lentamente). La documentación del kit Sprite a menudo sugiere que no se recomiendan los cálculos físicos exactos (que se ven específicamente en la sección "Fudging the Numbers"), pero esta incoherencia me tomó mucho tiempo para precisar. ¡Espero que esto ayude!
Así que tengo un objeto que tiene un cuerpo físico y la gravedad lo afecta. También es dinámico.
Actualmente, cuando los usuarios tocan la pantalla, ejecuto el código:
applyForce (0, 400)
El objeto sube unos 200 y luego vuelve a caer debido a la gravedad. Esto solo pasa parte del tiempo. Otras veces, hace que el objeto solo mueva 50 unidades en la dirección Y.
No puedo encontrar un patrón ... Pongo mi proyecto en Dropbox para que pueda abrirse si alguien está dispuesto a verlo.
https://www.dropbox.com/sh/z0nt79pd0l5psfg/bJTbaS2JpY
EDITAR: Parece que esto sucede cuando el jugador se está recuperando del suelo ligeramente por un momento después del impacto. ¿Hay alguna forma de hacerlo para que el jugador no rebote en absoluto?
EDIT 2: traté de resolver esto usando el parámetro de fricción y solo permití que el jugador "saltara" cuando la fricción era = 0 (pensarías que sería en todos los casos donde el jugador estaba en el aire) pero la fricción parece ser mayor que 0 en todo momento. ¿De qué otra manera podría detectar si el jugador está tocando un objeto (aparte de usar la ubicación y)?
Gracias
Solución sugerida
Si está intentando implementar una función de salto, le sugiero que mire applyImpulse
lugar de applyForce
. Aquí está la diferencia entre los dos, como se describe en la Guía de programación del kit de Sprite :
Puedes elegir aplicar una fuerza o un impulso:
Se aplica una fuerza durante un período de tiempo basado en la cantidad de tiempo de simulación que transcurre entre la aplicación de la fuerza y el momento en que se procesa el siguiente fotograma de la simulación. Por lo tanto, para aplicar una fuerza continua a un cuerpo, debe realizar las llamadas de método apropiadas cada vez que se procese un nuevo marco. Las fuerzas se usan generalmente para efectos continuos.
Un impulso realiza un cambio instantáneo a la velocidad del cuerpo que es independiente de la cantidad de tiempo de simulación que ha pasado. Los impulsos se usan generalmente para cambios inmediatos en la velocidad de un cuerpo.
Un salto es realmente un cambio instantáneo a la velocidad de un cuerpo, lo que significa que debes aplicar un impulso en lugar de una fuerza. Para utilizar el método applyImpulse:
calcule el cambio instantáneo deseado en la velocidad, multiplíquelo por la masa del cuerpo y utilícelo como el parámetro de impulse
en la función. Creo que verás mejores resultados.
Explicación de comportamiento inesperado
Si llama a applyForce:
fuera de su función update:
lo que está sucediendo es que su fuerza se está multiplicando por el tiempo transcurrido entre la aplicación de la fuerza y el procesamiento del siguiente fotograma de la simulación. Este multiplicador no es una constante, por lo que estás viendo un cambio diferente en la velocidad cada vez que llamas applyForce:
de esta manera.