How to add and remove a SKErmitterNode within a sequence? - swift

I'm adding a SKErmitterNode from the update() function. I want to remove the SKErmitterNode after 2 seconds, therefore I made a sequence but in the sequence, I can´t add Nodes. And if I add the Node outside of the sequence it gets added over and over again(because I´m doing all of this in the update function) Does someone know a better way to do this?
Here is my Code from the update function:
override func update(_ currentTime: CFTimeInterval) {
if player.position.y <= player.size.height / 2{
self.player.removeFromParent()
if let particles = SKEmitterNode(fileNamed: "MyParticle.sks") {
particles.position = player.position
let addParticle = addChild(particles)
let wait = SKAction.wait(forDuration: 2.0)
let removeParticle = SKAction.removeFromParent()
let particleSequence = SKAction.sequence([addParticle, wait, removeParticle]) //Error ->Cannot convert value of type 'Void' to expected element type 'SKAction'
self.run(SKAction.run(particleSequence))
}
}

So what I recommend for you to do is to create a function like the following
func myExplosion (explosionPosition: CGPoint){
let explosion = SKEmitterNode(fileNamed: "MyParticle")// borrowed this from you
explosion?.position = explosionPosition
explosion?.zPosition = 3
self.addChild(explosion!)
self.run(SKAction.wait(forDuration: 2)){//you can always change the duration to whatever you want
explosion?.removeFromParent()
}
}
then when it is time to use this function, use it like so
myExplosion(explosionPosition: player.position)
Hope this can help you out.

Related

Randomly run two SKActions at same time for two separate SKSpriteNodes

Title says it all. I've got two SKSpriteNodes [ leftTrap, rightTrap] with two separate SKActions [ rotateSequenceLeft, rotateSequence] that need to run at the same time but need to do it randomly.
SKSpriteNodes with attached SKActions
Need to run these two in parallel at same random intervals.
leftTrap.run(SKAction.repeatForever(rotateSequenceLeft))
rightTrap.run(SKAction.repeatForever(rotateSequence))
What I have tried
I have tried to group the leftTrap node and action rotateSequenceLeft with a wait duration range action. But it seems that the created group never even runs the wait duration action.
let randomPivotInterval = SKAction.wait(forDuration: 1.0, withRange: 5.0)
let leftGroup = SKAction.group([rotateSequenceLeft, randomPivotInterval])
let rightGroup = SKAction.group([rotateSequence, randomPivotInterval])
leftTrap.run(SKAction.repeatForever(leftGroup))
rightTrap.run(SKAction.repeatForever(rightGroup))
If both flippers need to be in sync at all times, just apply the action to the left flipper, and do this for the right:
func didEvaluateActions(){
rightTrap.angle = -leftTrap.angle
}
This will ensure both flippers are always at the opposite angle
You can try something like this
class GameScene: SKScene {
var leftTrap: SKSpriteNode! // TODO: populate this
var rightTrap: SKSpriteNode! // TODO: populate this
func start() {
let rotateTraps = SKAction.run { [weak self] in
guard let self = self else { return }
self.rotate(trap: self.leftTrap, clockwise: .random())
self.rotate(trap: self.rightTrap, clockwise: .random())
}
let wait = SKAction.wait(forDuration: 5)
let sequence = SKAction.sequence([rotateTraps, wait])
run(.repeatForever(sequence))
}
private func rotate(trap: SKSpriteNode, clockwise: Bool) {
// TODO: put rotation code here
}
}
How does it work?
The start() method creates a rotateTraps action which, each time it is executed, call self?.rotate on the left trap passing a random bool for the clockwise param and then does the same for the right trap.
Then the rotateTraps action is wrapped into a sequence and repeated forever every 5 seconds.
If for whatever reason you don't want to add a post-evaluate phase like in Knight0fDragon's answer, then you can probably do something like this:
func operateTrap() {
run(.wait(forDuration: .random(in: 1.0 ... 6.0)) { // On completion...
// I assume both sequences take the same time.
leftTrap.run(rotateSequenceLeft)
rightTrap.run(rotateSequenceRight) { self.operateTrap() }
}
}

for loop not executing code sequencially (swift + spritekit)

Here's my code:
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let origin = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
let delay = SKAction.waitForDuration(2.0)
for (var i = 0; i < 6; i++) {
//generate a random point on a circle centered at origin with radius of 500
let point = randomPointOnCircle(500, center: origin)
let chanceToSpawn = arc4random_uniform(100)
switch chanceToSpawn {
case 0...60:
self.runAction(delay){
let smallBall = SKSpriteNode(imageNamed: "smallBall”)
self.addChild(smallBall)
smallBall.xScale = 0.05
smallBall.yScale = 0.05
smallBall.position = point
let finalDest = CGPointMake(origin.x, origin.y)
let moveAction = SKAction.moveTo(finalDest, duration: 5.0)
smallBall.runAction(SKAction.sequence([SKAction.waitForDuration(0.1), moveAction, SKAction.runBlock({
smallBall.removeFromParent()
})]))
delay
}
break
case 61...80:
//same as case 0…60, smallBall changed to “mediumBall”
break
case 81...99:
//same as case 0…60, smallBall changed to “largeBall”
break
default:
break
} // ends switch statement
}//ends for loop
}//end didMoveToView
Basically I have balls that spawn on a random point on a circle and move towards the center. But the problem I am having is that no matter how I rearrange things (or rewrite things, i.e., trying different loops or defining functions for spawning the ball and the movement of the ball and simply calling them from inside the switch statement) all of the balls spawn at the same time. I am trying to make it so they spawn one after the other (meaning a ball spawns, the thread waits a few seconds, then spawns another ball.), and I can't seem to figure out why each ball is spawning at the same time as all the other balls.
I'm fairly new to Swift/Spritekit programming and I feel like I'm overlooking some small detail and this is really bugging me. Any help appreciated!
For loops are executed faster than you think! Your for loop is probably executed instantly.
"Why is that? I have already called self.runAction(delay) to delay the for loop!" you asked. Well, the delay action doesn't actually delay the for loop, unfortunately. It just delays the other actions you run on self.
To fix this, try delaying different amounts, dependind on the for loop counter:
self.runAction(SKAction.waitForDuration(i * 2 + 2)) { ... }
This way, balls will appear every two seconds.
try this, may be due to same time u are adding the actions for each balls,
smallBall.runAction(SKAction.sequence([SKAction.waitForDuration(0.1 + (i * 0.35/2)), moveAction, SKAction.runBlock({
smallBall.removeFromParent()
})])) // delay added (0.1 + (increment the daly))
try this
Your problem is you are calling runAction on each iteration of your for loop. This means that all your actions will run concurrently.
What you want to do is create an array of actions. then after the for loop, run a sequence of actions.
(Note: I took the liberty of cleaning up your code)
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let origin = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
let delay = SKAction.waitForDuration(2.0)
var sequence = [SKAction]()
for (var i = 0; i < 6; i++) {
//generate a random point on a circle centered at origin with radius of 500
let point = randomPointOnCircle(500, center: origin)
let chanceToSpawn = arc4random_uniform(100)
var ballname = "smallBall”
switch chanceToSpawn {
case 61...80:
//same as case 0…60, smallBall changed to “mediumBall”
ballname = "mediumBall”
case 81...99:
//same as case 0…60, smallBall changed to “largeBall”
ballname = "largeBall”
default: ()
} // ends switch statement
sequence.append(delay)
sequence.append(
SKAction.run()
{
[unowned self] in
let ball = SKSpriteNode(imageNamed: ballName)
self.addChild(ball)
ball.xScale = 0.05
ball.yScale = 0.05
ball.position = point
let finalDest = CGPointMake(origin.x, origin.y)
let moveAction = SKAction.moveTo(finalDest, duration: 5.0)
let briefWait = SKAction.waitForDuration(0.1)
let remove = SKAction.removeFromParent()
let moveSeq = SKAction.sequence([briefWait ,moveAction,remove])
ball.runAction(moveSeq)
}
)
}//ends for loop
self.run(SKAction.sequence(sequence))
}//end didMoveToView

Repeating an action forever with a global function

I have a rectangle that needs to be constantly moving up, but is also declared globally like so so that I can call it in multiple places:
var obstacle = SKNode!
override func didMoveToView {
obstacle = rectangle()
}
func rectangle() -> SKNode {
let rect = SKSpriteNode(imageNamed: "Rectangle#x2")
rect.size = CGSizeMake(30, 30)
rect.position = CGPointMake(210, -250)
rect.physicsBody?.categoryBitMask = PhysicsCatagory.littleRect
rect.physicsBody?.contactTestBitMask = PhysicsCatagory.bigRect
rect.physicsBody?.collisionBitMask = 0
rect.physicsBody = SKPhysicsBody(rectangleOfSize: rect.size)
rect.physicsBody?.dynamic = true
rect.physicsBody?.affectedByGravity = false
rect.runAction(
SKAction.moveByX(0, y: 1200,
duration: NSTimeInterval(6.5)))
addChild(rect)
return rect
}
When I attempt to run it as an action repeating forever like so, i get the error "cannot convert value of type SKNode to argument runBlock" :
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(rectangle),
SKAction.waitForDuration(4.0)])))
So is there a way to declare this sort of action for a function set up like this? Thank you in advance.
First of all, this var obstacle = SKNode! will produce an error. You should declare an implicitly unwrapped optional like this:
var obstacle:SKNode!
About the main question (without analyzing the logic of what code actually does,)...You are passing an instance of SKNode class to +runBlock: method (which accepts a closure), thus the error. To fix this, you have to pass a closure, like this:
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock({[unowned self] in self.rectangle()}),
SKAction.waitForDuration(4.0)])))
}

Simple Action Loop

I am trying to repeat a set of actions by running them through a loop. My loop is fine when running something else, but it seems to have trouble running the actions. My code is as follows:
let pulse1 = SKAction.scaleTo(2.0, duration: 1.0)
let pulse2 = SKAction.scaleTo(0.5, duration: 1.0)
var i = 0
override func didMoveToView(view: SKView) {
for var i = 0; i <= 100; i++ {
self.sun.runAction(pulse1)
self.sun.runAction(pulse2)
}
This will cause the node to pulse1 and pulse2 each once but never again. If I add
println("")
to the loop, it runs whatever text properly, but for some reason doesn't run the actions like it runs the text. Or maybe it does and I don't understand how SKAction works? Either way, the loop is executing properly, I believe. I am not quite sure what's wrong with the SKAction call in the loop.
scaleTo simply changes the node's scale. Once pulse1 goes to 2.0 and pulse2 gets to 0.5, runAction runs repeatedly but you never change the scale for either pulse ever again.
That's why you're only seeing it do work the first time.
Instead of using a for loop, try something like this:
override func didMoveToView(view: SKView) {
if (i % 2 == 0) {
let pulse = SKAction.scaleTo(2.0, duration: 1.0)
} else {
let pulse = SKAction.scaleTo(0.5, duration: 1.0)
}
[self.sun runAction:pulse completion:^{
if( i < 100 )
{
didMoveToView(view);
}
}];
}
Maybe you can use
class func repeatAction(_ action: SKAction,
count count: Int) -> SKAction
Put as many single actions in a sequence an run repeatAction for x times.

Looping an A to B animation in Swift

I'm a swift newbie trying to loop an A to B positional animation. I'm not sure how to reset the position so the animation can loop. Any help appreciated.
import SpriteKit
class GameScene: SKScene {
let Cloud1 = SKSpriteNode(imageNamed:"Cloud_01.png")
override func didMoveToView(view: SKView) {
view.scene!.anchorPoint = CGPoint(x: 0.5,y: 0.5)
Cloud1.position = CGPoint(x: -800,y: 0)
Cloud1.xScale = 0.5
Cloud1.yScale = 0.5
self.addChild(Cloud1)
//DEFINING SPRITE ACTION & REPEAT
let animateCloud1 = SKAction.moveToX(800, duration: 1.4);
let repeatCloud1 = SKAction.repeatActionForever(animateCloud1)
let group = SKAction.group([ animateCloud1,repeatCloud1]);
//RUNNING ACTION
self.Cloud1.runAction(group);
}
override func update(currentTime: NSTimeInterval) {
if(Cloud1.position.x == 800){
Cloud1.position.x = -800
}
}
}
If I understand your question correctly, you want the Sprite to move back and forth between its current location and the new location you specified.
If so, a way to do this would be to create two animations and put them in a sequence. Then repeat the sequence forever.
let animateCloud = SKAction.moveToX(800, duration: 1.4)
let animateCloudBackwards = SKAction.moveToX(Cloud1.position.x, duration: 0)
// Sequences run each action one after another, whereas groups run
// each action in parallel
let sequence = SKAction.sequence([animateCloud, animateCloudBackwards])
let repeatedSequence = SKAction.repeatActionForever(sequence)
Cloud1.runAction(repeatedSequence)