SpriteKit SKColor fade in / fade out - swift

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

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)

SpriteNode disappears when returning to GameScene

I have a background sprite that I display in my GameScene. When I go to another scene (BuildViewController) and come back by background is gone. I'm sure it is a simple fix and it isn't anymore complicated than what I have said above. Heres some code :)
while length <= 6 {
while wide <= 6 {
let imageViewBackground = UIImageView(frame: CGRect(x:0 + (wide*100), y:0 + (length*200), width: 100, height: 200))
imageViewBackground.image = UIImage(named: "grass.png")
self.view?.addSubview(imageViewBackground)
self.view?.sendSubview(toBack: imageViewBackground)
wide = wide + 1
}
wide = 0
length = length + 1
}
You may be finding that by adding your imageView to the view controller along with the SKScene that things are drawing all oddly (it could also be that your imageView is getting dealloc'd but I can't say based on your code).
I'm assuming you're using the basic Xcode template to set up your views and stuff; so to make the grass, try something like this in your GameScene class instead of the code above (which I assume is in your GameViewController class). You can put the code in didMove(to view: SKView) or you can add a sceneDidLoad() func. Make a SKSpriteNode for the grass and tile it like you are doing already. The two methods I mention here are the ones most closely related to the viewDidLoad and viewWillAppear methods in UIViewController land.
while length <= 6 {
while wide <= 6 {
let grassSprite = SKSpriteNode(imageNamed: "grass.png")
grassSprite.position = CGPoint(x: (wide * 100), y: (length * 200))
self.addChild(grassSprite
wide += 1
}
wide = 0
length += 1
}
All of this being said, it appears you are trying to make a grass background for something, in which case, you should probably look into SKTileGroup which has some nice conveniences.

Creating endless background without image

I'm creating a simple game with Swift and SpriteKit.
I want to add an endless background (vertically), I only found answers for background with images but I need to do it without image, only background color.
I thought about checking if the player is in the frame.maxY, if so, to move it back to the starting point, but I was wondering if there is a better idea.
//Does not matter which ring we chose as all ring's 'y' position is the same.
func moveBackgroundUp(){
if ((mPlayer.position.y >= self.frame.maxY) || (mRingOne.position.y >= self.frame.maxY)) {
mPlayer.position.y = 150 //mPlayers original starting point.
for ring in mRings {
ring.position.y = 350
}
}
}
Thanks in advance!
Don't just move a background up the screen, that' really isn't the way to go about it. What you should do is detect the position of the camera (assuming it moves with the player), and when it's position is about to occupy space outside of the occupied space of your current background sprite, add a new background sprite to the scene where the last one left off. Here is an example of how to do that with just a red sprite:
First add a property to the scene to track level position:
// To track the y-position of the level
var levelPositionY: CGFloat = 0.0
Now create a method to update your background:
func updateBackground() {
let cameraPos = camera!.position
if cameraPos.y > levelPositionY - (size.height * 0.55) {
createBackground()
}
}
func createBackground() {
// Create a new sprite the size of your scene
let backgroundSprite = SKSpriteNode(color: .red, size: size)
backgroundSprite.anchorPoint = CGPoint(x: 0.5, y: 0)
backgroundSprite.position = CGPoint(x: 0, y: levelPositionY)
// Replace backgroundNode with the name of your backgroundNode to add the sprite to
backgroundNode.addChild(backgroundSprite)
levelPositionY += backgroundSprite.size.height
}
Now you want to call updateBackground inside your overridden update(_:) method:
override func update(_ currentTime: TimeInterval) {
// All your other update code
updateBackground()
}
Also, make sure to create an initial background when you first create the scene:
override func didMove(to view: SKView) {
createBackground()
}
NOTE! - It's important to set the custom anchor point for the background sprite for this code to work properly. An anchor of (0.5, 0) allows the background sprite to be anchored in the middle of the scene on the x-axis, but at the bottom of the scene on the y-axis. This allows you to easily stack one on top of the other.
EDIT - I forgot to mention that it's also a good idea to conserve resources and remove any background nodes that are outside the viewable area and won't be coming back in (i.e. a continuous scrolling game where you can't go backwards). You could do that by updating your updateBackground method above:
func updateBackground() {
let cameraPos = camera!.position
if cameraPos.y > levelPositionY - (size.height * 0.55) {
createBackground()
}
// Make sure to change 'backgroundNode' to whatever the name of your backgroundNode is.
for bgChild in backgroundNode.children {
// This will convert the node's coordinates to scene's coordinates. See below for this function
let nodePos = fgNode.convert(fgChild.position, to: self)
if !isNodeVisible(bgChild, positionY: nodePos.y) {
// Remove from it's parent node
bgChild.removeFromParent()
}
}
}
func isNodeVisible(_ node: SKNode, positionY: CGFloat) -> Bool {
if !camera!.contains(node) {
if positionY < camera!.position.y - size.height * 2.0 {
return false
}
}
return true
}
So above you just loop through all the children inside your background node and detect if they are out of view, and if so remove them from the parent. Make sure to change my generic backgroundNode to whatever the name of your background node is.

Continuously changing color property of SKShapeNode during gameplay (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.