Swift: SKLabelNode fade animation doesn't fade - ios

I'm just trying to make a SKLabelNode fade in, here's my code:
let welcome = SKLabelNode(text: "Welcome")
welcome.fontName = "HelveticaNeue-Light"
welcome.fontSize *= size.width/welcome.frame.width
welcome.fontColor = UIColor(white:1,alpha:0)
welcome.horizontalAlignmentMode = .center
welcome.verticalAlignmentMode = .center
welcome.position = CGPoint(x:size.width/2,y:size.height/2)
addChild(welcome)
let fadein = SKAction.fadeIn(withDuration: 1)
let remove = SKAction.removeFromParent()
welcome.run(SKAction.sequence([fadein,remove]))
But it doesn't work, and I can't figure out why. The strange part is the removeFromParent part works fine, just not the fade in. I already tried changing the font, making the label fade out and even making a custom action that changes the alpha, all of which have failed. I just can't figure out what the problem is.
Any idea will be appreciated.
Thanks in advance.

Instead of setting the fontColor's alpha to 0, set the SKLabelNode's alpha to 0 before running the fadeIn action on it. This is because the actions are applied to the nodes themselves, not to property inside of the nodes. (E.G. In your case: fadeIn affects SKLabel.alpha, not SKLabel.fontColor.alpha)

Related

CAEmitterLayer, eventually, has delay when adding EmitterCells

A very bizarre issue we've been seeing (gifs below),
We have a presented View Controller that has a TeamBadgeView,
which is a button that emits emoji as CAEmitterCells
Tapping this button lets users spam a fire emoji on their screen
Dismissing the presented view controller, and re-present the view controller, and now there is a delay. The more times I present/dismiss the view controller, the CAEmitterCell becomes more and more unresponsive
Confirmed that this is not a leak issue, the view controller and button are being properly deallocated
I have tried moving the CAEmitterLayer and CAEmitterCell around, holding a reference in the button, and declaring locally, but similar issues
Perhaps most bizarre, if I do not press the button at all, and simply present/dismiss the viewcontroller many times, and then press the button, there is a delay. The only time there isn't a delay is pressing the button on the first time the View Controller is presented
I have confirmed that the button's action is being fired correct, everytime I spam the button. It's just that the emitter cell is not rendering for a few seconds. And some of the emitter cells just don't render at all
It's gotten to the mind-boggling point, does anybody have any ideas or leads on what this could be?
First presentation of ViewController:
After 5th presentation of ViewController (Pressing button at same rate):
ViewController code:
let teamBadgeView = TeamBadgeView.fromNib()
teamBadgeView.configure()
Button code:
class TeamBadgeView: UIView {
let emitter = CAEmitterLayer()
let fireSize = CGSize(width: 16, height: 18)
let fireScale: CGFloat = 0.8
func configure() {
emitter.seed = UInt32(CACurrentMediaTime())
emitter.emitterPosition = CGPoint(x: bounds.midX, y: 0)
emitter.emitterShape = CAEmitterLayerEmitterShape.line
emitter.emitterSize = fireSize
emitter.renderMode = CAEmitterLayerRenderMode.additive
layer.addSublayer(emitter)
}
#IBAction func tapAction(_ sender: Any) {
emitFire()
}
private func emitFire() {
let cell = CAEmitterCell()
let beginTime = CACurrentMediaTime()
cell.birthRate = 1
cell.beginTime = beginTime
cell.duration = 1
cell.lifetime = 1
cell.velocity = 250
cell.velocityRange = 50
cell.yAcceleration = 100
cell.alphaSpeed = -1.5
cell.scale = fireScale
cell.emissionRange = .pi/8
cell.contents = NSAttributedString(string: "🔥").toImage(size: fireSize)?.cgImage
emitter.emitterCells = [cell]
}
}
Instead of setting the emitterCells array every time:
emitter.emitterCells = [cell]
...append the new cell to it. Make sure to initialize it to an empty array if it's nil though, or else the append will not work:
if emitter.emitterCells == nil {
emitter.emitterCells = []
}
emitter.emitterCells?.append(cell)
Thanks to #TylerTheCompiler we were able to figure this out, and it was really lame.
One line change, instead of setting the emitterCells, we needed to append
emitter.emitterCells = [cell]
became
emitter.emitterCells?.append(cell)
Why we didn't notice this was because it appears there is a weird interaction with Hero transitions. Our ViewController is presented via a Hero Transition, and for some reason the first time it's presented, emitterCells = [cell] works as expected... but then for some reason, for each subsequent Hero Transition to the ViewController, the cells start emitting slower and slower until it's back to the expected slow state. Incredibly strange, perhaps a bug in Hero, but who knows

Why SpriteKit's 'addChild' method result in chaos

I'm in a trouble of SpriteKit's 'addChild' method.
I tried to add a simple button to my scene , and the button is formed of one background image and one label.So I wrote code like this:
override func didMove(to view: SKView) {
let buttonBg = SKSpriteNode(imageNamed: "mainButton_green")
let buttonLabel = SKLabelNode(text: "Label")
self.addChild(buttonBg)
self.addChild(buttonLabel)
}
Then I ran the program on simulator and I found that result is strange.Sometimes it appeared right,like this:
correct appearence
But sometimes, 'buttonLabel' will be behind of 'buttonBg', just like this:
wrong appearence
Why?
ps. I had print self.children , and the result was '[buttonBg,buttonLabel]' in each situation , my skview.ignoresSiblingOrder was always false.
A couple of things you can do
let buttonBg = SKSpriteNode(imageNamed: "mainButton_green")
buttonBg.zPosition = 1
let buttonLabel = SKLabelNode(text: "Label")
buttonLabel.zPosition = 2
which manually sets the z layering of your objects
I also think that if you are creating a button object you should add the label as a child of the background
let buttonBg = SKSpriteNode(imageNamed: "mainButton_green")
buttonBg.zPosition = 1
let buttonLabel = SKLabelNode(text: "Label")
buttonLabel.zPosition = 1
self.addChild(buttonBg)
buttonBg.addChild(buttonLabel)
I always add a zPosition even if its the only child.
Your issue is not a real problem: when you create your elements, following your case they'll be rendered in the main thread following some rules like:
statements order
zPosition property
alpha property
etc..
So, something like a litlle label could be drawed before your button background causing this annoying aspect.
A good thing to do when you want to create a button should be:
let buttonBg = SKSpriteNode(imageNamed: "mainButton_green")
self.addChild(buttonBg)
let buttonLabel = SKLabelNode(text: "Label")
buttonBg.addChild(buttonLabel)
In other words, put your label as a child of your background, this should will guarantee to you a correct zPosition levels (without change zPosition properties), a correct label position (by default CGPoint.zero) always if you haven't changed (leave untouched) the anchorPoint of your elements.

Adding animation to 3D models in ARKit

In this video an object is given an animation to hover around the room when placed and then when tapped it gets dropped down with another animation.
How can I add this kind of animations in my project?
Is adding an already animated object the only way?
Thank you
https://youtu.be/OS_kScr0XkQ
I think the hovering up and down is most likely an animation sequence. The following animation sequence for a hovering effect will work inside the first drop down selection function.
let moveDown = SCNAction.move(by: SCNVector3(0, -0.1, 0), duration: 1)
let moveUp = SCNAction.move(by: SCNVector3(0,0.1,0), duration: 1)
let waitAction = SCNAction.wait(duration: 0.25)
let hoverSequence = SCNAction.sequence([moveUp,waitAction,moveDown])
let loopSequence = SCNAction.repeatForever(hoverSequence)
node2Animate.runAction(loopSequence)
self.sceneView.scene.rootNode.addChildNode(node2Animate)
The second part to stop the animation when you tap the node put this inside the tap gesture function.
node2animate.removeAllActions()
The last part dropping to the floor, the node2animate might be a physicsBody and before the tap the gravity attribute
node2animate.physicsBody?.isAffectedByGravity = false
after the tap you set it to true
node2animate.physicsBody?.isAffectedByGravity = true
There is other stuff going on as well, collision with the floor have also been set etc.

SKAction.colorizeWithColor makes SKLabelNode disappear

I'm using SKLabelNode. I'm creating it and add it to my scene as a child and it displays with no problem, but when I try to change it's color (not fontColor) with the colorizeWithColor() method the label fades out.
Here is the line with the problem:
myLabel.runAction(SKAction.colorizeWithColor(SKColor.blueColor(), colorBlendFactor: 1.0, duration: duration))
I printed to the console the myLabel.color property after the completion of this action and here is what I get:
Optional(UIDeviceRGBColorSpace 0.99178 0.99178 1 0.00822043)
As you can see, the alpha value is almost 0, so I guess this is why the label disappears, but I don't understand why this is happening.
Thanks in advance.
UPDATE:
Ok, so I found actually this and is my bad that I didn't searched before asking. Here is the documentation about colorizeWithColor method:
This action can only be executed by an SKSpriteNode object. When the
action executes, the sprite’s color and colorBlendFactor properties
are animated to their new values.
So maybe anybody does know a good work around for colorize a SKLabelNode that is always updating?
!!!LAST UPDATE!!!
I was able to find a solution, but 0x141E came up with a even more nice solution, which I used and created the next method that works nice when you need to make transition from color A to color B. In solution suggested by 0x141E you ever come back to fontColor and it causes to blink when you change color. In my case it changes the fontColor, not the color property, which causes a pretty nice transition (Of course not great).
Thanks again 0x141E for the really nice approach!!!
Here is my solution:
This particular case works great when you call the method for parameter withDuration = 0.5
However if you need other time, you can play around with the sent withDuration parameter or the multiplier, that in my code is 5.
Even there of course should be a better solution so if you find please share it. For my needs this one works fantastic.
First of all a video so you can see how it works: https://www.youtube.com/watch?v=ZIz8Bn0-hUA&feature=youtu.be
func changeColorForLabelNode(labelNode: SKLabelNode, toColor: SKColor, withDuration: NSTimeInterval) {
labelNode.runAction(SKAction.customActionWithDuration(withDuration, actionBlock: {
node, elapsedTime in
let label = node as SKLabelNode
let toColorComponents = CGColorGetComponents(toColor.CGColor)
let fromColorComponents = CGColorGetComponents(label.fontColor.CGColor)
let finalRed = fromColorComponents[0] + (toColorComponents[0] - fromColorComponents[0])*CGFloat(elapsedTime / (CGFloat(withDuration)*5))
let finalGreen = fromColorComponents[1] + (toColorComponents[1] - fromColorComponents[1])*CGFloat(elapsedTime / (CGFloat(withDuration)*5))
let finalBlue = fromColorComponents[2] + (toColorComponents[2] - fromColorComponents[2])*CGFloat(elapsedTime / (CGFloat(withDuration)*5))
let finalAlpha = fromColorComponents[3] + (toColorComponents[3] - fromColorComponents[3])*CGFloat(elapsedTime / (CGFloat(withDuration)*5))
labelNode.fontColor = SKColor(red: finalRed, green: finalGreen, blue: finalBlue, alpha: finalAlpha)
}))
}
You can colorize an SKLabelNode with an SKAction by creating a custom action. Here's an example of how to do that
myLabel.color = SKColor.blueColor()
myLabel.colorBlendFactor = 0.0
let duration:NSTimeInterval = 2.0
myLabel.runAction(SKAction.customActionWithDuration(duration, actionBlock: {
node, elapsedTime in
let label = node as SKLabelNode
label.colorBlendFactor = elapsedTime / CGFloat(duration);
}))

Fading a shadow together with the SKSpriteNode that casts it

Here's my setup, using Sprite Kit. First, I create a simple sprite node within a SKScene, like so:
let block = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(90, 160))
block.zPosition = 2
block.shadowCastBitMask = 1
addChild(block)
Then add a light node to the scene:
let light = SKLightNode()
light.categoryBitMask = 1
light.falloff = 1
addChild(light)
Sure enough, the block now casts a nice little shadow:
Now I fade the block by manipulating its alpha value, for example by running an action:
let fadeOut = SKAction.fadeAlphaTo(0.0, duration: 5.0)
block.runAction(fadeOut)
Here's the awkward situation: while the block becomes more and more translucent, the shadow stays exactly the same. This is how it looks like just a moment before the end of the action:
And once the alpha drops to 0.0 entirely, the shadow suddenly disappears, from one frame to the next.
It would be much nicer, however, to have the shadow slowly become weaker and weaker, as the object casting it becomes more and more transparent.
Question:
Is an effect like this possible with Sprite Kit? If so, how would you go about it?
This is a little tricky because the shadow cast by an SKLightNode isn't affected by the node's alpha property. What you need to do is fade out the alpha channel of the shadowColor property of the SKLightNode at the same time you're fading out your block.
The basic steps are:
Store the light's shadowColor and that color's alpha channel for reference.
Create a SKAction.customActionWithDuration which:
Re-calculates the value for the alpha channel based on the original and how much time has past so far in the action.
Sets the light's shadowColor to its original color but with the new alpha channel.
Run the block's fade action and the shadow's fade action in parallel.
Example:
let fadeDuration = 5.0 // We're going to use this a lot
// Grab the light's original shadowColor so we can use it later
let shadowColor = light.shadowColor
// Also grab its alpha channel so we don't have to do it each time
let shadowAlpha = CGColorGetAlpha(shadowColor.CGColor)
let fadeShadow = SKAction.customActionWithDuration(fadeDuration) {
// The first parameter here is the node this is running on.
// Ideally you'd use that to get the light, but I'm taking
// a shortcut and accessing it directly.
(_, time) -> Void in
// This is the original alpha channel of the shadow, adjusted
// for how much time has past while running the action so far
// It will go from shadowAlpha to 0.0 over fadeDuration
let alpha = shadowAlpha - (shadowAlpha * time / CGFloat(fadeDuration))
// Set the light's shadowColor to the original color, but replace
// its alpha channel our newly calculated one
light.shadowColor = shadowColor.colorWithAlphaComponent(alpha)
}
// Make the action to fade the block too; easy!
let fadeBlock = SKAction.fadeAlphaTo(0.0, duration: fadeDuration)
// Run the fadeBlock action and fadeShadow action in parallel
block.runAction(SKAction.group([fadeBlock, fadeShadow]))
The following is one way to ensure that the shadow and block fade-in/fade-out together. To use this approach, you will need to declare light and block as properties of the class.
override func didEvaluateActions() {
light.shadowColor = light.shadowColor.colorWithAlphaComponent(block.alpha/2.0)
}
EDIT: Here's how to implement the above.
class GameScene: SKScene {
let light = SKLightNode()
let block = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(90, 160))
override func didMoveToView(view: SKView) {
/* Setup your scene here */
block.zPosition = 2
block.shadowCastBitMask = 1
block.position = CGPointMake(100, 100)
addChild(block)
light.categoryBitMask = 1
light.falloff = 1
addChild(light)
let fadeOut = SKAction.fadeAlphaTo(0.0, duration: 5.0);
let fadeIn = SKAction.fadeAlphaTo(1.0, duration: 5.0);
block.runAction(SKAction.sequence([fadeOut,fadeIn,fadeOut]))
}
override func didEvaluateActions() {
light.shadowColor = light.shadowColor.colorWithAlphaComponent(block.alpha/2.0)
}
}

Resources