Why is my node slowing down when it collects a coin? - swift

My game basically is a jumping game when you tap the screen the heroNode jumps and collects coins coming from the right part of the screen. When it collects the coin the hero node slows down and it goes out of the view. Why does this happen? Heres the code I have.
func coins() {
let moveToLeft = SKAction.moveByX(-self.size.width, y: 0, duration: 2.0)
let repeatMoveToLeft = SKAction.repeatActionForever(moveToLeft)
let removeFromScene = SKAction.removeFromParent()
let sequenceThisMoveAndRemove = SKAction.sequence([repeatMoveToLeft, removeFromScene])
goldCoins.position = CGPointMake(self.size.width / 0.6, self.size.height / 2)
goldCoins.zPosition = 15
goldCoins.setScale(0.9)
goldCoins.runAction(sequenceThisMoveAndRemove)
addChild(goldCoins)
goldCoins.physicsBody = SKPhysicsBody(circleOfRadius: 5)
goldCoins.physicsBody?.affectedByGravity = false
goldCoins.physicsBody?.allowsRotation = false
goldCoins.physicsBody?.categoryBitMask = GoldCoinCategory
goldCoins.physicsBody?.contactTestBitMask = HeroCategory
goldCoins.physicsBody?.collisionBitMask = 0
func addHero() {
let anim = SKAction.animateWithTextures([heroTextureOne, heroTextureTwo], timePerFrame: 0.2)
let run = SKAction.repeatActionForever(anim)
theHero = SKSpriteNode(texture: heroTextureOne)
theHero.runAction(run)
theHero.physicsBody = SKPhysicsBody(circleOfRadius: 50)
theHero.physicsBody?.affectedByGravity = true
theHero.physicsBody?.allowsRotation = false
theHero.physicsBody?.categoryBitMask = HeroCategory
theHero.setScale(0.5)
theHero.position = CGPointMake(self.size.width / 4.0, self.size.height / 2.0)
theHero.zPosition = 15
addChild(theHero)
}
if firstBody.categoryBitMask == HeroCategory && sixthBody.categoryBitMask == GoldCoinCategory {
sixthBody.node!.removeFromParent()

One possibility is that you're making a lot of gold coins that never get removed from the scene graph, and that's bogging down your performance.
Look at your first four lines of coins(). You create a forever-repeating action, and then create a sequence with the forever-repeating action and then the "remove from scene" action. A sequence performs the given actions in order, but a forever-repeating action will never end, so that "remove from scene" action will never be triggered.
So when you addChild( goldCoins ), those coins are never going to go away. And the only other way they can apparently be removed is with a collision. So if you play the game, and if a lot of goldCoins get added, then you're going to have an unbounded number of coins in play. After a while, having enough of those coins, all running actions, could cause your game to slow down.
Another possibility is that all you're removing is the sprite node and not the physics body from the simulation. This is suggested by that last line you included. If you remove the node, the coin will disappear, but the physics body will still be in play, still affecting other physics bodies. If you want to fully remove the coin - and its effect on the physics simulation - you'll need to remove its physics body, too.

Related

Running SKActions on Multiple SKNodes in Sequence

I am creating a card game and having trouble having SKActions run in a sequence across multiple SK Objects (Nodes, Textures, Labels, etc.). Any help would be much appreciated! In the example below, I am trying to create a "dealing" motion. Where 1 card gets dealt, some time elapses, then the next card gets dealt. I tried using this, but with 52 cards, it's not exactly the simplest approach.
First, I tried creating an array of SKActions for each card. However, I don't believe I can run the below using one command based on my understanding of the documentation. Each action needs to be run against the specific Sprite Object as opposed to running a whole sequence of actions across multiple Sprite Objects.
let dealAction = SKAction[]()
for card in deck {
let move = SKAction....
dealAction.append(move)
}
run.SKAction.sequence(dealAction) // This will not work.
Then I tried this with the hope that the loop would complete each cards block of code before moving on to the next card. However, all the actions run at the same time. Now, I am a bit lost and I don't know exactly how to implement this efficiently. The only thing I could think of was creating a "timingIndex", where .2 seconds gets added to the wait time for each card. So even though they are all running at the same time, the wait time grows for each card. Not sure if this is the best way to approach the problem however and was hoping there was a more elegant solution.
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: 1)
card.run(SKAction.sequence[move, wait])
}
// Possible Solution
let timingIndex = 0.2
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: timingIndex)
card.run(SKAction.sequence[move, wait])
timingIndex += 0.2
}
import SpriteKit
import UIKit
let screenH = 100
let screenW = 50
class Player {
var handLocation: CGPoint
var pile = [Card]()
init() {
handLocation = CGPoint(x: 100, y: 583)
}
}
class Card: SKSpriteNode {
}
struct Game {
var player1 = Player()
var player2 = Player()
var deck = [Card]()
func dealDeck() {
for card in deck {
player1.pile.append(card) // This would theoretically loop through both players
card.position = CGPoint(x: screenW / 2, y: screenH / 2)
let move = SKAction.move(to: player1.handLocation, duration: 1)
card.run(move)
}
}
}
This worked! Worried about the use of this overall and if there exists a better solution still, but for the time being, this worked.
// Possible Solution
let timingIndex = 0.2
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: timingIndex)
card.run(SKAction.sequence[move, wait])
timingIndex += 0.2
}

SKAction with Key changes Sprite Behavior

I've been learning Swift while laid up from back surgery and any help with this is greatly appreciated.
Issue Description:
I have a function that pushes a sprite backwards when hit. When I use
self.runAction(jumpBackSequence)
I get the desired behavior. (The sprite is pushed back a good distance.)
Now when I update this to use "withKey" (because I need to be able to stop the forward action again on the next contact the forward movement is greatly dimished.
self.runAction(jumpBackSequence, withKey: "moveStraightLine")
My function creating this action is below:
func pushBack(){
self.removeActionForKey("moveStraightLine")
let height:CGFloat = 5
let jumpDistanceA:CGFloat = 20
let jumpDistanceB:CGFloat = 20
let duration:NSTimeInterval = 0.15
let jumpUp = SKAction.moveByX(0, y:height, duration:duration)
let moveBack = SKAction.moveByX(jumpDistanceA, y: 0, duration: duration)
let jStep1 = SKAction.group([jumpUp, moveBack])
let jumpDown = SKAction.moveByX(0, y:(height * -1), duration:duration)
let moveBack2 = SKAction.moveByX(jumpDistanceB, y: 0, duration: duration)
let jStep2 = SKAction.group([jumpDown, moveBack2])
let moveZombie1 = SKAction.moveToX(0, duration: spawnSpeed1() )
let removeZombie1 = SKAction.removeFromParent()
let moveAndRemoveZombie1 = SKAction.sequence([moveZombie1, removeZombie1])
let jumpBackSequence = SKAction.sequence([jStep1, jStep2, moveAndRemoveZombie1])
self.runAction(jumpBackSequence)
// self.runAction(jumpBackSequence, withKey: "moveStraightLine")
}
You are using moveTo, not moveBy, so when you add the action back in to move the zombie, you are now saying starting from the closer spot, take X seconds to move to the end, not take X seconds - the time already travelled. You do not want this due to the effect you see. Use moveBy, and the zombie speed will stay consistant
I'm not sure why this behavior happened, but I realized that the runAction withKey was actually moving the code the specified distance where as the example without the key was not. When I updated the code to use a fraction of screen width it behaves the same with and without a key - which makes me think this might actually be an minor bug in Swift.
To fix the issue I updated the distances to
let jumpDistanceA:CGFloat = (self.scene?.size.width)! * 0.15
let jumpDistanceB:CGFloat = (self.scene?.size.width)! * 0.15
I hope this helps someone else.
Best regards,
mpe

How to gradually speed up falling nodes over time?

I have two dots that fall parallel to each other from the top of my screen that are to be matched with two circles on the bottom that can be rotated with touch. I have them generated like this:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(generateDots),
SKAction.waitForDuration(1.0)])))
}
func generateDots() {
let count = 2
let index=Int(arc4random_uniform(UInt32(count)))
let dots = SKSpriteNode(imageNamed: "Color\(index+1)")
dots.physicsBody = SKPhysicsBody(circleOfRadius: 10)
dots.physicsBody?.dynamic = true
dots.physicsBody?.affectedByGravity = false
for i in 0..<2 {
dots.physicsBody?.categoryBitMask = UInt32(0x1 << index)
dots.physicsBody?.contactTestBitMask = UInt32(0x1 << index)
}
addChild(dots)
dots.size = CGSizeMake(45, 45)
dots.position = CGPointMake(150, 400)
dots.runAction(
SKAction.moveByX(0, y: -900,
duration: NSTimeInterval(11.5)))
}
}
Is there any way to gradually speed up either how fast they're falling or gradually change the waitForDuration so that over time it will produce a node every 3 sec, then 2 sec, then 1 sec and so forth?
This is completely doable! You just need to add some variables.
If you want to change how fast they fall then you need to make a variable like
Var droptime:NSTimeInterval = 11.5
Then in your "dropdot()" method you need to do two things.
At the beginning subtract or devide your droptime variable like...
Droptime -= 1
Then at the end when you generate the falling action make it
Duration: droptime
Instead of what it was before.
If you want to make the generation time be shorter then you need to make a function that you can trigger each time you want to make your action that the scene runs (like you did in the viewdidload) and edit it so that it has variable wait and triggers itself. Also you will need to self trigger it once in your didMoveToView method.
func controlMethod() {
waitdur -= 1
runAction(SKAction.repeatActionForever( SKAction.sequence([
SKAction.runBlock(generateDots),
SKAction.waitForDuration(waitdur),
SKAction.runBlock(controlMethod)
])))
}
Good luck!
So sorry for the formatting! I'm on mobile... Hopefully someone can fix it.

Does Sprite Kit SKAction with time duration Slow Function Execution?

I have a function in my Sprite kit game where the game character dies and has a death animation. In this same method I set some attributes to let my update function know that the game is over so that the score can stop being incremented and some other things. But when this runs, the deathAnimation function seems to slow down the execution of the other variables that are being set. So the score keeps being incremented when it should stop for example. Why is this? is it something to do with my update function or does the animation with a time duration slow the entire method from being executed right away?
Thanks for the help in advance!
Here is my deathAnimation method
func deathAnimation() {
//set shield for death
self.yourDead = true
self.shield.position = CGPointMake(self.frame.maxX * 2, self.frame.maxY + self.ape.size.height * 10)
self.shield.hidden = true
self.shieldActivated = false
//set Ape image to default
self.ape.runAction(SKAction.setTexture(SKTexture(imageNamed: "Ape"), resize: true))
self.ape.zRotation = 0
//changes physicsBody values so He doesn't collide
self.ape.physicsBody?.dynamic = false
self.ape.physicsBody?.categoryBitMask = ColliderType.Asteroid.rawValue
self.ape.physicsBody?.contactTestBitMask = ColliderType.Ape.rawValue
self.ape.physicsBody?.collisionBitMask = ColliderType.Ape.rawValue
self.ape.zPosition = 10 //bring the ape to the front
let death = SKAction.sequence([
SKAction.group([
SKAction.scaleBy(4, duration: 0.5),
SKAction.moveTo(CGPointMake(self.frame.minX + ape.size.width * 2, self.frame.minY - ape.size.width * 2), duration: 2),
SKAction.repeatAction(SKAction.rotateByAngle(CGFloat(M_PI_4), duration: 0.2), count: 8)
]),
SKAction.runBlock({self.moveToGameOverView();})
])
ape.runAction(death) //run the animation sequence
}
Here is my code where I check if the player is Dead or not and this is within the update function. I didn't include all of the update function because it is probably more than you would care to look at.
//take Asteroids off the screen and increment score
enumerateChildNodesWithName("asteroid", usingBlock: {(node: SKNode!, stop: UnsafeMutablePointer <ObjCBool>) -> Void in
//move the asteroids off the screen
node.position = CGPointMake(node.position.x, node.position.y + self.gravity)
//if it is out of screen
if node.position.y > self.frame.size.height + self.largeAsteroid.size.width {
node.removeFromParent()
if !self.yourDead { //if your not dead
self.score++
self.scoreText.text = String(self.score)
//increase Asteroid speed
if self.score > 20 * self.tensCounter {
self.gravity++
self.tensCounter++
}
}
}
})
The code provided looks fine. However you could try checking a few things.
Make sure that you are not calling deathAnimation() over and over again.
Make sure you are not doing your enumerateChildNodesWithName before deathAnimation().
Make sure you aren't incrementing the score somewhere else.
Those are the only reasons I can think that your score would continue to go up after you set self.yourDead = true Hopefully that helps.

SpriteKit Animation isn't loading when it supposed to be. What am I doing wrong?

I'm making a simple game. Hero should jump over the enemies and if they collide - enemy should eat the hero. I have 4 types of enemies randomly spawning in front of a hero. I use the first sprite of every enemy-array as a main, the rest are for animation. But when they collide - nothing happens, it's still the first sprite.
That's how I'm doing it:
let mouseAtlas = SKTextureAtlas(named: "mouse")
mouseArray.append(mouseAtlas.textureNamed("mouse_0"));
mouseArray.append(mouseAtlas.textureNamed("mouse_1"));
mouseArray.append(mouseAtlas.textureNamed("mouse_2"));
mouseArray.append(mouseAtlas.textureNamed("mouse_3"));
Mouse is an enemy, cookie is a hero.
mouse = SKSpriteNode(texture: mouseArray[0]);
self.mouse.position = CGPointMake(CGRectGetMaxX(self.frame) + self.cookie.size.width, self.cookieSpot + self.cookie.size.height + self.cookie.size.height / 2)
self.mouse.size = CGSizeMake(self.cookie.size.width + self.cookie.size.width / 2, self.cookie.size.height + cookie.size.height / 2)
self.addChild(cookie)
self.addChild(mouse)
Then I'm applying physic bodies to them:
self.cookie.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.cookie.size.width / 2))
self.cookie.physicsBody?.affectedByGravity = false
self.cookie.physicsBody?.categoryBitMask = ColliderType.Cookie.rawValue
self.cookie.physicsBody?.collisionBitMask = ColliderType.Pet.rawValue
self.cookie.physicsBody?.contactTestBitMask = ColliderType.Pet.rawValue
self.mouse.physicsBody = SKPhysicsBody(rectangleOfSize: self.mouse.size)
self.mouse.physicsBody?.dynamic = false
self.mouse.physicsBody?.categoryBitMask = ColliderType.Pet.rawValue
self.mouse.physicsBody?.contactTestBitMask = ColliderType.Cookie.rawValue
self.mouse.physicsBody?.collisionBitMask = ColliderType.Cookie.rawValue
And then I'm trying to make an animation when contact begins:
func didBeginContact(contact: SKPhysicsContact) {
eatenByMouse(); eatenByHamster(); eatenByRabbit(); eatenByCat()
}
func eatenByMouse() {
self.groundSpeed = 0
self.cookie.hidden = true
let animateAction = SKAction.animateWithTextures(self.mouseArray, timePerFrame: 0.1)
}
Like I said, there are 4 types of enemies but they are practically the same. What am I doing wrong with that? An I also interested, could I make physics body of enemies only on one side? I mean how can one make my hero eaten only if it touched enemies mouth for example? And if it touches the tail it would proceed rolling and jumping? Many thanks!
You have to run the SKAction after creating it.
func eatenByMouse() {
self.groundSpeed = 0
self.cookie.hidden = true
let animateAction = SKAction.animateWithTextures(self.mouseArray, timePerFrame: 0.1)
self.mouse.runAction(animateAction) // added line.
}
You can use SKPhysicsContact.contactPoint to detect point of collision.