SpriteKit SKSequence to make UI Buttons appear - swift

In a gameOverScene of my SpriteKit game, I have 3 buttons which appear on the screen.
I'm trying to make them "pop in", one after the other... I figured a neat way was to fade them in from 0 alpha to full.
I'm currently doing it like this:
//animate in buttons
restartButton.alpha = 0
shareButton.alpha = 0
exitButton.alpha = 0
let bringInUIButtons = SKAction.sequence([
SKAction.waitForDuration(1.0),
SKAction(restartButton.runAction(SKAction.fadeInWithDuration(0.3))),
SKAction.waitForDuration(1.0),
SKAction(shareButton.runAction(SKAction.fadeInWithDuration(0.3))),
SKAction.waitForDuration(1.0),
SKAction(exitButton.runAction(SKAction.fadeInWithDuration(0.3))),
])
runAction(bringInUIButtons)
But they all fade in at once... Even though, as part of my sequence I wait for a second in-between each Action.
I even tried this:
//animate in buttons
restartButton.alpha = 0
shareButton.alpha = 0
exitButton.alpha = 0
let bringInUIButtons = SKAction.sequence([
SKAction.waitForDuration(1.0),
SKAction(restartButton.runAction(SKAction.fadeInWithDuration(0.3))),
SKAction.waitForDuration(1.0),
SKAction(shareButton.runAction(SKAction.fadeInWithDuration(0.6))),
SKAction.waitForDuration(1.0),
SKAction(exitButton.runAction(SKAction.fadeInWithDuration(0.9))),
])
runAction(bringInUIButtons)
...and by changing the fade in duration, so each button takes longer to appear, but it doesn't look very good.
Am I using the waitForDuration incorrectly? I figured it would wait before starting the next action in the sequence?
How can I make the next button appear after the first?

I think the problem is that you're having each button run its own "fade in" action, which means they'll execute their actions in parallel. If you want them to appear one at a time in the scene, it's better to have the scene run the action. Here's my solution...
When you instantiate restartButton, shareButton, and exitButton, set their name properties as shown below:
restartButton.name = "restartButton"
shareButton.name = "shareButton"
exitButton.name = "exitButton"
You'll use these name properties to specify the button on which you want to run a "fade in" action using the runAction(_:onChildWithName:) method.
When it's time to fade the buttons in, use this set of actions:
let fadeIn = SKAction.fadeInWithDuration(1)
let wait = SKAction.waitForDuration(1)
let bringInRestartButton = SKAction.runAction(fadeIn, onChildWithName: "restartButton")
let bringInShareButton = SKAction.runAction(fadeIn, onChildWithName: "shareButton")
let bringInExitButton = SKAction.runAction(fadeIn, onChildWithName: "exitButton")
let bringInUIButtons = SKAction.sequence([
bringInRestartButton,
wait,
bringInShareButton,
wait,
bringInExitButton
])
runAction(bringInUIButtons)

Related

Delay before beginning animation of NSView (AppKit)

The following code from here shows how to do a fade-out for NSView in Swift:
NSAnimationContext.runAnimationGroup({ context in
context.duration = 1
self.view.animator().alphaValue = 0
}, completionHandler: {
self.view.isHidden = true
self.view.alphaValue = 1
})
I am using this code to display a status notification -> i.e. the text appears, stays for around a few seconds, and then fades out. Is there a way to delay the start of the fade out to accomplish this?
Is there a way to delay the start of the fade out to accomplish this?
One way is to use NSTimer to run your animation after whatever delay you choose. For example, you could pass a block containing your code to +scheduledTimerWithTimeInterval:repeats:block:, and once the interval expires the block will run.

Using Swift, how do I animate the .setPosition() method of an NSSplitView without visually stretching its contents?

I would like to animate the appearance of a NSSplitViewItem using .setPosition() using Swift, Cocoa and storyboards. My app allows a student to enter a natural deduction proof. When it is not correct, an 'advice view' appears on the right. When it is correct, this advice view will disappear.
The code I'm using is the below, where the first function makes the 'advice' appear, and the second makes it disappear:
func showAdviceView() {
// Our window
let windowSize = view.window?.frame.size.width
// A CGFloat proportion currently held as a constant
let adviceViewProportion = BKPrefConstants.adviceWindowSize
// Position is window size minus the proportion, since
// origin is top left
let newPosition = windowSize! - (windowSize! * adviceViewProportion)
NSAnimationContext.runAnimationGroup { context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
func hideAdviceView() {
let windowSize = view.window?.frame.size.width
let newPosition = windowSize!
NSAnimationContext.runAnimationGroup{ context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
My problem is that the animation action itself is causing the text in the views to stretch, as you can see in this example: Current behaviour
What I really want is the text itself to maintain all proportions and slide gracefully in the same manner that we see when the user themselves moves the separator: Ideal behaviour (but to be achieved programmatically, not manually)
Thus far in my troubleshooting process, I've tried to animate this outside of NSAnimationContext; played with concurrent drawing and autoresizing of subviews in XCode; and looked generally into Cocoa's animation system (though much of what I've read doesn't seem to have direct application here, but I might well be misunderstanding it). I suspect what's going on is that the .animator() proxy object allows only alpha changes and stretches---redrawing so that text alignment is honoured during the animation might be too non-standard. My feeling is that I need to 'trick' the app into treating the animation as though it's being performed by the user, but I'm not sure how to go about that.
Any tips greatly appreciated...
Cheers

NSAnimationContext issue: no animation with isHidden property

I have the following view structure:
In the Date Stack View, I want to hide/unhide the Advanced Stack Viewin an animated way (like the height was reduced to zero).
I searched a lot for a solution and I came up with this:
dateStackView.wantsLayer = true
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.5
context.allowsImplicitAnimation = true
advancedDateStackView.animator().isHidden = true
self.view.layoutSubtreeIfNeeded()
}, completionHandler: nil)
But it does not work --> no animation
However, if I replace:
advancedDateStackView.animator().isHidden = true
by
advancedDateStackView.animator().alphaValue = 0,
I can see a fadeout animation for the advancedDateStackView. Which is not the desired behaviour, but proves the animation can work.
I also tried self.advancedDateStackView.animator().frame.size.height = 0
but no animation either.
Any help would be appreciated, I struggle to find a solution.
Thanks!!
EDIT to answer the comment below saying my question is a duplicate: Here my code works and allows me to perform a transition with alphaValue. However, what I need, and what does not work, is to perform a transition on isHidden.

SKAction Sequencing and Grouping Animations

I'm doing some death animations for a game, and wanted to ask for some help. I want my monster to disappear in a puff of smoke, but not before it animates a slash effect going across his body.
I have 3 animations that I want to use:
weaponSlash - a line that draws across the monster. Looks like you slashed him with a sword.
smoke - a puff of smoke that slowly expands out
monsterFalling - the monster falls back, startled
What I want to do is play it in this order:
Simultaneously, the slash appears & the monster starts to fall back
About 0.25s into the above animation, I want the cloud to start to appear
When the cloud is about to end (so maybe after 1s) I want the monster to disappear
Remove the smoke, the monster, the sword, etc, and drop some coins on the ground
I started like this, as a test that works somewhat: (ignore the above times for now)
//Cancel any current actions, like a monster attacking
monster.removeAllActions()
//since you can't play 3 animations on one node at the same time, you have to create 3 separate nodes for everything
let slash = SKSpriteNode()
let cloud = SKSpriteNode()
cloud.size = monster.size
slash.size = monster.size
monster.addChild(cloud)
monster.addChild(slash)
//Start the slash animation
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
//Start the cloud animation (how I get it is elsewhere and not relevant)
cloud.run(cloudAnimation)
//Run the monster death animation, followed by the cleanup/coin dropping
monster.run(SKAction.sequence([monster.deathAnimation(), SKAction.wait(forDuration: 1), postDeathActions]))
The variable PostDeathActions above simply removes the monster node and animates some coins falling.
WHERE I NEED SOME HELP
So the above code doesn't work so great in that the animations all run independently of each other. Based on this, you can see how regardless of whether the slash/cloud finish, the monster will run two actions: him falling back, followed by cleanup, which just removes the monster and spawns the coins. As you can see I tried to delay this by adding a 1s delay but this is all somewhat of a hack since I may have different monsters or attacks, etc, that are faster/slower. I'd rather guarantee that everything finishes before I despawn the monster.
I tried to group this into an SKAction.Run like so:
let preDeath = SKAction.run {
[unowned self] in
monster.run(monster.deathAnimation()
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
cloud.run(cloudAnimation)
}
but this runs everything at the same time again.
What I want to do is sequence it like this (pseudo code):
let preDeathAnimations = SKAction.Group([slash, cloud, monsterDeathAnimation])
])
SKAction.sequence([preDeathAnimations, postDeathActions])
So this way it'll run all 3 before running cleanup.
Is there a way to do something like this? I know Sequnce/Group need to be run against an SKNode, but I don't have 3 separate ones.
Thanks for your time reading this and any advice you can offer!
This is one idea that I had, but you could use threading + state + onCompletion blocks to take the math out of it. I didn't test it out fully but this general concept should work:
let slash = SKAction.fadeIn(withDuration: 0.5)
let fall = SKAction.fadeOut(withDuration: 0.25)
let puff = SKAction.fadeIn(withDuration: 0.1)
// Put in ALL of the actions from ALL parties that you want to happen prior to puff:
func findLongestTime(from actions: [SKAction]) -> TimeInterval {
var longestTime = TimeInterval(0)
for action in actions {
if action.duration > longestTime { longestTime = action.duration }
}
// Note, if you put a sequence into this function I don't know if it will work right..
// Might need another func like `findDurationOfSequence(_ sequence: SKAction) -> TimeInterval
return longestTime
}
// Note, if you have the monster doing more than falling prior to puff, then you will
// need to subtract those as well:
let monsterActionsPriorToPuff = [fall]
// Add the duration of all monster animations prior to puff:
var MAPTP_duration = TimeInterval(0)
for action in monsterActionsPriorToPuff {
MAPTP_duration += action.duration
}
// Calculate our final wait time, with no negative numbers:
var waitTime = findLongestTime(from: [slash, fall]) - MAPTP_duration
if waitTime < 0 { waitTime = 0 }
let wait = SKAction.wait(forDuration: waitTime)
// Our monster sequence (I forgot to add the disappear, just add after puff)
let monsterSequence = SKAction.sequence([fall, wait, puff])
// Player slashes:
SKSpriteNode().run(slash)
// Monster will wait 0.25 seconds after falling,
// for slash to finish before puffing:
SKSpriteNode().run(monsterSequence)
et me know if this idea isn't working I can try updating it.

SpriteKit: Cloning Nodes

So I would like to make a block of code that will run inside the sprite node every frame. The result would be me tapping and creating a bubble there that would then float up using sine. Is that possible to code into the bubble? Sorry if this seems vague, I'm just not sure what to call it. The closest thing I can think of it cloning and each clone runs the same script every frame. Anyways, Here's what I have now.
let bubble = SKSpriteNode(imageNamed:"bubble")
bubble.position.x = CGFloat(rand())*self.frame.width
bubble.position.y = -20
//Code for bubble to run each frame
self.addChild(bubble)
I think that what you are looking for is enumerateChildNodesWithName. When it is called, it will run the code it contains on every node with the name you put in it.
bubble.name = "bubble"
If you name bubble "bubble", all of its child nodes will also be named "bubble".
enumerateChildNodesWithName("bubble"){node, stop in
let bubbleChild:SKSpriteNode = node as! SKSpriteNode
//Run the code telling them what to do here. You should make it so that
//it can figure out how to move the node a slight amount based on its position
}
You could call this by putting it in a function that is called by a timer, and the timer should be called frequently, like every 0.017 seconds (Approximately 60 times a second) so that the motion is smooth. For example:
var timer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(0.017), target: self, selector: Selector("moveBubbles:"), userInfo: nil, repeats: true)
Then, to make a function that the timer calls:
func moveBubbles(timer: NSTimer!){
//enumerateChildNodesWithName here
}
There is more information on enumerateChildNodesWithName here, and more information on NSTimer here.