Search code examples
swiftspriteskshapenode

Drag SKShapeNode respecting other SKShapeNode's boundaries


I need to drag a Shape Node avoiding other shapes, if they hit then I would like boundaries to not be crossed. So if I have a ceiling and I drag the a ball up to it, I want it to hit the ceiling and not go through it.

I have a 'game' where I create a path between A floor and ceiling (SKShapeNode)shape nodes. I then have "ball" SKShapeNode in a circle shape to represent the player.

The Floor and ceilings are created from a fairly complicated UIBezierPath

The two walls are top and bottom and are animated by moving the x position in the func update(_ currentTime: TimeInterval) method.

Collision detection and contact testing are setup on the walls and the ball and these are working well for the most part. However after trying two methods for the ball drag I have come into some issues

Changing position Using the .position property to update the balls movement in the touchesMoved function

 override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
  let translation = CGVector(dx: location.x - previousLocation.x, dy: location.y - previousLocation.y)
        var newPosition = ball.position 
        newPosition.x += translation.dx
        newPosition.y += translation.dy
        ball.position = newPosition
}

This works well for the most part, the drag is up to date with the fingers movement. When dragged slowly it respects the boundaries of the floor and the ceiling However - if I drag quickly the ball will go straight through either the ceiling or the floor

ApplyImpulse Applying an impulse to the ball

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
                guard let touch = touches.first, let location = touchLocation else { return }
                let newLocation = touch.location(in: self)
                let scalingFactor: CGFloat = 0.1
                let touchMovement = CGVector(dx: newLocation.x - location.x, dy: newLocation.y - location.y)
                touchForce = CGVector(dx: touchMovement.dx * scalingFactor, dy: touchMovement.dy * scalingFactor)
                touchLocation = newLocation
                ball.physicsBody?.applyImpulse(touchForce!)
}

This respects the boundaries, but has an ease in / out effect which is not the desired application of movement for this experience. It's not really representative of the amount of dragging happening either.

Ideally I need something in-between both of these methods

Additional code for reference

Ceiling / floor type object

class Wall: SKShapeNode {
    init(size: CGSize) {
        super.init()
        let bottomPath = UIBezierPath()
        bottomPath.move(to: CGPoint(x: 0, y: -19.99))
        bottomPath.addLine(to: CGPoint(x: 5723.5, y: -19.99))
        bottomPath.addLine(to: CGPoint(x: 5723.5, y: 35.51))
        bottomPath.addLine(to: CGPoint(x: 4915.5, y: 35.51))
        bottomPath.addCurve(to: CGPoint(x: 4892.87, y: 44.88), controlPoint1: CGPoint(x: 4907.01, y: 35.51), controlPoint2: CGPoint(x: 4898.87, y: 38.88))
       /// many more paths 

        bottomPath.close()
        self.path = bottomPath.cgPath
        strokeColor = UIColor.green
        fillColor = UIColor.clear
        lineWidth = 5
        lineCap = .round
        lineJoin = .round
        zPosition = 0
        name = "bottom"
        physicsBody = SKPhysicsBody(edgeLoopFrom: self.path!)
        physicsBody?.isDynamic = false
        physicsBody?.affectedByGravity = false
        physicsBody?.categoryBitMask = 1
        physicsBody?.contactTestBitMask = 2
        physicsBody?.collisionBitMask = 1
        physicsBody?.restitution = 0
        physicsBody?.usesPreciseCollisionDetection = true
        physicsBody?.density = 100
        physicsBody?.mass = 100
    }

Ball

class Ball: SKShapeNode {
    convenience init(radius: CGFloat) {
        self.init(circleOfRadius: radius)
        
        fillColor = .red
        zPosition = 0
        physicsBody = SKPhysicsBody(circleOfRadius: radius)
        physicsBody?.affectedByGravity = false
        physicsBody?.categoryBitMask = 2
        physicsBody?.contactTestBitMask = 1
        physicsBody?.collisionBitMask = 1
        physicsBody?.restitution = 1
        physicsBody?.usesPreciseCollisionDetection = true
    }
}

Basically the game is a simple - drag along a path and count how long it takes and how many times you hit the walls. But my lack of knowledge on the best way to achieve a reflective drag speed as well as object boundaries being respected is proving to make this hard to achieve so any help is much appreciated

Thanks!


Solution

  • I was able to resolve this by adding a check to ensure that the proposed move position was not intersecting the shape

    func intersectsWalls(_ position: CGPoint) -> Bool {
     if bottomShape.contains(position) {
                return true
            }
            if topShape.contains(position) {
                return true
            }
            return false
        }