多次切换场景后 SpriteKit 游戏卡死
Posted
技术标签:
【中文标题】多次切换场景后 SpriteKit 游戏卡死【英文标题】:SpriteKit game freezing after switching scenes multiple times 【发布时间】:2015-09-07 20:24:15 【问题描述】:我的一个精灵在游戏场景和游戏结束场景之间多次切换后冻结。一旦我死亡并重新启动大约 6-7 次,我的“敌人精灵”不再响应我设备的倾斜(如果有,则需要很长时间)。使用屏幕操纵杆时,我的播放器仍然可以正常移动。我的 FPS 一直是 60。
class GameScene: SKScene, SKPhysicsContactDelegate
let joyStickSprite = SKSpriteNode(imageNamed: "flatLight09")
let playerSprite = SKSpriteNode(imageNamed: "p3_front")
let enemySprite = SKSpriteNode(imageNamed: "elementExplosive001")
let coinSprite = SKSpriteNode(imageNamed: "gold_1")
var left = false
var right = false
var enemyCount = 0
var randomNextCoin = Int(arc4random_uniform(5)+1)
var motionManager = CMMotionManager()
var destY:CGFloat = 0.0
struct CollisionCategoryBitmask
static let Player: UInt32 = 0x00
static let Enemy: UInt32 = 0x01
static let Floor: UInt32 = 0x02
static let Coin: UInt32 = 0x03
var background = SKSpriteNode(imageNamed: "blue_land")
override func didMoveToView(view: SKView)
self.physicsWorld.contactDelegate = self
createBackground()
createPlayer()
createJoyStick()
createScreenBorder()
createEnemy()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.currentQueue(), withHandler:
(accelerometerData: CMAccelerometerData!, error: NSError!) in
var currentY = self.enemySprite.position.y
let acceleration = accelerometerData.acceleration
self.destY = (CGFloat(acceleration.y) * 0.75) + (self.destY * 0.25)
)
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>)
let location = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(location)
if let name = touchedNode.name
if name == "joyStick"
if location.x < joyStickSprite.position.x
left = true
right = false
else
right = true
left = false
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent)
left = false
right = false
func presentGameOver ()
removeAllChildren()
let newScene = GameScene(size: size)
newScene.scaleMode = scaleMode
let reveal = SKTransition.flipHorizontalWithDuration(0.5)
view?.presentScene(newScene, transition: reveal)
enemySprite.physicsBody?.dynamic = false
func createBackground ()
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
background.size.height = self.size.height
addChild(background)
func createPlayer ()
playerSprite.position = CGPoint(x: self.size.width / 2, y: playerSprite.size.height/2)
playerSprite.physicsBody = SKPhysicsBody(circleOfRadius: playerSprite.size.width / 2)
playerSprite.physicsBody?.dynamic = false
playerSprite.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Player
playerSprite.physicsBody?.collisionBitMask = 0
playerSprite.physicsBody?.contactTestBitMask = CollisionCategoryBitmask.Enemy | CollisionCategoryBitmask.Coin
addChild(playerSprite)
func movePlayerLeft ()
let moveLeft = SKAction.moveByX(-10, y: 0, duration: 1)
playerSprite.runAction(moveLeft)
func movePlayerRight ()
let moveRight = SKAction.moveByX(10, y: 0, duration: 1)
playerSprite.runAction(moveRight)
func createEnemy ()
var randomX = Int(arc4random_uniform(600))
var randomXCG = CGFloat(randomX)
enemyCount += 1
enemySprite.position = CGPoint(x: randomXCG, y: self.size.height)
enemySprite.physicsBody = SKPhysicsBody(circleOfRadius: enemySprite.size.width / 2)
enemySprite.physicsBody?.dynamic = true
enemySprite.physicsBody?.allowsRotation = true
enemySprite.physicsBody?.restitution = 0.0
enemySprite.physicsBody?.friction = 0.0
enemySprite.physicsBody?.angularDamping = 0.0
enemySprite.physicsBody?.linearDamping = 0.0
enemySprite.physicsBody?.affectedByGravity = true
enemySprite.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Enemy
enemySprite.physicsBody?.collisionBitMask = CollisionCategoryBitmask.Floor
println("enemey count \(enemyCount)")
println("next coin \(randomNextCoin)")
addChild(enemySprite)
func createCoins ()
var randomX = Int(arc4random_uniform(600))
var randomXCG = CGFloat(randomX)
randomNextCoin = Int(arc4random_uniform(10))
enemyCount = 0
coinSprite.size.height = playerSprite.size.height/2
coinSprite.size.width = coinSprite.size.height
coinSprite.position = CGPoint(x: randomXCG, y: self.size.height)
coinSprite.physicsBody = SKPhysicsBody(circleOfRadius: enemySprite.size.width/2)
coinSprite.physicsBody?.dynamic = true
coinSprite.physicsBody?.affectedByGravity = true
coinSprite.physicsBody?.categoryBitMask = CollisionCategoryBitmask.Coin
addChild(coinSprite)
func createJoyStick ()
joyStickSprite.setScale(0.4)
joyStickSprite.position = CGPoint(x: self.size.width/1.1, y: joyStickSprite.size.height)
joyStickSprite.name = "joyStick"
joyStickSprite.userInteractionEnabled = false
addChild(joyStickSprite)
func updateEnemyPosition ()
if enemySprite.size.height > enemySprite.position.y
enemySprite.position.x = enemySprite.position.x + destY*20
func didBeginContact(contact: SKPhysicsContact)
let firstNode = contact.bodyA.node as! SKSpriteNode
let secondNode = contact.bodyB.node as! SKSpriteNode
if (contact.bodyA.categoryBitMask == CollisionCategoryBitmask.Player) &&
(contact.bodyB.categoryBitMask == CollisionCategoryBitmask.Enemy)
let transition = SKTransition.revealWithDirection(SKTransitionDirection.Down, duration: 1.0)
let scene = SecondScene(size: self.scene!.size)
scene.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view!.presentScene(scene, transition: transition)
if (contact.bodyA.categoryBitMask == CollisionCategoryBitmask.Player) &&
(contact.bodyB.categoryBitMask == CollisionCategoryBitmask.Coin)
coinSprite.removeFromParent()
func createScreenBorder ()
// 1. Create a physics body that borders the screen
let borderBody = SKPhysicsBody(edgeFromPoint: CGPointMake(0.0, 0.0), toPoint: CGPointMake(self.size.width, 0.0))
// 2. Set the friction of that physicsBody to 0
borderBody.friction = 0
borderBody.categoryBitMask = CollisionCategoryBitmask.Floor
// 3. Set physicsBody of scene to borderBody
self.physicsBody = borderBody
override func update(currentTime: CFTimeInterval)
//detect where on joystick player is touching
if left == true
movePlayerLeft()
if right == true
movePlayerRight()
//move player to other side when going off screen
if playerSprite.position.x < -20.0
playerSprite.position = CGPoint(x: self.size.width + 20.0, y: playerSprite.position.y)
else if (playerSprite.position.x > self.size.width + 20.0)
playerSprite.position = CGPoint(x: -20.0, y: playerSprite.position.y)
//remove enemeny if off screen
if enemySprite.position.x < -20.0 || enemySprite.position.x > self.size.width + 20.0
self.enemySprite.removeFromParent()
createEnemy()
if randomNextCoin == enemyCount
println("coin dropped")
coinSprite.removeFromParent()
createCoins()
updateEnemyPosition()
有人有什么建议吗?
【问题讨论】:
【参考方案1】:通过在motionManager.startAccelerometerUpdatesToQueue
闭包中引用self.destY
,您正在创建一个强引用循环。从文档中,
如果您将闭包分配给类实例的属性,并且闭包通过引用实例或其成员来捕获该实例,您将在闭包和实例之间创建一个强引用循环。
此引用循环会阻止您的场景被释放。而且由于您没有停止运动管理器,因此在您转换场景时,旧的管理器(复数)仍在运行。这可能会导致当前场景在多次转换后冻结。
Swift 使用捕获列表来避免强引用循环,其中捕获列表具有以下形式
[ /* weak or unowned + object, ...*/ ]
/* parameters */ in
您可以将闭包中的捕获定义为unowned 或weak。从文档中,
当闭包和它捕获的实例总是相互引用时,将闭包中的捕获定义为无主引用,并且总是同时被释放。
和
相反,当捕获的引用在未来某个时间点可能变为 nil 时,将捕获定义为弱引用。弱引用始终是可选类型,并在它们引用的实例被释放时自动变为 nil。这使您能够检查它们是否存在于闭包的主体中。
以下是如何将捕获列表添加到加速度计处理程序的示例:
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue.currentQueue())
[unowned self] accelerometerData, error in
var currentY = self.enemySprite.position.y
let acceleration = accelerometerData.acceleration
self.destY = (CGFloat(acceleration.y) * 0.75) + (self.destY * 0.25)
最后,在转换场景之前停止加速度计更新是个好主意
override func willMoveFromView(view: SKView)
motionManager.stopAccelerometerUpdates()
【讨论】:
太棒了。非常感谢。这让世界变得与众不同,我非常感谢您解释其背后的思考和推理。以上是关于多次切换场景后 SpriteKit 游戏卡死的主要内容,如果未能解决你的问题,请参考以下文章
如何在另一个视图控制器中访问 SpriteKit 场景的变量?
将游戏中心添加到游戏后第一次接触时 Spritekit 游戏场景停止
如何使用 Swift 正确切换 SpriteKit 中的 SKScene?