Stopping an running SKAction - Sprite Kit - swift

The following code will animate a rotation.
let something:SKSpriteNode = SKSpriteNode()
func start(){
let rotateAction = SKAction.rotateToAngle(CGFloat(M_PI), duration: 10.0)
something.runAction(SKAction.sequence([rotateAction]))
}
Now I want to stop the animation within the animating duration. Is there anything similar to the following? I want to stop the animation when the user touches the screen.
func stop(){
something.SKAction.stop()
}

You just have to use either:
something.paused = false // or true to pause actions on the node
something.removeAllActions() to definitely remove actions associated to the node
name your action when launching something.runAction(action,withKey:"action1") and then something.removeActionForKey("action1") to remove a given action
You may also change the speed if needed.

Firstly, run the action with a key so you can identify it later:
something.runAction(rotateAction, withKey: "rotate action")
Then you can stop it later by calling
something.removeActionForKey("rotate action")
Alternatively, you can remove all actions also
something.removeAllActions()

Related

SpriteKit - change animation speed dynamically

I am new to the SpriteKit framework and cannot figure out how to do the following:
In the middle of the scene I have a sprite to which I apply an .animateWithTextures SKAction. Now I simply want to increase the speed of that animation, or decrease its duration for the same effect.
I made the SKAction a property of my GameScene class so I can access its properties from everywhere, but neither changing its speed nor its duration affects the animation.
I read several threads, the closest to my problem being this one:
How to change duration of executed SpriteKit action
which is from seven years ago, and none of the answers is working for me.
Here is basically what I do:
class GameScene: SKScene {
var thePlayer: SKSpriteNode = SKSpriteNode()
var playerAnimation: SKAction?
...
...
func setInitialAnimation() {
let walkAnimation = SKAction.repeatForever(SKAction(named: "PlayerWalk", duration: 1)!)
self.playerAnimation = walkAnimation
self.thePlayer.run(walkAnimation)
}
func changeAnimationDuration(to duration: CGFloat) {
self.playerAnimation?.duration = duration
}
}
The animation starts when the setInitialAnimation method is called, but changing the action's duration has no effect. The only thing that works so far is removing the old action from the player sprite and running a new one.
Should it not be possible to change the properties of a running SKAction or is that some kind of fire and forget mechanism?
Play around with the timePerFrame to get the correct animation speed.
var frames = [SKTexture]()
for i in 1...10 {
frames.append(SKTexture(imageNamed: "YourTextureAnimationFrame\(i)"))
}
let animate = SKAction.animate(with: frames, timePerFrame: 0.025)
//timePerFrame setting the speed of the animation.
node.run(animate)

Swift UIViewPropertyAnimator automatically plays when leaving app

Good day everyone!
I've a problem with my app. I'm using an UIViewPropertyAnimator to animate my blur. I let the blur effect run from nil (0) to .light (1) but because the light effect is just too much I set the UIViewPropertyAnimator to 0.1 (.fractioncomplete = 0.1).
This works greath! But the problem is that if you leave the app (not kill, just leaving by pressing home button or going to another app), it automatically plays and goes from 0.1 to 1.
I think this is a bug? I hope someone has a solution for this.
Here some pictures: (not enough reputation to post them directly)
picture with blur on 0.1
picture with blur on 1
Code:
let effectView = UIVisualEffectView(effect: nil)
var animator: UIViewPropertyAnimator?
viewdidload:
effectView.frame = view.bounds
effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mainView.addSubview(effectView)
animator = UIViewPropertyAnimator(duration: 1, curve: .linear) {
self.effectView.effect = UIBlurEffect(style: .light)
}
blur action (need to start/pause first because of a known bug from apple to wake the animator up):
self.animator?.startAnimation()
self.animator?.pauseAnimation()
self.animator?.fractionComplete = CGFloat(0.1)
Thanks in advance !
I solved this issue by setting the pausesOnCompletion property of UIViewPropertyAnimator to true, immediately after setting the fractionComplete. No need to restart the animator.
I reproduced your issue, and confirm this behavior, but I doubt it's a bug. Property animator just returns from paused state to active and runs animation. To overcome it you have to stop it and then finish in current fraction of the state (respective states are described in docs). One more issue here - somehow stopping it right after setting fractionComplete stops it at 0 so you have to wait a while (very short). Try this code under your "blurAction":
self.animator?.startAnimation()
self.animator?.pauseAnimation()
self.animator?.fractionComplete = CGFloat(0.1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.animator?.pauseAnimation()
self.animator?.stopAnimation(true)
self.animator?.finishAnimation(at: .current)
}
I verified it works for background/foreground app lifecycle change.

Drag and drop children of the same sprite

I'm making a game using Sprite Kit and I want to be able to drag and drop boxes as they travel down the screen.
Here's the gist of the code: I spawn the boxes on a timer and they move down the screen.
override func didMoveToView(view: SKView) {
let timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: Selector("spawnBox"), userInfo: nil, repeats: true)
}
func spawnBox() {
/* Set up the box */
addChild(box)
let boxMoveDown = SKAction.moveToY(-100, duration: 5.0)
let actionDone = SKAction.removeFromParent()
box.runAction(SKAction.sequence([boxMoveDown, actionDone]))
}
But the problem is I how can I move a specific child which I am touching without affecting all the other 'children'? I understand that at the moment, every time I spawn a box it's exactly the same so I can't be specific when I set a individual child's position.
Here's what's inside my touchesBegan and touchesMoved functions
if let touch = touches.first {
let location = touch.locationInNode(self)
let objects = nodesAtPoint(location) as [SKNode]
if objects.contains(nodeAtPoint(location)) && nodeAtPoint(location).name == "box" {
box.position = location
box.removeAllActions()
}
}
The - box.position = location is what
needs changing.
Hopefully you understand my idea. I've tried to keep included code to what's necessary. I'm quite new to Sprite Kit which you can probably tell.
If I were you, I would handle it this way:
Create a custom class for your box nodes that extends SKSpriteNode.
In this custom class, override the touch property.
Then set the position inside this function based on location.
Now all you need to worry about is your zPosition, whatever child has the highest zPosition will be the one that gets called on touch.
You do not need to worry about nodesAtPoint or what not anymore, the API will handle all that for you.

show menu when paused a game ins spritekit

I created a game like as tower defence. I can successfully "pause" all game. And then i can resume the game with these code:
let startLocation = CGPoint(x: location.x, y: location.y);
let myNodeName = self.nodeAtPoint(location)
if(myNodeName.Name == "Start"){
self.view!.paused = false
}
if(myNodeName.Name == "pause"){
self.view!.paused = true
self.showPausedMenu()
}
In showPausedMenu function. I design a menu with SKLabelNodes and SKSpriteNodes. However when i paused the game all of these "node creation" things also paused. So nothing happen. How i can show my menu while game is paused?
thank you.
Problem is that you're pausing the view itself. Pausing the view stops all of the logic.
If you need to do a pause menu, you need to either add a subview that will appear on top of your view to manage all of the interactions with the user, or you need to not pause the view, but put the view ELEMENTS on pause and create things on top of it.
The best method would be to make use of a SKNode group which consist of all the objects that you want to be paused.
In this way, you retain full control of the elements that you want to pause or unpause
I had solve my problem. While paused the game, all screen freezed however gamescene still detect all "touch" and if it has node name then we can do some operations. At this point, the proble is only showing my menu. So here is my solution.before I paused game, i added my menu on the screen. then pause the game.
In live version is
let startLocation = CGPoint(x: location.x, y: location.y);
let myNodeName = self.nodeAtPoint(location)
if(myNodeName.Name == "Start"){
self.view!.paused = false
self.removePausedMenu()
}
if(myNodeName.Name == "pause"){
self.showPausedMenu()
}
showPausedMenu is:
func showPausedMenu(){
.....
.....
/* Create menu and buttons and labels with NODES. and add to parent as child nodes. */
self.view!.paused = true
}

How do I make a trail behind my character in SpriteKit?

I'm using SpriteKit (with Xcode 6 and Swift) and I have a character on the screen that I move around with on screen joysticks, and I want a little trail to follow behind him. How do I do that?
How big would my image need to be, and what would it need to look like?
Also what would I use in my code?
You should take a look at SKEmitterNode; it will "emit" particles that you can use as your trail. You can design the look and feel of your particles right in Xcode by adding a "SpriteKit Particle File" to your project:
You'd then load the particle file in to a new SKEmitterNode like so:
let emitter = SKEmitterNode(fileNamed: "CharacterParticle.sks")
Then you'll need to set the SKEmitterNode's targetNode property to your SKScene so that the particles it emits don't move with your character (i.e. they leave a trail):
emitter.targetNode = scene
Then add your emitter to your character's SKNode. Lets assume you have an SKNode for your character called character, in that case the code would simply be:
character.addChild(emitter)
Typically this sort of thing would be done in your scene's setup method (in Apple's SpriteKit template, it's usually in didMoveToView). It could also be done in your character's custom SKNode or SKSpriteNode class, if you have one. If you put it in didMoveToView, it would look something like:
override func didMoveToView(view: SKView) {
// ... any character or other node setup ...
let emitter = SKEmitterNode(fileNamed: "CharacterParticle.sks")
emitter.targetNode = self
character.addChild(emitter)
// ... any other setup ...
}
Although SKEmitterNode is a fine option. I would suggest you use a SKSpriteNode instead. The Emitters in Xcode cause a lot of lag when used frequent and in sequence.
The best way to create a trail in my opinion is by preloading a SKTexture when loading up the application. For this I would suggest creating a class like this.
class AssetsManager {
private init() {};
static let shared = AssetsManager();
func preloadAssets(with texture: SKTexture) {
texture.preload {
print("Sprites preloaded")
}
}
And than calling it as so in either your AppDelegate or MenuScene:
AssetsManager.shared.preloadAssets(with: SKTexture(imageNamed: "yourImage"))
Than for the "Creating a trail part":
Create a timer
var timer: Timer!
Start your timer
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(ballTrail), userInfo: nil, repeats: true)
Create the ballTrail function
#objc func ballTrail() {
let trail = SKSpriteNode(texture: SKTexture(imageNamed: "your Image"))
trail.size = size
trail.position = player.position
trail.zPosition = player.positon - 0.1
addChild(trail)
trail.run(SKAction.scale(to: .zero, duration: seconds))
trail.run(SKAction.fadeOut(withDuration: seconds)
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
trail.removeFromParent()
}
}
You can fiddle around with the actions and timings you would like to use. Hopefully this will help someone!