Strange behaviour of SKPhysicsBody - sprite-kit

In my app I want to create a physics body for a node programmatically. However when I create the physics body programmatically, it doesn't seem to work, although the physics body created in the SpriteKit editor does work. When the physicsbody is created programmatically, it does not collide with another node, when it is created in the editor, it does.
Here is my code:
physicsBody = SKPhysicsBody(rectangleOf: size)
if let body = self.physicsBody{
body.isDynamic = true
body.affectedByGravity = false
body.allowsRotation = false
body.categoryBitMask = CollisionCategoryBitmask.shootWall.rawValue
body.collisionBitMask = CollisionCategoryBitmask.circle.rawValue
body.contactTestBitMask = CollisionCategoryBitmask.circle.rawValue
}
size is the size property of the node.
When I now comment out the first line where the physics body is assigned to the node and instead set the physics body in the SpriteKit scene editor it does work. Note that the code inside the if condition is being executed in both cases.
Update:
What is interesting is that when I do this:
physicsBody = SKPhysicsBody(bodies: [physicsBody!])
which shouldn't have any impact because it its basically changing nothing, then it doesn't work as well. Is this a bug in SpriteKit?

When you do this
required init?(coder aDecoder: NSCoder) {
factor = 0.5
super.init(coder: aDecoder)
self.pBody()
}
physicsWorld does not exist yet, so the body does not get placed in the world.
You want to do it after it is created, so throw your code into a closure to be launched after your setup completes. You can do this using DispatchQueue
required init?(coder aDecoder: NSCoder) {
factor = 0.5
super.init(coder: aDecoder)
DispatchQueue.main.async {
self.pBody() //This will fire after the SKS finished building the scene, so physicsWorld will exist
}
}

Related

SKEmitterNode doesn't work when used as a reference node within another reference node

I'm having an issue with using a particle emitter with reference nodes.
It works when it's referenced directly in the scene (for example, inside the GameScene.sks file), but it doesn't work when it's referenced within another .sks file, and that file/node is then referenced in GameScene.sks, although other referenced nodes appear correctly.
Here's the hierarchy:
GameScene.sks
- Reference Node to RocketFire.sks (emitter) <-- works
- Reference Node to Rocket.sks
Rocket.sks
- Reference Node to RocketFire.sks (emitter) <-- doesn't work in GameScene
- Reference Node to Engine.sks (non-emitter nodes) <-- works in GameScene
RocketFire.sks (SpriteKit particle file)
Engine.sks (SpriteKit scene file with non-emitter nodes)
Is there the wrong way to use Reference Nodes or particle emitters?
(Let me know if more info is needed)
EDIT:
I actually made it work some of the time. Sometimes it would suddenly start working when I remove and re-add the referenced nodes in all the files, or if I change the node hierarchy. But still, I can't identify a consistent reason for when it works and when it doesn't, because since changing it and putting it back to when it worked, doesn't fix it. (Feels like a bug, honestly)
The problem is that since iOS 10 all sprite nodes are by default paused. This includes your scene. It is not enough to just make your scene not paused (isPaused = false) you have to make the children not paused as well.
for your example I added to smoke emitters to my plane.sks file which is dragged onto my GameScene.sks file as an SKReferenceNode
and in my Plane.swift file, note I've put isPaused = false on one of the emitters and it still doesn't work
var leftSmoke: SKEmitterNode!
var rightSmoke: SKEmitterNode!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
if let leftSmoke = self.childNode(withName: "//leftSmoke") as? SKEmitterNode {
self.leftSmoke = leftSmoke
leftSmoke.isPaused = false
}
if let rightSmoke = self.childNode(withName: "//rightSmoke") as? SKEmitterNode {
self.rightSmoke = rightSmoke
}
}
the plane with the scene running (no emitters)
set the plane object to not paused (inside Plane.swift)
func setup() {
self.isPaused = false
if let leftSmoke = self.childNode(withName: "//leftSmoke") as? SKEmitterNode {
self.leftSmoke = leftSmoke
leftSmoke.isPaused = false
}
if let rightSmoke = self.childNode(withName: "//rightSmoke") as? SKEmitterNode {
self.rightSmoke = rightSmoke
}
}
and voila
FYI
It probably works intermittently for you because you might have a break point set. Whenever you have a breakpoint set and the code stops it unPauses nodes automatically

Different SKTexture causes insane lag

I tried making two identical characters (two classes that extend from SKSpriteNode) for the game, the only difference is the SKTexture they're using. I'll refer to them as A and B. Whenever I let A shoot a bullet, it doesn't lag but when I let B shoot a bullet, it lags just a little bit. The problem became clear when I spam shoot from A, it stays at a constant 60 FPS but when I spam shoot from B, it lags, down to around 50 FPS.
A's texture is "Lampy.png" and I tried setting B's texture as "Lampy2.png". Those 2 images are exactly the same, the only difference is the file name, but B lags.
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy2")
}
Code above is from B and it lags
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy")
}
Code above is from A and it does not lag
Other parts of the two classes are exactly the same. Did I import the images incorrectly or something? I can't remember how I imported Lampy but for Lampy2 I dragged it directly from Finder to the assets folder.
Edit - Here's the full class code
import Foundation
import SpriteKit
class Lampy: Player{
override init(){
super.init()
playerBody.texture = SKTexture(imageNamed: "Lampy")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func shoot(){
let thiccBullet = Thicc()
thiccBullet.position = position
thiccBullet.bulletBody.zRotation = playerBody.zRotation
thiccBullet.owner = self;
parent?.addChild(thiccBullet)
}
}
At the line with playerBody.texture = SKTexture(imageNamed: "Lampy")
if I change Lampy to Lampy2 then it lags.

SKPhysicsBody rises CPU usage

I'm just about done with a game that is based on physics. I noticed that Xcode is saying the energy impact is "Very High" which makes sense because the CPU usage is about 21-26%. (I'm using an iPhone 6 in the release configuration, not the simulator) I figured out where the problem seems to be which is the SKPhysicsBody of each node. When I comment out the initialization of the physics body the cpu usage drops to 4-7% and energy impact reads "Low". Can someone give me some insight on what to do?
class Item:SKSpriteNode {
var id:[Int]
init(type: String, ID: [Int]) {
self.id = ID
var newTexture = SKTexture(imageNamed: type)
super.init(texture: newTexture, color: UIColor(), size: newTexture.size())
self.name = type
setPhysicsBody()
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func setPhysicsBody() {
physicsBody = SKPhysicsBody(circleOfRadius: 3)
physicsBody?.isDynamic = false
physicsBody?.categoryBitMask = bitMask.obstacleCategory
physicsBody?.contactTestBitMask = bitMask.ballCategory
physicsBody?.collisionBitMask = bitMask.ballCategory
physicsBody?.restitution = 0.05
physicsBody?.friction = 0.1
}
}
You can help ease the CPU load by setting the SKPhysicsBody's contactTestBitMask and collisionBitMask. By default, the CPU is keeping track of each physics body and its relation to all other physics bodies. By setting the contactTestBitMask and collisionBitMask, you can tell the CPU to only worry about the objects you need the physics body to interact with.

iOS SpriteKit Collision Detection Fails After Decode Save

I have a game where Falling Nodes fall down and hit the Base Number Sprite Node and game logic is run from there. When I set up a new game from scratch the collision detection works exactly how it should. My problem occurs when I create a game from a previous save using NSCoding. In both cases (new game and continue from save game) the physics bodies are the same - dynamic, same size body, same contactTestBitMask, same categoryBitMask. I have tested all of this so I know it is true. The physics contact delegate is also set to the right object. In a game continued from a save, however, the contacts are not registered and I cannot figure out why. The only thing I can think of, but am unable to figure out is object which is set as my physics contact delegate and is the parent of the objects I want collision detection for gets loaded/unarchived without me actually calling decodeObjectForKey for it.
Any help would be much appreciated
func initBaseNumberSpritePhysicsBody() {
baseNumberSprite.physicsBody = nil
baseNumberSprite.physicsBody = SKPhysicsBody(rectangleOfSize: baseNumberSprite.size)
baseNumberSprite.physicsBody!.categoryBitMask = baseNumberCategory
baseNumberSprite.physicsBody!.contactTestBitMask = fallingNodeCategory
baseNumberSprite.physicsBody!.collisionBitMask = 0
baseNumberSprite.physicsBody!.usesPreciseCollisionDetection = true
baseNumberSprite.physicsBody!.allowsRotation = false
}
func initPhysicsBodyForFallingNode(node: NumberNode) {
node.physicsBody = nil
node.physicsBody = SKPhysicsBody(rectangleOfSize: node.size)
node.physicsBody!.categoryBitMask = fallingNodeCategory
node.physicsBody!.contactTestBitMask = baseNumberCategory
node.physicsBody!.collisionBitMask = 0
node.physicsBody!.allowsRotation = false
node.physicsBody!.velocity = nodeVelocity
}
func didBeginContact(contact: SKPhysicsContact) {
if isContactBetween(fallingNodeCategory, and: baseNumberCategory, contact: contact) {
handleContactBetweenFallingNodeAndBaseNumber(contact)
} else {
print("\nUNKNOWN CONTACT OCCURED\n")
}
updateInternalState()
checkGameOverCondition()
}
required init?(coder aDecoder: NSCoder) {
// gameZone = aDecoder.decodeObjectForKey(gameZoneKey) as! GameZone
super.init(coder: aDecoder)
gameZone = self.children[0] as! GameZone //Not decoded by itself but somehow decoded with the this GameScene Object (the "self" object here)
gameZone.delegate = self
self.physicsWorld.contactDelegate = gameZone
}
override func encodeWithCoder(aCoder: NSCoder) {
super.encodeWithCoder(aCoder)
aCoder.encodeObject(gameZone, forKey: gameZoneKey) //Gets coded
}
I was able to figure out my own problem here. A fundamental understanding I was lacking is the SKNodes encode their own child nodes.
// gameZone = aDecoder.decodeObjectForKey(gameZoneKey) as! GameZone
The gameZone object is a child node. So I was encoding it as well as other key objects twice which was leading to my problem. The problem was had nothing to do with the physics world contact delegate or encoding/decoding.

sknode can't see added children

Here i created a menu class which contains a few items. I want to display these sprites in the main class. I experimented with this by creating an object associating with the sknode class in the touches began method, but when i added the menu object in the main class using the addChild thing, nothing showed up.
class menu:SKNode {
let background = SKSpriteNode(imageNamed:"background")
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(){
super.init()
var fixedSize = self.frame.width/11
background.size = CGSizeMake(self.frame.width-fixedSize, self.frame.size.height-fixedSize)
background.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2)
self.addChild(background)
}
}
//In the main method i said let settings = menu() self.addChild(settings) nothing shows up
The frame property of an SKNode is equal to CGRectZero, so when you try to set the size of your background node it will also end up as CGRectZero.
An easy fix to your problem would be to add custom initializer and call that with the size of the scene.
class menu:SKNode {
let background = SKSpriteNode(imageNamed:"background")
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(size: CGSize) {
super.init()
var fixedSize = size.width/11
background.size = CGSizeMake(size.width-fixedSize, size.height-fixedSize)
background.position = CGPointMake(size.width/2, size.height/2)
self.addChild(background)
}
}