Circles not bouncing elastically in SpriteKit's physics engine - ios

I've been messing around with Swift and SpriteKit's physics engine and wanted to make a simple billiards style game. I've got a simple ball/table setup made but when the balls collide with each other or the wall, they lose much of their velocity, even though their friction and linear damping are set to 0 and their restitution is set to 1. Obviously billiard balls have some friction and don't have max restitution, but for my game's sake, I would like to have completely elastic collisions. I've tinkered around with all of the physical properties, and can't find a combination that yields elastic collisions. I've also checked if the walls' physical properties have a say in the matter, and no, they don't. Changing the walls' friction or restitution did not change the results. Thanks for the help!
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
for i in 0...5
{
addChild(newBall())
}
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
}
func newBall() -> SKShapeNode
{
let radius = CGFloat(Int(arc4random_uniform(10)+10))
var ball = SKShapeNode(circleOfRadius: radius)
ball.lineWidth = 1
ball.strokeColor = SKColor.whiteColor()
ball.fillColor = UIColor.cyanColor()
ball.position = CGPointMake(CGFloat(Int(arc4random_uniform(400)+50)), CGFloat(Int(arc4random_uniform(220)+50)))
var body = SKPhysicsBody(circleOfRadius: radius)
ball.physicsBody = body
body.friction = 0
body.restitution = 1
body.linearDamping = 0
body.allowsRotation = true
body.velocity = CGVectorMake(CGFloat(Int(arc4random_uniform(50)+50)), CGFloat(Int(arc4random_uniform(50)+50)))
return ball
}
}

Related

Swift + Physics: Wrong angle calculation on collision

I have a simple SpriteKit App with walls and a ball. Both setup with a SKPhysicsBody.
When I apply a force in one direction, I expect the ball to reflect at the wall with the same angle when it collide but in opposite direction.
But sometimes I see the angle is weird. I played a lot with all the physicsBody properties, but was not able to fix it. Sometimes the first reflections look good, but then the third or sixth and sometimes the first reflection is at wrong angle.
I read from different posts, people are kinda self-calculating the "correct direction". But I can't imagine SpriteKits physic engine is not capable to do so.
Check my attached image to understand what I mean. Can anybody help on this? I don't want to start playing around with Box2d for Swift, since this looks like it will be too hard for me to integrate into Swift.
This is the physicsWorld init on my GameScene.swift file where all my elements are added to:
self.physicsWorld.gravity = CGVectorMake(0, 0)
Adding all my code would be way too much, so I add the important pieces. Hope its enough for analyze. All elements like the ball, walls are SKSpriteNode's
This is the physicsBody code for the ball:
self.physicsBody = SKPhysicsBody(circleOfRadius: Constants.Config.playersize+self.node.lineWidth)
self.physicsBody?.restitution = 1
self.physicsBody?.friction = 0
self.physicsBody?.linearDamping = 1
self.physicsBody?.allowsRotation = false
self.physicsBody?.categoryBitMask = Constants.Config.PhysicsCategory.Player
self.physicsBody?.collisionBitMask = Constants.Config.PhysicsCategory.Wall | Constants.Config.PhysicsCategory.Enemy
self.physicsBody?.contactTestBitMask = Constants.Config.PhysicsCategory.Wall | Constants.Config.PhysicsCategory.Enemy
This is the physicsBody for the walls:
el = SKSpriteNode(color: UIColor.blueColor(), size: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))
el.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))
el.physicsBody?.dynamic = false
el.physicsBody?.categoryBitMask = Constants.Config.PhysicsCategory.Wall
el.physicsBody?.collisionBitMask = Constants.Config.PhysicsCategory.Player
el.physicsBody?.contactTestBitMask = Constants.Config.PhysicsCategory.Player
At the end I just call the applyImpulse function on the balls physicsBody to make it moving in the physics simulation.
Also check my attached second image/gif. It shows the edge collision problem with a simple skphysics app without any special parameterization. just a rectangle with a ball in it and one vector applied as impulse.
Heres my full non-working code using the solution from appzYourLife.
class GameScene: SKScene {
private var ball:SKShapeNode!
override func didMoveToView(view: SKView) {
let screen = UIScreen.mainScreen().bounds
let p = UIBezierPath()
p.moveToPoint(CGPointMake(0,0))
p.addLineToPoint(CGPointMake(screen.width,0))
p.addLineToPoint(CGPointMake(screen.width, screen.height))
p.addLineToPoint(CGPointMake(0,screen.height))
p.closePath()
let shape = SKShapeNode(path: p.CGPath)
shape.physicsBody = SKPhysicsBody(edgeLoopFromPath: p.CGPath)
shape.physicsBody?.affectedByGravity = false
shape.physicsBody?.dynamic = false
shape.strokeColor = UIColor.blackColor()
self.addChild(shape)
ball = SKShapeNode(circleOfRadius: 17)
ball.name = "player"
ball.position = CGPoint(x: 20, y: 20)
ball.fillColor = UIColor.yellowColor()
ball.physicsBody = SKPhysicsBody(circleOfRadius: 17)
ball.physicsBody?.angularDamping = 0
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.restitution = 1
ball.physicsBody?.friction = 0
ball.physicsBody?.allowsRotation = false
self.addChild(ball)
self.ball.physicsBody?.applyImpulse(CGVector(dx: 2.4, dy: 9.7))
}
I just realized the mass and density of my ball sprite is nil. Why that? Even setting it keeps in nil.
Since i cannot imagine this is some unknown bug in SpriteKits physics engine,
i very hope someone can help me here as this is a full stopper for me.
It is a (known) bug in SpriteKits physic engine. But sometimes just playing with the values will make this bug disappear - for example, try changing the ball's speed (even by small value) every time it's hitting the wall.
Another solutions are listed here:
SpriteKit physics in Swift - Ball slides against wall instead of reflecting
(The same problem as yours, already from 2014, still not fixed..)
SpriteKit is working fine
I created a simple project and it is working fine.
The problem
As #Sulthan already suggested in a comment, I think the problem in your code is this line
el.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: Constants.Config.wallsize, height: Constants.Config.wallsize))
Here instead of creating the physical border for your scene, you are creating a rectangular physics body which overlap the physics body of the Ball producing unrealistic behaviours.
To create the physics border of my scene I simply used this
physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
Full code
By the way, this is the full code for my project
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let ball = Ball()
ball.position = CGPoint(x:frame.midX, y:frame.midY)
addChild(ball)
physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
ball.physicsBody!.applyImpulse(CGVector(dx:50, dy:70))
physicsWorld.gravity = CGVector(dx:0, dy:0)
}
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
let physicsBody = SKPhysicsBody(circleOfRadius: texture.size().width/2)
physicsBody.restitution = 1
physicsBody.friction = 0
physicsBody.linearDamping = 0
physicsBody.allowsRotation = false
self.physicsBody = physicsBody
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I also used scene.scaleMode = .ResizeFill inside GameViewController.swift.

Setting up a Ground node using SpriteKit?

I've been designing a game where I want the game to stop as soon as the ball makes contact with the ground. My function below is intended to set up the ground.
class GameScene: SKScene, SKPhysicsContactDelegate {
var ground = SKNode()
let groundCategory: UInt32 = 0x1 << 0
let ballCategory: UInt32 = 0x1 << 1
//Generic Anchor coordinate points
let anchorX: CGFloat = 0.5
let anchorY: CGFloat = 0.5
/*Sets the background and ball to be in the correct dimensions*/
override init(size: CGSize) {
super.init(size: size)
//Create the Phyiscs of the game
setUpPhysics()
setUpGround()
setUpBall()
}
func setUpPhysics() -> Void {
self.physicsWorld.gravity = CGVectorMake( 0.0, -5.0 )
self.physicsWorld.contactDelegate = self
}
func setUpGround() -> Void {
self.ground.position = CGPointMake(0, self.frame.size.height)
self.ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, self.frame.size.height)) //Experiment with this
self.ground.physicsBody?.dynamic = false
self.ground.physicsBody?.categoryBitMask = groundCategory //Assigns the bit mask category for ground
self.ball.physicsBody?.contactTestBitMask = ballCategory //Assigns the contacts that we care about for the ground
/*Added in*/
self.ground.physicsBody?.dynamic = true
self.ground.physicsBody?.affectedByGravity = false
self.ground.physicsBody?.allowsRotation = false
/**/
self.addChild(self.ground)
}
func setUpBall() -> Void {
ball.anchorPoint = CGPointMake(anchorX, anchorY)
self.ball.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2)
self.ball.name = "ball"
self.ball.userInteractionEnabled = false
ball.physicsBody?.usesPreciseCollisionDetection = true
self.ball.physicsBody?.categoryBitMask = ballCategory //Assigns the bit mask category for ball
self.ball.physicsBody?.collisionBitMask = wallCategory | ceilingCategory //Assigns the collisions that the ball can have
self.ball.physicsBody?.contactTestBitMask = groundCategory //Assigns the contacts that we care about for the ball
addChild(self.ball) //Add ball to the display list
}
The problem I am noticing is that when I run the iOS Simulator, the Ground node is not being detected. What am I doing wrong in this case?
Is your ground node not showing up at all? I believe your ground should be an SKSpriteNode. Add that SKSpriteNode as a child of the SKNode object you created.
The SKNode is an empty node and the SKSpriteNode has an actual visual representation. And instead of doing it in a separate function, it would be much simpler if you did it in the DidMoveToView itself.
var objects = SKNode()
addChild(objects)
let ground = SKNode()
ground.position = .......
let colorsprite1 = SKSpriteNode(imageNamed: "yourimageoftheground")
ground.addChild(colorsprite1)
ground.physicsBody?.dynamic = false
Yes you were right to set ground's physics definition as not dynamic. I don't know why people are telling you otherwise. You don't want it to move upon contact with your ball thingies or whatever it is you have moving around in your scene.
Add all other physics definitions you need. Consider using a ContactTestBitMask as you want SpriteKit to let you know when a collision with the ground occurs, instead of SpriteKit handling it by itself. Details about that mask is easily available in StackOverFlow with examples.
objects.addChild(ground)
Hope this helps
Your ballCategory should be assigned to self.ground.contactBitMask not the ball.
You need to set the dynamic property to true in order for contact detection to work.
At the same time, you can set the following properties.
ground.affectedByGravity = false
ground.allowsRotation = false

SKPhysicsBody scale with parent scaling

I have a node, A, in a parent node, B.
I'm using setScale on node B to give the illusion of zooming, but when I do that, node A and all of its siblings do not have the expected physics bodies.
Even though A appears smaller and smaller, its physics body stays the same size. This leads to wacky behavior.
How can I "zoom out" and still have sensible physics?
SKPhysicsBody cannot be scaled. As a hack you could destroy the current physics body and add a smaller one as you scale up or down but unfortunately this is not a very efficient solution.
The SKCameraNode (added in iOS9) sounds perfect for your scenario. As opposed changing the scale of every node in the scene, you would change the scale of only the camera node.
Firstly, you need to add an SKCameraNode to your scene's hierarchy (this could all be done in the Sprite Kit editor too).
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
let camera = SKCameraNode()
camera.position = CGPoint(x: size.width / 2, y: size.height / 2)
self.addChild(camera)
self.camera = camera
}
Secondly, since SKCameraNode is a node you can apply SKActions to it. For a zoom out effect you could do:
guard let camera = camera else { return }
let scale = SKAction.scaleBy(2, duration: 5)
camera.runAction(scale)
For more information have a look at the WWDC 2015 talk: What's New In Sprite Kit.
A simple example that shows that a physics body scales/behaves appropriately when its parent node is scaled. Tap to add sprites to the scene.
class GameScene: SKScene {
var toggle = false
var node = SKNode()
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: view.frame)
scaleMode = .ResizeFill
view.showsPhysics = true
addChild(node)
let shrink = SKAction.scaleBy(0.25, duration: 5)
let grow = SKAction.scaleBy(4.0, duration: 5)
let action = SKAction.sequence([shrink,grow])
node.runAction(SKAction.repeatActionForever(action))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(node)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
if (toggle) {
sprite.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width/2.0)
}
else {
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
}
sprite.xScale = 0.125
sprite.yScale = 0.125
sprite.position = location
node.addChild(sprite)
toggle = !toggle
}
}
}

Centering the camera on the character

I am a beginner and I have started my first project. It should be a little game where you can run to the left and to the right. But I don't know how to set the camera settings.
I'd be very thankful if you could help me, tell me how I can improve my code and what is wrong.
Here is my Code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var ground:SKSpriteNode = SKSpriteNode()
var hero: MPCharacter!
var tree:MPTree!
override func didMoveToView(view: SKView) {
backgroundColor = UIColor(red: 155.0/255.0, green: 201.0/255.0, blue: 244.0/255.0, alpha: 1.0)
self.anchorPoint = CGPointMake(0.5, 0.5)
// --> hero <--
hero = MPCharacter()
hero.position = CGPointMake(frame.size.width/2, view.frame.size.height/2)
hero.zPosition = 100
hero.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(hero.size.width, hero.size.height))
hero.physicsBody!.dynamic = true
addChild(hero)
// --> ground <--
ground = SKSpriteNode(imageNamed: "ground")
ground.size = CGSizeMake(frame.size.width, frame.size.height/3)
ground.position = CGPointMake(frame.size.width/2, frame.size.height/2 - ground.size.height)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(frame.size.width, ground.size.height - 25))
ground.physicsBody!.dynamic = false
addChild(ground)
// --> Tree <--
tree = MPTree()
tree.position = CGPointMake(frame.size.width/2, ground.size.height + tree.size.height/3 - 10)
tree.zPosition = 10
tree.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(tree.size.width, tree.size.height - 10))
tree.physicsBody!.dynamic = false
addChild(tree)
// --> Physics <--
self.physicsWorld.gravity = CGVectorMake(0, -5.0)
self.physicsWorld.contactDelegate = self
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch:AnyObject in touches {
let location = touch.locationInNode(self)
if location.x < CGRectGetMidX(self.frame) {
hero.physicsBody!.velocity = CGVectorMake(0, 0)
hero.physicsBody!.applyImpulse(CGVectorMake(-5, 10))
}
else {
hero.physicsBody!.velocity = CGVectorMake(0, 0)
hero.physicsBody!.applyImpulse(CGVectorMake(5, 10))
}
}
}
}
Im not quite sure what you are trying to achieve. One way to make your character 'run' to the left (or right) is by moving a background (and other game objects) to the left when the character is running right (and vice versa). This way your character will always be in the center of the screen, because he is not really moving, you create an 'illusion' that he is moving in your gameworld. Giving your character a run animation will make it look even better...
There is a tutorial on the website of Ray Wenderlich which also covers 'parallax scrolling':
http://www.raywenderlich.com/49625/sprite-kit-tutorial-space-shooter
Hope this helps..
If you're trying to center the camera on the player, Apple Docs provide you a nice example for how to do so over here (I would add code, but the best way to learn is to get your hands dirty). You might have to translate the methods to swift, but it is the general implementation for what you wish to do. The problem with the answer above is that if you would move the background, you would have to calculate the forces and the impulses directly and apply the inverse vectors to the background. This might require more processing than with simply moving the camera. Hope this helps!!

SKPhysics with Swift and SpriteKit

I am trying to create a iOS game using Swift Programming Language.
I want to use swipe gestures on the screen to make the character (player) move up, down, left and right
So far so good. But I want some physics. I want the player to collide with the edges of the screen. (and later I want it to collide with other stuff, but I need to know the code first ;)
QUESTION: How do I identify a collision between 2 objects (player and edge of screen)
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var player:SKSpriteNode = SKSpriteNode()
let moveUp = SKAction.moveByX(0, y:2000, duration:10.0) //it is set to 2000 because I want to see if it collides with the edge of the screen
let moveDown = SKAction.moveByX(0, y: -200, duration: 2.0)
// Define the bitmasks identifying various physics objects
let worldCategory:UInt32 = 0x1 << 0
let playerCategory:UInt32 = 0x1 << 1
override func didMoveToView(view: SKView) {
}
override init(size:CGSize) {
super.init(size: size)
self.backgroundColor = SKColor.whiteColor()
player = SKSpriteNode(imageNamed: "Kundvagn1")
player.position = CGPointMake(self.frame.size.width/2, player.size.height/2 + 20)
self.addChild(player)
player.runAction(moveUp)
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.dynamic = true //I dont think the question marks should be there, but Xcode doesn´t accept the code if there is no question marks there
player.physicsBody?.categoryBitMask = playerCategory
player.physicsBody?.contactTestBitMask = worldCategory
player.physicsBody?.collisionBitMask = 0
player.physicsBody?.usesPreciseCollisionDetection = true
}
func didBeginContact(contact: SKPhysicsContact!) {
println("Collision")
}
Assuming that you want the left and right edges to block, simply add two SKShapeNode rectangular objects to your scene, set their heights to the screen height, and place them just beyond the left and right edges of the screen. Set up their physics bodies and you should be good to go.
If you want all the edges of the screen to be boundaries, you need to setup a line-based physics body for your scene, as detailed here: Add boundaries to an SKScene
I added this in case you need a code example to jonogilmour's instructions . Turn on the ShowsPhysics = true in your viewController to see physics. Both sides should be blue. If you want a contact notification of course you would add to this code with your bit mask stuff. Hope this helps!
var leftEdge = SKNode()
leftEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointZero, toPoint: CGPointMake(0.0, self.size.height))
leftEdge.position = CGPointZero;
self.addChild(leftEdge)
var rightEdge = SKNode()
rightEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointZero, toPoint: CGPointMake(0.0, self.size.height))
rightEdge.position = CGPointMake(self.size.width, 0.0);
self.addChild(rightEdge)

Resources