17 Replies
      Latest reply on Jan 19, 2020 12:13 AM by mirxvid
      mirxvid Level 1 Level 1 (0 points)

        Hello there

         

        I began coding for iOS after a few year and, though I like how it works in Xcode, I can't help but getting suspicious at how something doesn't seem to work properly, for exemple, in this snippet, the tiles will go were I expect them to, but won't fade away by a half.:

         

                        addChild(shape)
                        let placeIt = SKAction.group(
                            [
                            SKAction.fadeAlpha(by: 0.5, duration: 2),
                            SKAction.move(to: CGPoint(x: CGFloat(x) * (SIZE + SPACE) + dX, y: CGFloat(y) * (SIZE + SPACE) + dY), duration: 2)])
                        placeIt.timingMode = SKActionTimingMode.easeInEaseOut
                       
                        NSLog("moving %@", shape.name!)
                        shape.run(placeIt, withKey: "placeIt \(shape.name!)")
                        NSLog("%@ moved", shape.name!)
        
        

        Even stranger, I invoke a function just after this loops which targets 2 specific shapes in order to fade them back in, then rotate them. Of course, they've not been faded, but they won't rotate and even the NSLog statements won't be anywhere to see.

         

        Later in the game it works if I rotate once (on touchEnd), but If I want to also rotate the surrounding, it does stop to rotate aven once by further attempts.

         

        These shapes use Physics. I know the bitmasks are right but there never seem to have a contact even though it should happen. Show physics is relevant: there's a border when expected and rotating the shapes should trigger a contact which used to happen, but not anymore. I also never got the scaling to get properly.

         

        Seriously, I don't understand what is going here

        Should the SKActions only be run in a specific context?

         

        Please help!

        Mirx

        • Re: Is the SpriteKit broken in iOS13.3?
          bg2b Level 1 Level 1 (10 points)

          There are a few earlier threads on this forum and various threads on Stack Overflow discussing issues with physics bodies from textures in iOS 13.*.  I'm not sure that those are all fixed yet (I worked around them back when the latest was 13.2 and haven't gone back to see the story in 13.3), so perhaps that has something to do with your contact issues.  On the other hand, when I was having problems, showsPhysics would display obviously-broken physics body outlines.

           

          I've had no other issues though, despite working extensively with a large portion of the SpriteKit API over the last half year.  When stuff like logging messages don't show up, it seems like something much odder is going on.

            • Re: Is the SpriteKit broken in iOS13.3?
              mirxvid Level 1 Level 1 (0 points)

              Thanks, I am not sure this does help, except making me consider a shameful switch to the dreaded Unity Engine.

               

              Do you know where I could find some doc/examples about where to actually perform my SKActions? I thought these could all be performed from the SKScene.didMove method as they'd be sequentially performed in a single thread?

                • Re: Is the SpriteKit broken in iOS13.3?
                  bg2b Level 1 Level 1 (10 points)

                  The actions will get run sequentially on your main thread, but sticking stuff in didMove may not be what you want.  The only thing I ever used there was an initial action to happen on scene appearance (e.g., to wait for a couple of seconds, then start a new game).  And things like background nodes that have something like an infinitely repeating animation could be placed there, though I usually put those in the scene creation.  But once the scene gets going then the actions are all scheduled from other places. E.g., in response to contacts between nodes, or from the update function, or triggered as a result of user interaction, or based on completions of other actions, etc.

                   

                  Maybe you need to provide more context, because it's really not clear how you have things structured.

                    • Re: Is the SpriteKit broken in iOS13.3?
                      mirxvid Level 1 Level 1 (0 points)

                      Thanks. I really value your friendly help.

                       

                      I want to:

                       

                      1) begin by placing a bunch of differently colored SKShapeNode objects on the SKScene. I'd cram there as much 32x32 CGSize as possible.

                       

                      2) The game will then begin by a pair of automatic turns:

                      a) having my (lower-left corner shape) initialized (changing its color and actually turning around in order to contact the surrounding similarly colored shapes using SKPhysicsContactDelegate.didBegin() ) and take possession of them

                      b) same for the computer opponent (upper right corner)

                       

                      3) At this point, the game may begin for real: either I or the computer play(s) and choose a color which cannot be the opponent's in order to chain-reaction all of his shapes to absorb even more similarly colored, unonwned surrounding ones.

                       

                      But then I am currently stuck because the inital tour doesn't quite take place: only my tile NSLogs me it has rotated but it actually doesn't.

                       

                      Bits of code:

                       

                          override func didMove(to view: SKView) {
                              /* Set the scale mode to scale to fit the window */
                              scaleMode = .resizeFill
                              // Set physics
                              physicsWorld.gravity = .zero
                              physicsWorld.contactDelegate = self
                             
                              backgroundColor = SKColor.darkGray
                             
                              label = SKLabelNode(fontNamed: "Chalkduster")
                              label.text = "Color ZooZ 2"
                              label.horizontalAlignmentMode = .center
                              label.verticalAlignmentMode = .center
                              label.color = SKColor.systemPink
                              label.fontSize = 48
                              label.position = CGPoint(x: size.width/2, y: size.height/2)
                              addChild(label)
                              installShapes()
                          }
                         
                          func installShapes() {
                              X = size.width / (SIZE + SPACE) - 1
                              Y = size.height / (SIZE + SPACE) - 1
                              dX = (size.width - (X * (SIZE + SPACE))) / 1
                              dY = (size.height - (Y * (SIZE + SPACE))) / 1
                              NSLog("MIRKO display: %0.0f x %0.0f & Nb: %0.0f x %0.0f & dX: %0.0f / dY: %0.0f", size.width, size.height, X, Y, dX, dY)
                             
                              self.run(SKAction.run {
                                  for x in 0...Int8(self.X - 1) {
                                      for y in 0...Int8(self.Y - 1) {
                                          self.addShape(atX: x, andY: y)
                                          }
                                      }
                                  }, completion: {
                                      NSLog("End of installShapes, about to rename the player shapes")
                                      self.renameShape(from: "\(0).\(0)", to: "me")
                                      self.renameShape(from: "\(self.X-1).\(self.Y-1)", to: "him")
                              })
                          }
                         
                          func renameShape(from oldName : String, to newName : String) {
                              if let s = shapeByName(name: oldName) as SKShapeNode? {
                                  s.strokeColor = s.fillColor
                                  s.physicsBody?.categoryBitMask = PhysicsCategory.mine
                                  s.physicsBody?.contactTestBitMask = PhysicsCategory.unset
                                  s.physicsBody?.collisionBitMask = PhysicsCategory.none
                                  s.name = newName
                                  s.alpha = 1.0
                                  propagate(shape: s)
                              }
                          }
                         
                          func addShape(atX x: Int8, andY y : Int8) {
                              let shape = SKShapeNode(rectOf: CGSize(width: SIZE, height: SIZE), cornerRadius: SIZE / 4)
                              shape.name = "\(x).\(y)"
                              shape.position = CGPoint(x: 0, y: 0)
                              let generator = ColorGenerator()
                              shape.fillColor = generator.random()
                              shape.strokeColor = UIColor.lightGray
                              shape.lineWidth = BORDER
                      
                              shape.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: SIZE, height: SIZE))
                              shape.physicsBody?.isDynamic = false
                              shape.physicsBody?.categoryBitMask = PhysicsCategory.unset
                              shape.physicsBody?.contactTestBitMask = PhysicsCategory.mine | PhysicsCategory.his
                              shape.physicsBody?.collisionBitMask = PhysicsCategory.none
                              shape.physicsBody?.usesPreciseCollisionDetection = true
                              shape.isPaused = false
                      
                              addChild(shape)
                              let placeIt = SKAction.group(
                                  [SKAction.fadeAlpha(by: 0.5, duration: 2),
                                  SKAction.move(to: CGPoint(x: CGFloat(x) * (SIZE + SPACE) + dX, y: CGFloat(y) * (SIZE + SPACE) + dY), duration: 2),
                                  SKAction.wait(forDuration: 0.1)])
                              placeIt.timingMode = SKActionTimingMode.easeInEaseOut
                             
                              NSLog("moving %@", shape.name!)
                              shape.run(placeIt, withKey: "placeIt \(shape.name!)")
                              NSLog("%@ moved", shape.name!)
                          }
                      
                          func shapeAt(x mX : Int8, y mY : Int8) -> SKShapeNode? {
                              let allNodes = nodes(at: CGPoint(x: CGFloat(mX) * (SIZE + SPACE) + dX, y: CGFloat(mY) * (SIZE + SPACE) + dY))
                              for node in allNodes {
                                  if let o = node as? SKShapeNode {
                                      return o
                                  }
                              }
                              return nil
                          }
                         
                          func shapeByName(name s:String) -> SKShapeNode? {
                              if let c = childNode(withName: s) as? SKShapeNode {
                                  return c
                              }
                              return nil
                          }
                         
                          func propagate(shape s : SKShapeNode) {
                              NSLog("Propagating...")
                              if s.isPaused { s.isPaused = false }
                              let rotate = SKAction.rotate(toAngle: .pi, duration: 0.5)
                              rotate.timingMode = SKActionTimingMode.easeInEaseOut
                              NSLog("rotating")
                              s.run(rotate, withKey: "rotate \(s.name!)")
                              NSLog("rotated")
                          }

                       

                      Which SKScene member funcion should I override for which part of the game init/turn, etc?

                       

                      Once again, thanks!

                        • Re: Is the SpriteKit broken in iOS13.3?
                          bg2b Level 1 Level 1 (10 points)

                          It's hard to say without being able to try it and see what happens, but my impression is that you're essentially setting up a bunch of nodes in one corner, scheduling actions to have them move (in parallel) into position, renaming two nodes, and then scheduling actions to have them do some rotation (also in parallel with everything else).  I suspect you want things to happen in a more sequential order:

                           

                          1. The shapes start in a corner and spread out to form an array (perhaps in parallel, perhaps one-by-one).

                          2. One corner node to do some rotating.

                          3. The second corner node to do some rotating.

                           

                          Possibly you want 2 and 3 to happen in parallel, but in any case, from your description it sounds like 1 should definitely happen before 2 and/or 3.  That's not what you've written though.

                           

                          In particular, lines 28-38 add a bunch of nodes, schedule actions for those nodes, and then immediately (before the actions run) do the renameShape stuff.

                           

                          The completion of the run action means the completion of adding the nodes and scheduling their actions, but it does not mean the completion of those scheduled actions.

                           

                          There are various ways of sequencing things.  E.g., you could pass a completion block that would do the rotations and have addShape schedule that after the completion of the actions for one of the nodes (assuming they all move into position simultaneously).  Or you could just have another action that would wait(forDuration:) a bit longer than what it takes the nodes to move into position and then runs a block to do the rotations.

                           

                          As a side note, when you're writing SKAction.group, those are happening in parallel.  So in a group with actions that have duration 2, an action that is a delay of 0.1 is having no effect at all.  Maybe you meant SKAction.sequence, or .sequence([.group([...]), .wait(..)])

                            • Re: Is the SpriteKit broken in iOS13.3?
                              bg2b Level 1 Level 1 (10 points)

                              Oh, also sequences like NSLog, run, NSLog are just going to do two log messages in quick succession.  It's not waiting for the run to finish.  If you want that effect, you need to have the second NSLog in a completion block for the run.

                              • Re: Is the SpriteKit broken in iOS13.3?
                                mirxvid Level 1 Level 1 (0 points)

                                This is the intro of the game:

                                1. The shapes start in a corner and spread out to form an array (perhaps in parallel, perhaps one-by-one).

                                They all go simultaneously to their definite place. They however don't fadeAlpha which is kinda bugging me.

                                 

                                Then for the first turn only, I want these to happen in parallel:

                                2. One corner node to do some rotating.

                                3. The second corner node to do some rotating.

                                 

                                Once this is set the game can go on with one of the corners absorbing its neighbours by taking their colors, then the opponent and so on until one reaches more than 50%.

                                 

                                I kicked off my SpriteKit approach using a Ray Wenderlich tutorial but it doesn't quite seem to work at me... many SKaction, even grouped don't work (even though they NSLog so)...

                                 

                                I fixed the short wait which I indeed wanted to sequence after the group, but then it doesn't work because it just means that it starts after the others were started, but not completed.

                                 

                                Do you have a typical working link to something like:

                                 

                                run(SKAction.run {
                                   manythingsinparallel()
                                }, completion: {
                                  otherthings() 
                                }

                                 

                                ...or maybe I should find ou how I could get a notification from all SKAction.group once it has completed all of its subtasks?

                                 

                                Pity I don't find much around as I am lacking the right jargon to perform my searches...

                                 

                                Thanks again.

                                  • Re: Is the SpriteKit broken in iOS13.3?
                                    bg2b Level 1 Level 1 (10 points)

                                    The fadeAlpha I've never used, but probably the name is confusing you and it's adding the amount specified to alpha.  Since you're starting at alpha = 1, adding 0.5 is not going to do anything.  Try -0.5.

                                     

                                    For doing actions in parallel on a bunch of nodes, then running something after all have finished, here's a couple of approaches.

                                     

                                    // If all nodes have actions that take the same amount of time
                                    func makeNodes(numNodes: Int, then whenDone: @escaping () -> Void) {
                                      let nodeAction = ...
                                      let node1 = makeNode()
                                      node1.run(nodeAction, completion: whenDone)
                                      for _ in 1 ..< numNodes {
                                        let node = makeNode()
                                        node.run(nodeAction)
                                      }
                                    }
                                    // If node actions may take random unknown amounts of time
                                    class Coordinator {
                                      var remaining: Int
                                      let action: () -> Void
                                     
                                      init(numNodes: Int, whenDone action: @escaping () -> Void) {
                                        remaining = numNodes
                                        action = whenDone
                                      }
                                    
                                      func finished() {
                                        remaining -= 1
                                        if remaining == 0 {
                                          action()
                                        }
                                      }
                                    }
                                    
                                    func makeNodes(numNodes: Int, then whenDone: @escaping () -> Void) {
                                      let coordinator = Coordinator(numNodes: numNodes, whenDone: whenDone)
                                      for _ in 0 ..< numNodes {
                                        let node = makeNode()
                                        let nodeAction = ...
                                        node.run(.sequence([nodeAction, .run { coordinator.finished() }]))
                                      }
                                    }
                                    // If node actions may take varying times but you can compute a max
                                    func makeNodes(numNodes: Int, then whenDone: @escaping () -> Void) {
                                      for _ in 0 ..< numNodes {
                                        let node = makeNode()
                                        let nodeAction = ...
                                        node.run(nodeAction)
                                      }
                                      let delayNode = SKNode()
                                      addChild(delayNode)
                                      delayNode.run(.sequence([.wait(forDuration: maxDelay), .removeFromParent()]), completion: whenDone)
                                    }

                                     

                                    (I probably have some typos in there since I'm only typing and not compiling/running, but you get the idea.)

                                      • Re: Is the SpriteKit broken in iOS13.3?
                                        mirxvid Level 1 Level 1 (0 points)

                                        Thanks, I will try and implement it other the week-end!

                                         

                                        Where did you find such ideas? It's not obvious it should be done this way in the Apple Developer Doc and I have been looking for days elsewhere...

                                          • Re: Is the SpriteKit broken in iOS13.3?
                                            bg2b Level 1 Level 1 (10 points)

                                            Once you experiment with actions a while, your mental model of how they work and the various combination/completion options will get filled in. I spent the last 6 months doing a lot of that.

                                              • Re: Is the SpriteKit broken in iOS13.3?
                                                mirxvid Level 1 Level 1 (0 points)

                                                Excellent, I went further

                                                 

                                                Now, I wonder why these Physics bodies: 2 adjacent squares do not generate an SKPhysicsContact when one rotates by .pi:

                                                 

                                                Bitmasks:

                                                    struct PhysicsCategory {
                                                        static let none      : UInt32 = 0
                                                        static let all       : UInt32 = UInt32.max
                                                        static let mine      : UInt32 = 0b001       // 1
                                                        static let his       : UInt32 = 0b010       // 2
                                                        static let us        : UInt32 = 0b011       // 3
                                                        static let unset     : UInt32 = 0b100       // 4
                                                    }

                                                 

                                                Tiles:

                                                        shape.physicsBody?.isDynamic = true
                                                        shape.physicsBody?.categoryBitMask = PhysicsCategory.unset
                                                        shape.physicsBody?.contactTestBitMask = PhysicsCategory.mine | PhysicsCategory.his
                                                        shape.physicsBody?.collisionBitMask = PhysicsCategory.none
                                                        shape.physicsBody?.usesPreciseCollisionDetection = true
                                                        shape.isPaused = false

                                                 

                                                Player (a Tile which is customized):

                                                            if (newName == "me") {
                                                                s.physicsBody?.categoryBitMask = PhysicsCategory.mine
                                                            } else {
                                                                s.physicsBody?.categoryBitMask = PhysicsCategory.his
                                                            }
                                                            s.physicsBody?.contactTestBitMask = PhysicsCategory.unset
                                                            s.physicsBody?.collisionBitMask = PhysicsCategory.none
                                                            s.name = newName
                                                
                                                

                                                 

                                                Whatever I do, there's nothing in the logs involving a contact???

                                                  • Re: Is the SpriteKit broken in iOS13.3?
                                                    bg2b Level 1 Level 1 (10 points)

                                                    Nothing immediately obvious from that snippet.  Did you turn on showsPhysics to see the physics bodies?

                                                      • Re: Is the SpriteKit broken in iOS13.3?
                                                        mirxvid Level 1 Level 1 (0 points)

                                                        Yes I did. The borders are relevant and rotate accordingly. I want to use the physics not to have to (re)code a massive matrix routine to determine which neighbours should or should not be affected...

                                                         

                                                        I tried to make the "unset" shapes to react to everything but their installation triggers a huge load of contacts. I also tested that the players' shapes NSLog their category bitmasks and it's also relevant.

                                                         

                                                        Strange.

                                                          • Re: Is the SpriteKit broken in iOS13.3?
                                                            bg2b Level 1 Level 1 (10 points)

                                                            Everything isDynamic? I'm not sure whether that matters or not; I read that only one shape needs to be dynamic for collisions to be flagged, but I've never experimented with it.  What's didBegin look like?

                                                              • Re: Is the SpriteKit broken in iOS13.3?
                                                                mirxvid Level 1 Level 1 (0 points)
                                                                extension GameScene: SKPhysicsContactDelegate {
                                                                   
                                                                    func didBegin(_ contact: SKPhysicsContact) {
                                                                        NSLog("SKPhysicsContact detected")
                                                                        if contact.bodyA.categoryBitMask == contact.bodyB.categoryBitMask {
                                                                            return
                                                                        }
                                                                        var player: SKPhysicsBody
                                                                        var that: SKPhysicsBody
                                                                
                                                                        if contact.bodyA.categoryBitMask != PhysicsCategory.shape {
                                                                            player = contact.bodyA
                                                                            that = contact.bodyB
                                                                        } else {
                                                                            player = contact.bodyB
                                                                            that = contact.bodyA
                                                                        }
                                                                        if let o = that.node as? SKShapeNode, let p = player.node as? SKShapeNode {
                                                                            if (o.fillColor == p.fillColor) {
                                                                                NSLog("Collision between %@ and %@.", p.name!, o.name!)
                                                                                o.strokeColor = p.fillColor
                                                                                o.physicsBody?.categoryBitMask = p.physicsBody!.categoryBitMask
                                                                                if (p.physicsBody!.categoryBitMask == PhysicsCategory.me) {
                                                                                    mySquares.append(o)
                                                                                    for q in mySquares {
                                                                                        propagate(shape: q)
                                                                                    }
                                                                                }
                                                                                if (p.physicsBody!.categoryBitMask == PhysicsCategory.it) {
                                                                                    hisSquares.append(o)
                                                                                    for q in hisSquares {
                                                                                        propagate(shape: q)
                                                                                    }
                                                                                }
                                                                            }
                                                                        }
                                                                    }
                                                                }
                                                                  • Re: Is the SpriteKit broken in iOS13.3?
                                                                    bg2b Level 1 Level 1 (10 points)

                                                                    And you're getting no "SKPhysicsContact detected" messages? I'm afraid I'm about at the limit of being able to remotely debug. I'd suggest just making a simple test scene, two squares, set the physics bodies and category/contact bitmasks, and do the rotation. I do this and do get contact messages:

                                                                     

                                                                    class GameScene: SKScene, SKPhysicsContactDelegate {
                                                                      let squareSize = CGSize(width: 100, height: 100)
                                                                    
                                                                      func makeSquare(color: UIColor, category: UInt32) -> SKSpriteNode {
                                                                        let square = SKSpriteNode(color: color, size: squareSize)
                                                                        let body = SKPhysicsBody(rectangleOf: squareSize)
                                                                        square.physicsBody = body
                                                                        square.zPosition = 1
                                                                        body.categoryBitMask = category
                                                                        body.contactTestBitMask = category ^ 0b11
                                                                        addChild(square)
                                                                        return square
                                                                      }
                                                                    
                                                                      func didBegin(_ contact: SKPhysicsContact) {
                                                                        guard let node1 = contact.bodyA.node else { return }
                                                                        guard let node2 = contact.bodyB.node else { return }
                                                                        print("contact \(node1.name!) and \(node2.name!)")
                                                                      }
                                                                    
                                                                      func rotate(_ sprite: SKSpriteNode) {
                                                                        sprite.run(.rotate(byAngle: .pi, duration: 1))
                                                                      }
                                                                    
                                                                      override func didMove(to view: SKView) {
                                                                        let square1 = makeSquare(color: .red, category: 0b01)
                                                                        square1.position = .zero
                                                                        square1.name = "square1"
                                                                        let square2 = makeSquare(color: .blue, category: 0b10)
                                                                        square2.position = CGPoint(x: square1.position.x + 1.05 * squareSize.width, y: square1.position.y)
                                                                        square2.name = "square2"
                                                                        run(.wait(forDuration: 3)) { self.rotate(square1) }
                                                                      }
                                                                    
                                                                      override func update(_ currentTime: TimeInterval) {
                                                                        // Called before each frame is rendered
                                                                      }
                                                                    
                                                                      override init(size: CGSize) {
                                                                        super.init(size: size)
                                                                        physicsWorld.gravity = .zero
                                                                        physicsWorld.contactDelegate = self
                                                                        anchorPoint = CGPoint(x: 0.5, y: 0.5)
                                                                      }
                                                                    
                                                                      required init(coder aDecoder: NSCoder) {
                                                                        fatalError()
                                                                      }
                                                                    }
                                                                    

                                                                     

                                                                    I also checked that at least one (but not both) bodies must have isDynamic true. One thing I hadn't thought about is that the squares will push each other during the rotation, which is kind of suggesting that you may not want the physics engine involved at all. That is, I'd probably use just a discrete grid representation and have a rotating square check the neighboring cells for their status in order to capture them.

                                                                      • Re: Is the SpriteKit broken in iOS13.3?
                                                                        mirxvid Level 1 Level 1 (0 points)

                                                                        Once again thanks for the time you devoted helping me.

                                                                        I went in the addShape func and made all the tiles non-dynamic by default, then when naming the players tiles, I made these active.

                                                                        It works.

                                                                        The physics engin might get too busy if all of the tiles are active...