Continuously changing color property of SKShapeNode during gameplay (Swift) - swift

I have an SKShapeNode var Circle = SKShapeNode(circleOfRadius: radius) in the background of my spritekit game that uses swift. The circle is for aesthetic purposes so nothing interacts with it. I'd like Circle.strokeColor to continuously change at all times. My current code changes the stroke color property of the SKShapeNode but it does not display the color changes because I'm changing the property after it has been added to the background. The color stays the same throughout the game until the the game ends and the circle is removed from the background then recreated. My code changes the color by adding 1 to var colorTime: CGFloat = 0.0 every time the update function runs, and then relating the RGB values of the Circle's color to cosine functions using that colorTime variable. How can I continuously change (and display) the color of the circle?
override func update(currentTime: CFTimeInterval) {
if last_update_time == 0.0 {
delta = 0
} else {
delta = currentTime - last_update_time
}
last_update_time = currentTime
colorTime += 100
redColor = (cos(colorTime/100)+1)/2
greenColor = (cos(colorTime/200 - 2.09)+1)/2
blueColor = (cos(colorTime/300 - 4.18)+1)/2
circleColor = UIColor(red: redColor, green: greenColor, blue: blueColor, alpha: 1)
Circle.strokeColor = circleColor
}

I didn't realize this code actually works. The only issue ended up being that the game would crash after the game restarts because I would add the SKShapeNode to the background again without ever removing it. I added Circle.removeFromParent() to my restartGame function and now I'm good.

Related

Why does NSColor.controlTextColor change according to background color?

I'm working on a Cocoa application and I find that as long as the font color of NSTextField is set to NSColor.controlTextColor, the font will change according to the background color of NSTextField.
For example, when I set the background color to white, the font becomes black.
But when I set the background color to black, the font turns white.
I want to define an NSColor to achieve the same effect. How to achieve it?
If you want to pass in any color and then determine which text color would be more ideal - black or white - you first need to determine the luminance of that color (in sRGB). We can do that by converting to grayscale, and then checking the contrast with black vs white.
Check out this neat extension that does so:
extension NSColor {
/// Determine the sRGB luminance value by converting to grayscale. Returns a floating point value between 0 (black) and 1 (white).
func luminance() -> CGFloat {
var colors: [CGFloat] = [redComponent, greenComponent, blueComponent].map({ value in
if value <= 0.03928 {
return value / 12.92
} else {
return pow((value + 0.055) / 1.055, 2.4)
}
})
let red = colors[0] * 0.2126
let green = colors[1] * 0.7152
let blue = colors[2] * 0.0722
return red + green + blue
}
func contrast(with color: NSColor) -> CGFloat {
return (self.luminance() + 0.05) / (color.luminance() + 0.05)
}
}
Now we can determine whether we should use black or white as our text by checking the contrast between our background color with black and comparing it to the contrast with white.
// Background color for whatever UI component you want.
let backgroundColor = NSColor(red: 0.5, green: 0.8, blue: 0.2, alpha: 1.0)
// Contrast of that color w/ black.
let blackContrast = backgroundColor.contrast(with: NSColor.black.usingColorSpace(NSColorSpace.sRGB)!)
// Contrast of that color with white.
let whiteContrast = backgroundColor.contrast(with: NSColor.white.usingColorSpace(NSColorSpace.sRGB)!)
// Ideal color of the text, based on which has the greater contrast.
let textColor: NSColor = blackContrast > whiteContrast ? .black : .white
In this case above, the backgroundColor produces a contrast of 10.595052467245562 with black and 0.5045263079640744 with white. So clearly, we should use black as our font color!
The value for black can be corroborated here.
EDIT: The logic for the .controlTextColor is going to be beneath the surface of the API that Apple provides and beyond me. It has to do with the user's preferences, etc. and may operate on views during runtime (i.e. by setting .controlTextColor, you might be flagging a view to check for which textColor is more legible during runtime and applying it).
TL;DR: I don't think you have the ability to achieve the same effect as .controlTextColor with an NSColor subclass.
Here's an example of a subclassed element that uses its backgroundColor to determine the textColor, however, to achieve that same effect. Depending on what backgroundColor you apply to the class, the textColor will be determined by it.
class ContrastTextField: NSTextField {
override var textColor: NSColor? {
set {}
get {
if let background = self.layer?.backgroundColor {
let color = NSColor(cgColor: background)!.usingColorSpace(NSColorSpace.sRGB)!
let blackContrast = color.contrast(with: NSColor.black.usingColorSpace(NSColorSpace.sRGB)!)
let whiteContrast = color.contrast(with: NSColor.white.usingColorSpace(NSColorSpace.sRGB)!)
return blackContrast > whiteContrast ? .black : .white
}
return NSColor.black
}
}
}
Then you can implement with:
let textField = ContrastTextField()
textField.wantsLayer = true
textField.layer?.backgroundColor = NSColor.red.cgColor
textField.stringValue = "test"
Will set your textColor depending on the layer's background.

Black Line in Vertical Moving Swift Background

i have a Problem and need your help.
I have modified a code from this old thread
here
That i get a vertical moving background. The code works nice. But i have between every moving background image a tiny black line.
looks like something overlaps. But why?
It would be nice if anyone can help me to fix this error.
Thanks alot
Here is my code:
var bg = SKSpriteNode()
var bg2 = SKSpriteNode()
var bg3 = SKSpriteNode()
var parallax = SKAction()
override func didMove(to view: SKView) {
bg = SKSpriteNode(imageNamed: "back1")
bg.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
bg.zPosition = 1
bg.size = self.size
bg2 = SKSpriteNode(imageNamed: "back2")
bg2.position = CGPoint(x: self.size.width/2, y: self.size.height/2+self.size.height)
bg2.zPosition = 1
bg2.size = self.size
bg3 = SKSpriteNode(imageNamed: "back3")
bg3.position = CGPoint(x: self.size.width/2, y:self.size.height/2+self.size.height+self.size.height)
bg3.zPosition = 1
bg3.size = self.size
self.addChild(bg)
self.addChild(bg2)
self.addChild(bg3)
parallax = SKAction.repeatForever(SKAction.move(by: CGVector(dx: 0, dy: -self.frame.size.height), duration: 4))
bg.run(parallax)
bg2.run(parallax)
bg3.run(parallax)}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bg.position.y <= -self.frame.size.height {
bg.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg2.position.y <= -self.frame.size.height {
bg2.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg3.position.y <= -self.frame.size.height {
bg3.position.y = self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
}
You are having floating point rounding issues. The concept of 1/2 a pixel does not exist, so when you get to a position like 12.5, the system needs to either make it 12, or 13.
Now, since decimal does not convert well to fractal binary you are going to end up with numbers like 12.499929932092434234234324 and 12.50000000342423423424, but as far as you know, it is still 12.5.
To fix this, you need to force your position to always round in the same direction, either up or down.
You are probably going to want to round down since most grids work in a 0 based indexing system.
To fix your code, we need to do:
override func update(_ currentTime: TimeInterval) {
bg.position.x.round(.down)
bg.position.y.round(.down)
bg2.position.x.round(.down)
bg2.position.y.round(.down)
bg3.position.x.round(.down)
bg3.position.y.round(.down)
// Called before each frame is rendered
if bg.position.y <= -self.frame.size.height {
bg.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg2.position.y <= -self.frame.size.height {
bg2.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
if bg3.position.y <= -self.frame.size.height {
bg3.position.y += self.frame.size.height*2
//this ensures that your backgrounds line up perfectly
}
}
Now of course, as you get better with development, you are going to want to move these types of things outside of your update function. Eventually, you will want to perform these checks when position changes, this way if nothing is moving, you are not needlessly executing these lines of code to fix a position that does not need fixing.
Edit:
Noticed another problem, you need to do += the height, not = height, because if you are at -1, that will also cause a gap, since you are not accounting for the line of height - 1 (Basically, your first image ends at height - 2, and your new image starts at height, making a gap at height - 1)

SpriteKit: Coloring the Background

i still try to learn Swift and SpriteKit and i have a new question.
So i am following this Tutorial/Video: Noise Field
My project now looks like this: My Project
I have 100 Particle Objects moving around and i want to blend some color of the particles to the white background. Inside the tutorial it is quiet easy. You just create the Background once, and inside the draw function (in SpriteKit this would be the update() function) you give your objects an alpha value like 0.1.
SpriteKit works quiet different. If i change the alpha value under draw my Particles are now almost hidden, but the color is not being "drawn" on the background.
I know this is because SpriteKit works different then the p5 library for javascript. But i wonder how i could get the same effect inside SpriteKit..
So inside the update function i have 2 loops, one for columns and one for rows. I have x columns and y rows - and for each "cell" i create a random CGVector. Now my Particles are moving around based on the CGVector of the cell which is the nearest to the Particles position.
My Particle Class looks like this:
class Particle: SKShapeNode{
var pos = CGPoint(x: 0, y: 0)
var vel = CGVector(dx: 0, dy: 0)
var acc = CGVector(dx: 0, dy: 0)
var radius:CGFloat
var maxSpeed:CGFloat
var color: SKColor
And i have a function which looks like this to show the Particles:
func show(){
self.position = self.pos
let rect = CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: self.radius*2, height: self.radius*2))
let bezierP = UIBezierPath(ovalIn: rect)
self.path = bezierP.cgPath
self.fillColor = self.color
self.strokeColor = .clear
}
And in the update function i have this loop:
for particle in partikelArr{
particle.updatePos()
particle.show()
}
How could i now "colorize" the white background or "draw" my particle on the background based on the particles position, color, shape and size?
Thanks and best regards
EDIT:
So i know create for each particle a new SKShapeNode inside the update function, so this works and it looks like the Particles have colorized the background:
This was created like this inside the update function:
for particle in partikelArr{
let newP = SKShapeNode(path: particle.path!)
newP.position = particle.pos
newP.fillColor = particle.farbe
newP.strokeColor = .clear
newP.zPosition = 1000
newP.alpha = 0.1
self.addChild(newP)
}
But i do not want to create SKShapeNodes for each Particle (on the screenshot i have used 5 Particles, after some seconds i already have over 800 nodes on the scene). I would like to let my 100 Particles really "draw" their Shape and Color on the white Background node.

Alternative way to fade the background color into another color

in my touches began method i put this simple line of code which fades the background color to red.
runAction(SKAction.colorizeWithColor(SKColor.redColor(), colorBlendFactor: 1.0, duration: 1.0))
everything is working fine, but the problem is that the code doesn't do anything using ios 7. I was wondering if there is an alternative way to make the background fade into a different color, or if there's an ios 7 version of this code.
There are multiple ways to transition from one color to another. One of the most straightforward approaches is to linearly interpolate between the two colors by combining a progressively larger fraction of the starting color's RGB components with a progressively smaller fraction of the ending color's RBG components over time:
red = starting_red * (1.0 - fraction) + ending_red * fraction
green = starting_green * (1.0 - fraction) + ending_green* fraction
blue = starting_blue * (1.0 - fraction) + ending_blue * fraction
where fraction starts at 0 and ends at 1 in increments of
fraction += delta_time * step_size
One way to implement this approach is to add code to the didMoveToView method of GameScene. However, if your game contains multiple scenes, a better strategy is to extend SKAction to add a class method that creates a custom action, so it can be used by all scenes.
First, define a structure to store the starting and ending RGB color components. Add this outside of the definition of GameScene.
struct ColorComponents {
var red:CGFloat
var green:CGFloat
var blue:CGFloat
init(color:SKColor) {
self.init()
var alpha:CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
}
init() {
red = 0
green = 0
blue = 0
}
}
Then, extend SKAction by adding the following method that changes the background color to another color. Note that extensions must be defined outside of a class.
extension SKAction {
static func changeColor(startColor:SKColor, endColor:SKColor, duration:NSTimeInterval) -> SKAction {
// Extract and store starting and ending colors' RGB components
let start = ColorComponents(color: startColor)
let end = ColorComponents(color: endColor)
// Compute the step size
let stepSize = CGFloat(1/duration)
// Define a custom class to gradually change a scene's background color
let change = SKAction.customActionWithDuration(duration) {
node, time in
let fraction = time * stepSize
let red = start.red * (1.0 - fraction) + end.red * fraction
let green = start.green * (1.0 - fraction) + end.green * fraction
let blue = start.blue * (1.0 - fraction) + end.blue * fraction
if let scene = node as? SKScene {
scene.backgroundColor = SKColor(red: red, green: green, blue: blue, alpha: 1.0)
}
}
return change
}
}
Lastly, create and run an SKAction
runAction(SKAction.changeColor(backgroundColor, endColor: SKColor.blueColor(), duration: 5))
Add this to didMoveToView in your SKScene subclasses, such as GameScene.

SpriteKit SKColor fade in / fade out

Below is a computed property and I've assign this property to backgroundcolor of some SKScene. Now, when the hour changed which means the backgroundColor changed, how do I do could make these color changed with fade-in or fade-out effect? now the background color will changed immediately but that's too straight forward.May be it should be use SKAction? but I cannot find a way.
static var backgroundColor:SKColor {
if GameViewController.hour > 6 && GameViewController.hour < 18 {
return SKColor.whiteColor()
}
if (GameViewController.hour > 16 && GameViewController.hour < 20) || GameViewController.hour > 5 && GameViewController.hour < 8 {
return SKColor.grayColor()
} else { return SKColor.blackColor()}
}
and here I assign backgroundColor to a refer.
backgroundColor = GameViewController.backgroundColor
Yes, use an SKAction to change the color. You need colorizeWithColor:blendFactor:duration:. Specify the color you need, the blend factor (use 1.0 to completely change the color) and duration for how long you need it. It also might be easier to make an SKSpriteNode that you would use for your background. So, for example:
var background: SKSpriteNode = SKSpriteNode(color: color, size: self.frame.size)
var colorize: SKAction = SKAction.colorizeWithColor(color, colorBlendFactor: 1.0, duration: someDurationInSeconds)
Hope this helps