I am making a Swift game similar to Air Hockey in SpriteKit.
I am trying to have accurate/expected 'impulses' applied to the puck when it is struck by the player's mallet.
I have access to the player's velocity. I also have accurate collision detection between the two circles by using func didBegin(_ contact: SKPhysicsContact)
. Additionally I have the expected direction the ball should bounce based on where it struck the player's mallet (since its a circle this direction might be different than you would expect by looking at the player's dx and dy.)
This is what Im currently doing but it feels a bit unnatural and off:
func didBegin(_ contact: SKPhysicsContact)
{
if (contact.bodyA.categoryBitMask == BodyType.ball.rawValue && contact.bodyB.categoryBitMask == BodyType.player.rawValue) && (contact.bodyA.contactTestBitMask == 25 || contact.bodyB.contactTestBitMask == 25) && bottomTouchForCollision == true
{
ball!.physicsBody!.applyImpulse(CGVector(dx: CGFloat(UserDefaults.standard.float(forKey: "BottomVelocity")) * contact.contactNormal.dx, dy: CGFloat(UserDefaults.standard.float(forKey: "BottomVelocity")) * contact.contactNormal.dy))
}
}
What is the best approach to have an accurate and responsive impulse to the ball. I think solely depending on the contact.contactNormal.dy
for the direction feels unnatural because that is the direction expected if the player's circle was in one place not moving. I've been struggling with this issue for weeks and don't know how to best approach it. Code explanation is appreciated but anything helps.
I've asked this question in a similar way and received this as a response:
"Consider the case of the ball hitting a stationary mallet. It will reflect off with the same speed, and the final direction of motion is such that angle-of-incidence equals angle-of-reflection at the contact point. Then for a mallet moving with velocity vector v, subtract v from all velocities to transform to the stationary-mallet case. Calculate ball velocity in the transformed problem. Then transform back by adding v to the result. (If you're calculating it like that, you don't have to applyImpulse, just set the new velocity directly.)"
I am having trouble understanding how to implement this idea, but if anyone could help with pseudocode or code that would be great!
Edit: Here is how I have is with help from below.
if (contact.bodyA.categoryBitMask == BodyType.ball.rawValue && contact.bodyB.categoryBitMask == BodyType.player.rawValue) && (contact.bodyA.contactTestBitMask == 75 || contact.bodyB.contactTestBitMask == 75) && northTouchForCollision == true
{
let vmallet = CGVector(dx: CGFloat(UserDefaults.standard.float(forKey: "NorthForceDX")), dy: CGFloat(UserDefaults.standard.float(forKey: "NorthForceDY")))
let vball = ball?.physicsBody?.velocity
let vrelativedx = vball!.dx - vmallet.dx
let vrelativedy = vball!.dy - vmallet.dy
let vrelative = CGVector(dx: vrelativedx, dy: vrelativedy)
let c = (vrelative.dx * contact.contactNormal.dx) + (vrelative.dy * contact.contactNormal.dy)
let vperpendicular = CGVector(dx: contact.contactNormal.dx * c, dy: contact.contactNormal.dy * c)
let vtangential = CGVector(dx: vrelative.dx - vperpendicular.dx, dy: vrelative.dy - vperpendicular.dy)
// vtangential is unchanged in the collision, vperpendicular reflects
let newvrelative = CGVector(dx: vtangential.dx - vperpendicular.dx, dy: vtangential.dy - vperpendicular.dy)
let newvball = CGVector(dx: newvrelative.dx + vmallet.dx, dy: newvrelative.dy + vmallet.dy)
// set ball velocity to newvball
// ball?.physicsBody?.velocity = newvball
ball!.physicsBody!.applyImpulse(newvball)
}