I have a question about (continuous) SpriteKit collision detection.
I have a SwiftUI/SpriteKit project in which balls fall from above and the user tries to catch the balls with a cup.
The user moves the cup horizontally by dragging their finger.
The physics work well with one exception -- the balls properly collide with each other, and with the cup, except when the cup is being moved.
When the cup is being moved by the user, the balls that had fallen into the cup tunnel right through the side of the cup.
Again, the balls collide with the cup correctly, and settle in the cup, except when the cup is moving.
EDIT: I added categoryBitMask and collisionBitMask to both the balls and the cup, as suggested in the comments. But the problem remains.
enum CollisionTypes: UInt32 {
case ball = 1
case cup = 2
}
The cup:
func cup() -> SKSpriteNode {
let screenSize = UIScreen.main.bounds
let cupNode = SKSpriteNode(imageNamed: "cup.png")
let cupNodeChild = SKSpriteNode(imageNamed: "cupOutline.png")
cupNode.position = CGPoint(x: screenSize.width*0.5, y: MainData.cupY)
cupNode.size = CGSize(width: screenSize.width * 0.25, height: screenSize.width * 0.35)
cupNode.name = "cup"
cupNode.zPosition = 1.0
cupNodeChild.size = CGSize(width: screenSize.width * 0.25, height: screenSize.width * 0.35)
cupNodeChild.name = "cupCollisionNode"
cupNodeChild.physicsBody = SKPhysicsBody(texture: cupNodeChild.texture!, size: CGSize(width: screenSize.width * 0.25, height: screenSize.width * 0.35))
cupNodeChild.physicsBody?.restitution = 0.0
cupNodeChild.physicsBody?.isDynamic = false
cupNodeChild.physicsBody?.usesPreciseCollisionDetection = true
cupNodeChild.physicsBody?.categoryBitMask = CollisionTypes.cup.rawValue
cupNodeChild.physicsBody?.collisionBitMask = CollisionTypes.ball.rawValue
cupNode.addChild(cupNodeChild)
return cupNode
}
The balls:
func initializeBalls(screenWidth: CGFloat, screenHeight: CGFloat) {
MainData.balls.removeAll()
for i in 0..<numOfBalls {
let radius = screenWidth * 0.02
let x = screenWidth * CGFloat.random(in: 0...1)
let y = screenHeight
let ballNode = SKSpriteNode(imageNamed: "ball.png")
ballNode.position = CGPoint(x: x, y: y)
ballNode.size = CGSize(width: radius*2, height: radius*2)
ballNode.name = "ball\(i)"
ballNode.physicsBody = SKPhysicsBody(circleOfRadius: radius)
ballNode.physicsBody?.usesPreciseCollisionDetection = true
ballNode.physicsBody?.mass = 5.0
ballNode.physicsBody?.isDynamic = true
ballNode.physicsBody?.categoryBitMask = CollisionTypes.ball.rawValue
ballNode.physicsBody?.collisionBitMask = CollisionTypes.cup.rawValue
ballNode.physicsBody!.contactTestBitMask = ballNode.physicsBody!.collisionBitMask
ballNode.physicsBody?.restitution = 0.1
MainData.balls.append((
x: x,
y: y,
radius: radius,
img: "ball.png",
node: ballNode
))
}
}
Here's how I move the cup, in my GameScene:
func touchMoved(toPoint pos : CGPoint) {
let action = SKAction.move(to: CGPoint(x: pos.x, y: MainData.cupY), duration: 0)
cupNode?.run(action)
}
Why is this tunneling happening, and how do I avoid it?
Related
So I'm creating a game in Swift with Spritekit and have ran into an issue with getting my game to work. I'm still a beginner with programming so I've likely missed out on a solution to this myself.
Anyway, so the game concept is a simple arcade vertical scroller that involves a player trying to dodge platforms as it descends downward. The mechanics (so far) are a stationary player on the y axis that can move left and right along the x axis while the platforms scroll upward along with the background moving with the platforms to give a visual effect of descent. I've gotten a build working to be fully playable, but there's an issue with spawning the platforms perfectly spaced out. Here's a sketch:
Concept Image
The picture on the left what I'm trying to achieve, while the one on the right is my current and flawed method. The main issue with the one on the right, is that it uses a collision to trigger spawning which means the spawn trigger node (red line) has to be 1 pixel tall to allow for perfect spacing. If the spawn trigger node is more than 1 pixel tall, then the collision may not trigger on that the first pixel of contact and trigger the node a few pixels deep which throws off the entire spacing. Also if the spawn trigger is only 1 pixel tall, it often won't trigger unless the everything is scrolling at slow speeds.
I've tried to think of other methods to approach this but I'm at a loss. I cannot use a simple timer to spawn nodes at intervals because the speed at which the game scrolls is variable and is constantly changing by player controls. The only two other options I can think of (which I don't know how to do either) is either spawn node sets at fixed y-positions and set that on a loop, or change it so the player is actually descending downward while everything is generating around it (seems tougher and maybe unnecessary). I'm considering just rewriting my createPlatforms() method if I need to, but here's the code for that and the background anyway:
var platformGroup = Set<SKSpriteNode>()
var platformSpeed: CGFloat = 0.6 { didSet { for platforms in platformGroup { platforms.speed = platformSpeed } } }
var platformTexture: SKTexture!
var platformPhysics: SKPhysicsBody!
var platformCount = 0
var backgroundPieces: [SKSpriteNode] = [SKSpriteNode(), SKSpriteNode()]
var backgroundSpeed: CGFloat = 1.0 { didSet { for background in backgroundPieces { background.speed = backgroundSpeed } } }
var backgroundTexture: SKTexture! { didSet { for background in backgroundPieces { background.texture = backgroundTexture } } }
func createPlatforms() {
let min = CGFloat(frame.width / 12)
let max = CGFloat(frame.width / 3)
var xPosition = CGFloat.random(in: -min ... max)
if platformCount >= 20 && platformCount < 30 {
stage = 0
setTextures()
xPosition = frame.size.width * 0.125
} else if platformCount == 30 {
stage = 2
setTextures()
} else if platformCount >= 50 && platformCount < 60 {
stage = 0
setTextures()
xPosition = 184
} else if platformCount == 60 {
stage = 3
setTextures()
}
platformPhysics = SKPhysicsBody(rectangleOf: CGSize(width: platformTexture.size().width, height: platformTexture.size().height))
let platformLeft = SKSpriteNode(texture: platformTexture)
platformLeft.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformLeft.physicsBody?.isDynamic = false
platformLeft.physicsBody?.affectedByGravity = false
platformLeft.physicsBody?.collisionBitMask = 0
platformLeft.scale(to: CGSize(width: platformLeft.size.width * 3, height: platformLeft.size.height * 3))
platformLeft.zPosition = 20
platformLeft.name = "platform"
platformLeft.speed = platformSpeed
let platformRight = SKSpriteNode(texture: platformTexture)
platformRight.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformRight.physicsBody?.isDynamic = true
platformRight.physicsBody?.collisionBitMask = 0
platformRight.scale(to: CGSize(width: platformRight.size.width * 3, height: platformRight.size.height * 3))
platformRight.zPosition = 20
platformRight.name = "platform"
platformRight.speed = platformSpeed
let scoreNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: frame.width, height: 32))
scoreNode.physicsBody = SKPhysicsBody(rectangleOf: scoreNode.size)
scoreNode.physicsBody?.isDynamic = false
scoreNode.zPosition = 100
scoreNode.name = "scoreDetect"
scoreNode.speed = platformSpeed
let platformTrigger = SKSpriteNode(color: UIColor.orange, size: CGSize(width: frame.width, height: 4))
platformTrigger.physicsBody = SKPhysicsBody(rectangleOf: platformTrigger.size)
platformTrigger.physicsBody?.isDynamic = true
platformTrigger.physicsBody?.affectedByGravity = false
platformTrigger.physicsBody?.categoryBitMask = Collisions.detect
platformTrigger.physicsBody?.contactTestBitMask = Collisions.spawn
platformTrigger.physicsBody?.collisionBitMask = 0
platformTrigger.physicsBody?.usesPreciseCollisionDetection = true
platformTrigger.zPosition = 100
platformTrigger.name = "platformTrigger"
platformTrigger.speed = platformSpeed
let newNodes: Set<SKSpriteNode> = [platformLeft, platformRight, scoreNode, platformTrigger]
for node in newNodes {
platformGroup.insert(node)
}
let yPosition = spawnNode.position.y - transitionPlatform.size().height
let gapSize: CGFloat = -frame.size.width / 6
print(gapSize)
platformLeft.position = CGPoint(x: xPosition + platformLeft.size.width - gapSize, y: -yPosition)
platformRight.position = CGPoint(x: xPosition + gapSize, y: -yPosition)
scoreNode.position = CGPoint(x: frame.midX, y: platformLeft.position.y - platformLeft.size.height / 2)
platformTrigger.position = CGPoint(x: frame.midX, y: platformLeft.position.y)
print(platformLeft.position.y)
print(platformLeft.frame.midY)
let endPosition = frame.maxY + frame.midY
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
for node in newNodes {
let moveSequence = SKAction.sequence([
moveAction,
SKAction.removeFromParent(),
SKAction.run {
self.platformGroup.remove(node)
}
])
addChild(node)
nodeArray.append(node)
node.run(moveSequence)
}
platformCount += 1
}
func startPlatforms() {
let create = SKAction.run { [unowned self] in
self.createPlatforms()
}
run(create)
}
func createBackground() {
for i in 0 ... 1 {
let background = backgroundPieces[i]
background.texture = backgroundTexture
background.anchorPoint = CGPoint(x: 0, y: 0)
background.zPosition = -5
background.size = CGSize(width: frame.size.width, height: frame.size.width * 2.5)
background.position = CGPoint(x: 0, y: background.size.height + (-background.size.height) + (-background.size.height * CGFloat(i)))
self.addChild(background)
nodeArray.append(background)
let scrollUp = SKAction.moveBy(x: 0, y: background.size.height, duration: 5)
let scrollReset = SKAction.moveBy(x: 0, y: -background.size.height, duration: 0)
let scrollLoop = SKAction.sequence([scrollUp, scrollReset])
let scrollForever = SKAction.repeatForever(scrollLoop)
background.run(scrollForever)
}
}
Does anybody have any suggestions on how I approach this or change it so it would work perfectly everytime?
My collision detection is working most of the time, but sometimes one physics body will go inside the other, when they should collide. I have a car with physics and road sides with physics. The car will blow through the road sides occasionally. I'm moving the car forward by position, however I have also tried moving forward by force and the same thing happens. Does anyone have any idea what would be causing this?
I've defined my physics categorys like so;
struct PhysicsCategory: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) { self.rawValue = rawValue }
static let CarCategory = PhysicsCategory(rawValue: 0b00001)
static let RoadSidesCategory = PhysicsCategory(rawValue: 0b00010)
static let NoCollisionsCategory = PhysicsCategory(rawValue: 0b00000)
}
Giving the car physics like so;
func giveCarPhysics(physics: Bool) {
//give physicsBody to the car and set to collide with the background
if physics {
self.size = CGSize(width: self.size.width, height: self.size.height)
self.physicsBody = SKPhysicsBody(rectangleOf: CGSize (width: self.size.width * 0.6, height: self.size.height * 0.9), center: CGPoint(x: self.anchorPoint.x, y: self.anchorPoint.y + self.size.height / 2))
self.physicsBody?.mass = vehicleMass
self.physicsBody?.usesPreciseCollisionDetection = true
self.physicsBody?.friction = 0.5
self.physicsBody?.restitution = 0.2
self.physicsBody?.affectedByGravity = false
self.physicsBody?.isDynamic = true
self.physicsBody?.linearDamping = airResistance
self.physicsBody?.categoryBitMask = PhysicsCategory.CarCategory.rawValue
self.physicsBody?.collisionBitMask = PhysicsCategory.RoadSidesCategory.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.RoadSidesCategory.rawValue
}
}
And then giving the road sides physics;
func createPhysicsRight() {
for (index, backgroundImageRight) in self.physicsRight.enumerated() {
let roadSpriteR = SKSpriteNode()
roadSpriteR.anchorPoint = CGPoint(x: 0.5, y: 0.5)
roadSpriteR.position = CGPoint(x: 0, y: CGFloat(self.physicsCounterR) * self.frame.size.height)
roadSpriteR.zPosition = 0
roadSpriteR.name = "RoadR \(index)"
print(roadSpriteR.name as Any)
roadSpriteR.physicsBody = SKPhysicsBody(polygonFrom: backgroundImageRight)
roadSpriteR.physicsBody?.mass = backgroundMass
roadSpriteR.physicsBody?.affectedByGravity = false
roadSpriteR.physicsBody?.isDynamic = false
roadSpriteR.physicsBody?.categoryBitMask = PhysicsCategory.RoadSidesCategory.rawValue
roadSpriteR.physicsBody?.collisionBitMask = PhysicsCategory.CarCategory.rawValue
roadSpriteR.physicsBody?.contactTestBitMask = PhysicsCategory.CarCategory.rawValue
roadSpriteRArray.append(roadSpriteR)
}
}
func createPhysicsLeft() {
for (index, backgroundImageLeft) in self.physicsLeft.enumerated() {
let roadSpriteL = SKSpriteNode()
roadSpriteL.anchorPoint = CGPoint(x: 0.5, y: 0.5)
roadSpriteL.position = CGPoint(x: 0, y: CGFloat(self.physicsCounterL) * self.frame.size.height)
roadSpriteL.zPosition = 0
roadSpriteL.name = "RoadL \(index)"
roadSpriteL.physicsBody = SKPhysicsBody(polygonFrom: backgroundImageLeft)
roadSpriteL.physicsBody?.mass = backgroundMass
roadSpriteL.physicsBody?.affectedByGravity = false
roadSpriteL.physicsBody?.isDynamic = false
roadSpriteL.physicsBody?.categoryBitMask = PhysicsCategory.RoadSidesCategory.rawValue
roadSpriteL.physicsBody?.collisionBitMask = PhysicsCategory.CarCategory.rawValue
roadSpriteL.physicsBody?.contactTestBitMask = PhysicsCategory.CarCategory.rawValue
roadSpriteLArray.append(roadSpriteL)
}
}
I am trying to create a spinning fortune wheel action via SKAction. I have a SKNode which used as wheel, this SKNode is a circle that divided to four quarters (each quarter in different color). also I set an SKAction (which is repeating for 10 counts) that spin the SKNode around fixed point (the node's center). The problem is that the action is running well but it stops suddenly and not slowing down - like a real wheel. I don't really have an idea how to set this animation, I mean to slow the spinning down before the action is stop.
Here is my code so far:
class GameScene: SKScene {
let colors = [SKColor.yellow, SKColor.red, SKColor.blue, SKColor.purple]
override func didMove(to view: SKView) {
createWheel()
let sq = CGRect(x: size.width/2, y: size.height/2, width: 300, height: 300)
let sqx = SKShapeNode(rect: sq)
sqx.lineWidth = 2
sqx.fillColor = .clear
sqx.setScale(1.0)
addChild(sqx)
}
func createWheel() {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: -200))
path.addArc(withCenter: CGPoint.zero,radius: 200,startAngle: CGFloat(0.0), endAngle: CGFloat(3.0 * Double.pi / 2),clockwise: false)
path.addLine(to: CGPoint(x: 200, y: 0))
let obstacle = obstacleByDuplicatingPath(path, clockwise: true)
obstacle.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(obstacle)
let rotateAction = SKAction.rotate(byAngle: CGFloat((3.0 * CGFloat(Double.pi / 2)) - 90), duration: 0.5)
//obstacle.run(SKAction.repeatForever(rotateAction))
obstacle.run(SKAction.repeat(rotateAction, count: 10))
}
func obstacleByDuplicatingPath(_ path: UIBezierPath, clockwise: Bool) -> SKNode {
let container = SKNode()
var rotationFactor = CGFloat(Double.pi / 2)
if !clockwise {
rotationFactor *= -1
}
for i in 0...3 {
let section = SKShapeNode(path: path.cgPath)
section.fillColor = colors[i]
section.strokeColor = colors[i]
section.zRotation = rotationFactor * CGFloat(i);
let origin = CGPoint(x: 0.0, y: 0.0)
switch i {
case 0:
section.position = CGPoint(x: (origin.x + 10), y: (origin.y - 10))
case 1:
section.position = CGPoint(x: (origin.x + 10), y: (origin.y + 10))
case 2:
section.position = CGPoint(x: (origin.x - 10), y: (origin.y + 10))
case 3:
section.position = CGPoint(x: (origin.x - 10), y: (origin.y - 10))
default:
print("bolbol")
}
container.addChild(section)
}
return container
}
}
edit:
I was thinking about it and I tried to do it via SKAction, I set another action but this time I set their duration to a long one. first it run a action of duration 0.5, then of 2 and at end of 4. I looks pretty good but still not smooth as I want it to be.
here is my code:
let rotateAction = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 0.5)
let rotateAction2 = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 2)
let rotateAction3 = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 4)
let wait = SKAction.wait(forDuration: 5)
let g1 = SKAction.repeat(rotateAction, count: 10)
let group = SKAction.group([wait, g1, rotateAction2, rotateAction3])
what do you think? there is any way to do it better??
edit 2:
Continued to #Ali Beadle answer, I tried to do it via physics body, the problem now is the when I drag finger on the screen the SKShapeNode (shape) in continue to rotate and never stops. can you detect what is wrong?
class GameScene: SKScene {
var start: CGPoint?
var end:CGPoint?
var startTime: TimeInterval?
let shape = SKShapeNode.init(rectOf: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
let sceneBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
shape.fillColor = SKColor.red
shape.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
shape.physicsBody = SKPhysicsBody.init(rectangleOf: CGSize(width: 50, height: 50))
shape.physicsBody?.affectedByGravity = false
shape.physicsBody?.isDynamic = true
addChild(shape)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.start = touch.location(in: self)
self.startTime = touch.timestamp
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.end = touch.location(in: self)
var dx = ((self.end?.x)! - (self.start?.x)!)
var dy = ((self.end?.y)! - (self.start?.y)!)
let magnitude:CGFloat = sqrt(dx*dx+dy*dy)
if(magnitude >= 25){
let dt:CGFloat = CGFloat(touch.timestamp - self.startTime!)
if dt > 0.1 {
let speed = magnitude / dt
dx = dx / magnitude
dy = dy / magnitude
print("dx: \(dx), dy: \(dy), speed: \(speed) ")
}
}
let touchPosition = touch.location(in: self)
if touchPosition.x < (self.frame.width / 2) {
self.shape.physicsBody?.angularVelocity = 10
self.shape.physicsBody?.applyAngularImpulse(-180)
} else {
self.shape.physicsBody?.angularVelocity = 10
self.shape.physicsBody?.applyAngularImpulse(180)
}
}}
I have created an open source prize spinning wheel in Spritekit that uses physics for realistic movement and flapper control. It also allows the user to drag the wheel to spin or generates a random spin by pushing the center of the wheel.
https://github.com/hsilived/SpinWheel
You can add realistic movement like this by using the built-in Physics simulation of SpriteKit. This will allow you to give your wheel a mass and friction and then use forces to rotate it. It will then slow down realistically.
In outline see Simulating Physics in the Apple Documentation:
To use physics in your game, you need to:
Attach physics bodies to nodes in the node tree and configure their physical properties. See SKPhysicsBody.
Define global characteristics of the scene’s physics simulation, such as gravity. See SKPhysicsWorld.
Where necessary to support your gameplay, set the velocity of physics bodies in the scene or apply forces or impulses to them. ...
The most appropriate method for your wheel is probably to make the wheel pinned to the scene and then rotate it with applyAngularImpulse.
I have a game and I can't seem to get the node to randomly spawn on x position, it will randomly spawn but often will go off screen and the game is then useless and have to restart it:
func addEnemy () {
//enemy
let minValue = self.size.width / 8;
let maxValue = self.size.width-20;
let spawnPoint = UInt32(maxValue - minValue);
Enemy = SKSpriteNode(imageNamed: "Enemy")
Enemy.size = CGSize(width: 150, height: 200)
Enemy.position = CGPoint(x: CGFloat(arc4random_uniform(spawnPoint)), y: self.size.height)
self.addChild(Enemy)
}
let min: CGFloat = 15.0
let max: CGFloat = 200.0
let randomCGFloatBetweenMinAndMax = CGFloat(rand())/CGFloat(RAND_MAX)*(max-min)+min
or if you prefer
let randomCGFloatBetweenMinAndMax2 = CGFloat(arc4random_uniform(UInt32(max-min)) + UInt32(min))
I'm making a game in Sprite Kit and Swift and I have a Sprite at the bottom of the screen and falling Sprites from the top which I want to catch and stick to the Sprite at the bottom, so I'm trying to use SKPhysicsJointFixed but when the objects collide instead of the falling object sticking to the one at the bottom which is supposed to catch and have it attached, it seems as if the bottom Sprite adapts the physics of the falling sprite and then falls off the screen with it. Here's the code I have in my didBeginContact method. and skewer is the name of the Sprite at the bottom which should always be at the bottom and not disappear.
if contact.bodyA.node!.name == "Skewer"
{
let boundX = skewer.physicsBody?.node?.position.x
let fixedJoint = SKPhysicsJointFixed.jointWithBodyA(contact.bodyA.node!.physicsBody, bodyB: contact.bodyB.node!.physicsBody, anchor: CGPoint(x: boundX!, y: boundY))
physicsWorld.addJoint(fixedJoint)
// contact.bodyB.node!.removeFromParent()
}
else
{
contact.bodyA!.node!.removeFromParent()
}
and the physics for the bottom screen Sprite are here
func makeSkewer()
{
skewer.name = "Skewer"
skewer.position = CGPoint(x: size.width * 0.5, y: size.height * 0.244)
skewer.physicsBody = SKPhysicsBody(rectangleOfSize: skewer.size)
skewer.physicsBody?.affectedByGravity = false
skewer.physicsBody?.categoryBitMask = kSkewerCategory
skewer.physicsBody?.contactTestBitMask = kFoodCategory
skewer.physicsBody?.collisionBitMask = kSceneEdgeCategory
addChild(skewer)
}
and physics for the falling Sprites are here
func random() ->CGFloat
{
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat,max: CGFloat) -> CGFloat
{
return random() * (max - min) + min
}
func addFood()
{
let randomCatchIndex = Int(arc4random_uniform(UInt32(foods.count)))
let food = SKSpriteNode(imageNamed: foods[randomCatchIndex])
let actualX = random(min: food.size.width/2, max: size.width - food.size.width/2)
let actualDuration = random(min: CGFloat(1.5), max: CGFloat(8.0))
let actionMove = SKAction.moveTo(CGPoint(x: actualX, y: -food.size.height/2), duration: NSTimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
food.physicsBody = SKPhysicsBody(rectangleOfSize: food.size)
food.position = CGPoint(x: actualX, y: size.height + food.size.height/2)
food.physicsBody?.categoryBitMask = kFoodCategory
food.physicsBody?.contactTestBitMask = kSkewerCategory
food.physicsBody?.collisionBitMask = 0x0
food.physicsBody?.dynamic = true
food.runAction(SKAction.sequence([actionMove, actionMoveDone]))
addChild(food)
}
Set the skewer to not have dynamic physics. What you have currently is it not being affected by gravity, and as soon as it locks onto the food (which is traveling down and has momentum), the skewer moves with it.
In the creation of the skewer, run the following line:
skewer.physicsBody?.dynamic = false
You can also now ignore the affectedByGravity as that is something that only affects dynamic objects.