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

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.

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
}

Choosing to spawn different SKSpriteNodes

I am making a game and I have objects which fall from the top of the screen to the bottom. I want to spawn choose between the objects and drop one of them. I currently the drop all at the same time.
func ShapePicker() -> SKSpriteNode{
let shapeArray = [purpleOctagon, coin, greenTriangle, orangeHexagon]
let MaxValue = self.size.width / 2 - 200
let MinValue = self.size.width / 3 * 0.95
let rangeMax = UInt32(MaxValue)
let rangeMin = UInt32(MinValue)
purpleOctagon.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(purpleOctagon)
greenTriangle.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(greenTriangle)
coin.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(coin)
return shapeArray[Int(arc4random_uniform(UInt32(shapeArray.count)))]
}
I would like the program to randomly .addChild because right now it just puts them on the screen.
Your code implies that you want all of them to be on the screen, and then one randomly drops... So you do want to continue to .addChild. What you want, is for them to NOT drop all at once.
So, you need to change the .physicsBody.pinned to true to keep them at the top of the screen.
Then, in your update() you can check for how much time has passed, etc, and after a certain # of seconds you can do an arc4random_uniform and use that result to change the .pinned property of one of the nodes (thus causing that one and that one only to fall).
So, if the coin is 0, triangle is 1, and octagon is 2, then, in your .update keep track of the time elapsed and after say 3 seconds, do a random check 0-2, and that check will perform:
switch result {
case 0: // coin
coin.physicsBody?.pinned = false // makes it drop
case 1: // triangle
...
Just make sure that your nodes are in the proper scope so that you can do the logic in update()
If I read your Q wrong, and you only want to spawn and then drop just one, then you would still need the above switch statement, but instead of changing physicsBody you would do .addChild instead.
so inside of your func would be more like:
// This can be global or inside of your GameScene:
var myGlobalCurrentTime: CFTimeInterval
override func update(currentTime: CFTimeInterval) {
myGlobalCurrentTime = myTimerUpdateTime(currentTime)
func myDropFunc() {
... // Initialize your nodes
let result = myRandomNumber(3)
switch result {
case 0:
coin.position
= CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax),
y: self.size.height)
self.addChild(coin)
case 1:
...
}
}
// Execute the func:
myDropFunc()
}
I'm still a bit confused by your code and question, so please clarify in the comments so I can update this answer if needed.
You can move the addChild call out of this function, it returns a SKSpriteNode, which you can then add.
let sprite = ShapePicker()
addChild(sprite)
Or, just add the one randomly chosen and return it. You don't need to addChild nodes that you're not planning on using

Closure CallBack Only Retain Last Object Created in Swift

i'm not sure if the title fit my question but my question is as below.
First of all, i will talk how i encounter this problem. Basically, I'm creating a game(you can just imagine) which has A MainPlayer and Many Of Enemies(AI). So, the MainPlayer will move around and enemies will chase after him. As you can imagine, the MainPlayer's position will update in every frame(maybe) and i need also to update enemies' chasing position(which is main character's position). So i'm using Closure to do it. The reason why i'm not using array to store all the enemies and update it every frame is because the enemies can be killed and will random spawn new enemies in the amount of time i set. If i use array it is kind of tricky and unsafe.
So, back to my question, i created this beginning of GameScene class:
typealias CallBacks = () -> Void
var playerDidMoveCallBacks: CallBacks?
This is my create enemy class: (The callback is at most bottom)
// Create Enemies
func createEnemyAtPosition(position: CGPoint) {
let enemyNode = EnemyNode()
enemyNode.name = "ENEMY_NODE"
enemyNode.position = position
enemyNode.setScale(1.5)
addChild(enemyNode)
let sprite = SKSpriteNode(imageNamed: "enemyWisp")
enemyNode.addChild(sprite)
enemyNode.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width / 2)
enemyNode.physicsBody?.dynamic = true
enemyNode.physicsBody?.categoryBitMask = CollisionCategoryBitMask.Enemy
enemyNode.physicsBody?.collisionBitMask = 0
enemyNode.physicsBody?.contactTestBitMask = CollisionCategoryBitMask.ThrowingKnife | CollisionCategoryBitMask.Player
enemyNode.physicsBody?.usesPreciseCollisionDetection = true
let distanceWillCollideWithPlayer = sqrt(pow(enemyNode.position.x - self.playerNode.position.x, 2) + pow(enemyNode.position.y - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
enemyNode.runAction(enemyMoveAction)
println("\((unsafeAddressOf(enemyNode)))")
// Update Player Position
playerDidMoveCallBacks = {() -> Void in
let distanceWillCollideWithPlayer = sqrt(pow(enemyNode.position.x - self.playerNode.position.x, 2) + pow(enemyNode.position.y - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
println("\((unsafeAddressOf(enemyNode)))")
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
enemyNode.runAction(enemyMoveAction)
}
}
When i know my hero changed position i call the callback like this:
if self.playerDidMoveCallBacks != nil {
self.playerDidMoveCallBacks!()
}
But this can only work for the last object created and i think this make sense. Anyone can give me a solution?
What you want is an array of callbacks.
var playerDidMoveCallBacks: [CallBack]?
As every enemy is captured in the closure you could just iterate the array and call each callback.
for callback in playerDidMoveCallBacks {
callback()
}
But the solution of 0x141E is better.
The problem is that the enemy could not exist anymore when you call the closure, but the closure captures the object which can lead to strange behaviour. (ghost enemies)
With help from 0x141E i create this line of code in func update and solve my problem
// Update Enemies Position
for childNode in children {
let childSprite = childNode as? SKNode
if (childSprite?.name == "ENEMY_NODE") {
let distanceWillCollideWithPlayer = sqrt(pow(CGFloat(childSprite!.position.x) - self.playerNode.position.x, 2) + pow(CGFloat(childSprite!.position.y) - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
childSprite!.runAction(enemyMoveAction)
}
}

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.

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

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.