Collision in Spritekit not collide with another node(SpriteKit中的冲突不会与其他节点冲突)

本文介绍了SpriteKit中的冲突不会与其他节点冲突的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将以下问题描述为StackOverflow标准。

总结问题

我对两个节点发生冲突有问题。一个是由人群组成的,每个人都是以相同方式定义的我的人群中的一个项目(为了清楚起见,在一段时间内包括在内,并使用索引和索引来创建人群行)。我想在到达一个节点后消失(作为平民)到底部,然后到顶部,人群移走沿途产生的平民。实际上我有这样一件事,func告诉我碰撞发生了,但它没有发生,因为什么都没有发生,被群众捕获的民用节点没有消失,也没有用removefromparent()移除。我的编译器没有收到错误消息,它对他有效。我的范围是:检测群众在路径中的民用节点,并将此节点从路径中移除。

我尝试的内容 我试了很多办法来解决这个问题。第一件事是遵循许多关于如何碰撞遮罩等的教程。工作。我知道他们是做什么的。但我尝试的是为最后一排人群制作一条看不见的线,只是为了看看问题是否出在人群本身,如果平民节点到达碰撞,这条看不见的线就像他与人群接触一样,但它没有这种效果。我学习了很多教程,比如HackingWithSWIFT,YouTube教程,但对我来说,步骤很清楚,但什么都没有发生(抱歉,重复了)。

显示代码 我的问题是GameScene.sks,因为它只是一个包含所有功能的文件。

import SpriteKit
import GameplayKit

enum CategoryMask: UInt32 {
    case civilian_value = 1
    case crowd_value = 2
    case background_value = 0 
}

enum GameState {
    case ready
    case playing
    case dead
}

var gameState = GameState.ready {
    didSet {
        print(gameState)
    }
}

class GameScene: SKScene, SKPhysicsContactDelegate {

let player = SKSpriteNode(imageNamed: "player1")
let textureA = SKTexture(imageNamed: "player1")
let textureB = SKTexture(imageNamed: "player2")
let pause = SKSpriteNode(imageNamed: "pause-button")
let resume = SKSpriteNode(imageNamed: "pause-button")

var civilian = SKSpriteNode()

let pauseLayer = SKNode()
let gameLayer = SKNode()

weak var sceneDelegate: GameSceneDelegate?

//main
override func didMove(to view: SKView) {
    self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    
    self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
    self.physicsWorld.contactDelegate = self
    physicsBody = SKPhysicsBody(edgeLoopFrom: frame)

    //func for dynamic background
    moveBackground(image: ["background1", "background2", "background3", "background1"], x: 0, z: -3, duration: 5, size: CGSize(width: 0.5, height: 1.0))
    
    character(player: player)
    
    run(SKAction.repeatForever(
        SKAction.sequence([
            SKAction.run(civilians),
            SKAction.wait(forDuration: 3.0)])))
    
    run(SKAction.run(crowdSpawn))
    
    pause.name="pause"
    pause.position = CGPoint(x: frame.minX/1.3, y: frame.minY/1.15)
    pause.size=CGSize(width: 0.1, height: 0.1)
    pause.zPosition = 4
    addChild(pause)
    
    if self.scene?.isPaused == true {
        resume.name="resume"
        resume.position = CGPoint(x: frame.minX/1.5, y: frame.minY/1.15)
        resume.size=CGSize(width: 0.1, height: 0.1)
        resume.zPosition = 12
        addChild(resume)
    }

}

func pauseGame() {
    sceneDelegate?.gameWasPaused()

    let barr = SKSpriteNode()
    let barrbehind = SKSpriteNode()
    let buttonresume = SKSpriteNode(imageNamed: "back")
    
    
    barrbehind.name = "barrbehind"
    barrbehind.zPosition = 9
    barrbehind.color = SKColor.black
    barrbehind.size = CGSize(width: frame.width, height: frame.height)
    barrbehind.alpha = 0.5
    self.addChild(barradietro)

    barr.name = "bar"
    barr.size = CGSize(width: 0.4, height: 0.5)
    barr.color = SKColor.white
    barr.zPosition = 10
    self.addChild(barr)

    buttonresume.name = "resume"
    buttonresume.zPosition = 11
    buttonresume.color = SKColor.black
    buttonresume.size = CGSize(width: 0.1, height: 0.1)
    buttonresume.alpha = 0.5
    self.addChild(buttonresume)
    
    self.scene?.isPaused = true

}

//random func (it helps for generate randomly civilians along the path
func random() -> CGFloat {
    return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}

func random(min: CGFloat, max: CGFloat) -> CGFloat {
    return random() * (max - min) + min
}

//func to define civilians
func civilians() {
    let civilian = SKSpriteNode(imageNamed: "PV")
    civilian.name = "civilian"
    //posiziono il civile
    civilian.position = CGPoint(x: frame.size.width/8.0 * random(min: -1.5, max: 1.5), y: -frame.size.height * 0.45)
    
    civilian.physicsBody = SKPhysicsBody(rectangleOf: civilian.size)
    civilian.zPosition = 3
    
    civilian.physicsBody?.categoryBitMask = CategoryMask.civilian_value.rawValue
    
    civilian.physicsBody?.collisionBitMask = CategoryMask.crowd_value.rawValue
    civilian.physicsBody?.contactTestBitMask = CategoryMask.crowd_value.rawValue
    
    civilian.physicsBody?.isDynamic = true
    
    //civilian size
    civilian.size=CGSize(width: 0.2, height: 0.2)
    //civilian movement
    
    civilian.run(
        SKAction.moveBy(x: 0.0, y: frame.size.height + civilian.size.height,duration: TimeInterval(1.77)))
 
    addChild(civilian)
   
}

//func for the main character
func character(player: SKSpriteNode){
    player.position = CGPoint(x: 0, y: 0)
    player.size = CGSize(width: 0.2, height: 0.2)
    let animation = SKAction.animate(with: [textureB,textureA], timePerFrame:0.2)
    player.position = CGPoint(x: frame.midX, y: frame.midY)
    addChild(player)
    player.run(SKAction.repeatForever(animation))
}

//func for generate the crowd
func crowdSpawn(){
    
    var i = 0.0
    var j = 0.25
    var crowdRaw : Bool = true
    while crowdRaw {
        
        if i <= 1 {
            let crowd = SKSpriteNode(imageNamed: "player1")
            crowd.name = "crowd"
            //posiziono il civile
            crowd.size=CGSize(width: 0.15, height: 0.15)
            crowd.position = CGPoint(x: -frame.size.width / 3.6 + CGFloat(i)/2 * crowd.size.width , y: frame.size.height / 2 + (CGFloat(j)*2) * -crowd.size.height)
            crowd.zPosition = 3
            
            let animation = SKAction.animate(with: [textureB,textureA], timePerFrame:0.25)
            crowd.run(SKAction.repeatForever(animation))
            crowd.run(SKAction.moveBy(x: frame.size.width / 16.0 + CGFloat(i) * crowd.size.width, y: 0, duration: 0))
            
            
            let infectedCollision = SKSpriteNode(color: UIColor.red,
                                                 size: CGSize(width: 1, height: 0.1))
            infectedCollision.physicsBody = SKPhysicsBody(rectangleOf: infectedCollision.size)
            infectedCollision.physicsBody?.categoryBitMask = CategoryMask.crowd_value.rawValue
            //collisionBitMask : qui la linea della folla non può collidere con il civilian
            infectedCollision.physicsBody?.collisionBitMask = CategoryMask.civilian_value.rawValue
            infectedCollision.physicsBody?.contactTestBitMask = CategoryMask.civilian_value.rawValue
            infectedCollision.physicsBody?.isDynamic = true
            
            infectedCollision.name = "infectedCollision"
            
            infectedCollision.position = crowd.position
            addChild(crowd)

            addChild(infectedCollision)

            i += 0.25
        } else {
            j += 0.25
            i = 0.0
        }
        
        if j == 1 {
            crowdRaw = false
        }
    }
    
}

func didBegin(_ contact: SKPhysicsContact) {

    if contact.bodyA.node?.position == contact.bodyB.node?.position {
        let actionMoveDone = SKAction.removeFromParent()
        civilian.run(SKAction.sequence([actionMoveDone]))
    }
}


//func about the touches
func touchDown(atPoint pos : CGPoint) {
    let action = SKAction.move(to: pos, duration: 1.0)
    // playerSprite is a SpriteKit sprite node.
    player.run(action)
}


//func about the touches 
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    switch gameState {
    case .ready:
        gameState = .playing
        
    case .playing:
        for t in touches {
            let location = t.location(in: self)
            
            player.position.x = location.x/2
            
            for node in self.nodes(at: location){
                
                if node.name == "civilian" {
                    
                    let explode = SKAction.colorize(with: UIColor.systemBlue,colorBlendFactor: 5.0, duration: 2)
                    let vanish = SKAction.fadeOut(withDuration: 2.0)
                    node.run(explode , completion: {
                        node.run(vanish) {
                            node.removeFromParent()
                            
                        }
                    })
                }else if node.name == "pause" {
                    pauseGame()
                    
                }else if node.name == "resume" {
                    self.scene?.isPaused = false
                }
            }
        }
        
    case .dead:
        print("dead")
    }
    
    
    
}

//function to have different backgrounds in scrolling (3 backgrounds in a loop)
func moveBackground(image: [String], x: CGFloat, z:CGFloat, duration: Double, size: CGSize) {
    for i in 0...3 {
        
        let background = SKSpriteNode(imageNamed: image[i])
        
        background.position = CGPoint(x: x, y: size.height * CGFloat(i))
        background.size = size
        background.zPosition = z
        
        let move = SKAction.moveBy(x: 0, y: -background.size.height*3, duration: 0)
        let back = SKAction.moveBy(x: 0, y: background.size.height*3, duration: duration)
        
        let sequence = SKAction.sequence([move,back])
        let repeatAction = SKAction.repeatForever(sequence)
        
        addChild(background)
        background.run(repeatAction)  
    }
}

}

推荐答案

好的,回想一下这一切SpriteKit是如何工作的很有趣:d

您遇到的第一个问题是创建节点/精灵。解决方案可以是某种或多或少具有抽象性的Factory模式。GameScene不必知道节点是如何初始化/配置的。Scene只能知道存在哪种类型的节点,这足以使它们准备好使用。

    //MARK: - Factory
protocol AbstractFactory {
    func getNode()-> SKNode
    func getNodeConfig()->SpriteConfig
}

class CivilianFactory : AbstractFactory {
    
    // Local Constants
    private struct K {
        static let size = CGSize(width: 32, height: 32)
        static let name = "civilian"
        static let color = UIColor.yellow
    }
    
    // Here we get Civilian sprite config
    func getNodeConfig() -> SpriteConfig {
        
        let physics = SpritePhysicsConfig(categoryMask: Collider.civilian, contactMask: Collider.player | Collider.wall, collisionMask: Collider.none)
        
        return  SpriteConfig(name: K.name, size: K.size, color: K.color, physics: physics)
    }
    
    func getNode() -> SKNode {
        
        let config = getNodeConfig()
        
        let sprite = SKSpriteNode(color: config.color, size: config.size)
        sprite.color = config.color
        sprite.name = config.name
        sprite.zPosition = 1
        
        if let physics = config.physics {
            sprite.physicsBody = SKPhysicsBody(rectangleOf: config.size)
            sprite.physicsBody?.isDynamic = physics.isDynamic
            sprite.physicsBody?.affectedByGravity = physics.isAffectedByGravity
            sprite.physicsBody?.categoryBitMask = physics.categoryMask
            sprite.physicsBody?.contactTestBitMask = physics.contactMask
            sprite.physicsBody?.collisionBitMask = physics.collisionMask
        }
    }
  
        
        return sprite
    }
}

与此相同,您将根据需要创建其他工厂(只需复制工厂并更改视觉/物理数据设置)。对于本例,我将设置PlayerFactory

使用Next方法我将创建我的节点:

 private func getNode(factory:AbstractFactory)->SKNode{
        return factory.getNode()
 }

然后像这样使用它:

let node = getNode(factory: self.civiliansFactory) // or self.whateverFactory
在这里,您只需提供所需的工厂(可以是符合AbstractFactory的任何内容),作为回报,您将获得所需的节点(您可以在此处返回SKNode中的任何内容)。通过这种方式,我们向外界隐藏了initialization流程、依赖项等(GameScene),并将所有内容放在一个位置。

非常灵活,加上从场景中删除了一堆重复的代码。

下面是精灵创建的配置结构:

//MARK: - Sprite Config
struct SpriteConfig {
    
    let name:String
    let size:CGSize
    let color:UIColor
    let physics:SpritePhysicsConfig? // lets make this optional
}

struct SpritePhysicsConfig {
    
    let categoryMask: UInt32
    let contactMask: UInt32
    let collisionMask:UInt32
    let isDynamic:Bool
    let isAffectedByGravity:Bool
    
    init(categoryMask:UInt32, contactMask:UInt32, collisionMask:UInt32, isDynamic:Bool = true, isAffectedByGravity:Bool = false){
        self.categoryMask = categoryMask
        self.contactMask = contactMask
        self.collisionMask = collisionMask
        self.isDynamic = isDynamic
        self.isAffectedByGravity = isAffectedByGravity
    }
}

现在我需要一些有用的扩展:

//MARK: - Extensions

//Extension borrowed from here : https://stackoverflow.com/a/37760551
extension CGRect {
    func randomPoint(x:CGFloat? = nil, y:CGFloat? = nil) -> CGPoint {
        let origin = self.origin
        return CGPoint(x: x == nil ? CGFloat(arc4random_uniform(UInt32(self.width))) + origin.x : x!,
                       y: y == nil ? CGFloat(arc4random_uniform(UInt32(self.height))) + origin.y : y!)
    }
}

//Extension borrowed from here:  https://stackoverflow.com/a/33292919

extension CGPoint {
    func distance(point: CGPoint) -> CGFloat {
        return abs(CGFloat(hypotf(Float(point.x - x), Float(point.y - y))))
    }
}

和GameScene:

//MARK: - Game Scene
class GameScene: SKScene {
    
    //MARK: - Local Constants
    
    // It's always good to have some kind of local constants per file, so that you have all variables in one place when it comes to changing/tuning
    private struct K {
        
        struct Actions {
            static let civilianSpawningKey = "civilian.spawning"
            static let playerMovingKey = "player.moving"
            static let spawningDuration:TimeInterval = 0.7
            static let spawningRange = 0.2
            static let fadeOutDuration:TimeInterval = 0.35
        }
        
        struct General {
            static let playerSpeed:CGFloat = 350
        }
        
    }
    
    //MARK: - Private Properties
    private var player:SKSpriteNode?
    
    // Just in case, nodes are removed after physics simulation is done (in didSimulatePhysics which is called in each frame)
    // Frame-Cycle Events : https://developer.apple.com/documentation/spritekit/skscene/responding_to_frame-cycle_events
    private var trash:[SKNode] = []
    
    private let civilianFactory = CivilianFactory()
    private let playerFactory = PlayerFactory()
    
    //MARK: - Scene lifecycle
    override func sceneDidLoad() {
        
        physicsWorld.contactDelegate = self
        spawnCivilians()
    }
    
    //MARK: - Creating & Spawning sprites
    
    private func getNode(factory:AbstractFactory)->SKNode{
        return factory.getNode()
    }
    
    private func spawnCivilian(at position: CGPoint){
        
        let node = getNode(factory: civilianFactory)
        node.position = position
        
        addChild(node)
    }
    
    private func spawnPlayer(at position: CGPoint){
        
        // If its a first time, create player and leave it there
        guard let `player` = player else {
            
            let node = getNode(factory: playerFactory)
            node.position = position
            
            self.player = (node as? SKSpriteNode)
            
            addChild(node)
            
            return
        }
        
        // If player exists, move it around
        let distance =  player.position.distance(point: position)
        let speed = K.General.playerSpeed
        
        // To maintain same moving speed, cause if we use constant here, sprite would move faster or slower based on a given distance
        let duration = distance / speed
        let move = SKAction.move(to: position, duration:duration)
        
        // This is a good way to check if some action is running
        if player.action(forKey: K.Actions.playerMovingKey) != nil {
            player.removeAction(forKey: K.Actions.playerMovingKey)
            
        }
        player.run(move, withKey: K.Actions.playerMovingKey)
    }
    
    private func spawnCivilians(){
        
        let wait = SKAction .wait(forDuration: K.Actions.spawningDuration, withRange: K.Actions.spawningRange)
        
        let spawn = SKAction.run({[weak self] in
            guard let `self` = self else {return}
            
            self.spawnCivilian(at: self.frame.randomPoint())
        })
        
        let spawning = SKAction.sequence([wait,spawn])
        
        self.run(SKAction.repeatForever(spawning), withKey:K.Actions.civilianSpawningKey)
        
    }
    
    //MARK: - Touches Handling
    func touchDown(atPoint pos : CGPoint) {
        spawnPlayer(at: pos)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        for t in touches { self.touchDown(atPoint: t.location(in: self)) }
    }
}

所以我几乎评论了所有的东西。给你:

  • 在场景加载后立即开始无限生成平民
  • 触摸即可将玩家添加到场景
  • 在下一次触摸时,玩家(以相同的速度)移动到触摸位置

和联系人:

//MARK: - Physics
struct Collider{
    static let player : UInt32 = 0x1 << 0
    static let civilian : UInt32 = 0x1 << 1
    static let wall : UInt32 = 0x1 << 2
    static let none : UInt32 = 0x0
}

extension GameScene: SKPhysicsContactDelegate{
    
    //MARK: - Removing Sprites
    override func didSimulatePhysics() {
        
        for node in trash {
              // first remove node from parent (with fadeOut)
              node.run(SKAction.sequence([SKAction.fadeOut(withDuration: K.Actions.fadeOutDuration), SKAction.removeFromParent()])) 
        }
        trash.removeAll() // then empty the trash
    }
    
    //MARK: Removing
    func didBegin(_ contact: SKPhysicsContact) {
        
        guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else {
            
            //Silliness like removing a node from a node tree before physics simulation is done will trigger this error
            fatalError("Physics body without its node detected!")
        }
        
        let mask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
        
        switch mask {
            
        // Contact between player and civilian detected
        case Collider.player | Collider.civilian:
            
            if let civilian = (contact.bodyA.categoryBitMask == Collider.civilian ? nodeA : nodeB) as? SKSpriteNode
            {
                trash.append(civilian)
            }
            
        default:
            
            break
        }
    }
}

我想这些联系人和节点移除是您的问题。关键是,当didSimulatePhysics方法完成时,从节点树中移除具有物理实体的节点更安全。评论中有一个链接解释了每一帧发生的事情,但最重要的是,物理引擎保留了物理实体,因为模拟尚未完成,但节点被移除,这往往会导致一些意想不到的结果。

因此,要尝试此操作,只需将其复制/粘贴到您的GameScene中。下面是它的外观:

通过观察节点计数标签,您可以看到节点实际上是如何被删除的。(要启用这些标签,只需(在您的视图控制器类中)使用(self.view as? SKView)?.showsNodeCount = true, showsFPS, showsPhysics等)。

这篇关于SpriteKit中的冲突不会与其他节点冲突的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!