Game crash only on iPhone X and iPhone 8 Plus - swift

I'm working on game and the game works perfect on all iPhones and iPad expect iPhone X and iPhone 8 Plus
When I add sprite to the scene the game crash (only on iPhone X and iPhone 8 Plus) because the sprite is already added to scene, On others iPhone the app not crash, I think the bug is in my timer, maybe the bug it's the iPhone simulator? What do you think?
Code:
class GameScene: SKScene, SKPhysicsContactDelegate {
var finger = SKSpriteNode(imageNamed: "Finger") //Finger to swipe the biscuit
var biscuit = SKSpriteNode(imageNamed: "Biscuit")
var glass = SKSpriteNode(imageNamed: "Glass")
var defaultbiscuitpos:CGPoint = CGPoint()
var defaultfingerpos:CGPoint = CGPoint()
/*** Drag Biscuit vars ***/
var touchpoint:CGPoint = CGPoint()
var istouching:Bool = false
var fadeoutfinger = SKAction()
var fadeinfinger = SKAction()
var fingergroup = SKAction()
var burnanimtion = SKAction()
var movefinger = SKAction()
let fingertimer:String = "fingertimer"
var isGameover:Bool = false
//Game mode enum
enum gamemode {
case dip
case ready
case out
case gameover
}
//Game mode (Dip,Ready,Out or game over by enum) **Now is Dip
var mymode = gamemode.dip
override func didMove(to view: SKView) {
//Finger
finger.name = "Finger"
finger.position = CGPoint(x: biscuit.position.x + finger.frame.width, y: biscuit.position.y)
defaultfingerpos = finger.position
finger.alpha = 1.0
finger.zPosition = 5
//Start finger timer to make animation
createTimer(name: fingeranimation, waitt: 3.0, sprite: finger, actioname: fingertimer)
}
//Finger timer func
func fingeranimation () {
//Check if timer is over 4 seconds and the title is dip
if mymode == gamemode.dip {
//Add finger to screen
addChild(finger)
//Set fade in animation for finger
fadeinfinger = SKAction.fadeIn(withDuration: 2.0)
//Set move animation for finger
movefinger = SKAction.moveTo(y: glass.frame.midX, duration: 2.0)
//Set fade out animation for finger
fadeoutfinger = SKAction.fadeOut(withDuration: 2.0)
fingergroup = SKAction.group([fadeinfinger,movefinger,fadeoutfinger])
finger.run(fingergroup, completion: {
//Remove finger from screen
self.finger.removeFromParent()
//Return the finger to apper and return the finger to default position
self.finger.alpha = 1.0
self.finger.position = self.defaultfingerpos
})
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch
let location = touch.location(in: self)
//Tap on biscuit
if biscuit.contains(location) && mymode != gamemode.gameover {
touchpoint = location
istouching = true
biscuit.physicsBody?.pinned = false
//Stop the finger animation timer
stopTimer(actioname: fingertimer, sprite: finger)
}
}
//Make timer function
func createTimer (name:#escaping os_block_t , waitt:TimeInterval, sprite:SKSpriteNode?,actioname: String) {
let myaction = SKAction.sequence([SKAction.wait(forDuration: waitt), SKAction.run(name)])
run(SKAction.repeatForever(myaction), withKey: actioname)
}
//Stop timer function
func stopTimer(actioname:String, sprite:SKSpriteNode?) {
removeAction(forKey: actioname)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if istouching && isGameover == false {
let dt:CGFloat = 1.0/15
let distance = CGVector(dx: touchpoint.x - biscuit.position.x, dy: touchpoint.y - biscuit.position.y * 1.65)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
self.biscuit.physicsBody!.velocity = velocity
}
}
}

This problem is now fixed with the release of Xcode 9.1.

Related

Moving sprite with finger gesture

I'm trying to move the sprite (red dot) whenever I move my finger anywhere on the screen of the device. The following code seems to work only within a small invisible circle rather than in the whole area of the screen, meaning the sprite seems to encounter some sort of wall and doesn't go any further. Anyone can figure out why?
import SpriteKit
import GameplayKit
var positionX = CGFloat()
var positionY = CGFloat()
var newPositionX = CGFloat()
var newPositionY = CGFloat()
var redDotPosition = CGPoint()
var oldPosition = CGPoint()
var newPosition = CGPoint()
class GameScene: SKScene {
var redDot : SKSpriteNode?
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
private var spinnyNode : SKShapeNode?
override func sceneDidLoad() {
redDot = childNode(withName: "redDot") as? SKSpriteNode
redDot?.texture = SKTexture(imageNamed: "redDot")
redDot?.position = CGPoint(x: frame.midX , y: frame.midX)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self))
oldPosition = t.previousLocation(in: self)
let oldPositionX = oldPosition.x
let oldPositionY = oldPosition.y
newPositionX = t.location(in: self).x
newPositionY = t.location(in: self).y
deltaX = newPositionX - oldPositionX
deltaY = newPositionY - oldPositionY
redDot?.position = CGPoint(x: redDot!.position.x + deltaX , y: redDot!.position.y + deltaY)
}
}
Nevermind!
the problem lied in the fact that I had accidentally anchored my sprite to the scene, therefore it wasn't moving more than a certain amount.

Multiple Contacts Registering

Any help would be greatly appreciated. I am relatively new to this and just feel like I beginning to understand coding.
My issue -----
I am having difficult trying to fix a problem with a SpriteKit tutorial that I have been enhancing as a way to hone and improve my skills as a newbie.
I am experiencing multiple contacts when my player “crashes” on the ground.
More than one life is removed, which causes my game to generate a 'Attemped to add a SKNode which already has a parent: ’ error adding the player back to the scene.
I have tried player.removeFromParent everywhere I can think of inside my code.
The game works flawlessly as long as I “crash” into “enemies” in the air. Once the player contacts the ground, it’s all over. If I “kill” the player upon contact with the ground, there is no issue, but I want the game to continue as long as the player still has lives whether the contact the ground or and enemy.
I really believe the issue would be fixed if the multiple contact problem could be resolved.
class GameScene: SKScene, SKPhysicsContactDelegate {
// Set up the Texure Atlases
var images = SKSpriteNode()
var textureAtlas = SKTextureAtlas()
var textureArray = [SKTexture]()
var touchingScreen = false
var obstacle = SKSpriteNode()
// Generates a Random number between -350 and 350 (the center of the axis being 0)
let rand = GKRandomDistribution(lowestValue: -350, highestValue: 350)
var timer: Timer?
// This method is called when the Game Launches
override func didMove(to view: SKView) {
// Adds a pixel perfect physicsBody to the player
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.texture!.size())
player.physicsBody?.categoryBitMask = 1
player.position = CGPoint(x: -400, y: 275)
// Disables the affects of a collisions (+pushing, rotation etc.) on the player when a collision with another SKSpriteNode occurs
player.physicsBody?.allowsRotation = false
player.physicsBody?.collisionBitMask = 0
addChild(player)
physicsWorld.gravity = CGVector(dx: 0, dy: -2)
physicsWorld.contactDelegate = self
timer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
}
// This method is called when the User touches the Screen
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touchingScreen = true
}
// This method is called when the User stops touching the Screen
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touchingScreen = false
}
// This method is called before each frame is rendered
override func update(_ currentTime: TimeInterval) {
// Constrains the player to the scene area
if player.position.y > 275 {
player.position.y = 275
}
// Moves the player up when the screen is being touched
if touchingScreen {
player.physicsBody?.velocity = CGVector(dx: 0, dy: 200
)
}
}
func didBegin(_ contact: SKPhysicsContact) {
// Exits the method if either node is nil (doesn't exist)
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
// Check to see if either node is player and, if so, call the playerHit method and pass in the other node
if nodeA == player {
playerHit(nodeB)
} else if nodeB == player {
playerHit(nodeA)
}
}
func createEnemy() {
// Check for Bonus Creation
checkForBonusCreation()
// Choose a Random Enemy
let pickEnemy = Int(arc4random_uniform(3))
switch pickEnemy {
case 0:
obstacle = SKSpriteNode(imageNamed: "enemy-balloon")
animateBalloon()
case 1:
obstacle = SKSpriteNode(imageNamed: "enemy-bird")
animateBird()
case 2:
obstacle = SKSpriteNode(imageNamed: "enemy-plane")
animatePlane()
default:
return
}
// Positions the enemy
obstacle.zPosition = -2
obstacle.position.x = 768
obstacle.size = (CGSize(width: obstacle.size.width * 0.7, height: obstacle.size.width * 0.7))
// Prevents the obstacle from being spawned too low on the scene
if obstacle.position.y < -150 {
obstacle.position.y = -150
}
addChild(obstacle)
// Adds pixel perfect collision detection to the enemies
obstacle.physicsBody = SKPhysicsBody(texture: obstacle.texture!, size: obstacle.texture!.size())
// Then we set isDynamic to false so grivty will not affect the obstacles
obstacle.physicsBody?.isDynamic = false
// Assigns 1 to it's contactTestBitMask so that it know to detect a collision with the player
obstacle.physicsBody?.contactTestBitMask = 1
// Names the obstacle so we can track collisions properly
obstacle.name = "enemy"
// Spawn an enemy at a random y-axis
obstacle.position.y = CGFloat(rand.nextInt())
// Moves the obstacles across to and off the left hand side of the screen over 9 seconds athen removes thier nodes so they don't chew up memory
let move = SKAction.moveTo(x: -768, duration: 9)
let remove = SKAction.removeFromParent()
let action = SKAction.sequence([move, remove])
obstacle.run(action)
}
func playerHit(_ node: SKNode) {
if node.name == "enemy" {
player.removeFromParent()
node.removeFromParent()
lives -= 1
balloonPop()
showLivesRemaining()
} else if node.name == "ground" {
player.removeFromParent()
lives -= 1
balloonPop()
showLivesRemaining()
}
}
func balloonPop() {
player.removeFromParent()
}
func showLivesRemaining() {
if lives >= 3 {
lives = 3
} else if lives <= 0 {
lives = 0
player.removeFromParent()
}
}
// Jump to the restart or quit scene
func restartGame() {
player.removeFromParent()
let wait = SKAction.wait(forDuration: 2.0)
let showPlayer = SKAction.run {
player.position = CGPoint(x: -400, y: 275)
self.addChild(player)
}
let sequence = SKAction.sequence([wait, showPlayer])
run(sequence)
}
// Displays GAME OVER and takes the player to the "Restart or Quit" choice scene
func endGame() {
player.removeFromParent()
let gameOver = SKSpriteNode(imageNamed: "game-over")
gameOver.zPosition = 15
addChild(gameOver)
// Waits 2 seconds and fade into the Restart Scene
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
if let scene = RestartScene(fileNamed: "RestartScene") {
scene.scaleMode = .aspectFill
self.view?.presentScene(scene, transition: SKTransition.crossFade(withDuration: 1.5))
}
}
}
}
What a lot of games do is give a buffer period after a player is hit where they flash the player or turn the player red for a couple of seconds. during this time the player is not prone to more hits and gives the player an opportunity to move to safety.
func didBegin(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
//don't detect contact if the player is hit
if ((contactAName == "player") || (contactBName == "player")) && !player.isHit {
if (contactAName == "ground") || (contactBName == "ground") {
print("groundcontact with player")
player.isHit = true
//good idea to flash player or pulse player to let user know player is hit
self.run(.wait(forDuration: 3.0)) {
//wait for 3 seconds and make the player hittable again
self.player.isHit = false
}
return
}
}
}
Edit
the contact detection code fires at up to 60 times a second. When your player hits an enemy you remove that enemy, making more contact to the enemy not possible. However when the player hits the ground the ground does not get removed and thus player keeps losing lives. after the player hits the ground try bumping the player up a 100px away from the ground and see if that stops the multiple contact issue.

Rotate a sprite along an axis

In SpriteKit I need to rotate a sprite along an axis (e.g. the one that passes through the center of the sprite) just like a wheel to be spinned by the user.
I tried to use applyTorque function (to apply a force that is only angular and not linear), but I cannot handle the different forces caused by different movements on the screen (longer the touch on the screen, stronger the force to apply).
Can someone help me to understand how to deal with this problem?
Here is an answer that spins a ball according to how fast you swipe left / right:
class GameScene: SKScene {
let speedLabel = SKLabelNode(text: "Speed: 0")
let wheel = SKShapeNode(circleOfRadius: 25)
var recognizer: UIPanGestureRecognizer!
func pan(recognizer: UIPanGestureRecognizer) {
let velocity = recognizer.velocity(in: view!).x
// Play with this value until if feels right to you.
let adjustor = CGFloat(60)
let speed = velocity / adjustor
wheel.physicsBody!.angularVelocity = -speed
}
// Scene setup:
override func didMove(to view: SKView) {
removeAllChildren()
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
wheel.fillColor = .blue
wheel.physicsBody = SKPhysicsBody(circleOfRadius: 25)
wheel.physicsBody!.affectedByGravity = false
let wheelDot = SKSpriteNode(color: .gray, size: CGSize(width: 5, height:5))
wheel.addChild(wheelDot)
wheelDot.position.y += 20
wheel.setScale(3)
speedLabel.setScale(3)
speedLabel.position.y = (frame.maxY - speedLabel.frame.size.height / 2) - 45
recognizer = UIPanGestureRecognizer(target: self, action: #selector(pan))
view.addGestureRecognizer(recognizer)
addChild(wheel)
addChild(speedLabel)
}
override func didSimulatePhysics() {
speedLabel.text = "Speed: \(abs(Int(wheel.physicsBody!.angularVelocity)))"
}
}
Here is a basic example of spinning a wheel clockwise or counterclockwise depending on if you press on left / right side of screen. Hold to increase speed:
class GameScene : SKScene {
enum Direction { case left, right }
var directionToMove: Direction?
let wheel = SKShapeNode(circleOfRadius: 25)
let speedLabel = SKLabelNode(text: "Speed: 0")
override func didMove(to view: SKView) {
// Scene setup:
anchorPoint = CGPoint(x: 0.5, y: 0.5)
removeAllChildren()
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
wheel.fillColor = .blue
wheel.physicsBody = SKPhysicsBody(circleOfRadius: 25)
wheel.physicsBody!.affectedByGravity = false
let wheelDot = SKSpriteNode(color: .gray, size: CGSize(width: 5, height:5))
wheel.addChild(wheelDot)
wheelDot.position.y += 20
wheel.setScale(3)
speedLabel.setScale(3)
speedLabel.position.y = (frame.maxY - speedLabel.frame.size.height / 2) - 45
addChild(wheel)
addChild(speedLabel)
}
// Change this to touchesBegan for iOS:
override func mouseDown(with event: NSEvent) {
// Change this to touches.first!.location(in: self) for iOS.
let location = event.location(in: self)
// Determine if touch left or right side:
if location.x > 0 {
directionToMove = .right
}
else if location.x < 0 {
directionToMove = .left
}
}
override func mouseUp(with event: NSEvent) {
// Stop applying gas:
directionToMove = nil
print("lol")
}
override func update(_ currentTime: TimeInterval) {
// This is how much speed we gain each frame:
let torque = CGFloat(0.01)
guard let direction = directionToMove else { return }
// Apply torque in the proper direction
switch direction {
case .left:
wheel.physicsBody!.applyTorque(torque)
case .right:
wheel.physicsBody!.applyTorque(-torque)
}
}
override func didSimulatePhysics() {
// Speedometer:
speedLabel.text = "Speed: \(abs(Int(wheel.physicsBody!.angularVelocity)))"
}
}

Swift 3: class doesn't function correctly

So I've got a class named FlashyPaddleEffect. I also mention it in another class, GameScene, but it doesn't apply any effect that it should. The paddle should be flashing blue and white colors, but it simply stays white. The paddle also should lose its physics body when it is blue.
If you need any other information, don't hesitate to ask.
NOTE: There might be problems with the code I gave (because of indents, it's quite tricky to make code by indenting 4 spaces every line, sorry).
import SpriteKit
import GameplayKit
class FlashyPaddleEffect {
var node = SKSpriteNode()
var ballNode = SKSpriteNode()
var updateTimer: Timer? = nil
var timer: Timer? = nil
#objc func changeNodeColor() {
switch node.color {
case SKColor.blue: node.color = SKColor.white
case SKColor.white: node.color = SKColor.blue
default: _ = 1 + 2
}
}
#objc func update() //I used the objc prefix to silence the warning the selectors of the timers produced. {
let previousPhysicsBody = node.physicsBody
if node.color == SKColor.blue {
node.physicsBody = nil
}
node.physicsBody = previousPhysicsBody
}
func make(applyEffectTo: SKSpriteNode, ball: SKSpriteNode) {
node = applyEffectTo
ballNode = ball
timer = Timer.scheduledTimer(timeInterval: 0.6, target: self, selector: #selector(changeNodeColor), userInfo: nil, repeats: true)
updateTimer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(update), userInfo: nil, repeats: true)
_ = node
_ = timer
}
}
class GameScene: SKScene {
var ball = SKSpriteNode()
var player = SKSpriteNode()
var enemy = SKSpriteNode()
var scores = [0, 0]
var initScores = [0, 0]
var scoreLabels: [SKLabelNode]? = nil
let playLabel = SKLabelNode()
let timeLabel = SKLabelNode()
let timeUpLabel = SKLabelNode()
var secondsLeft: Int = 180
var initialTime: Int? = nil
var timer = Timer()
var amountOfPauseMenuCloses = 0
var resultsLabel = SKLabelNode()
let flashEffect = FlashyPaddleEffect() //I define a variable to mention the class easier later on.
override func didMove(to view: SKView) {
//Initialize nodes of the scene editor.
ball = self.childNode(withName: "Ball") as! SKSpriteNode
player = self.childNode(withName: "PPaddle") as! SKSpriteNode
enemy = self.childNode(withName: "EPaddle") as! SKSpriteNode
//Set styles for the Play Notification Label (referred to as PNL later)
playLabel.position = CGPoint(x: 0, y: 0)
playLabel.text = "Tap anywhere to play."
playLabel.name = "playLabel"
//Set styles for the Timer Label (referred to as Timer later)
timeLabel.position = CGPoint(x: 90, y: 0)
timeLabel.text = String(secondsLeft)
//Doing manipulations connected with scores here.
scores = [0, 0]
initScores = scores
scoreLabels = [self.childNode(withName: "PScoreLabel") as! SKLabelNode, self.childNode(withName: "EScoreLabel") as! SKLabelNode]
//Create a border for our ball to bounce off.
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 1
border.linearDamping = 0
border.angularDamping = 0
self.physicsBody = border
//To avoid the ball's damping.
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.angularDamping = 0
showPause() //Show the (pause) menu at the beginning
//Set a variable to refer to as a time standard later.
initialTime = secondsLeft
//The game timer.
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(reduceSecondFromTimer), userInfo: nil, repeats: true)
flashEffect.make(applyEffectTo: player, ball: ball) //This is where I reference the class in another class.
}
//A function to show the (pause) menu.
func showPause(hideNodes: Bool = true) {
self.addChild(playLabel)
if hideNodes == true {
ball.removeFromParent()
player.removeFromParent()
enemy.removeFromParent()
scoreLabels?[0].removeFromParent()
scoreLabels?[1].removeFromParent()
}
}
override func update(_ currentTime: TimeInterval) {
var alreadyChangedTextLabel = false
for i in scoreLabels! {
if i.name == "PScoreLabel" {i.text = String(scores[0])}
if i.name == "EScoreLabel" {i.text = String(scores[1])}
}
if ball.position.y <= player.position.y {scores[1] += 1}
if ball.position.y >= enemy.position.y {scores[0] += 1}
if secondsLeft == 0 {
showPause(hideNodes: false)
if alreadyChangedTextLabel == false {
timeUpLabel.text = "TIME UP! \(whoIsWinning(scores: scores)) won!"
alreadyChangedTextLabel = true
}
timeUpLabel.name = "timeUpLabel"
timeUpLabel.position = CGPoint(x: 0, y: 180)
if !(self.children.contains(timeUpLabel)) {
self.addChild(timeUpLabel)
}
timeLabel.removeFromParent()
}
if self.children.contains(playLabel) {
secondsLeft = initialTime!
}
timeLabel.text = String(secondsLeft)
let alignWithBall = SKAction.move(to: CGPoint(x: ball.position.x, y: enemy.position.y), duration: 0.8)
enemy.run(alignWithBall)
}
func initGame() {
//Initializing process for every game.
scores = initScores
ball.position = CGPoint(x: 0, y: 0)
if amountOfPauseMenuCloses == 1 {ball.physicsBody?.applyImpulse(CGVector(dx: 15, dy: 15))}
secondsLeft = initialTime!
timeLabel.text = String(secondsLeft)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for childNode in self.children {
if childNode.name == "playLabel" {
if self.children.contains(playLabel) {
playLabel.removeFromParent()
}
if !(self.children.contains(player)) {
self.addChild(player)
}
if !(self.children.contains(enemy)) {
self.addChild(enemy)
}
if !(self.children.contains(ball)) {
self.addChild(ball)
}
if !(self.children.contains((scoreLabels?[0])!)) {
self.addChild((scoreLabels?[0])!)
}
if !(self.children.contains((scoreLabels?[1])!)) {
self.addChild((scoreLabels?[1])!)
}
if !(self.children.contains(timeLabel)) {
self.addChild(timeLabel)
}
if self.children.contains(timeUpLabel) {
timeUpLabel.removeFromParent()
}
amountOfPauseMenuCloses += 1
initGame()
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
if self.nodes(at: t.location(in: self)).contains(player) {player.position.x = t.location(in: self).x}
}
}
func reduceSecondFromTimer() {
secondsLeft -= 1
}
func whoIsWinning(scores: Array<Int>) -> String {
var r: String? = nil
if scores[0] >= scores[1] {
r = "You"
}
if scores[1] >= scores[0] {
r = "Enemy"
}
return r!
}
}
Thanks a lot for answers.
P.S It's my first question ever so don't judge me strictly.
1) Do not use NSTimer use SKAction. I can see the way you are doing it you stack timer after timer, this is bad.
2) Do not have your temp variable global (node in this case), it makes code hard to read
3) Do not remove your physics body, simply remove the category.
func make(applyEffectTo: SKSpriteNode, ball: SKSpriteNode) {
ballNode = ball
let blue = SKAction.colorize(with SKColor.blue, colorBlendFactor: 1.0, duration sec: 0)
let white = SKAction.colorize(with SKColor.white, colorBlendFactor: 1.0, duration sec: 0)
let wait = SKAction.wait(for:0.6)
let turnOnPhysics = SKAction.run({applyEffectTo.physicsBody?.categoryBitmask = #######})
let turnOffPhysics = SKAction.run({applyEffectTo.physicsBody?.categoryBitmask = 0})
let seq = [blue, turnOffPhysics,wait,white,turnOnPhysics,wait]
let repeat = SKAction.repeatForever(seq)
applyEffectTo.run(repeat, withKey:"flashing")
}
Note: I have no idea what your categoryBitmask is, you need to fill it in

Swift | I'm trying to rotate a sprite and change the direction of the rotation ever time I tap

I want to rotate a single sprite indefinitely, and every time I tap the button, I want the sprite to rotate in the opposite direction (back and forth from clockwise to counter-clockwise etc.
Below is the code that I have:
http://pastebin.com/Avj8Njwt
class GameScene: SKScene {
var center = SKSpriteNode()
var bg = SKSpriteNode()
var bigCircle = SKSpriteNode()
let counterClockwise = SKAction.rotateByAngle(CGFloat(3.14), duration:1)
let clockwise = SKAction.rotateByAngle(CGFloat(-3.14), duration:1)
var spin = SKAction()
override func didMoveToView(view: SKView) {
//Background
var bgTexture = SKTexture(imageNamed: "images/bg.png")
bg = SKSpriteNode(texture:bgTexture)
bg.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(center)
//Center Circle
var bigCircleTexture = SKTexture(imageNamed: "images/bigCircle.png")
bigCircle = SKSpriteNode(texture:bigCircleTexture)
bigCircle.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(bigCircle)
//Center Triangle
var centerTexture = SKTexture(imageNamed: "images/center.png")
center = SKSpriteNode(texture:centerTexture)
center.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(center)
spin = clockwise
center.runAction(SKAction.repeatActionForever(spin))
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
if spin == clockwise {
spin = counterClockwise
}
else {
spin = clockwise
}
center.runAction(SKAction.repeatActionForever(spin))
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Your problem is you're not removing the old SKAction trying to rotate your SKSpriteNode. To do that you need to keep track of which way your sprite is rotating. If I was going to implement this I would subclass SKSpriteNode, like so:
class RotatingSprite: SKSpriteNode {
// This is used to keep track of which way the sprite is rotating.
enum Direction {
case Left, Right
mutating func inverse() {
switch self {
case .Left : self = .Right
case .Right: self = .Left
}
}
}
// These names will be the keys used when running an action.
// This will allow you to stop the rotate-left or rotate-right actions.
static let rotateLeftName = "RotateLeftAction"
static let rotateRightName = "RotateRightAction"
var rotationDirection: Direction? {
didSet {
if let r = rotationDirection {
switch r {
// Checks the sprite isn't already rotating to the left.
// If it isn't, make the sprite rotate to the left.
case .Left where oldValue != .Left:
rotateLeft()
case .Right where oldValue != .Right:
rotateRight()
default:
break
}
}
}
}
private func rotateLeft() {
// Remove the action rotating the sprite to the right.
self.removeActionForKey(RotatingSprite.rotateRightName)
// And start the action rotating the sprite to the left.
let rotateAction = SKAction.rotateByAngle(-CGFloat(M_PI), duration: 1.0)
self.runAction(SKAction.repeatActionForever(rotateAction), withKey: RotatingSprite.rotateLeftName)
}
private func rotateRight() {
self.removeActionForKey(RotatingSprite.rotateLeftName)
let rotateAction = SKAction.rotateByAngle(CGFloat(M_PI), duration: 1.0)
self.runAction(SKAction.repeatActionForever(rotateAction), withKey: RotatingSprite.rotateRightName)
}
}
Now you can use RotatingSprite like so:
class GameScene: SKScene {
let rotatingSprite = RotatingSprite(texture:bgTexture)
override func didMoveToView(view: SKView) {
rotatingSprite.position = CGPoint(x: frame.midX, y: frame.midY)
self.addChild(rotatingSprite)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
// If the sprite isn't turning you've got to set it off.
if rotatingSprite.rotationDirection == nil {
rotatingSprite.rotationDirection = .Left
// If it is turning, change its direction.
} else {
rotatingSprite.rotationDirection!.inverse()
}
}
}
Hope that helps!
It is extremely easy to achieve this. Try this ,
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
[sprite removeAllActions];
SKAction *action;
if (isClockWise)
{
action = [SKAction rotateByAngle:M_PI duration:1];
}
else
{
action = [SKAction rotateByAngle:-M_PI duration:1];
}
isClockWise = !isClockWise;
[sprite runAction:[SKAction repeatActionForever:action]];
}
Where sprite is SKSpriteNode and initiate isClockWise to Yes or No depending on your initial movement direction.
A quick and dirty way to do this is the following:
import SpriteKit
class GameScene: SKScene {
var center = SKSpriteNode()
var bg = SKSpriteNode()
var bigCircle = SKSpriteNode()
let counterClockwise = SKAction.rotateByAngle(CGFloat(3.14), duration:1)
let clockwise = SKAction.rotateByAngle(CGFloat(-3.14), duration:1)
var spin = SKAction()
// this is used to identify which direction we are going in. When we change it spin is changed as well
var isClockwise: Bool = true {
didSet {
if isClockwise {
spin = clockwise
} else {
spin = counterClockwise
}
}
}
let actionKey = "spin" // this is used to identify the SKAction
override func didMoveToView(view: SKView) {
//Background
var bgTexture = SKTexture(imageNamed: "images/bg.png")
bg = SKSpriteNode(texture:bgTexture)
bg.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(center)
//Center Circle
var bigCircleTexture = SKTexture(imageNamed: "images/bigCircle.png")
bigCircle = SKSpriteNode(texture:bigCircleTexture)
bigCircle.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(bigCircle)
//Center Triangle
var centerTexture = SKTexture(imageNamed: "images/center.png")
center = SKSpriteNode(texture:centerTexture)
center.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(center)
isClockwise = true // set the initial direction to clockwise
center.runAction(SKAction.repeatActionForever(spin), withKey: actionKey)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// remove the existing spin action
center.removeActionForKey(actionKey)
// reset the direction (this will automatically switch the SKAction)
isClockwise = !isClockwise
center.runAction(SKAction.repeatActionForever(spin), withKey: actionKey)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
You need to remove the action before you apply a new one - you can selectively remove the action by calling runAction(action:,withKey:). This enables you to be able to remove that same action using the same key. The logic for changing spin is in didSet for the isClockwise var declaration.
It was as simple as placing center.removeAllActions in each if statement to make sure the sprite isn't currently moving when the direction is supposed to be changed.