I'm creating a small game where the ball needs to bounce on the upper, left and right wall, while it should stop as soon as it touches the floor.
I already have a working contact-detection code and delegate that is called in the correct situation and with the correct timing.
Creating a small circle in the collision point found in the func didBegin(_ contact: SKPhysicsContact) creates the node in the correct position.
In the same method I call ball.physicsBody?.velocity = .zero and ball.position = contact.contactPoint and the ball correctly stops. The problem is that sometimes the ball stops a frame too late, after it's already bounced off the floor.
The ball has a restitution of 1 because I would like it to bounce without slowing down, except when touching the floor.
I have already tried to set the position to zero, the velocity to zero, its isDynamic attribute to false. Each of these tries does what it's supposed to do, but sometimes it happens a frame too late.
Both the ball and the floor have the usesPreciseCollisionDetection property set to true.
If I try to add some artificial delay, the contact method is called again (breaking part of the gameplay).
Here is the code of my ball node:
class Ball: SKSpriteNode {
convenience init() {
self.init(imageNamed: "earth")
physicsBody = SKPhysicsBody(circleOfRadius: 12.5)
physicsBody?.usesPreciseCollisionDetection = true
physicsBody?.categoryBitMask = Masks.ball
physicsBody?.contactTestBitMask = Masks.floor
physicsBody?.allowsRotation = false
physicsBody?.restitution = 1
physicsBody?.friction = 0
physicsBody?.angularDamping = 0
physicsBody?.linearDamping = 0
}
}
and here is the code for my didBegin method:
func didBegin(_ contact: SKPhysicsContact) {
ball.physicsBody?.velocity = .zero
ball.position = contact.contactPoint
}
I would expect the ball to stop while still touching the floor, and not a frame after it's already bounced off.
Related
I am making a game in Swift using spritekit and I want 2 objects on screen, one that I move with the touchesMoved function (player) and the other to stay still in its position in the screen (cell).
I wanted to make the objects physicsbody's so that I could work out collisions between them, but now whenever the scene loads the objects fall straight away. And the object moved through touch falls as soon as the user stops touching the screen.
Here's all of the code relating to the object that should stay stationary in the scene and I am unsure what makes it fall.
//Defining the cell
let cell = SKSpriteNode(imageNamed: "cell.png")
override func didMove(to view: SKView){
self.physicsWorld.contactDelegate = self
cell.physicsBody?.categoryBitMask = cellCategory
cell.physicsBody?.contactTestBitMask = playerCategory
cell.physicsBody?.isDynamic = true
cell.physicsBody = SKPhysicsBody(rectangleOf: cell.frame.size)
//Adding the cell to the scene
cell.position = CGPoint(x:size.width * 0.9, y:size.height * 0.9)
cell.size = CGSize(width: 50, height: 50)
addChild(cell)
Any help in what makes the object fall and how I can stop it from happening would be greatly appreciated.
Gravity is by default on. You can either turn it off entirely by setting the gravity property of the scene's physicsWorld:
// somewhere in the scene's initialization, or in didMove if you like
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
See https://developer.apple.com/documentation/spritekit/skphysicsworld for info on that.
Or you can turn off whether individual objects are affected by gravity. E.g.,
let body = SKPhysicsBody(circleOfRadius: 10.0)
body.affectedByGravity = false
See https://developer.apple.com/documentation/spritekit/skphysicsbody, and in particular, https://developer.apple.com/documentation/spritekit/skphysicsbody/1519774-affectedbygravity
You have a different problem in that you're setting the properties of your cell's physics body before you're creating it. cell.physicsBody?... = will not assign anything until cell.physicsBody exists. So those assignments must be moved after you create cell.physicsBody = SKPhysicsBody(rectangleOf: ...)
I have simple gameplay that functions like this:
My goal is to detect when the ball hits the white part (game over scene would load) or when the ball hits the gray part (game continues)
Because the shapes shrink and have a lot of stuff going on, I figured it would be a lot more efficient to just create the SKPhysicsBody when I need it, and then remove it after the collision checks happen. So when I click the screen this happens: (after 5*0.0325 seconds--how long it takes the circle to shrink--has passed, it will add the physicsbody to both the gray and white part so I can detect which the ball is touching)
DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: {
let trackPhysics = SKPhysicsBody(texture: track.texture!, size: track.texture!.size())
trackPhysics.isDynamic = false
trackPhysics.affectedByGravity = false
track.physicsBody = trackPhysics
let goalPhysics = SKPhysicsBody(texture: goal.texture!, size: goal.texture!.size())
goalPhysics.isDynamic = false
goalPhysics.affectedByGravity = false
goal.physicsBody = goalPhysics
})
Which works. The SKPhysicsBody are applied to both the gray and white part perfectly without any loss in framerate and follow the rotation. The problem is... how do I detect which is the ball touching? Since they did not formally collide, it will not call the collision at all (which makes sense since they did not really collide
This is the basic logic, and it works perfectly until I want to check the results:
someone touches the screen
circles shrink
right after they shrink it will create an SKPhysicsBody for both the gray and white part based on its texture
Problem is here... how do I detect which the ball is touching?
I have tried this:
func didBegin(_ contact: SKPhysicsContact) {
print("touching!")
}
func didEnd(_ contact: SKPhysicsContact) {
print("not touching")
}
which has no messages at all, and have tried using allContactedBodies() 1 second after the SKPhysicsBody were applied, but returns a count with 0 for all of them. I have even tried making this 5 seconds and still not working
DispatchQueue.main.asyncAfter(deadline: .now() + seconds + 1, execute: {
print(track.physicsBody!.allContactedBodies().count)
print(goal.physicsBody!.allContactedBodies().count)
})
This is what it looks like with the SKPhysicsBody applied, you can see the physics is applied perfectly
Am I doing something wrong? Any ideas?
The solution (as discovered in the comments above), was:
Set the isDynamic property of nodes to true for collision detection to work properly.
Ensure that didBegin(contact:) is being called, by double-checking that the scene's physicsWorld.contactDelegate is set.
Ensure the masks and physics bodies are set at the proper times and locations.
I have a weird problem where my SKReferenceNode does not collide properly after being scaled up. It collides well in the center, but it ignores collisions and contacts on the edges.
Here is the first photo of the scene. The SKReferenceNode was scaled up significantly, and as seen in this photo, does not collide correctly on the edges. The PhysicsBody appears to be correct (with showPhysics on), yet the ball refuses to collide.
The SKReferenceNode is using an alpha collision mask, because I need to change it to a larger sprite in the future to do animations and such. Additionally, non-scaled objects work completely fine. Finally, after the ball collides with the center, which does work, and the block is reset, collisions start working as expected again. The code to fix it is probably in the reset function, but I reset everything before the level is loaded, so this wouldn't make sense. Here is a part of my reset code:
func reset() {
physicsWorld.gravity = CGVectorMake(0, -9.8)
for grav in gravityBlock {
grav.reset()
}
gravity = -9.8
//Resets blocks
for blocks in destroyedBlocks { //the blocks are stored in destroyedBlocks when collided with
blocks.reset()
}
destroyedBlocks = []
/*Irrelevant code removed*/
}
Here's my blocks.reset() function:
override func reset() {
super.reset()
self.physicsBody?.categoryBitMask = 1
removeAllActions()
self.texture = self.text
shadow.hidden = false
self.alpha = 0
shadow.alpha = 0
let appear = SKAction(named: "Appear")!
self.runAction(appear)
shadow.runAction(SKAction.fadeInWithDuration(0.25))
}
Here is super.reset()
func reset() {
self.hidden = false
state = .NotBroken
}
Thanks so much!
When you scale the sprite you are just changing it's visual representation. The Physics Body stays the same. That's why you are getting the "strange" during the collisions.
You can see that showing the physics uses in your scene, just open GameViewController and add this line
skView.showsPhysics = true
inside viewDidLoad, just after this line
skView.showsNodeCount = true
Then run again your game, the physics boundaries now will be highlighted.
I have a SKSpriteNode (Bird) with a SKPhysicsBody (Circle).
The bird node is affected by gravity and falls down, until it reaches a y position of 100px.
override func didSimulatePhysics() {
if (bird.position.y < 100) {
bird.position = CGPointMake(bird.position.x, 100)
}
}
The problem is, that the physicsBody is moving away from the Node.
The physicsBody should stay centered on the Bird.
As soon as I tap the screen the Bird gets an impulse and moves up. Immediately the physicsBody is back in place.
This is the physicsBody code:
bird.physicsBody = SKPhysicsBody(circleOfRadius: bird.frame.width * 0.5)
bird.physicsBody!.contactTestBitMask = PhysicsCategory.LeftBoard | PhysicsCategory.RightBoard | PhysicsCategory.Border
bird.physicsBody!.categoryBitMask = PhysicsCategory.Bird
bird.physicsBody!.collisionBitMask = PhysicsCategory.None
bird.physicsBody!.mass = 1
bird.physicsBody!.affectedByGravity = false
bird.physicsBody!.allowsRotation = false
You need to stop the velocity. You are forcing the position but the velocity continues after each step.
Also you should typically make such changes in the update method before the physics are simulated.
I need to pause the balls for my game. I want them to stop in place but they are moving by impulse and if I make them non-dynamic to stop them, the impulse goes away. I'm trying to pause them and un-pause them, and they still keep going in the same direction. I tried
ball.paused = true
but that didn't work. Anyone know?
Instead of freezing the nodes, I froze the scene with
scene?.physicsWorld.speed = 0
because that freezes all of the nodes and not my time or score integers which is exactly what I needed.
Here is how I solved it in my game:
var ballVelocity = CGVector()
func pauseAction(ball: SKSpriteNode){
if ball.dynamic {
//Ball is moving. Save the current velocity of ball.
ballVelocity = ball.velocity
//Stop the ball
ball.physicsBody.dynamic = false
}else{
//Ball is paused.
//Resume ball movement.
ball.physicsBody.dynamic = true
//Add the saved velocity.
ball.velocity = ballVelocity
}
}
You can use ball.physicsBody.velocity = CGVectorMake(0, 0); but keep in mind that if the ball is affected by gravity, it will still drop.