Khalifa AlHamdi
Khalifa AlHamdi

Reputation: 1

How do I applyImpulse at a certain angle in SpriteKit using Swift 5?

I am trying to throw a ball in an iOS game in the direction of touchEnded method. I have searched a number of threads but some are old and for the others I could not figure out the issue.

What I'm trying to do is as follows:

1- Add A ball as SKShapeNode - Set affectedByGravity to false.

2- On touchesEnded - I get the location CGPoint (somehow I get the y position reversed - I correct it).

3- I calculate the angle bearing using atan().

4- I calculate the CGVector dx and dy impulses based on the sin() and cos() of the angle.

5- I applyImpulse to the ball based on the CGVector above.

My issue is that the ball is not shot exactly to the touchesEnded direction. If the touchesEnded direction is vertically below the ball, then it works. The further away from vertical, the larger the deviation.

To help troubleshoot and visualize the issue, I added a line and end blue ball in the direction that the ball should follow. The red ball should ideally collide with the blue ball. However, the real direction I get is as per the green ball added. The green ball should ideally be on the line.

I was trying to do exactly like what was done here but I don't get the results: SpriteKit physics applyImpulse in direction

Appreciate your help. '''

import SpriteKit

class GameScene: SKScene {
    var sprite = SKShapeNode()
    var radius: CGFloat = 10
    var ballSpawnPosition: CGPoint = CGPoint(x: 0, y: 0)
    override func didMove(to view: SKView) {

        self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
        ballSpawnPosition = CGPoint(x: frame.midX, y: frame.maxY - 50)

        spawnBall()
    }

    func spawnBall() {

        sprite = SKShapeNode(circleOfRadius: radius)
        sprite.physicsBody = SKPhysicsBody(circleOfRadius: radius)
        sprite.physicsBody?.collisionBitMask = 0x1
        sprite.fillColor = .red
        sprite.position = ballSpawnPosition
        addChild(sprite)
        sprite.physicsBody?.affectedByGravity = false
    }


    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            let position = touch.location(in: view)
            let blueTestTouchPointBall = SKShapeNode(circleOfRadius: radius)
            blueTestTouchPointBall.physicsBody = SKPhysicsBody(circleOfRadius: radius)
            blueTestTouchPointBall.physicsBody?.categoryBitMask = 0x1
            blueTestTouchPointBall.fillColor = .blue
            let y = frame.size.height - position.y
            blueTestTouchPointBall.position = CGPoint(x: position.x, y: y)
            blueTestTouchPointBall.physicsBody?.affectedByGravity = false
            addChild(blueTestTouchPointBall)

            var path = CGMutablePath()
            path.move(to: ballSpawnPosition)
            path.addLine(to: CGPoint(x: position.x, y: y))
            path.closeSubpath()

            let line = SKShapeNode()
            line.path = path
            line.strokeColor = .white
            line.lineWidth = 2
            addChild(line)
        }
    }


    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

        if let touch = touches.first { //should not work if dragged.
            let position = touch.location(in: view)
            let dx = position.x - ballSpawnPosition.x
            let dy = frame.size.height - abs( position.y - ballSpawnPosition.y)
            let ang = atan(abs(dx)/abs(dy))
            //let tang = sqrt(dx*dx + dy*dy)
            let impulse: CGFloat = 15.0
            let x_impulse = impulse * sin(ang) * dx/abs(dx)
            let y_impulse = -1.0 * impulse * cos(ang)
//            let x_impulse = impulse * dx/abs(dx) * dx/tang
//            let y_impulse = impulse * -1 * dy/tang
            sprite.physicsBody?.applyImpulse(CGVector(dx: x_impulse , dy: y_impulse ))


            let greenTestBall = SKShapeNode(circleOfRadius: radius/2)
            greenTestBall.fillColor = .green
            greenTestBall.position = CGPoint(x: ballSpawnPosition.x + 300 * sin(ang) * dx/abs(dx), y: ballSpawnPosition.y - 300 * cos(ang) )
            addChild(greenTestBall)

        }

    }

}

'''

Upvotes: 0

Views: 258

Answers (1)

Left as an exercise
Left as an exercise

Reputation: 517

You are mixing points in different coordinate systems. The location of the UITouch should be converted to the node in which the ball resides (typically the scene, but it could be another node) using the location(in:) method of UITouch and passing in the SKNode. See here for more on coordinate conversions in SpriteKit. Since the ball is a child of the scene, change

let position = touch.location(in: view)

to

let position = touch.location(in: self) // i.e. the `GameScene`

The angle calculation also needs to be adjusted supposing that you are using the ball spawn position as the reference point and the take the angle with respect to the vertical. Change

let dy = frame.size.height - abs(position.y - ballSpawnPosition.y)

to

let dy = abs(position.y - ballSpawnPosition.y)

Changing the above creates what I believe you are looking for. Note that momentum is additive: applying an impulse right while the ball is moving left will not necessarily cause the ball to start moving left (unless the impulse is great enough)

Upvotes: 1

Related Questions