swiftsprite-kitskspritenodeskphysicsbodyskphysicscontact

EdgeFromLoop doesn't seem to call didBegin?


I'm trying to write a game where stars are falling from the top of the iphone screen to the bottom. I want to remove my star sprites when they're out of the screen. I've tried to use physicsContactDelegate, and create a edge around my frame with edgeFromLoop(self.frame), but didBegin is never called.

Below is my gameScene.swift file

import SpriteKit
import GameplayKit
import UIKit

class GameScene: SKScene, SKPhysicsContactDelegate {

    var entities = [GKEntity]()
    var graphs = [String : GKGraph]()

    private var lastUpdateTime : TimeInterval = 0

    struct PhysicsCategory {
      static let none: UInt32 = 0
      static let all : UInt32 = UInt32.max
      static let star: UInt32 = 0b1       // 1
      static let edge: UInt32 = 0b10      // 2
    }

    private var edge: SKPhysicsBody!

    override func sceneDidLoad() {

        self.lastUpdateTime = 0

        edge = SKPhysicsBody(edgeLoopFrom: self.frame)
        edge.categoryBitMask = PhysicsCategory.edge
        edge.contactTestBitMask = PhysicsCategory.star
        edge.collisionBitMask = PhysicsCategory.star
    }


    override func didMove(to view: SKView) {

        physicsWorld.contactDelegate = self

        run(SKAction.repeatForever(
            SKAction.sequence([
                SKAction.run(addStar),
                SKAction.wait(forDuration: 1.0)
            ])
      ))
    }

    func didBegin(_ contact: SKPhysicsContact) {

      print("didBegin called")
      // 1
      var firstBody: SKPhysicsBody
      var secondBody: SKPhysicsBody
      if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
      } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
      }

      if((firstBody.categoryBitMask & PhysicsCategory.star != 0) &&
          (secondBody.categoryBitMask & PhysicsCategory.edge != 0)){

          print("call destroyStar")
          if let star = firstBody.node as? SKSpriteNode {
              destroyStar(star: star)
          }
      }
    }

    func addStar(){

        let star = SKSpriteNode(imageNamed: "star")

        let scale = CGFloat.random(in: 0.1 ... 0.5)
        star.xScale = scale
        star.yScale = scale
        star.zPosition = 1.0

        star.physicsBody = SKPhysicsBody(rectangleOf: star.size)
        star.physicsBody?.linearDamping = 1.0
        star.physicsBody?.friction = 1.0

        star.physicsBody?.isDynamic = true // 2
        star.physicsBody?.categoryBitMask = PhysicsCategory.star // 3
        star.physicsBody?.contactTestBitMask = PhysicsCategory.edge // 4
        star.physicsBody?.collisionBitMask = PhysicsCategory.edge

        let actualX = CGFloat.random(in: (-1*size.width/2)+50 ... (size.width/2)-50)

        star.position = CGPoint(x: actualX, y: self.size.height)
        addChild(star)
    }

    override func update(_ currentTime: TimeInterval) {
        // Called before each frame is rendered

        // Initialize _lastUpdateTime if it has not already been
        if (self.lastUpdateTime == 0) {
            self.lastUpdateTime = currentTime
        }

        // Calculate time since last update
        let dt = currentTime - self.lastUpdateTime

        // Update entities
        for entity in self.entities {
            entity.update(deltaTime: dt)
        }

        self.lastUpdateTime = currentTime
    }

    func destroyStar(star: SKSpriteNode){

        print("star destroyed")
        star.removeFromParent()

    }
}

And this is my GameViewController

import UIKit
import SpriteKit
import GameplayKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Load 'GameScene.sks' as a GKScene. This provides gameplay related content
        // including entities and graphs.
        if let scene = GKScene(fileNamed: "GameScene") {

            // Get the SKScene from the loaded GKScene
            if let sceneNode = scene.rootNode as! GameScene? {

                // Copy gameplay related content over to the scene
                sceneNode.entities = scene.entities
                sceneNode.graphs = scene.graphs

                // Set the scale mode to scale to fit the window
                sceneNode.scaleMode = .aspectFit

                // Present the scene
                if let view = self.view as! SKView? {
                    view.presentScene(sceneNode)

                    view.ignoresSiblingOrder = true

                    view.showsFPS = true
                    view.showsNodeCount = true
                }
            }
        }
    }

    override var shouldAutorotate: Bool {
        return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if UIDevice.current.userInterfaceIdiom == .phone {
            return .allButUpsideDown
        } else {
            return .all
        }
    }

    override var prefersStatusBarHidden: Bool {
        return true
    }
}

I'm a swift beginner, I know my question is probably really basic but I'd really appreciate any help! Thanks!


Solution

  • You need to make edge and SKNode() and then assign the physicsBody to edge.

    try this.

    let edge = SKNode()
    
    override func didMove(to View: SKView) {
        edge.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
        edge.physicsBody.categoryBitMask = PhysicsCategory.edge
        edge.physicsBody.contactTestBitMask = PhysicsCategory.star
        edge.physicsBody.collisionBitMask = PhysicsCategory.star
    
        self.addChild(edge)
    }