Render SKLabel ontop of SKSprite - swift

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;
}

Related

Blur face in face detection in vision kit

I'm using Apple tutorial about face detection in vision kit in a live camera feed, not an image.
https://developer.apple.com/documentation/vision/tracking_the_user_s_face_in_real_time
It detects the face and adds some lines using CAShapeLayer to draw lines between different parts of the face.
fileprivate func setupVisionDrawingLayers() {
let captureDeviceResolution = self.captureDeviceResolution
let captureDeviceBounds = CGRect(x: 0,
y: 0,
width: captureDeviceResolution.width,
height: captureDeviceResolution.height)
let captureDeviceBoundsCenterPoint = CGPoint(x: captureDeviceBounds.midX,
y: captureDeviceBounds.midY)
let normalizedCenterPoint = CGPoint(x: 0.5, y: 0.5)
guard let rootLayer = self.rootLayer else {
self.presentErrorAlert(message: "view was not property initialized")
return
}
let overlayLayer = CALayer()
overlayLayer.name = "DetectionOverlay"
overlayLayer.masksToBounds = true
overlayLayer.anchorPoint = normalizedCenterPoint
overlayLayer.bounds = captureDeviceBounds
overlayLayer.position = CGPoint(x: rootLayer.bounds.midX, y: rootLayer.bounds.midY)
let faceRectangleShapeLayer = CAShapeLayer()
faceRectangleShapeLayer.name = "RectangleOutlineLayer"
faceRectangleShapeLayer.bounds = captureDeviceBounds
faceRectangleShapeLayer.anchorPoint = normalizedCenterPoint
faceRectangleShapeLayer.position = captureDeviceBoundsCenterPoint
faceRectangleShapeLayer.fillColor = nil
faceRectangleShapeLayer.strokeColor = UIColor.green.withAlphaComponent(0.7).cgColor
faceRectangleShapeLayer.lineWidth = 5
faceRectangleShapeLayer.shadowOpacity = 0.7
faceRectangleShapeLayer.shadowRadius = 5
let faceLandmarksShapeLayer = CAShapeLayer()
faceLandmarksShapeLayer.name = "FaceLandmarksLayer"
faceLandmarksShapeLayer.bounds = captureDeviceBounds
faceLandmarksShapeLayer.anchorPoint = normalizedCenterPoint
faceLandmarksShapeLayer.position = captureDeviceBoundsCenterPoint
faceLandmarksShapeLayer.fillColor = nil
faceLandmarksShapeLayer.strokeColor = UIColor.yellow.withAlphaComponent(0.7).cgColor
faceLandmarksShapeLayer.lineWidth = 3
faceLandmarksShapeLayer.shadowOpacity = 0.7
faceLandmarksShapeLayer.shadowRadius = 5
overlayLayer.addSublayer(faceRectangleShapeLayer)
faceRectangleShapeLayer.addSublayer(faceLandmarksShapeLayer)
rootLayer.addSublayer(overlayLayer)
self.detectionOverlayLayer = overlayLayer
self.detectedFaceRectangleShapeLayer = faceRectangleShapeLayer
self.detectedFaceLandmarksShapeLayer = faceLandmarksShapeLayer
self.updateLayerGeometry()
}
How can I fill inside the lines (different part of face) with a blurry view? I need to blur the face.
You could try placing a UIVisualEffectView on top of your video feed, and then adding a masking CAShapeLayer to that UIVisualEffectView. I don't know if that would work or not.
The docs on UIVisualEffectView say:
When using the UIVisualEffectView class, avoid alpha values that are less than 1. Creating views that are partially transparent causes the system to combine the view and all the associated subviews during an offscreen render pass. UIVisualEffectView objects need to be combined as part of the content they are layered on top of in order to look correct. Setting the alpha to less than 1 on the visual effect view or any of its superviews causes many effects to look incorrect or not show up at all.
I don't know if using a mask layer on a visual effect view would cause the same rendering problems or not. You'd have to try it. (And be sure to try it on a range of different hardware, since the rendering performance varies quite a bit between different versions of Apple's chipsets.)
You could also try using a shape layer filled with visual hash or a "pixellated" pattern instead of blurring. That would be faster and probably render more reliably.
Note that face detection tends to be a little jumpy. It might drop out for a few frames, or lag on quick pans or change of scene. If you're trying to hide people's faces in a live feed for privacy, it might not be reliable. It would only take a few un-blurred frames for somebody's identity to be revealed.

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.

How to scale to be at the same distance in all devices?

I'm having problems opening my game in different divices , in the 6s iphone plus looks much bigger the circle the center, also the small circle that is on the line changes position , I would like that the center circle was the same size and that the small circle always this half on the line.
import SpriteKit
struct Circle {
var position:CGPoint
var radius:CGFloat
}
class GameScene: SKScene {
let node = SKNode()
let sprite = SKShapeNode(circleOfRadius: 6)
var rotation:CGFloat = CGFloat(M_PI)
var circles:[Circle] = []
var circuloFondo = SKSpriteNode()
var orbita = SKSpriteNode()
let padding2:CGFloat = 26.0
let padding3:CGFloat = 33.5
let padding5:CGFloat = 285.5
var circulo = SKSpriteNode()
override func didMoveToView(view: SKView) {
scaleMode = .ResizeFill
backgroundColor = UIColor(red: 0.3, green: 0.65, blue: 0.9, alpha: 1)
orbita = SKSpriteNode(imageNamed: "orbita2")
orbita.size = CGSize(width:view.frame.size.width - padding2 , height: view.frame.size.width - padding2)
orbita.color = UIColor.whiteColor()
orbita.colorBlendFactor = 1
orbita.alpha = 1
orbita.position = view.center
self.addChild(orbita)
orbita.zPosition = 3
circuloFondo = SKSpriteNode(imageNamed: "circuloFondo")
circuloFondo.size = CGSize(width:view.frame.size.width - padding5 , height: view.frame.size.width - padding5)
circuloFondo.color = UIColor.whiteColor()
circuloFondo.alpha = 1
circuloFondo.position = view.center
self.addChild(circuloFondo)
circuloFondo.zPosition = 0
let radius1:CGFloat = (view.frame.size.width - padding3)/2 - 1
let radius2:CGFloat = (view.frame.size.width - padding5)/2 + 6.5
circles.append(Circle(position: view.center, radius: radius1))
circles.append(Circle(position: view.center, radius: radius2))
addChild(node)
node.addChild(sprite)
if let circle = nextCircle() {
node.position = circle.position
sprite.fillColor = SKColor.whiteColor()
sprite.zPosition = 4.0
sprite.position = CGPoint(x:circle.radius, y:0)
rotate()
}
You can get the width of the screen like this:
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
and then set elements in your UI to be a proportion of the screenWidth. For instance:
let radius1:CGFloat = screenWidth/4
//this would always give you a radius that is one quarter of the screen width
I've used this method a few times with success, hope it works for you.
Your main problem is your scene mode is set to ResizeFill. This will cause you so many headaches as you will have to do all the scaling yourself, hence the circles are different sizes on different devices. Having scale mode resizeFill will also affect things such as the physics engine or fontSizes which you will need to adjust for on every device.
I would recommend you use scene scale mode .AspectFill with the default scene size of 1024*768 (landscape) or 768*1024 (portrait). This is the same as the xCode default game template.
This way everything will look exactly the same on all iPhones. On iPads there will be slightly more screen space at the top and bottom which you simply cover with your background. The main trick is that you position your stuff from the center.
Furthermore you can use the universal assets in the asset catalogue and everything will look great and not blurry.
The only thing that you might have to adjust for this way is that on iPads you might need to move some buttons up/down if you want them on the top/bottom edge.
I strongly recommend you consider this as I can talk from experience that using scale mode ResizeFill is really bad. I have been through this pain with 2 games before I rewrote them because they were so inconsistent on all devices causing me so many bugs in the process. Lets not talk about the time I wasted testing on all devices, adjusting values until it felt right.
Hope this helps.

Object positioning is off

Hi, I’m trying to get an object (in this case a green frog) to spawn in line with the player sprite (the red frog) on a platform which is as wide as the scene and what i mean by this, is getting the object to spawn so that when the player advances it doesn’t overlap the object. (The picture shows how the green frog is between two red frogs and not in line with one of the red frogs)
My code for positioning of the objects is as follows
obstacle.position = CGPointMake(-(backgroundSprite.size.width / 2) + CGFloat(randomX) + (spacing * CGFloat(i)), 0)
this currently spawns it on the left side the screen half off the scene. the background sprite is what the object is being added to which is defined like so:
let theSize:CGSize = CGSizeMake(levelUnitWidth, levelUnitHeight)
let tex:SKTexture = SKTexture(imageNamed: imageName)
backgroundSprite = SKSpriteNode(texture: tex, color: SKColor.blackColor(), size: theSize)
random x is also what spawns them randomly on the x axis of the background sprite (which I have also tried adjusting with no luck)
let randomX = arc4random_uniform( UInt32 (backgroundSprite.size.height) )
lastly spacing is the distance between the objects in the same level unit.
let spacing:CGFloat = 250
I have tried implementing the player sprites width as a reference and is hasn’t worked. Can some please tell me what i’m doing wrong here.
Here is the full code if you need to look at it all:
if (theType == LevelType.road) {
for (var i = 0; i < Int(numberOfObjectsInLevel); i++) {
let obstacle:Object = Object()
obstacle.theType = LevelType.road
obstacle.createObject()
addChild(obstacle)
let spacing:CGFloat = 250
obstacle.position = CGPointMake((backgroundSprite.size.width / 4) + CGFloat(randomX) + (spacing * CGFloat(i)), 0)
}
EDIT:
I have tried implementing that code you made in your edit post with code I had already and this is what I got.
if (theType == LevelType.road) {
let xAxisSpawnLocations: [CGFloat] = {
var spawnLocations:[CGFloat] = []
//Create 5 possible spawn locations
let numberOfNodes = 5
for i in 0...numberOfNodes - 1 {
/*
Spacing between nodes will change if:
1) number of nodes is changed,
2) screen width is changed,
3) node's size is changed.
*/
var xPosition = (frame.maxX - thePlayer.size.width) / CGFloat((numberOfNodes - 1)) * CGFloat(i)
//add a half of a player's width because node's anchor point is (0.5, 0.5) by default
xPosition += thePlayer.size.width/2.0
spawnLocations.append( xPosition )
}
return spawnLocations
}()
print(xAxisSpawnLocations)
let yAxisSpawnLocations: [CGFloat] = [0]
let obstacle:Object = Object()
obstacle.theType = LevelType.road
obstacle.createObject()
addChild(obstacle)
let randx = xAxisSpawnLocations[Int(arc4random_uniform(UInt32(xAxisSpawnLocations.count)))]
obstacle.position = CGPoint(x: randx, y: yAxisSpawnLocations[0] )
obstacle.zPosition = 200
}
EDIT 2:
so implemented the code again this time the right way and I got this:
So the player still isn't in line with the objects and for some reason it only spawns on the right side of the screen. I think it is because I have a worldNode that holds everything.
the worldNode holds the player which has a starting point of (0,0) in the worldNode and it also holds the level units which holds the objects. the camera position is centered on the player node I'm not sure if this is the problem but i'll provide the code below so you can have a look at it.
let startingPosition:CGPoint = CGPointMake(0, 0)
The woldNode Code:
let worldNode:SKNode = SKNode()
//creates the world node point to be in the middle of the screen
self.anchorPoint = CGPointMake(0.5, 0.5)
addChild(worldNode)
//adds the player as a child node to the world node
worldNode.addChild(thePlayer)
thePlayer.position = startingPosition
thePlayer.zPosition = 500
The camera positioning code:
override func didSimulatePhysics() {
self.centerOnNode(thePlayer)
}
//centers the camera on the node world.
func centerOnNode(node:SKNode) {
let cameraPositionInScene:CGPoint = self.convertPoint(node.position, fromNode: worldNode)
worldNode.position = CGPoint(x: worldNode.position.x , y:worldNode.position.y - cameraPositionInScene.y )
}
I pretty sure this is my problem but tell me what you think.
Like I said in comments, the key is to predefine coordinates for x (and y) axis and spawn nodes based on that. First, let's define a player inside your GameScene class:
let player = SKSpriteNode(color: .redColor(), size: CGSize(width: 50, height: 50))
Now, predefine spawn locations (for both x and y axis):
let xAxisSpawnLocations: [CGFloat] = [50.0, 125.0, 200.0, 275.0, 350.0, 425.0]
let yAxisSpawnLocations: [CGFloat] = [50.0, 125.0, 200.0, 275.0]
Now when we know possible positions, let position our player first and add it to the scene:
player.position = CGPoint(x: xAxisSpawnLocations[0], y: yAxisSpawnLocations[0])
player.zPosition = 10
addChild(player)
You could create those positions based on player's width and height and screen's size, but because of simplicity, I've hardcoded everything.
So, lets fill one row, right above the player with green frogs:
for xLocation in xAxisSpawnLocations {
let greenFrog = SKSpriteNode(color: .greenColor(), size: player.size)
greenFrog.position = CGPoint(x: xLocation, y: yAxisSpawnLocations[1])
addChild(greenFrog)
}
The result would be something like this:
Or, for example, move the player by one place to the right, and make a column of green frogs right above him:
player.position = CGPoint(x: xAxisSpawnLocations[1], y: yAxisSpawnLocations[0])
for yLocation in yAxisSpawnLocations {
let greenFrog = SKSpriteNode(color: .greenColor(), size: player.size)
greenFrog.position = CGPoint(x: xAxisSpawnLocations[1], y: yLocation)
addChild(greenFrog)
}
And it should look like this:
EDIT:
Based on your comments, this is how you could distribute nodes across the screen based on number of nodes, screen width and node's size:
let xAxisSpawnLocations: [CGFloat] = {
var spawnLocations:[CGFloat] = []
//Create 5 possible spawn locations
let numberOfNodes = 5
for i in 0...numberOfNodes - 1 {
/*
Spacing between nodes will change if:
1) number of nodes is changed,
2) screen width is changed,
3) node's size is changed.
*/
var xPosition = (frame.maxX - player.size.width) / CGFloat((numberOfNodes - 1)) * CGFloat(i)
//add a half of a player's width because node's anchor point is (0.5, 0.5) by default
xPosition += player.size.width/2.0
spawnLocations.append( xPosition )
}
return spawnLocations
}()
print(xAxisSpawnLocations)
You should handle what is happening when too much nodes are added, or if nodes are too big, but this can give you a basic idea how to distribute nodes along x axis and preserve the same distance between them.

SKCropNode not rendering greyscale mask values in SpriteKit

I have an SKCropNode that is working fine except for one thing - all of the greyscale values in the mask image that are not black or white are getting set to solid white.
This means that all greyscale alpha values are not rendering with transparency, but are just being set to be fully opaque.
Here is my code:
let foregroundTexture = SKTexture(image: UIImage(contentsOfFile:NSBundle.mainBundle().resourcePath!.stringByAppendingPathComponent("P01_foreground.jpg"))!)
var foreground = SKSpriteNode(texture: foregroundTexture)
let maskTexture = SKTexture(image: UIImage(contentsOfFile:NSBundle.mainBundle().resourcePath!.stringByAppendingPathComponent("P01_mask.png"))!)
var mask = SKSpriteNode(texture: maskTexture)
var cropNode = SKCropNode()
cropNode.addChild(foreground)
cropNode.maskNode = mask
cropNode.position = CGPoint(x: device.x/2, y: device.y/2)
cropNode.zPosition = 2.0
self.addChild(cropNode)
Does anyone know why the greyscale areas are being set to white or how I can achieve the desired result? Thanks in advance!
Unfortunately it looks like you can't. SKCropNode currently only has one property, the maskNode itself. There are no additional settings available to do what you want.