Deleting SKNode on contact with SKPhysicsWorld Boundaries in Swift 3 - ios

I am looking to create a unique opening screen for an IOS app, and I would like to have a background of balls dropping to create visual interest. I am creating the balls in the didMove() function, and they are working fine, but only some of the time. I believe the frame is too large for the display, which is making the random X and Y coordinates sometimes occur outside of the screen.
self.physicsWorld.gravity = CGVector.init(dx: 0, dy: -9.8)
let sceneBody = SKPhysicsBody(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
let minXValue: CGFloat = 0
let maxXValue: CGFloat = self.frame.size.width
let minYValue: CGFloat = 0
let maxYValue: CGFloat = self.frame.size.height
let wait = SKAction.wait(forDuration: 0.4)
let run = SKAction.run {
let randomXNumber = (CGFloat(arc4random()).truncatingRemainder(dividingBy: maxXValue)) + minXValue
let randomYNumber = (CGFloat(arc4random()).truncatingRemainder(dividingBy: maxYValue)) + minYValue
print("Random Y Number: \(randomYNumber)")
print("Random X Number: \(randomXNumber)")
let ball = SKShapeNode(circleOfRadius: 20)
ball.fillColor = SKColor(colorLiteralRed: 212.0, green: 217.0, blue: 128.0, alpha: 1)
ball.position = CGPoint.init(x: randomXNumber, y: randomYNumber)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 20)
ball.physicsBody?.affectedByGravity = true
self.addChild(ball)
}
self.run(SKAction.repeatForever(SKAction.sequence([wait, run])))
Here is my code for the random balls.
I would also like to have these balls get removed from the view when it hits the bottom of the container, in order to increase performance on all devices and not have redundant nodes.
Which function or block of code would I need to implement this concept?

Setting the scene size
In GameViewController.swift find the function viewDidLoad and add the following line right above skView.presentScene(scene)
scene.size = skView.bounds.size
Remove balls from view
To remove balls from the scene you can detect when there is a collision between the edgeLoop around the scene and the balls. Then call removeFromParent on the ball. First, change the class declaration like so:
class GameScene: SKScene, SKPhysicsContactDelegate
and then add
physicsWorld.contactDelegate = self in didMoveToView.
Second, above the class declaration in GameScene.swift, create the physics categories:
enum PhysicsCategory:UInt32
{
case edge = 1
case ball = 2
}
Next, assign those categories to the edge and the balls. (Add the first two lines under sceneBody and insert the third line where you defined the other ball physics properties):
self.physicsBody?.categoryBitMask = PhysicsCategory.edge.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.ball.rawValue
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball.rawValue
And finally, check for collisions:
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == PhysicsCategory.edge.rawValue && secondBody.categoryBitMask == PhysicsCategory.ball.rawValue || firstBody.categoryBitMask == PhysicsCategory.ball.rawValue && secondBody.categoryBitMask == PhysicsCategory.edge.rawValue
{
if firstBody.categoryBitMask == PhysicsCategory.ball.rawValue {
firstBody.node?.removeFromParent()
} else {
secondBody.node?.removeFromParent()
}
}

Related

How to make collision between a node that was hard-coded, (in swift file), and an node that was placed in the scene editor? - Spritekit

We are trying to create a collision between the player, who's physics body (CategoryBitMask, CollisionBitMask, ContactTestBitMask) was programmed in the swift file, and the enemy that was placed in the scene editor and its physics body (CategoryBitMask = 2, CollisionBitMask = 4294967295 'default', ContactBitMask = 1). The code shown below shows what was tested but resulted in no collision.
import SpriteKit
import GameplayKit
class ClassicLevelScene: SKScene, SKPhysicsContactDelegate {
// Physics Bodies Setup
let enemy:UInt32 = 2
// Player Constant
let player = SKSpriteNode(imageNamed: "car1")
override func didMove(to view: SKView) {
// Physics World
physicsWorld.contactDelegate = self
// Set up player
player.position = CGPoint(x: 30, y: -245)
player.zPosition = Layers.cars
player.size = CGSize(width: 60, height: 80)
player.name = "player"
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: CGSize(width: player.size.width, height: player.size.height))
player.physicsBody?.allowsRotation = false
player.physicsBody?.categoryBitMask = 1
player.physicsBody?.collisionBitMask = 7
player.physicsBody?.contactTestBitMask = 1
addChild(player)
}
func didBegin(_ contact: SKPhysicsContact) {
/* Player touches anything, game over */
let contactA:SKPhysicsBody = contact.bodyB
let contactB:SKPhysicsBody = contact.bodyB
if contactA.categoryBitMask == 2 || contactB.categoryBitMask == 2 {
// execute code to respond to object hitting ground
minusHeart()
spikeSparkExplosion()
}
}
You likely have various issues. First this is probably a typo:
let contactA:SKPhysicsBody = contact.bodyB // you meant bodyA?
let contactB:SKPhysicsBody = contact.bodyB
Second, you don't seem overly clear about contacts vs collisions. Collisions = objects bounce off each other. Contacts = you get notified when objects touch. From the code it seems like you want the latter, and you may not want the collisions at all. Anyway you should have categories 1 and 2 for the category bitmasks (that part looks OK) and contact bit masks of 2 and 1 (respectively). That is, your player has category 1, and you want to know when it touches something in category 2. So the player's contact test bit mask should be 2 (you have it set to 1).

SKPhysicsBodies not colliding after addition of SKPhysicsJointLimit

I currently have two SKSpriteNodes that I have added SKPhysicsBodies to. When they have no SKJoint attached, they collide as expected. As soon as I add the SKPhysicsJoint, they just pass right through each other. Any joint I add functions properly, but the SKPhysicsJointLimit only limits the extent to which the nodes can travel apart from each other, not how close they can get. How can I fix this?
Here is code I am using for the joint:
let joint = SKPhysicsJointLimit.joint(withBodyA: object1.physicsBody!, bodyB: object2.physicsBody!, anchorA: CGPoint(x: object1.position.x + iconController.position.x, y: object1.position.y + iconController.position.y), anchorB: CGPoint(x: object2.position.x + iconController.position.x, y: object2.position.y + iconController.position.y))
joint.maxLength = screen.height * 0.4
physicsWorld.add(joint)
PhysicsBody of both nodes:
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
self.physicsBody?.allowsRotation = false
self.physicsBody?.friction = 0
self.physicsBody?.mass = 0.1
I have tested it with different values for the above modifications of the SKPhysicsBody and it performs the same.
An SKPhysicsJoint object connects two physics bodies so that they are simulated together by the physics world.
You can use also SKPhysicJointPin:
A pin joint allows the two bodies to independently rotate around the
anchor point as if pinned together.
If your objects work well before the SKPhysicsJoint addition with the physic engine so they fired the didBeginContact as you wish and as you have setted, I think your problem is simply a wrong anchor. Try to add:
let skView = self.view as! SKView
skView.showsPhysics = true
to your scene initialization code: you will see an outline of the physic bodies and maybe you'll see the issue immediatly.
To help you I'll try to make an example of elements configured to collide each other:
enum CollisionTypes: UInt32 {
case Boundaries = 1
case Element = 2
}
class GameScene: SKScene,SKPhysicsContactDelegate {
private var elements = [SKNode]()
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
let boundariesFrame = CGRectMake(20, 20, 200, 400)
let boundaries = SKShapeNode.init(rect: boundariesFrame)
boundaries.position = CGPointMake(350,150)
let boundariesBody = SKPhysicsBody.init(edgeLoopFromRect: boundariesFrame)
boundariesBody.dynamic = false
boundariesBody.categoryBitMask = CollisionTypes.Boundaries.rawValue
boundariesBody.contactTestBitMask = CollisionTypes.Element.rawValue
boundaries.physicsBody = boundariesBody
addChild(boundaries)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
body.dynamic = true
body.categoryBitMask = CollisionTypes.Element.rawValue
body.contactTestBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
body.collisionBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
}
}
}
Hope it can help you to find your issue.

Balancing a sprite

So my problem regards the rotation of a sprite. I want it look like it's balancing. When you tap left - the sprite moves left, when you tap right - the sprite moves right. To get a better idea, put a pencil on it's end and try balance it... Yeah... that (but only in x axis).
Currently I am rotating a sprite either clockwise or anti-clockwise dependent upon whether I tap the left or right side of the screen. This is all done using SKActions.
The problem with this is that it results in a very 'jerky' and not particularly realistic motion.
I assume that I want to use physics body and something similar to velocity but my questions are:
the use of velocity is right... isn't it?
would i better off with a volume based sprite (and get it to take into account it's volume and mass) or just a simple edge based sprite?
Thanks in advance!
-- Code -
This is how I'm currently rotating and moving my sprite:
import SpriteKit
enum rotationDirection{
case clockwise
case counterClockwise
case none
}
// Creates GameScene and initialises Sprites in Scene //
class GameScene: SKScene, SKPhysicsContactDelegate {
// Rotation direction variable for ship motion (rotation and movement) //
var currentRotationDirection = rotationDirection.none
var xVelocity: CGFloat = 0
var sprite = SKSpriteNode()
// Setup Scene here //
override func didMoveToView(view: SKView) {
// Background colour //
self.backgroundColor = SKColor.whiteColor()
// sprite Physics + add's sprite //
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
sprite.physicsBody?.affectedByGravity = true
sprite.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addSprite()
// Initialises sprite node and it's properties //
func addSprite() {
// Sprite dimension properities //
sprite.name = "sprite"
sprite = SKSpriteNode(imageNamed: "sprite")
sprite.setScale(0.5)
sprite.position = CGPointMake(frame.midX, 220)
sprite.anchorPoint = CGPoint(x: 0.5, y: 0.25)
sprite.zPosition = 1;
// sprite Physics properties //
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
sprite.physicsBody?.categoryBitMask = UInt32(shipCategory)
sprite.physicsBody?.dynamic = true
sprite.physicsBody?.contactTestBitMask = UInt32(obstacleCategory)
sprite.physicsBody?.collisionBitMask = 0
self.addChild(sprite)
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Defines UI Touch //
let touch = touches.first as UITouch!
let touchPosition = touch.locationInNode(self)
// Sets up Inital Rotation Direction //
let newRotationDirection : rotationDirection = touchPosition.x < CGRectGetMidX(self.frame) ? .clockwise : .counterClockwise
// Left or Right movement based on touch //
if touchPosition.x < CGRectGetMidX(self.frame) {xVelocity = -75}
else {xVelocity = 75}
// Clockwise or anticlockwise rotation based on touch //
if currentRotationDirection != newRotationDirection && currentRotationDirection != .none {
reverseRotation()
currentRotationDirection = newRotationDirection
}
else if (currentRotationDirection == .none) {
setupRotationWith(direction: newRotationDirection)
currentRotationDirection = newRotationDirection
}
}
func reverseRotation() {
let oldRotateAction = sprite.actionForKey("rotate")
let newRotateAction = SKAction.reversedAction(oldRotateAction!)
sprite.runAction(newRotateAction(), withKey: "rotate")
}
func stopRotation() {
sprite.removeActionForKey("rotate")
}
func setupRotationWith(direction direction: rotationDirection){
let angle : CGFloat = (direction == .clockwise) ? CGFloat(M_PI) : -CGFloat(M_PI)
let rotate = SKAction.rotateByAngle(angle, duration: 2)
let repeatAction = SKAction.repeatActionForever(rotate)
sprite.runAction(repeatAction, withKey: "rotate")
}
override func update(currentTime: CFTimeInterval) {
let rate: CGFloat = 0.3; //Controls rate of motion. 1.0 instantaneous, 0.0 none.
let relativeVelocity: CGVector = CGVector(dx:xVelocity-sprite.physicsBody!.velocity.dx, dy:0);
sprite.physicsBody!.velocity=CGVector(dx:sprite.physicsBody!.velocity.dx+relativeVelocity.dx*rate, dy:0);
}
I think what your are going to want to look into is acceleration. Right now you are adjusting verlocityX with a static amount using:
if touchPosition.x < CGRectGetMidX(self.frame) {
xVelocity = -75
}
else {
xVelocity = 75
}
Doing this will result in your jerky motion. Which is actually just linear. Using acceleration you can avoid this.
For this you'll need two more variables for maxVelocity and acceleration. You want a maxVelocity probably to limit the velocity. Each frame you'll need to increase the velocity with the acceleration amount.
I suggest you try something like this:
velocity = 0 // No movement
acceleration = 2 // Increase velocity with 2 each frame
maxVelocity = 75 // The maximum velocity
if touchPosition.x < CGRectGetMidX(self.frame) {
xVelocity += acceleration
if xVelocity <= -maxVelocity {
xVelocity = -maxVelocity
}
}
else {
xVelocity -= acceleration
if xVelocity >= maxSpeed {
xVelocity = maxSpeed
}
}
Increasing your velocity like this will result with a rotation speed of 2 in the first frame, than 4 in the second and 6 in the thirds etc. So it will have an increasing effect. While at the same time, when trying to reverse it you wont have a jerky effect either. Say the velocity is at 50, you first decrease it to 48, 46, 44, etc. Not until the 0 point you will actually start rotating the other way.

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

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