Reputation: 137
One of my sprites is freezing after switching between the game scene and game over scene multiple times. Once I die and restart about 6-7 times, my 'enemy sprite' no longer respond to the tilt of my device (if it does, it takes a very long time). My player can still move around just fine when using the onscreen joystick. The whole time my FPS reads 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()
}
}
Does anyone have any suggestions?
Upvotes: 1
Views: 472
Reputation: 12753
By referencing self.destY
in the motionManager.startAccelerometerUpdatesToQueue
closure, you are creating a strong reference cycle. From the docs,
If you assign a closure to a property of a class instance, and the closure captures that instance by referring to the instance or its members, you will create a strong reference cycle between the closure and the instance.
This reference cycle is preventing your scene from being released. And since you do not stop the motion manager, the old managers (plural) are still running when you transition scenes. This is likely causing the current scene to freeze after multiple transitions.
Swift uses capture lists to avoid strong reference cycles, where a capture list has the form
{ [ /* weak or unowned + object, ...*/ ]
/* parameters */ in
}
You can define the capture in a closure as unowned or weak. From the docs,
Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.
and
Conversely, define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocated. This enables you to check for their existence within the closure’s body.
Here's an example of how to add a capture list to your accelerometer handler:
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)
}
Lastly, it's a good idea to stop the accelerometer updates before transitioning scenes
override func willMoveFromView(view: SKView) {
motionManager.stopAccelerometerUpdates()
}
Upvotes: 2