Strange black border when using SKCropNode - ios

I'm working on a game with SpriteKit and I have a square SKSpriteNode whose corners I want to make round. I can't use an SKShapeNode because I want to eventually add a gradient to it. So I decided to use as SKCropNode to crop it, but when I do so, a weird black border like this appears:
If I don't try to crop it, it looks like this:
This is my code I am using to create and crop it:
spriteNode = SKSpriteNode(color: color.skColor, size: CGSize(width: TileSize, height: TileSize))
let borderRadius: CGFloat = 2
let roundPath = CGPathCreateWithRoundedRect(CGRect(x: -TileSize / 2, y: -TileSize / 2, width: TileSize, height: TileSize), borderRadius, borderRadius, nil)
let mask = SKShapeNode()
mask.path = roundPath
mask.fillColor = SKColor.whiteColor()
mask.strokeColor = SKColor.whiteColor()
let cropNode = SKCropNode()
cropNode.maskNode = mask
let shadow = SKSpriteNode(color: SKColor.blackColor(), size: CGSize(width: TileSize, height: TileSize))
shadow.colorBlendFactor = 1
shadow.alpha = 0.25
let radius: CGFloat = 3.5
shadow.position = CGPoint(x: radius / 2, y: radius / 2)
let effectNode = SKEffectNode()
effectNode.filter = CIFilter(name: "CIGaussianBlur", withInputParameters: [kCIInputRadiusKey: radius])
effectNode.zPosition = spriteNode!.zPosition - 1
effectNode.addChild(shadow)
let bluredTile = SKShapeNode(path: roundPath)
bluredTile.addChild(effectNode)
cropNode.addChild(spriteNode!)
addChild(bluredTile)
addChild(cropNode)
Why is this happening, and how can I stop it?

Related

SKSpriteNode shadow rendering incorrectly?

I'm using Xcode 11.4.1 and Swift 5.2.2, and I am trying cast a shadow using an SKLightNode onto a circular SKSpriteNode.
However, the shadow is cast over the rectangular frame of the SpriteNode rather than the circle image png that I am using.
This is the desired effect
This is what happens
In the first image, I am using a 611x611px circle png while in the second I am using a 612x612 png. I have found that this "corner shadow" happens only when using specific image dimensions to create the SpriteNode.
Specifically, through my testing, square images with size <688px and >601px display no corner shadow, except sizes exactly 612 and 608. I am testing this in a playground but the same problem occurs in a full xcodeproj.
What have I done wrong here? I doubt my code is the issue but here it is:
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let background = SKSpriteNode(color: UIColor.blue, size: frame.size)
background.zPosition = 0
background.position = CGPoint(x: frame.midX, y: frame.midY)
background.lightingBitMask = 1
addChild(background)
let light = SKLightNode()
light.zPosition = 100
light.categoryBitMask = 1
light.falloff = 0.5
light.lightColor = UIColor.white
light.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(light)
let sprite = SKSpriteNode(imageNamed: "612.png")
sprite.color = UIColor.yellow
sprite.colorBlendFactor = 1
sprite.zPosition = 3
sprite.position = CGPoint(x: frame.midX, y: frame.midY + 100)
sprite.size = CGSize(width: 100, height: 100)
sprite.physicsBody = SKPhysicsBody(circleOfRadius: 50)
sprite.physicsBody?.categoryBitMask = 2
sprite.shadowCastBitMask = 1
sprite.lightingBitMask = 1
sprite.physicsBody?.isDynamic = false
addChild(sprite)
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
let scene = GameScene()
scene.scaleMode = .aspectFill
scene.size = sceneView.frame.size
sceneView.presentScene(scene)
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView

iOS Swift: create a mask from a UILabel

Hi I'm trying to use a label as a mask for a particle emitter layer.
My particles emitter is already set up, but I'm having a problem to get a mask from a label, this is my code that doesn't work so well.
func emitter() {
// define emitter layer as centered w 80% of smallest dimension
let image = emitterImage
let origin = CGPoint(x: view.bounds.midX - view.bounds.width / 2, y: view.bounds.midY - view.bounds.height / 2)
let center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
let size = CGSize(width: view.bounds.width, height: view.bounds.height)
let rect = CGRect(origin: origin, size: size)
let emitterLayer = CAEmitterLayer()
emitterLayer.emitterShape = CAEmitterLayerEmitterShape.rectangle
emitterLayer.emitterSize = rect.size
emitterLayer.emitterPosition = center
// define cells
let cell = CAEmitterCell()
cell.birthRate = Float(size.width * size.height / 10)
cell.lifetime = 1
cell.velocity = 10
cell.scale = 0.1
cell.scaleSpeed = -0.1
cell.emissionRange = .pi * 2
cell.contents = image.cgImage
emitterLayer.emitterCells = [cell]
// add the layer
view.layer.addSublayer(emitterLayer)
// mask
let font = UIFont(name: "HelveticaNeue", size: 64)!
var unichars = [UniChar]("Text".utf16)
var glyphs = [CGGlyph](repeating: 0, count: unichars.count)
let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)
if gotGlyphs {
let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)!
let path = UIBezierPath(cgPath: cgpath)
let mask = CAShapeLayer()
mask.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.white.cgColor
mask.lineWidth = 10.0
mask.path = path.cgPath
emitterLayer.mask = mask
}
}
Problem 1: I got just the first letter ("T") how could I attach all the characters path in one?
if gotGlyphs {
var paths: [UIBezierPath] = []
for glyph in glyphs {
let cgpath = CTFontCreatePathForGlyph(font, glyph, nil)!
let path = UIBezierPath(cgPath: cgpath)
paths.append(path)
}
In this way I got an array of all chars path, but how can I attach them??
Problem 2: The path is rotated by 180 degrees (why???)
Solved Using CATextLayer to create the mask from a label.
let textLayer = CATextLayer()
textLayer.frame = CGRect(x: 0, y: view.bounds.height / 6 * 1, width: yourLabel.frame.width, height: yourLabel.frame.height)
textLayer.rasterizationScale = UIScreen.main.scale
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = CATextLayerAlignmentMode.center
textLayer.fontSize = 30
textLayer.font = yourLabel.font
textLayer.isWrapped = true
textLayer.truncationMode = CATextLayerTruncationMode.end
textLayer.string = yourLabel.text
emitterLayer.mask = textLayer

Use an SKShapeNode for the mask of an SKCropNode (Swift)

How can I use an SKShapeNode for the mask of an SKCropNode? My code currently creates an SKShapeNode circle and then sets the crop node mask to it
shapeNode = SKShapeNode(circleOfRadius: 50)
shapeNode.fillColor = UIColor.red
shapeNode.lineWidth = 1
shapeNode.position = CGPoint(x: 0, y: 0)
shapeNode.zPosition = 4
cropNode = SKCropNode()
cropNode.maskNode = shapeNode
cropNode.addChild(Node)
But when I add the Node as a child of the cropNode it doesnt draw anything but a red circle (exactly as i set it to do)
Is there any way to use an SKShapeNode as the mask of an SKCropNode?
-Update-
I figured out how to make an SKSpriteNode drawn from the SKShapeNode
shapeNode = SKShapeNode(circleOfRadius: 60)
shapeNode.fillColor = UIColor.red
let shapeTexture = view.texture(from: shapeNode)
textureNode = SKSpriteNode(texture: shapeTexture)
textureNode.position = CGPoint(x: 0, y: 0)
textureNode.zPosition = 3
cropNode.maskNode = textureNode
cropNode.addChild(tileMap2)
self.addChild(cropNode)
But now the SKCropNode isnt working when applied to the SKSpriteNode how do I fix this?

CAKeyframeAnimation rotation don't draw real shadow

I want to rotate an UIImageView with CAKeyframeAnimation. The problem is that the shadow rotates with the object, making a non-real effect... The shadow must remain under the image, not rotating around it
This is my code. Very simple:
let imagen:UIImageView = UIImageView(image:UIImage(named: "material6.png"))
self.view.addSubview(imagen)
imagen.center = CGPoint(x: 100, y: 100)
imagen.layer.shadowOpacity = 0.75
imagen.layer.shadowOffset = CGSize(width: 0, height: 15)
//Animación del ángulo
let animacionAngulo = CAKeyframeAnimation(keyPath: "transform.rotation.z")
var valoresAngulo = [NSNumber]()
let degrees:Float = 360
let radians = degrees * Float.pi / Float(180.0)
valoresAngulo.append(NSNumber(value: 0))
valoresAngulo.append(NSNumber(value: radians))
animacionAngulo.values = valoresAngulo
//Permite añadir varias animaciones y gestionarlas a la vez
animacionAngulo.repeatCount = Float.infinity
animacionAngulo.duration = 2.0
imagen.layer.add(animacionAngulo, forKey: nil)
Any solution?
If the shadow is rotating along with the image. You can solve it by adding an UIView with same dimensions of your UIImageView below your UIImageView. If you add shadow to your UIView instead of UIImageView, you can achieve the desired effect in this case.
let imagen:UIImageView = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
imagen.center = CGPoint(x: 100, y: 100)
imagen.backgroundColor = UIColor.red
let belowView: UIView = UIView(frame: imagen.frame)
belowView.backgroundColor = UIColor.white
belowView.layer.shadowOpacity = 0.75
belowView.layer.shadowOffset = CGSize(width: 0, height: 15)
self.view.addSubview(belowView)
self.view.addSubview(imagen)
//Animación del ángulo
let animacionAngulo = CAKeyframeAnimation(keyPath: "transform.rotation.z")
var valoresAngulo = [NSNumber]()
let degrees:Float = 360
let radians = degrees * Float.pi / Float(180.0)
valoresAngulo.append(NSNumber(value: 0))
valoresAngulo.append(NSNumber(value: radians))
animacionAngulo.values = valoresAngulo
//Permite añadir varias animaciones y gestionarlas a la vez
animacionAngulo.repeatCount = Float.infinity
animacionAngulo.duration = 2.0
imagen.layer.add(animacionAngulo, forKey: nil)

CAKeyframeAnimation counter-clockwise

I want to animate along a bezierPath counter-clockwise.
At the moment the code below only allows me to do it clockwise.
let arrowHead = UIImageView()
arrowHead.image = UIImage(named: "arrowHead.png")
arrowHead.frame = CGRect(x: 55, y: 300, width: 12, height: 12)
let ovalPath = UIBezierPath(ovalInRect: CGRectMake(50, 240, 320, 240))
let ovalShapeLayer = CAShapeLayer()
ovalShapeLayer.fillColor = UIColor.clearColor().CGColor
ovalShapeLayer.strokeColor = UIColor.lightGrayColor().CGColor
ovalShapeLayer.lineWidth = 1.5
ovalShapeLayer.path = ovalPath.CGPath
self.view.layer.addSublayer(ovalShapeLayer)
let myKeyFrameAnimation = CAKeyframeAnimation(keyPath: "position")
myKeyFrameAnimation.path = ovalPath.CGPath
myKeyFrameAnimation.rotationMode = kCAAnimationRotateAuto
myKeyFrameAnimation.repeatCount = Float.infinity
myKeyFrameAnimation.duration = 6.0
// add the animation
arrowHead.layer.addAnimation(myKeyFrameAnimation, forKey: "animate position along path")
self.view.addSubview(arrowHead)
any ideas?
Ok got it.
myKeyFrameAnimation.speed = -1
Was very simple...
At alternative solution is to use bezierPathWithArcCenter... as documented here which allows you to specify the direction of the circle. Using -M_PI_2 as the start angle and 3 * M_PI_2 as the end angle will give a circle beginning and ending at the top.

Resources