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!
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
}