call func when action completes? - ios

First off, thanks for your time. I have a fairly simple problem to solve but can't seem to figure it out.
I'm making a Tower Defense game, I'd like to have it when the enemies reach their destination the life bar shrinks. I figure it must be as easy as adding another action to the sequence that calls a method that can reduce the life points but I haven't found a way. Any help would be greatly beneficial.
let enemy1 = SKSpriteNode(imageNamed: "magSquare.png")
enemy1.position = startPoint
let step1 : SKAction = SKAction.moveTo(firstTurn, duration: duration)
let step2 : SKAction = SKAction.moveTo(secondTurn, duration: duration)
let step3 : SKAction = SKAction.moveTo(thirdTurn, duration: duration)
let step4 : SKAction = SKAction.moveTo(fourthTurn, duration: duration)
let step5 : SKAction = SKAction.moveTo(fifthTurn, duration: duration)
let step6 : SKAction = SKAction.removeFromParent()
//let step7 : SKAction = SKAction.** call method **
enemy1.runAction(SKAction.sequence([step1, step2, step3, step4, step5, step6]))
self.addChild(enemy1)

let step7 = SKAction.runBlock({ self.yourfunc() })

Related

How to synchronize textures animation and sound in SpriteKit

I'm creating a RPGGameKit using SpriteKit to help me develop my iOS games. Now that my player can move, I added animations and an audio system.
I ran on a problem to synchronize textures and sounds. Like a step when my player walk.
let atlas = SKTextureAtlas(named: "Walk")
let textures = atlas.getTextures() // I created an extension that returns textures of atlas
let walkingAnimation = SKAction.animate(with: textures, timePerFrame: 1)
So, walkingAnimation will loop through textures and change it every 1 second.
Now, I want to play a walking sound when the texture changes.
I have look at SKAction and SpriteKit documentation but there is no callback for this SKAction.
If you want to try to get this done with me or you have ideas of how to do it, please leave a comment.
Thanks :)
Try this:
let frame1 = SKAction.setTexture(yourTexture1)
let frame2 = SKAction.setTexture(yourTexture2)
let frame3 = SKAction.setTexture(yourTexture3)
//etc
let sound = SKAction.playSoundFileNamed("soundName", waitForCompletion: false)
let oneSecond = SKAction.wait(forDuration: 1)
let sequence = SKAction.sequence([frame1,sound,oneSecond,frame2,sound,oneSecond,frame3,sound,oneSecond])
node.run(sequence)
So, for now I'm going to do it like this :
let textures = SKTextureAtlas(named: "LeftStep").getTextures()
var actions = [SKAction]()
for texture in textures {
let group = SKAction.group([
SKAction.setTexture(texture),
SKAction.playSoundFileNamed("Step.mp3", waitForCompletion: false)
])
let sequence = SKAction.sequence([
group,
SKAction.wait(forDuration: 0.5)
])
actions.append(sequence)
}
self.node.run(SKAction.repeatForever(SKAction.sequence(actions)))
Thanks #StefanOvomate
I've found myself in the same situation, currently I'm doing the below. From what I have read in the documentation and seen online, the only way to do it is to create the audio file length to match one rotation of the texture animation.
let walkAtlas = global.playerWalkAtlas
var walkFrames: [SKTexture] = []
let numImages = walkAtlas.textureNames.count
for i in 1...numImages {
let texture = "walk\(i)"
walkFrames.append(walkAtlas.textureNamed(texture))
}
walking = walkFrames
isWalking = true
animateMove()
}
func animateMove(){
let animateWalk = SKAction.animate(with: walking, timePerFrame: 0.05)
let soundWalk = global.playSound(sound: .walkSound)
let sequence = SKAction.sequence([soundWalk, animateWalk])
self.run(SKAction.repeatForever(sequence),withKey: "isMoving")
}
func stopMoving(){
self.removeAction(forKey: "isMoving")
isWalking = false
}

Adding nodes to GameScene from another class

I have here a struct that's used to pass a function over to GameScene in which I can call whenever needed.
The problem is the line GameScene.addChild(explosionFirst) generates the following error:
'SKSpriteNode' is not convertible to 'GameScene'
How am I able to add the node from another class to GameScene?
import Foundation
import SpriteKit
struct Explosion {
var explosions = [SKTexture]()
mutating func spawnExplosion(spawnPosition: CGPoint) {
for i in 0...79 {
let explosion = SKTexture(imageNamed: String(i))
explosions.append(explosion)
}
let explosionFirst = SKSpriteNode(texture: explosions[0])
explosionFirst.position = spawnPosition
explosionFirst.zPosition = 1
explosionFirst.setScale(5)
GameScene.addChild(explosionFirst)
let animation = SKAction.animate(with: explosions, timePerFrame: 0.1)
let scaleIn = SKAction.scale(to: 10, duration: 2.3)
let fadeOut = SKAction.fadeOut(withDuration: 1.0)
let delete = SKAction.removeFromParent()
let explosionSequence = SKAction.sequence([scaleIn, fadeOut, delete])
explosionFirst.run(animation)
explosionFirst.run(explosionSequence)
}
}
the compiler is seeing self.GameScene.addChild and yo don't have a reference to your GameScene object so create one
var gameScene : GameScene!
now use:
gameScene.addChild(explosionFirst)
Note: by default your initialiser will now have gameScene: GameScene so you will have to inject the Gamescene dependancy on init

Swift Timer SK.wait

I have balls spawning every 3 seconds and I want the balls to spawn faster if the score increases. The problem is my spawnrate stays the same 4 seconds and does not change when my score increases. Thanks for the helps m8s
var spawnRate : TimeInterval = 4
var wait = SKAction .wait(forDuration: spawnRate, withRange: 0.2)
if score >= 6 {
spawnRate = 3
wait = SKAction .wait(forDuration: spawnRate, withRange: 0.2)
}
let spawning = SKAction.sequence([wait,spawn])
I would rather refactor the code, and double check that score is really increased , plus cancel previous actions, in case they have not finished after previous launch. My assumption that you produce several actions and they are "accumulated" in the future.
var spawnRate : TimeInterval
var wait:SKAction!
if score >= 6 {
spawnRate = 3
wait = SKAction .wait(forDuration: spawnRate, withRange: 0.2)
}
else {
spawnRate = 4
wait = SKAction .wait(forDuration: spawnRate, withRange: 0.2)
}
let spawningKey ="spawningKey"
self.spinnyNode?.removeAction(forKey: waitKey)
let spawning = SKAction.sequence([wait,spawn])
self.spinnyNode?.run(spawning, withKey: spawningKey)

Declaring SKSpriteNode outside spawn function returns SIGABRT in Swift

I have a game that I am making where I have a player and an unlimited number of enemies that's spawn at a given position in a certain amount of time. These enemies are shooting bullets at the player. I need to be able to access the Enemy variable outside the SpawnEnemies() function so that I can use it in my SpawnBullets() function. I tried declaring the Enemy variable outside the SpawnEnemies() function but it returned Sigabrt and I don't know how to access the Enemy variable outside the function without getting this error.
Enemy declaration:
var Enemy = SKSpriteNode(imageNamed: "Enemy.png")
SpawnEnemies function:
func SpawnEnemies() {
let MinValue = self.size.width/8
let MaxValue = self.size.width-20
let SpawnPoint = UInt32(MaxValue-MinValue)
self.Enemy.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
let action = SKAction.moveToY(-70, duration: 3.0)
self.Enemy.runAction(SKAction.repeatActionForever(action))
self.addChild(Enemy)
}
SpawnBullets function:
func SpawnBullets(){
let Bullet = SKSpriteNode(imageNamed: "bullet.png")
Bullet.zPosition = -5
Bullet.position = CGPointMake(Enemy.position.x, Enemy.position.y)
let action = SKAction.moveToY(self.size.height + 30, duration: 1.0)
Bullet.runAction(SKAction.repeatActionForever(action))
self.addChild(Bullet)
}
Call SpawnEnemies function in didMoveToView():
var EnemyTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("SpawnEnemies"),userInfo: nil, repeats: true)
Call SpawnBullets function in didMoveToView():
var BulletTimer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector("SpawnBullets"),userInfo: nil, repeats: true)
I get this error which is a sigabrt but I used a breakpoint to figure out exactly where the error was and what it was:
Attemped to add a SKNode which already has a parent: <SKSpriteNode> name:'(null)' texture:[<SKTexture> 'Enemy.png' (60 x 80)] position:{132, 1024} scale:{1.00, 1.00} size:{60, 80} anchor:{0.5, 0.5} rotation:0.00
Adding children to a scene already is "infinite", you just need to use it to your advantage.
First rework your bullet function like this so that you are passing in an enemy.
func SpawnBullets(enemy : SKSpriteNode){
let Bullet = SKSpriteNode(imageNamed: "bullet.png")
Bullet.zPosition = -5
Bullet.position = CGPointMake(enemy.position.x, enemy.position.y)
let action = SKAction.moveToY(-self.size.height - 70, duration: 1.0)
Bullet.runAction(SKAction.repeatActionForever(action))
self.addChild(Bullet)
}
Then rework your enemy function so that overtime you call it you spawn a new enemy.
func SpawnEnemies() {
var Enemy = SKSpriteNode(imageNamed: "Enemy.png")
Enemy.name = "enemy";
let MinValue = self.size.width/8
let MaxValue = self.size.width-20
let SpawnPoint = UInt32(MaxValue-MinValue)
Enemy.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
let action = SKAction.moveToY(-70, duration: 3.0)
Enemy.runAction(SKAction.repeatActionForever(action))
self.addChild(Enemy)
}
In the function didMoveToView, lets add actions to the scene that handles our timing so that we do not use NSTimer
{
...
let spawnEnemy = SKAction.sequence([SKAction.runBlock(
{
[unowned self] in
self.SpawnEnemies();
}),SKAction.waitForDuration(1)]);
let spawnBullet = SKAction.sequence([SKAction.runBlock(
{
[unowned self] in
self.enumerateChildNodesWithName("enemy", usingBlock:
{
(enemy : SKNode,stop: UnsafeMutablePointer <ObjCBool>) in
  self.SpawnBullets(enemy as! SKSpriteNode);
});
}),SKAction.waitForDuration(0.2)]);
let group = SKAction.group([spawnEnemy, spawnBullet])
self.runAction(SKAction.repeatActionForever(group))
}
This code is not tested so let me know if I need to update anything.

How do I make my enemySprite respawn as new bad guy once its been killed?

I am having trouble with my game, I've managed to have my enemySprites all shoot at the hero in a synchronized matter and I've gotten them to play the "killed" animation once they've been hit. Although I've run into a rather small matter which I was really hoping you guys could help me with. The problem I have is that when my badGuy is killed they move off the screen and I don't know how I can program it so that a 'new' badGuy appears forever until the Hero sprite is killed.
This is my function to spawn my enemy:
func spawnEnemy(targetSprite: SKNode) -> SKSpriteNode {
if !gamePaused{
// create a new enemy sprite
let main = GameScene()
newEnemy = SKSpriteNode(imageNamed:"BNG1_1.png")
enemySprite.append(newEnemy)//ADD TO THE LIBRARY OF BADGUYS
newEnemy.xScale = 1.2
newEnemy.yScale = 0.6
newEnemy.physicsBody?.dynamic = true
newEnemy.physicsBody = SKPhysicsBody(texture: newEnemy.texture, size: newEnemy.size)
newEnemy.physicsBody?.affectedByGravity = false
newEnemy.physicsBody?.categoryBitMask = BodyType.badguyCollision.rawValue
newEnemy.physicsBody?.contactTestBitMask = BodyType.beamCollison.rawValue
newEnemy.physicsBody?.collisionBitMask = 0x0
let muv : UInt32 = (200 + (arc4random()%500))
let actualDuration = NSTimeInterval(random(min: CGFloat(3.0), max: CGFloat(4.0)))
let randomNum = CGPoint(x:Int (muv), y:Int (arc4random()%500))
// Create the actions
var actionMove = SKAction.moveTo(randomNum, duration: NSTimeInterval(actualDuration))
newEnemy.runAction(SKAction.sequence([actionMove]))
// position new sprite at a random position on the screen
var posX = arc4random_uniform(UInt32(sizeRect.size.width))
var posY = arc4random_uniform(UInt32(sizeRect.size.height))
newEnemy.position = CGPoint(x: screenSize.width*2 + newEnemy.size.width, y: random(min: newEnemy.size.height, max: screenSize.height - newEnemy.size.height))
let atlas = SKTextureAtlas(named: "BG1.atlas")
let anime = SKAction.animateWithTextures([atlas.textureNamed("BG1_1.png"), atlas.textureNamed("BG1_2.png"),
atlas.textureNamed("BG1_3.png"),
atlas.textureNamed("BG1_2.png"),
atlas.textureNamed("BG1_1.png")], timePerFrame: 0.1)
dinoRun = SKAction.repeatActionForever(anime)
newEnemy.runAction(dinoRun)
}
return newEnemy
}
And this is my function for once they are hit (this function is called when the collisionTest between my hero's laser and the badguy is recognized):
func deadBadGuy(){
//animation
var dinoRun:SKAction
var newdes = CGPoint(x: Int(arc4random()%500), y:0)
var actionMoov = SKAction.moveTo(newdes, duration: 3)
var goaway = SKAction.removeFromParent()
let aTlas = SKTextureAtlas(named: "dedBG.atlas")
let anime = SKAction.animateWithTextures([aTlas.textureNamed("deadBG1.png"), aTlas.textureNamed("deadBG2.png"),
aTlas.textureNamed("deadBG3.png"),
aTlas.textureNamed("deadBG2.png"),
aTlas.textureNamed("deadBG1.png")], timePerFrame: 0.1)
dinoRun = SKAction.repeatActionForever(anime)
newEnemy.runAction(dinoRun)
newEnemy.runAction(SKAction.sequence([actionMoov,goaway]))
score++
self.scoreNode.text = String(score)
enemySprites.newPlace(neewEnemy)
dead = true //i created this Boolean because the sprite keeps shooting even if its dead
}
This is the way I called for the collisionTest in my program in case you need it for more information:
func didBeginContact(contact:SKPhysicsContact){
if !gamePaused {
let firstNode = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(firstNode){
case BodyType.beamCollison.rawValue | BodyType.badguyCollision.rawValue:
deadBadGuy()
//enemySprites.spawnEnemy(sprite)
default:
print("hit")
}
Note: I tried having the func spawnEnemy being called during the collisionTest but that results in the BadGuy staying off screen shooting at my hero.
Update : I found out how to add a new enemy sprite once the other is dead all i had to do was
newEnemy.runAction(SKAction.sequence([actionMoov,goaway]), completion: {
self.addChild(self.enemySprites.spawnEnemy(sprite))
})
in the deadBadGuy function(the spawnEnemy function is in another class which I named enemySprites as a variable). However now I've run into a new issue and that is that it adds 10 enemySprites instead of one. How can I change that?
Update 2 : I figured out that issue too, I just needed to remove to dead Boolean method in the deadBadGuy function.
I found my answer and all I had to do was add this line to my deadBadGuy function and remove the dead boolean methods
newEnemy.runAction(SKAction.sequence([actionMoov,goaway]), completion: {
self.addChild(self.enemySprites.spawnEnemy(sprite))
})

Resources