iOS SpriteKit Collision Detection Fails After Decode Save - swift

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.

Related

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.

3D Object is not visible in real view in Swift

I'm trying to display a 3D drone object in through my camera. I have create ARSceneView and configure it properly and created a scene. I have also properly pass the object to that sceneView but when i run my app the object does not show any where, i have set its positioning also but still not getting the object anywhere. How can i see my object?My code to set scene and configuration is this,
func setupScene(){
let scene = SCNScene()
arView.scene = scene
}
func setupConfiguration(){
let configure = ARWorldTrackingConfiguration()
arView.session.run(configure)
}
func addDrone() {
drone.loadModel()
arView.scene.rootNode.addChildNode(drone)
}
This is my drone class for making the object as a childnode,
class Drone: SCNNode {
func loadModel() {
guard let virtualObjectScene = SCNScene(named: "Drone.scn") else { return }
let wrapperNode = SCNNode()
for child in virtualObjectScene.rootNode.childNodes {
wrapperNode.addChildNode(child)
}
addChildNode(wrapperNode)
}
}
How can i get to see that?
Looking at your code it looks like your Drone Class never gets initialised.
If your SCNScene is in an .scnassets folder you also need to include that in your path.
Lets say therefore, that you have a folder named: ARAssets.scnassets
Your Drone Class should look like this:
class Drone: SCNNode{
override init() {
super.init()
guard let virtualObjectScene = SCNScene(named: "ARAssets.scnassets/Drone.scn") else { return }
let wrapperNode = SCNNode()
for child in virtualObjectScene.rootNode.childNodes {
wrapperNode.addChildNode(child)
}
self.addChildNode(wrapperNode)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And to actually load it you should do something like this in your viewController:
func addDrone() {
let droneNode = Drone()
droneNode.position = SCNVector3(0, 0, -1.5)
self.augmentedRealityView.scene.rootNode.addChildNode(droneNode)
}
You may also need to adjust the scale property of your Drone Node as well e.g
droneNode.scale = SCNVector3(0.01, 0.01. 0.01)

Strange behaviour of SKPhysicsBody

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
}
}

Why is init(coder:) being called when I provide an init() function

I am using SpriteKit and I am loading a SceneKit file that contains a number of sprites with custom classes. The scene never actually loads though because it reaches the first custom class and throws the fatalerror from the required init?(coder:) initializer. The custom class implements an initializer though and I am having trouble pinning down why it is choosing that initializer over the one I provided.
Custom Class:
class Bat: SKSpriteNode, GameSprite {
var initialSize: CGSize = CGSize(width: 44, height: 24)
var textureAtlas: SKTextureAtlas = SKTextureAtlas(named: "Enemies")
var flyAnimation = SKAction()
init() {
super.init(texture: nil, color: .clear, size: initialSize)
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.affectedByGravity = false
createAnimations()
self.run(flyAnimation)
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func createAnimations() {
let flyFrames: [SKTexture] = [textureAtlas.textureNamed("bat"),
textureAtlas.textureNamed("bat-fly")]
let flyAction = SKAction.animate(with: flyFrames, timePerFrame: 0.12)
flyAnimation = SKAction.repeatForever(flyAction)
}
func onTap() {}
}
And here is the code attempting to load the scene and then loop through the children and initialize them:
Encounter Manager:
class EncounterManager {
// Store encounter file names
let encounterNames: [String] = [
"EncounterA"
]
// Each encounter is a node, store an array
var encounters: [SKNode] = []
init() {
// Loop through each encounter scene and create a node for the encounter
for encounterFileName in encounterNames {
let encounterNode = SKNode()
// Load the scene file into a SKScene instance and loop through the children
if let encounterScene = SKScene(fileNamed: encounterFileName) {
for child in encounterScene.children {
// Create a copy of the scene's child node to add to our encounter node
// Copy the position, name, and then add to the encounter
let copyOfNode = type(of: child).init()
copyOfNode.position = child.position
copyOfNode.name = child.name
encounterNode.addChild(copyOfNode)
}
}
// Add the populated encounter node to the array
encounters.append(encounterNode)
}
}
// This function will be called from the GameScene to add all the encounter nodes to the world node
func addEncountersToScene(gameScene: SKNode) {
var encounterPosY = 1000
for encounterNode in encounters {
// Spawn the encounters behind the action, with increasing height so they do not collide
encounterNode.position = CGPoint(x: -2000, y: encounterPosY)
gameScene.addChild(encounterNode)
// Double Y pos for next encounter
encounterPosY *= 2
}
}
}
What I have noticed using breakpoints though is that it never gets past loading the scene. It fails on the line if let encounterScene = SKScene(fileNamed: encounterFileName) and the error is the fatal error in the initializer from the Bat class.
Any help understanding why it picks one initializer over the other would be greatly appreciated!
You are doing:
if let encounterScene = SKScene(fileNamed: encounterFileName)
Which calls SKScene's init(fileNamed:) which loads a file and decodes it with SKScene's coder init. That init loads the file and decodes each element in it with the node's coder init.
If you want to load from a file, you need to implement the coder init.

Calling custom class methods after collision detection, which returns a SKPhysicsBody

I figured out how to do collision detection using PhysicsBody and this method works:
// Method call:
projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as SKSpriteNode)
// Method:
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode!) {
projectile.removeFromParent()
monster.removeFromParent()
}
The method call was placed in a method that serves as the SKPhysicsContact receiver. It gets a pair of SKPhysicsBody objects passed to it, checks that they are the right type, and then calls the other method to have those objects removed.
However, I want change the Monster to have hit points.
I created a separate class called Monster, which has the following code:
import Foundation
import SpriteKit
class Monster : SKSpriteNode {
var hp : Int
override init () {
let color = UIColor()
let texture = SKTexture(imageNamed: "monster")
let size = texture.size()
hp = 5
super.init (texture: texture, color: color, size: size)
self.physicsBody = SKPhysicsBody(rectangleOfSize: size)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func subtract () {
hp--
println(hp)
}
}
and so I want to run this code, customized for Monster:
projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as Monster)
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:Monster!) {
projectile.removeFromParent()
monster.subtract();
}
In Java, I could just cast it, such as (Monster)secondBody.node. I know that the secondBody is a Monster. The Monster holds reference to it's PhysicsBody, and inherits SKSpriteNode.
I tried this: let theMonster = secondBody.node as Monster but that didn't work, either.
The sprite kit physics engine seems to return PhysicsBody objects, on which I can call built-in methods like removeFromParent(). But I want to call my own subtract() method instead, which is part of Monster!
How can I trigger a custom method on an object that I received via collision detection?
I figured it out!
let theMonster = monster as Monster
projectile.removeFromParent()
theMonster.subtract();
Thanks!!