How to make a slow transition of color in sprite kit? - swift

Hello fellow developers,
I'm trying to change the color of a circle slowly in SpriteKit, making a transition from the original color to the new one with some time between them, transitioning from one color to the other visually. The problem that I have is that my code doesn't make that change slowly, it simply changes like if I directly assigned the new color. Here's my actual code:
func changeColor(c: UIColor) {
var r:CGFloat = 0
var g:CGFloat = 0
var b:CGFloat = 0
var a:CGFloat = 0
c.getRed(&r, green: &g, blue: &b, alpha: &a)
var r1:CGFloat = 0
var g1:CGFloat = 0
var b1:CGFloat = 0
var a1:CGFloat = 0
circle.fillColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1);
let wait = SKAction.waitForDuration(0.4)
r = (r - r1)/10
g = (g - g1)/10
b = (b - b1)/10
for(var i = 0; i < 10; i++) {
let cN = UIColor(red: (r1 + r*CGFloat(i)), green: (g1 + g*CGFloat(i)), blue: (b1 + b*CGFloat(i)), alpha: 1.0)
circle.fillColor = cN
circle.strokeColor = cN
runAction(wait)
}
}
I really belive that the problem is in "runAction(wait)", but I'm not sure. I'd like to know if there's a default function that does what I want, but I highly doubt it.

The problem is that you're trying to do all of this "live" in a for loop. All the code in your function runs in between frames of animation, so that won't work.
To make this clearer, let's ignore the wait part for a moment. When you run a function that looks like this:
func changeColor() { // unrolled loop and pseudocode colors for clarity
circle.fillColor = blue
circle.fillColor = red
circle.fillColor = green
}
...SpriteKit does change the circle's color three times—immediately—but it isn't drawing the results. Whatever color you set it to most recently appears only after all your code finishes running and SpriteKit draws the screen.
When you use SKActions, what you're doing is lining up instructions for SpriteKit to follow over time, which includes instructions for tasks that take more than one animation frame. But the non-SKAction calls in your code are still the same as they were before—they make immediate changes, the end result of which you see when the next frame draws.
So, when your code looks like this:
func changeColor() { // unrolled loop and pseudocode colors for clarity
circle.fillColor = blue
circle.runAction(SKAction.waitForDuration(0.4))
circle.fillColor = red
}
... the order of events is like this:
set color to blue
add a wait action to the list of planned actions
set color to red
draw the next frame (color is red)
execute the list of planned actions (wait for 0.4 sec before executing next action in sequence)
So, the first thing to do is to get your color changes to stop being between-frames changes and add them to the over-time task list — that is, change color via an SKAction. There's a colorizeWithColor action, but I'm not sure whether it applies to shape nodes (with their separate fill/stroke colors). Try it, and if that fails you can try a runBlock
action that directly sets fillColor/strokeColor.
Back to pseudocode to talk about the sequencing problem, though...
func changeColor() { // unrolled loop and pseudocode color actions
circle.runAction(mySetBlueColorAction)
circle.runAction(SKAction.waitForDuration(0.4))
circle.runAction(mySetRedColorAction)
}
If you run this you'll notice there's still a problem here. Just calling runAction adds separate actions that run (roughly) in parallel. So your circle is still changing colors twice, but both changes are happening at the same time. (Which one wins? I'm not sure there's a determinate answer.) And at the same time as your circle is getting confused about its color, it's also starting to wait 0.4 seconds for... what exactly?
A waitForDuration action makes sense only in the context of a sequence action—for it to be meaningful, there needs to be something after in in sequence to wait for.
So, if what you want to happen sounds like this sequence:
Set the color
Wait a bit
Set the color to something else
... then what you need is a sequence action describing what you want to happen in order:
let sequence = SKAction.sequence([
mySetBlueColorAction,
SKAction.waitForDuration(0.4),
mySetRedColorAction,
SKAction.waitForDuration(0.4),
mySetGreenColorAction,
// etc.
])

Related

PineScript - Keep in labels in the future when based on a fixed event

Thanks to the PineCoders on Twitter for pointing me in this direction.
I'm tracking both a hard stop (based on an entry indicator) and trailing stop and would like to keep them aligned.
bar_index+5 works great for the trailing stop, but for the hard stop, it simply places it 5 bars after entry indicator.
I've tried factoring in ta.barssince and it seems to have no effect, presumably since it believes the indicator is bar_index or something... I'm at a loss.
Here's the code:
SLAlabelText = str.tostring(SLCalc, "#.##")
var SLALabel = label.new(x = bar_index, y = SLCalc, color = color.red, style = label.style_none, textcolor = color.white, text=SLAlabelText)
if longBoy and stopLossA
label.set_xy(SLALabel, x = bar_index+5, y = SLCalc)
label.set_text(SLALabel, SLAlabelText)
label.set_tooltip(SLALabel, "Stop Loss: " + SLAlabelText)
"longBoy" is the indicator's confirmed state.
Chart image. Apologies for the meme stock, it was the best example.

Adding SKShapeNodes - using while loop blanks UI

I have a game which is running well but as soon as I introduce a while loop my entire UI goes blank.
I have some code which generates a sprite from an array and moves it down the screen
func addALetter() {
let randomX = CGFloat.random(in: 50...size.width - 50)
let shrink = SKAction.scale(to: 0.1, duration: 0.01)
let grow = SKAction.scale(to: 1, duration: 0.5)
let wait = SKAction.wait(forDuration: 0.7)
let spawn = SKAction.move(to: CGPoint(x: randomX, y: size.height - tileSize), duration: 0.001)
let move = SKAction.moveTo(y: -500, duration: 7)
let sequence = SKAction.sequence([shrink, spawn, grow, move, wait])
// scroll through the lettersArray
if activeLetter < lettersArray.count - 1 {
bubbles[activeLetter].removeAllActions()
bubbles[activeLetter].run(sequence)
activeLetter += 1
} else {
// go back to first in letter array
activeLetter = 0
bubbles[activeLetter].removeAllActions()
bubbles[activeLetter].run(sequence)
}
}
It is working fine triggered using an SKAction in my didMove to view run(SKAction.repeatForever(SKAction.sequence([SKAction.run(addALetter), SKAction.wait(forDuration: spawnTime)])))
but I have problems with that as I get to the end of the array because the action repeats too frequently making sprites disappear before I want them too.
So I tried using a while loop instead...
while gameOn == true {
addALetter()
}
to repeat the action. But then I get a completely blank UI - I assume because it's then stuck in the loop there's no time to update the UI?
Looking for a solid way to repeat the function that I can vary the frequency as the array gets to low numbers
It seems likely that the problem is that spawnTime is small enough that you're wrapping around through the letters too quickly and start doing bubbles[activeLetter].removeAllActions() before the bubble's previous action sequence has finished.
I think the most best way to deal with this sort of situation is to coordinate through completion blocks. I.e., use the run method with a closure to be called after the action finishes, https://developer.apple.com/documentation/spritekit/sknode/1483103-run. That way you don't wind up trying to adjust delays explicitly in an effort to keep actions from being prematurely cancelled. The completion blocks will update state that you can use to coordinate, or they can run other actions directly.
It's not clear from your question what behavior you want. But, e.g., you might have addALetter set a flag for the bubble when it starts the action sequence and include a completion block for the sequence to clear the flag. Then before addALetter restarts the sequence for a bubble it can make sure that the flag is clear; if it's not (the bubble is still running the previous sequence), just return without doing anything.

Infinite Background problem with division SpriteKit

I've been trying to implement an infinite background animation, which should change between 4 images of equal height and then repeat the sequence. However, it does not seem to work properly.
Note anchorPoint = CGPoint(x: 0.5, y: 0)
func updateBackground(currentTime: TimeInterval){
var delta: CGFloat = 0.0
if lastUpdate != nil {
delta = CGFloat(currentTime - lastUpdate)
}
//First increment position
activeBackground1.position.y += delta*backgroundVelocity
activeBackground2.position.y += delta*backgroundVelocity
//Detect bounds surpass
if activeBackground1.position.y > activeBackground1.size.height + screen.height/2 {
lastSky = (lastSky + 1)%4
sky1 = SKTexture(imageNamed: "sky" + String(lastSky))
activeBackground1.texture = sky1
//Reposition: background1 new position is equal to minus the entire height of
//background2 + its y size.
activeBackground1.position.y = -abs(activeBackground2.size.height-activeBackground2.position.y)
}
if activeBackground2.position.y > activeBackground2.size.height + screen.height/2 {
lastSky = (lastSky + 1)%4
sky1 = SKTexture(imageNamed: "sky" + String(lastSky))
activeBackground2.texture = sky1
activeBackground2.position.y = -abs(activeBackground1.size.height-activeBackground1.position.y)
}
}
The update algorithm works fine, but when it is needed to reposition one of the two background, it seems there is an offset of about 10.0 CGFloat from one background to another. What am I doing wrong?
EDIT: It turned out that the error was located in my image, which presented some blank rows and therefore generated visualisation glitches. So my code works perfectly.
I do the test and most likely you should use something like:
activeBackground2.position.y = activeBackground1.size.height + activeBackground1.position.y
instead of
activeBackground2.position.y = -abs(activeBackground1.size.height-activeBackground1.position.y)
I did this example and it works correctly: https://github.com/Maetschl/SpriteKitExamples/tree/master/InfiniteBackground/InfiniteBackground
Feel free to see and use.
Your problem is floating point math causing rounding errors. I am on a phone right now so I can’t wrote code, but what you want to do is have 1 parent SKNode to handle your entire background.
Add your background slivers to the parent node.
You then place the moving action on the parent only.
As each sliver leaves the screen, you take the sliver, and move it to the end of the other slivers.
This jumping should always be done with integer math, leaving no holes.
Basically:
The floating point moving math is done on the parent node.
The integer based tile jumping is done on each of the slivers.

Swift: Convert string to hex color code

I have generated user Ids (with a format like ""XzSYoKJaqKYREkdB2dgwt0fLOPP2"") from a database that I would like to use to create an UIColor.
I found several solutions on how to crate an UIColor with a color hex code, but I don't quite know how I would generate a color hex code (#ffffff) from a string like the one above. Any help would be appreciated, thanks.
There are far too many user ids to get a unique color for every possible user id. Your best option would be to find a way to narrow down each user id to one of the possible available colors and accept the fact that two users may have the same color.
One possible solution is would be to get the hashValue of the user id string and then reduce that Int down to one of the possible 16,777,216 colors.
let userId = "XzSYoKJaqKYREkdB2dgwt0fLOPP2" // or whatever the id is
let hash = abs(userId.hashValue)
let colorNum = hash % (256*256*256)
At this point colorNum is the range 0 - 0xFFFFFF
You can now create a color from colorNum.
let red = colorNum >> 16
let green = (colorNum & 0x00FF00) >> 8
let blue = (colorNum & 0x0000FF)
let userColor = UIColor(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: 1.0)
You will want to store this color in the user's profile since the hashValue isn't guaranteed to be the same each time your app runs.
As of my understanding, you're trying to generate a random colour each time for every user and store it in the Firebase? Correct me if I'm wrong.
I hope this solution would fix your issue of creating random colours each time. In short, wherever and whenever you need a random unique colour - call the method and check if the returned UIColor already exists in the database. If it doesn't exist then use it!
func getRandomColor() -> UIColor {
let randomRed:CGFloat = CGFloat(arc4random()) / CGFloat(UInt32.max)
let randomGreen:CGFloat = CGFloat(arc4random()) / CGFloat(UInt32.max)
let randomBlue:CGFloat = CGFloat(arc4random()) / CGFloat(UInt32.max)
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
}
I hope the above solution from Oscar De Moya's from https://classictutorials.com/2014/08/generate-a-random-color-in-swift/ would help you.

How to Improve SKEffectNode Performance (Swift)?

so in my project I have an SKEffectNode that I use to provide a glow effect around some of my spriteNodes. I use a spriteNode (blurNode) to get the color of my obstacle and then give it to the effectNode. This works fine.
let blurNode = SKSpriteNode(imageNamed: "neonLine.png")
blurNode.color = obstacle.color
blurNode.colorBlendFactor = 1.0
blurNode.size = CGSize(width: obstacle.size.width + 30, height: obstacle.size.height + 30)
let effectNode = SKEffectNode()
effectNode.shouldRasterize = true
obstacle.addChild(effectNode)
effectNode.addChild(blurNode)
effectNode.filter = CIFilter(name: "CIGaussianBlur", withInputParameters: ["inputRadius":30])
effectNode.alpha = 1.0
My issue occurs here.
let colorFadegreen = SKAction.sequence([SKAction.colorize(with: UIColor(red: 0, green: 0.6471, blue: 0.3569, alpha: 1.0), colorBlendFactor: 1.0, duration: 3)])
obstacle.removeAllActions()
obstacle.run(colorFadegreen)
blurNode.removeAllActions()
blurNode.run(colorFadegreen)
What I want to do is have the "glow" that's around the obstacle change colors with the obstacle. That is exactly what happens; however, when I do so my frame rate drops down to 30fps.
So, my question is does anyone know how to improve the performance of this task? Or is there maybe another way I could go about doing this.
One of the ideas I thought of would be to manually blur the "neonLine.png" in photoshop and then add it to the blur node like so
let blurNode = SKSpriteNode(imageNamed: "bluredNeonLine.png"). The only thing is I can never get the blur right it always looks off.
Any help would be very much appreciated. Thanks!
EDIT:
Here are some photos of the glows in my project:
Here is the glow and lines changing color:
Three answers to the performance question with regards glows:
Use a pre-rendered glow, as per your mentioning in the question, done in Photoshop or similar bitmap editor, exported as bitmap with opacity and used as an SKSpriteNode texture, probably with additive blending for best results, and colour caste to taste.
Bake a texture of the SKEffectNode that's creating a desirable glow within SpriteKit by making it into a texture, and then loading it into an SKSpriteNode, as per this example: https://stackoverflow.com/a/40137270/2109038
Rasterise the results from your SKEffectNode and then hope your changes to colour casts don't cause re-rendering. This is shown in a wonderful extension, here: https://stackoverflow.com/a/40362874/2109038
In all cases, you're best off rendering a white glow that fades out as you like, and then applying colour blend changes to it, since SpriteKit has this built in, and it's reasonably performant in the few tests I've done. This is known as colorizing:
You can change and animate both the blend amount: https://developer.apple.com/reference/spritekit/skspritenode/1519780-colorblendfactor
and the color being blended with the texture: https://developer.apple.com/reference/spritekit/skspritenode/1519639-color