SKSpriteNode subclass: method to remove all joints - ios

I have created a subclass of SKSpriteNode. I connect instances of that class together with joints of type SKPhysicsJointLimit. I do this within my didEndContact(contact: SKPhysicsContact) in my GameScene:
var joint = SKPhysicsJointLimit.jointWithBodyA(contact.bodyA, bodyB: contact.bodyB, anchorA: pos1!, anchorB: pos2!)
self.physicsWorld.addJoint(joint)
This works well so far.
Then i come to the point where i want to release the node from the joint. According to the SKPhysicsBody docs there is a property called "joints" which is an array holding SKPhysicsJoint objects. I thought thats exactly what I need, but I am not able to iterate over an instance's joints and remove them from the physicsWorld. To do the job i added a method to my custom SKSpriteNode subclass.
func freeJoints(world: SKPhysicsWorld){
if let joints = self.physicsBody?.joints {
for joint in joints{
println("found a joint: \(joint)")
// example print:
//found a joint: <PKPhysicsJointRope: 0x7fbe39e95c50>
world.removeJoint(joint as SKPhysicsJoint)
}
}
}
Calling the method fails after the println() statement with the message "Swift dynamic cast failed". I would really appreciate your opinion in how to work with an SKPhysicsBody's joint property. More specifically: How to use (cast?) the items in the array to be able to remove them from a scene's SKPhysicsWorld.

I spent a little more time in investigating this. This is what I have come up with:
I decided to add an property to my SKSpriteNode subclass and manage the joints myself
var joints: [SKPhysicsJointLimit]
override init(){
...
self.joints = []
...
}
Everytime I add an joint to the scene's SKPHysicsWorld I also add it to the joints array of the SKNNode itself. Whilst iterating the SKPHysicsBody's joints-Array failed (see question) at the point I wanted to cast it to SKPhysicsJoint, removing items from the physics world works as intended when iterating the array of SKPhysicsJointLimit items:
func freeJoints(world: SKPhysicsWorld){
for item in self.joints{
println("removing item from physics world \(item)")
world.removeJoint(item)
}
self.joints.removeAll(keepCapacity: false)
}
}
This seems not to be the most elegant way to do the job, since there already is a framework managed array that promises to be same thing. But I was unable to utilize it and this works for now.

Related

Change texture of SKSpriteNode in SKScene

I've been trying to change the texture of an SKSpriteNode in the scene, but XCode keeps giving me the error "Value of type 'SKNode' has no member 'texture'". However, I put an if is statement, so it should read it as an SKSpriteNode. I'm wondering if there are any other solutions in order to be able to change the texture of an SKSpriteNode in the scene. Thank you!
for child in children {
if child.name == "Enemy" || child.name == "FinalEnemy" {
if child is SKSpriteNode {
child.texture = SKTexture.init(image: #imageLiteral(resourceName: "Ninja58"))
}
}
}
You are asking the runtime environment “is this SKNode actually a SKSpriteNode” but the compiler is still treating child as an SKNode.
You need to get a reference to a SKSpriteNode to make the compiler happy.
You can do that by changing
if child is SKSpriteNode
To
if let child = child as? SKSpriteNode
This second one uses optional binding to create a new variable named child which is valid inside the if block and is a SKSpriteNode.
If child is not a SKSpriteNode then this will pass over this if statement and the code won’t be run so you will have the behaviour you want.
The rest of your code should work by making this change.
Edit after Knight0fDragon's comment
You could actually embed the condition into the for loop here to make it a bit more compact...
for child in children where child.name == "Enemy" || child.name == "FinalEnemy" {
guard let child = child as? SKSpriteNode else { continue }
child.texture = ...
}
Hmm... this isn't as nice as I wanted it and I would like the optional binding in the for loop condition...
Maybe that's possible but this will do for now :)
Edit By Knight0fDragon due to comments being limited:
Please give this a try, should only work in XCode 9+, and the cast may be unnecessary, I do not have access to XCode to test this.
for child in ArraySlice<SKSpriteNode>(children) as [SKSpriteNode]
where ["Enemy","FinalEnemy"].contains(child.name ?? "") {
child.texture = ...
}

Updating multiple Spritekit nodes with the same name

I am currently making a game with Spritekit & Swift3 for the first time; it is a 2D, side-scrolling endless runner. I'm trying to have certain nodes move with my camera, but the problem is that they will be procedurally generated, belonging to the same class, so I need them all to have the same name. Here's the relevant sections of code, before I go any further:
(All variables have been initialized)
//move these nodes with the camera
private var ChoosePieces: ChoosePiecesClass?;
override func didMove(to view: SKView) {
initializeGame();
}
override func update(_ currentTime: TimeInterval) {
enumerateChildNodes(withName: "ChoosePieces") {
node, stop in
_ = node as! SKSpriteNode
self.ChoosePieces?.moveWithCamera();
}
}
private func initializeGame() {
ChoosePieces = childNode(withName: "ChoosePieces") as? ChoosePiecesClass;
}
I have tested the moveWithCamera() function and it works well--it simply increments the node's x-value by 10, moving at the same pace as the camera so that the node remains on screen at the same location.
My problem is that I'm not exactly sure how to use enumerateChildNodes(withName: "String") with my nodes, so that it will recognize all of them and move them. Right now the pieces just stay still. I found out about this function from another person's post--he/she was trying to spawn "enemies" in his/her game. Thanks in advance!
Usually with enumerateChildNodes you do something with the node that is returned e.g. modifying your own example:
override func update(_ currentTime: TimeInterval) {
enumerateChildNodes(withName: "ChoosePieces") {
node, stop in
if let piece = node as? SKSpriteNode { // Cast to Sprite if we can
piece.moveWithCamera()
}
}
}
So for every node that is returned, we cast it to a new SKSpriteNode called 'piece (if we can - using the as? operator - because enumerateChildNodes returns SKNodes) ' and then call moveWithCamera on the 'piece' node.
(the line piece.moveWithCamera() I made up - your original code appeared to be calling a class method(?) to do something. You might want self.moveWithCamera(piece) etc)
In your code, you did nothing with the nodes returned by your enumeration.

Preloading sprite kit textures

So I was reading the apple documentation for best sprite kit practices. I came across this:
For example, if your game uses the same textures for all its gameplay, you might create a special loading class that runs once at startup. You perform the work of loading the textures once, and then leave them in memory. If a scene object is deleted and recreated to restart gameplay, the textures do not need to be reloaded.
And this would significantly help performance in my application. Can someone point me in the right direction to how I would go about achieving this?
I presume I would call a function to load up texture's in my View Controller? And then access that texture atlas?
The thing is, do you really want to cache the resources like that? Can't say I ever found a need for something of that nature. Anyways, if doing that somehow helps with your app's performance, then you can make a TextureManager class which would be a singleton (create separate file for TextureManager class), like this:
class TextureManager{
private var textures = [String:SKTexture]()
static let sharedInstance = TextureManager()
private init(){}
func getTexture(withName name:String)->SKTexture?{ return textures[name] }
func addTexture(withName name:String, texture :SKTexture){
if textures[name] == nil {
textures[name] = texture
}
}
func addTextures(texturesDictionary:[String:SKTexture]) {
for (name, texture) in texturesDictionary {
addTexture(withName: name, texture: texture)
}
}
func removeTexture(withName name:String)->Bool {
if textures[name] != nil {
textures[name] = nil
return true
}
return false
}
}
Here you are using dictionary and associate each texture with its name. Pretty simple concept. If there isn't a texture with the same name in a dictionary, then add it. Just beware of premature optimization.
The usage:
//I used didMoveToView in this example, but more appropriate would be to use something before this method is called, like viewDidLoad, or doing this inside off app delegate.
override func didMoveToView(view: SKView) {
let atlas = SKTextureAtlas(named: "game")
let texture = atlas.textureNamed("someTexture1")
let dictionary = [
"someTexture2": atlas.textureNamed("someTexture2"),
"someTexture3": atlas.textureNamed("someTexture3"),
"someTexture4": atlas.textureNamed("someTexture4"),
]
TextureManager.sharedInstance.addTexture(withName: "someTexture", texture: texture)
TextureManager.sharedInstance.addTextures(dictionary)
}
As I said, you have to put TextureManager implementation in a separate file, to make it real singleton. Otherwise, if you define it in GameScene for example, you will be able to call that private init, and then TextureManager will not be a real singleton.
So, with this code you can create some textures at the very beginning of the app lifecycle, like it is said in the docs:
For example, if your game uses the same textures for all its gameplay,
you might create a special loading class that runs once at startup.
and fill the dictionary with them. Later on, whenever you need a texture, you will not use atlas.textureNamed() method, but rather load it from a dictionary property of a TextureManager class. Also, when transitioning between scenes, that dictionary will survive scene's deinits, and will persist while app is alive.

Cocos2d iPHone SDK ccPhysicsCollisionPreSolve crashing while accessing object properties

I am working on Cocos2d iphone SDK and stuck with an issues. Check my code here.
Obstacle Class
#objc class Obstacle: CCNode {
weak var __pipe: CCSprite!
var ignoreCollision:Bool = false
override init!() {
super.init()
//NSLog("init plain")
userInteractionEnabled = true
ignoreCollision = false
}
func didLoadFromCCB() {
...
}
}
The main scene where I have placed collision delegate methods. The method is called once the player object collides with obstacle object.
func ccPhysicsCollisionPreSolve(pair: CCPhysicsCollisionPair!, hero: Player!, platform: Obstacle!) -> ObjCBool {
if !isGameOn {
NSLog("PLATFORM: Game finished")
return false
}
if platform.ignoreCollision {
platform.ignoreCollision = !platform.ignoreCollision
// For score updates
hudLayer.updatePlatform(++scorePlatforms)
}
return true
}
Now here, I am just trying to use simple Bool property from platform object and what I get is a crash. My app crashes on the if... condition statement where I am using that property. I am unable to get what is with this as I am simply using a property from object.
I checked the object and found platform shows me of type Some instead ob Obstacle. I have tried using
var p: Obstacle = platform as Obstacle
and replaced all platform with p but yet I am facing the crash. I thought the type now shows me some random hex number which might be the issue.
Can anyone help me here as I am unable to find out how I should access property from this platform object in ccPhysicsCollisionPreSolve method?
Sorry guys for the trouble but it was my mistake. I was understanding the same incorrectly.
The Obstacle class represents the platform as well as its background layer having tripple height of the device screen. But my ball collides only with that __pipe sprite in Obstacle class and I am referring the whole Obstacle class which is wrong.
I used platform.parent!.ignoreCollision and problem is solved. :)
This little miss costed me 3-4 days of R&D and work extra.

Is there a way to detect collisions between 2 SKSpriteNodes but allow them to overlap

I don't think there is a way to do this, but is there a way to detect when 2 SKSpriteNodes intersect with each other, but still allow them to overlap, so they don't actually bounce over each other?
I know I can just have 1 without a physics body, and then write some code to check their co-ordinates, but I thought maybe I might be missing something in Sprite Kit where I could detect this with SK methods.
You can use the contactDelegate property of the SKPhysicsWorld object:
// inside your header file
typedef NS_OPTIONS(NSUInteger, CollisionCategory) {
categoryOne = (1 << 0),
categoryTwo = (1 << 1)
};
// inside your SKScene sub-class implementation
- (void)setupContactDelegate {
self.physicsWorld.contactDelegate = self;
nodeA.categoryBitMask = categoryOne; // nodeA is category one
nodeA.collisionBitMask = ~categoryTwo; // nodeA does not collide w/ category two
nodeA.contactTestBitMask = categoryTwo; // nodeA tests for contacts w/ category two
nodeB.categoryBitMask = categoryTwo; // nodeB is category two
nodeB.collisionBitMask = ~categoryOne; // nodeB does not collide w/ category one
nodeB.contactTestBitMask = categoryOne; // nodeB tests for contacts w/ category one
}
- (void)didBeginContact:(SKPhysicsContact *)contact {
// do whatever you need to do when the contact begins
}
- (void)didEndContact:(SKPhysicsContact *)contact {
// do whatever you need to do when the contact ends
}
You'll also need to declare your SKScene sub-class as implementing the SKPhysicsContactDelegate protocol.
Here's more reference info:
SKScene Class Reference
SKPhysicsWorld Class Reference
SKPhysicsBody Class Reference
SKPhysicsContactDelegate Protocol Reference
SKPhysicsContact Class Reference

Resources