Collision in Spritekit not collide with another node(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中的冲突不会与其他节点冲突的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!