Alternative way to fade the background color into another color - sprite-kit

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.

Related

Render SKLabel ontop of SKSprite

I'm trying to create a UI using SpriteKit on top of a MetalView. I'm using SKRenderer to render a SKScene. With the orientation I'm using, I would expect the SKLabel to use the SKSprite's background. Instead, it's rendering transparently and showing the MetalView.
I've also played with the zPosition attribute.
cLabel = SKLabelNode(fontNamed: "ArialMT")
cLabel.text = "Test Text"
cLabel.fontSize = 65
cLabel.fontColor = SKColor.white
cLabel.position = CGPoint(x: 1125 / 2, y: (2436-2436 / 10))
scene.addChild(cLabel)
let sprite = SKSpriteNode(imageNamed: "banner.png")
sprite.position = CGPoint(x: 1125 / 2, y: (2436-2436 / 12))
sprite.name = "sprite"
scene.addChild(sprite)
//scene.addChild(sprite)
//sprite.addChild(cLabel)
The blending mode on your MTLRenderPipelineDescriptor is not configured properly.
You should use MTLRenderPipelineColorAttachmentDescriptor -
a color render target that specifies the color configuration and color operations for a render pipeline, to configure your blending mode.
The answer to this question solved my issue. I needed to add a shader to my node.
Is there really no way to style SKLabelNode?
This is the test shader I created.
void main()
{
vec4 color = SKDefaultShading(); // the current label color
//0.2 was determined to be a good roll off point
if(color.a < 0.2){
//#TOOD: Replace this with a uniform representing the background color
color.r = 0.498;
color.g = 0.278;
color.b = 0.866;
color.a = 1;
}
gl_FragColor = color;
}

Huge memory footprint using setStrokeColor in a UIViewRepresentable view

I have spent about one day trying to locate why my app consumes a huge amount of memory. I eventually located thw cause which apparently is not an issue of my code.
Using context.setStrokeColor(...) causes the final memory occupation grows from ~290Mb to 1Gb.
This the code reduced to proof the issue.
struct UIKitGraphView: UIViewRepresentable {
var viewModel: MainViewModel
var scale: Float
init(viewModel: MainViewModel, scale: Float, channelShift: Int) {
self.viewModel = viewModel
self.scale = scale
}
func makeUIView(context: Context) -> MyGraphView {
let view = MyGraphView()
view.viewModel = viewModel
view.backgroundColor = UIColor.white
return view
}
func updateUIView(_ graphView: MyGraphView, context: Context) {
graphView.scale = CGFloat(scale)
graphView.setNeedsDisplay()
}
class MyGraphView: UIView {
var viewModel: MainViewModel?
var scale: CGFloat = 1
override func draw(_ rect: CGRect) {
if let context = UIGraphicsGetCurrentContext() {
let width = bitWidth * scale
if viewModel?.values.count == 0 {
return
}
for channel in 0 ..< numberOfChannels - channelShift {
let channelPosition: CGFloat = spaceBetweenChannels * CGFloat(channel)
// In the original code the color values changes for each channel
let myColor = CGColor(red: 0.0, green: 0.2, blue: 0.3, alpha: 1.0)
// This call causes the memory allocation grows from ~290Mb to 1Gb
context.setStrokeColor(myColor)
let value0 = ((viewModel!.values[0] >> (channel + channelShift)) & 0x01)
let y = rect.size.height - bottomSpace - channelPosition - bitHeight * (value0 == .zero ? 0 : 1)
context.move(to: CGPoint(x: leftSpace, y: y))
context.addLine(to: CGPoint(x: leftSpace + width, y: y))
....
context.strokePath()
}
}
}
}
Running the code without the call to setStrokeColor the final memory allocation is about 290Mb, instead calling setStrokeColor the final memory allocations is about 1Gb.
What did I do wrong? Any idea how to fix this issue?
EDIT
While trying to find a workaround, I discovered that:
context.setStrokeColor(UIColor.black.cgColor) doesn't increase the memory
context.setStrokeColor(CGColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)) increases the memory
context.setStrokeColor(UIColor.[any color different from black].cgColor) increases the memory
EDIT 2
Previous tests was performed on iPhone 12 (iOS 14.2.1)
iPhone XR (iOS 14.1) has similar behavior getting to about 667Mb
iPhone XR (iOS 14.2) gets to about 667Mb
iPad Pro (10.5'') (iOS 14.0.1) starts at 126Mb and gets up to 455Mb
Then, apparently the problem is related to the device itself instead of the iOS version.
What you are seeing is dynamic graphics contexts in action.
The graphics context starts out as grayscale, as an efficiency measure, to save space. When you set the stroke color to an actual color, the graphics context has to be rewritten to accommodate that color, so it jumps to about three or four times its previous size, because now we have to store more bits for each pixel to express the color.
Now multiply that by the dimensions of the graphics context (which grows exponentially in the sense that there are two dimensions that need to be multiplied together), and multiply it again by the screen resolution of the particular device we're using, and you can imagine that this change in size would be quite noticeable.

Creating a progress indicator with a rounded rectangle

I am attempting to create a rounded-rectangle progress indicator in my app. I have previously implemented a circular indicator, but not like this shape. I would like it to look something like this (start point is at the top):
But I get this with 0 as the .strokeStart property of the layer:
My current code place in viewDidLoad():
let queueShapeLayer = CAShapeLayer()
let queuePath = UIBezierPath(roundedRect: addToQueue.frame, cornerRadius: addToQueue.layer.cornerRadius)
queueShapeLayer.path = queuePath.cgPath
queueShapeLayer.lineWidth = 5
queueShapeLayer.strokeColor = UIColor.white.cgColor
queueShapeLayer.fillColor = UIColor.clear.cgColor
queueShapeLayer.strokeStart = 0
queueShapeLayer.strokeEnd = 0.5
view.layer.addSublayer(queueShapeLayer)
addToQueue is the button which says 'Upvote'.
Unlike creating a circular progress indicator, I cannot set the start and end angle in the initialisation of a Bezier path.
How do I make the progress start from the top middle as seen in the first image?
Edit - added a picture without corner radius on:
It seems that the corner radius is creating the issue.
If you have any questions, please ask!
I found a solution so the loading indicator works for round corners:
let queueShapeLayer = CAShapeLayer()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Queue timer
let radius = addToQueue.layer.cornerRadius
let diameter = radius * 2
let totalLength = (addToQueue.frame.width - diameter) * 2 + (CGFloat.pi * diameter)
let queuePath = UIBezierPath(roundedRect: addToQueue.frame, cornerRadius: radius)
queueShapeLayer.path = queuePath.cgPath
queueShapeLayer.lineWidth = 5
queueShapeLayer.strokeColor = UIColor.white.cgColor
queueShapeLayer.fillColor = UIColor.clear.cgColor
queueShapeLayer.strokeStart = 0.25 - CGFloat.pi * diameter / 3 / totalLength // Change the '0.25' to 0.5, 0.75 etc. wherever you want the bar to start
queueShapeLayer.strokeEnd = queueShapeLayer.strokeStart + 0.5 // Change this to the value you want it to go to (in this case 0.5 or 50% loaded)
view.layer.addSublayer(queueShapeLayer)
}
After I had did this though, I was having problems that I couldn't animate the whole way round. To get around this, I created a second animation (setting strokeStart to 0) and then I placed completion blocks so I could trigger the animations at the correct time.
Tip:
Add animation.fillMode = CAMediaTimingFillMode.forwards & animation.isRemovedOnCompletion = false when using a CABasicAnimation for the animation to wait until you remove it.
I hope this formula helps anyone in the future!
If you need help, you can always message me and I am willing to help. :)

Swift : Animate SKEmitterNode

I am trying to create an explosion effect using SKEmitterNode, I followed some of this tutorial (http://www.waveworks.de/kill-your-characters-animating-explosions-with-skanimations-in-sprite-kit/), but it didn't seem to work. Here is what i have:
var superDabParticle = SKEmitterNode(fileNamed: "superDabParticle.sks")
func setUpEmiterNode(){
superDabParticle?.position = CGPoint(x: self.size.width * 0.5, y: self.size.height * 0.5)
let superDabParticleEffectNode = SKEffectNode()
superDabParticle?.zPosition = 1
superDabParticle?.setScale(0)
superDabParticleEffectNode.addChild(superDabParticle!)
self.addChild(superDabParticleEffectNode)
}
func animateEmiterNode(){
let birth = SKAction.runBlock { self.superDabParticle?.particleBirthRate = 0 }
superDabParticle!.runAction(SKAction.sequence([SKAction.waitForDuration(0.1), SKAction.scaleBy(1.5, duration: 0.1), birth]))
}
So i need to call setUpEmiterNode() when the app first launches up, then when the user taps a button, call animateEmiterNode to have the particles fly from the bottom of the screen up to about 75% of the screen, then fall back down; giving a sort of explosion under water effect. Im using the SPARK emitter effect. If you know my issue or have any other ways that are better to do this, please assist!!

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.