Cannot Display Sprites Above SK3DNode - ios

I'm displaying some basic 3D geometry within my SpriteKit scene using an instance of SK3DNode to display the contents of a SceneKit scene, as explained in Apple's article here.
I have been able to position the node and 3D contents as I want using SceneKit node transforms and the position/viewport size of the SK3DNode.
Next, I want to display some other sprites in my SpriteKit scene overlaid on top of the 3D content, but I am unable to do so: The contents of the SK3DNode are always drawn on top.
I have tried specifying the zPosition property of both the SK3DNode and the SKSpriteNode, to no avail.
From Apple's documentation on SK3DNode:
Use SK3DNode objects to incorporate 3D SceneKit content into a
SpriteKit-based game. When SpriteKit renders the node, the SceneKit
scene is animated and rendered first. Then this rendered image is
composited into the SpriteKit scene. Use the scnScene property to
specify the SceneKit scene to be rendered.
(emphasis mine)
It is a bit ambiguous withv regard to z-order (it only seems to mention the temporal order in which rendering takes place).
I have put together a minimal demo project on GitHub; the relevant code is:
1. SceneKit Scene
import SceneKit
class SceneKitScene: SCNScene {
override init() {
super.init()
let box = SCNBox(width: 10, height: 10, length: 10, chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
box.materials = [material]
let boxNode = SCNNode(geometry: box)
boxNode.transform = SCNMatrix4MakeRotation(.pi/2, 1, 1, 1)
self.rootNode.addChildNode(boxNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2. SpriteKit Scene
import SpriteKit
class SpriteKitScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
// Scene Background
self.backgroundColor = .red
// 3D Node
let objectNode = SK3DNode(viewportSize: size)
objectNode.scnScene = SceneKitScene()
addChild(objectNode)
objectNode.position = CGPoint(x: size.width/2, y: size.height/2)
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
objectNode.pointOfView = cameraNode
objectNode.pointOfView?.position = SCNVector3(x: 0, y: 0, z: 60)
objectNode.zPosition = -100
// 2D Sprite
let sprite = SKSpriteNode(color: .yellow, size: CGSize(width: 250, height: 60))
sprite.position = objectNode.position
sprite.zPosition = +100
addChild(sprite)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
...And the rendered result is:
(I want the yellow rectangle above the green box)

I made a Technical Support Incident with Apple about this and they just got back to me. The solution is actually very very simple.
If you want 2D sprites to render on top of SK3DNodes, you need to stop the contents of the SK3DNodes from writing to the depth buffer.
To do this, you just need to set writesToDepthBuffer to false on the SCNMaterial.
...
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
material.writesToDepthBuffer = false
...
Boom. Works.

Please note that this is just something I stumbled upon. I have no idea why it works and I probably wouldn't trust it without further understanding, but maybe it'll help find an explanation or a real solution.
It seems that having an SKShapeNode (with a fill) alongside an SK3DNode (either as a sibling, part of a sibling tree, or child), draws it in proper order. The SKShapeNode doesn't seem to need to intersect with the SK3DNode either.
The fill is important, as having a transparent fill makes this not work. Stroke doesn't seem to have any effect.
An SKShapeNode of extremely small size and almost zero alpha fill works too.
Here's my playground:
import PlaygroundSupport
import SceneKit
import SpriteKit
let viewSize = CGSize(width: 300, height: 150)
let viewportSize: CGFloat = viewSize.height * 0.75
func skview(color: UIColor, index: Int) -> SKView {
let scene = SKScene(size: viewSize)
scene.backgroundColor = color
let view = SKView(
frame: CGRect(
origin: CGPoint(x: 0, y: CGFloat(index) * viewSize.height),
size: viewSize
)
)
view.presentScene(scene)
view.showsDrawCount = true
// Draw the box of the 3d node view port
let viewport = SKSpriteNode(color: .orange, size: CGSize(width: viewportSize, height: viewportSize))
viewport.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
scene.addChild(viewport)
return view
}
func cube() -> SK3DNode {
let mat = SCNMaterial()
mat.diffuse.contents = UIColor.green
let box = SCNBox(width: viewportSize, height: viewportSize, length: viewportSize, chamferRadius: 0)
box.firstMaterial = mat
let boxNode3d = SCNNode(geometry: box)
boxNode3d.runAction(.repeatForever(.rotateBy(x: 10, y: 10, z: 10, duration: 10)))
let scene = SCNScene()
scene.rootNode.addChildNode(boxNode3d)
let boxNode2d = SK3DNode(viewportSize: CGSize(width: viewportSize, height: viewportSize))
boxNode2d.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
boxNode2d.scnScene = scene
return boxNode2d
}
func shape() -> SKShapeNode {
let shape = SKShapeNode(rectOf: CGSize(width: viewSize.height / 4, height: viewSize.height / 4))
shape.strokeColor = .clear
shape.fillColor = .purple
return shape
}
func rect(_ color: UIColor) -> SKSpriteNode {
let sp = SKSpriteNode(texture: nil, color: color, size: CGSize(width: 200, height: viewSize.height / 4))
sp.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
return sp
}
// The original issue, untouched.
func v1() -> SKView {
let v = skview(color: .red, index: 0)
v.scene?.addChild(cube())
v.scene?.addChild(rect(.yellow))
return v
}
// Shape added as sibling after the 3d node. Notice that it doesn't overlap the SK3DNode.
func v2() -> SKView {
let v = skview(color: .blue, index: 1)
v.scene?.addChild(cube())
v.scene?.addChild(shape())
v.scene?.addChild(rect(.yellow))
return v
}
// Shape added to the 3d node.
func v3() -> SKView {
let v = skview(color: .magenta, index: 2)
let box = cube()
box.addChild(shape())
v.scene?.addChild(box)
v.scene?.addChild(rect(.yellow))
return v
}
// 3d node added after, but zPos set to -1.
func v4() -> SKView {
let v = skview(color: .cyan, index: 3)
v.scene?.addChild(shape())
v.scene?.addChild(rect(.yellow))
let box = cube()
box.zPosition = -1
v.scene?.addChild(box)
return v
}
// Shape added after the 3d node, but not as a sibling.
func v5() -> SKView {
let v = skview(color: .green, index: 4)
let parent = SKNode()
parent.addChild(cube())
parent.addChild(rect(.yellow))
v.scene?.addChild(parent)
v.scene?.addChild(shape())
return v
}
let container = UIView(frame: CGRect(origin: .zero, size: CGSize(width: viewSize.width, height: viewSize.height * 5)))
container.addSubview(v1())
container.addSubview(v2())
container.addSubview(v3())
container.addSubview(v4())
container.addSubview(v5())
PlaygroundPage.current.liveView = container
TL;DR
In your code, try:
...
let shape = SKShapeNode(rectOf: CGSize(width: 0.01, height: 0.01))
shape.strokeColor = .clear
shape.fillColor = UIColor.black.withAlphaComponent(0.01)
// 3D Node
let objectNode = SK3DNode(viewportSize: size)
objectNode.addChild(shape)
...

Related

ARKit Showing broken UIWebView

I'm trying to detect an image using ARImageTrackingConfiguration and when the image is detected, an UIWebView shows up instead of the image scanned. I read a lot of problems regarding the new WBWebView but is not working either.
The problem it seems that is the UIWebView is not being updated from the main thread even thought I'm using the DispatchMain.
On a related topic (the code I use is pretty much the same), if I try to put a SKVideoNode instead of a UIWebView, the video gets played super laggy with some big pixels on top if I set plane.cornerRadius = 0.25
The code is the following
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let imageAnchor = anchor as? ARImageAnchor else { return }
updateQueue.async {
let physicalWidth = imageAnchor.referenceImage.physicalSize.width
let physicalHeight = imageAnchor.referenceImage.physicalSize.height
// Create a plane geometry to visualize the initial position of the detected image
let mainPlane = SCNPlane(width: physicalWidth, height: physicalHeight)
mainPlane.firstMaterial?.colorBufferWriteMask = .alpha
// Create a SceneKit root node with the plane geometry to attach to the scene graph
// This node will hold the virtual UI in place
let mainNode = SCNNode(geometry: mainPlane)
mainNode.eulerAngles.x = -.pi / 2
mainNode.renderingOrder = -1
mainNode.opacity = 1
// Add the plane visualization to the scene
node.addChildNode(mainNode)
// Perform a quick animation to visualize the plane on which the image was detected.
// We want to let our users know that the app is responding to the tracked image.
self.highlightDetection(on: mainNode, width: physicalWidth, height: physicalHeight, completionHandler: {
DispatchQueue.main.async {
let request = URLRequest(url: URL(string: "https://www.facebook.com/")!)
let webView = UIWebView(frame: CGRect(x: 0, y: 0, width: 400, height: 672))
webView.loadRequest(request)
let webViewPlane = SCNPlane(width: xOffset, height: xOffset * 1.4)
webViewPlane.cornerRadius = 0.25
let webViewNode = SCNNode(geometry: webViewPlane)
webViewNode.geometry?.firstMaterial?.diffuse.contents = webView
webViewNode.position.z -= 0.5
webViewNode.opacity = 0
rootNode.addChildNode(webViewNode)
webViewNode.runAction(.sequence([
.wait(duration: 3.0),
.fadeOpacity(to: 1.0, duration: 1.5),
.moveBy(x: xOffset * 1.1, y: 0, z: -0.05, duration: 1.5),
.moveBy(x: 0, y: 0, z: -0.05, duration: 0.2)
])
)
}
})
}
}
VIDEO VERSION
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let imageAnchor = anchor as? ARImageAnchor else { return }
updateQueue.async {
let physicalWidth = imageAnchor.referenceImage.physicalSize.width
let physicalHeight = imageAnchor.referenceImage.physicalSize.height
// Create a plane geometry to visualize the initial position of the detected image
let mainPlane = SCNPlane(width: physicalWidth, height: physicalHeight)
mainPlane.firstMaterial?.colorBufferWriteMask = .alpha
// Create a SceneKit root node with the plane geometry to attach to the scene graph
// This node will hold the virtual UI in place
let mainNode = SCNNode(geometry: mainPlane)
mainNode.eulerAngles.x = -.pi / 2
mainNode.renderingOrder = -1
mainNode.opacity = 1
// Add the plane visualization to the scene
node.addChildNode(mainNode)
// Perform a quick animation to visualize the plane on which the image was detected.
// We want to let our users know that the app is responding to the tracked image.
self.highlightDetection(on: mainNode, width: physicalWidth, height: physicalHeight, completionHandler: {
let size = imageAnchor.referenceImage.physicalSize
var videoNode = SKVideoNode()
switch imageAnchor.name {
case "Elephant-PostCard":
videoNode = SKVideoNode(fileNamed: "Movies/cat.mp4")
case "puppy":
videoNode = SKVideoNode(fileNamed: "puppy.mov")
default:
break
}
// invert our video so it does not look upside down
videoNode.yScale = -1.0
videoNode.play()
let videoScene = SKScene(size: CGSize(width: 1280, height: 720))
videoScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
videoScene.addChild(videoNode)
let plane = SCNPlane(width: size.width, height: size.height)
plane.cornerRadius = 0.25
plane.firstMaterial?.diffuse.contents = videoScene
let planeNode = SCNNode(geometry: plane)
plane.firstMaterial?.isDoubleSided = true
mainNode.addChildNode(planeNode)
})
}
}
UIWebView from UIKit module is now deprecated.
Use WKWebView from WebKit module instead.

SpriteKit: A gap between SKSpriteNodes

When the blue square falls down on the red one a gap will remain. But when I make the blue square fall from a lower position there is no gap. Also when I give the blue square a higher mass the gap (on the ground) under the red square disappears and when the mass is too high the red square is gone.
I also made the squares smaller because the amount of pixels didn't equal the pixels of the png file which led to an even bigger gap. And when I set "showsPhysics" to true there is also a gap to see and even bigger when the squares are not scaled down.
Gap between SKSpriteNodes (picture)
//: Playground - noun: a place where people can play
import UIKit
import SpriteKit
import PlaygroundSupport
class Scene: SKScene {
var square = SKSpriteNode(texture: SKTexture(imageNamed: "red"), size: SKTexture(imageNamed: "red").size())
var square2 = SKSpriteNode(texture: SKTexture(imageNamed: "blue"), size: SKTexture(imageNamed: "blue").size())
var label = SKLabelNode(fontNamed: "Chalkduster")
override func didMove(to view: SKView) {
self.addChild(square)
self.addChild(square2)
self.addChild(label)
}
override func didChangeSize(_ oldSize: CGSize) {
// Add Scene Physics
self.physicsBody?.usesPreciseCollisionDetection = true
self.physicsBody = SKPhysicsBody(edgeLoopFrom: CGRect(x: -1, y: -1, width: self.size.width + 2, height: self.size.height + 2))
self.backgroundColor = UIColor.white
self.physicsBody?.usesPreciseCollisionDetection = true
//Add square
square.texture?.usesMipmaps = true
square.zPosition = 11
square.physicsBody = SKPhysicsBody(rectangleOf: square.size)
square.physicsBody?.usesPreciseCollisionDetection = true
square.physicsBody?.mass = 0.1
square.physicsBody?.allowsRotation = false
square.physicsBody?.usesPreciseCollisionDetection = true
square.setScale(0.25)
square.position = CGPoint(x: self.frame.width / 2, y: self.square.size.height)
square.name = "square"
//Add square2
square2.texture?.usesMipmaps = true
square2.zPosition = 11
square2.physicsBody = SKPhysicsBody(rectangleOf: square2.size)
square2.physicsBody?.usesPreciseCollisionDetection = true
square2.physicsBody?.mass = 0.1
square2.physicsBody?.allowsRotation = false
square2.physicsBody?.usesPreciseCollisionDetection = true
square2.setScale(0.25)
square2.position = CGPoint(x: self.frame.width / 2, y: self.square.size.height * 6)
square2.name = "square2"
//Add label
label.fontSize = 30
label.text = "w: \(self.frame.size.width) h: \(self.frame.size.height)"
label.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height - 30)
label.fontColor = UIColor.black
label.zPosition = 100
}
}
class ViewController: UIViewController {
let scene = Scene()
var viewSize = CGSize(width: 0, height: 0)
override func loadView() {
self.view = SKView()
}
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
view.frame = CGRect(origin: CGPoint(x: -1, y: -1), size: viewSize)
view.presentScene(scene)
view.showsPhysics = false
view.showsFPS = true
view.showsNodeCount = true
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scene.size = CGSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
}
}
PlaygroundPage.current.liveView = ViewController()
As proof from the print commands, there is no actual gap between the sprite nodes, and with second proof from a super-zoomed-in-screenshot:
The playground you provided does not replicate an issue, which makes me wonder... what is going on with your project?
pg #2:
The scene's width and height has to be multiplied by 2. So the scene has to be 4 times bigger than the ViewControllers view. It's also like that in the game template that Apple provides for Spritekit.
scene.size = CGSize(width: self.view.frame.size.width * 2, height: self.view.frame.size.height * 2)
https://github.com/simon2204/Playground

How to draw gradient with SKKeyframeSequence: as per Apple docs

The Apple docs on SKKeyframeSequence have brief sample code designed to create a gradient:
let colorSequence = SKKeyframeSequence(keyframeValues: [SKColor.green,
SKColor.yellow,
SKColor.red,
SKColor.blue],
times: [0, 0.25, 0.5, 1])
colorSequence.interpolationMode = .linear
stride(from: 0, to: 1, by: 0.001).forEach {
let color = colorSequence.sample(atTime: CGFloat($0)) as! SKColor
}
When combined with a drawing system of some sort, this is said to output this:
How can this be drawn from the sampling of the sequence of colours in the demo code?
ps I don't have any clue how to draw this with SpriteKit objects, hence the absence of attempted code. I'm not asking for code, just an answer on how to use this 'array' of colours to create a gradient that can be used as a texture in SpriteKit.
The colors are different for some reason, but here is what I came up with using their source code:
PG setup:
import SpriteKit
import PlaygroundSupport
let sceneView = SKView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 1000, height: 450)))
let scene = SKScene(size: CGSize(width: 1000, height: 450))
LOADSCENE: do {
scene.backgroundColor = .white
scene.anchorPoint = CGPoint(x: 0, y: 0.5)
scene.physicsWorld.gravity = CGVector.zero
sceneView.presentScene(scene)
PlaygroundPage.current.liveView = sceneView
}
Solution:
// Utility func:
func drawLine(from point1: CGPoint, to point2: CGPoint, color: SKColor) {
let linePath = CGMutablePath()
linePath.move(to: point1)
linePath.addLine(to: point2)
let newLine = SKShapeNode(path: linePath)
newLine.strokeColor = color
newLine.lineWidth = 1
newLine.zPosition = 10
scene.addChild(newLine)
newLine.position.x = point1.x
}
// Holds our soon-to-be-generated colors:
var colors = [SKColor]()
LOADCOLORS: do {
let colorSequence = SKKeyframeSequence(keyframeValues: [SKColor.green,
SKColor.yellow,
SKColor.red,
SKColor.blue],
times: [0, 0.25, 0.5, 1])
colorSequence.interpolationMode = .linear
stride(from: 0, to: 1, by: 0.001).forEach {
colors.append(colorSequence.sample(atTime: CGFloat($0)) as! SKColor)
}
}
DRAWGRAD: do {
for i in 1...999 {
let p1 = CGPoint(x: CGFloat(i), y: scene.frame.minY)
let p2 = CGPoint(x: CGFloat(i), y: scene.frame.maxY)
drawLine(from: p1, to: p2, color: colors[i])
}
print("Give me my 25 cookie points, please and TY")
}
You should then be able to get this as a texture as such:
let texture = sceneView.texture(from: scene)
Rendering this took about a million years to render on my gen2 i5 at 2.6ghz for some reason. Will have to look into that, unless it was just a PG bug...

SceneKit: vertically center scaled SCNNode inside parent?

The goal is to scale a SCNNode and vertically center it within its parent.
However, scaling a SCNNode does not affect its bounding box and computing the scaled height does not work. Without an accurate height, how can you vertically center the node inside its parent?
To illustrate the problem with using the scaled height, see the attached file, Tiki.dae. The original height of the asset (as shown by the bounding box) is 324.36. If you set the Y-scale to 0.01, however, the height doesn't become ~3.24. It becomes smaller than 3, which you can prove by fitting it comfortably within a sphere of height 3 (radius of 1.5).
The code below attempts to center a scaled node inside its parent, but it doesn't work.
Note: reference node is the fox/panda reference node from the WWDC 2015 fox demo.
// Create reference node
let referenceNode = SCNReferenceNode(URL: referenceURL)
referenceNode?.load()
// Scale reference node
let scale = Float(3)
referenceNode?.scale = SCNVector3(x: scale, y: scale, z: scale)
// Create geometry for sphere
let sphereGeometry = SCNSphere(radius: (gridSphere.geometry as! SCNSphere).radius)
//sphereGeometry.materials = gridSphere.geometry!.materials
sphereGeometry.firstMaterial!.diffuse.contents = gPurpleColor
// Create sphere to hold reference node, position at same place as <gridSphere>
let liveSphere = SCNNode(geometry: sphereGeometry)
liveSphere.position = gridSphere.position
// Center reference node inside <liveSphere>
var min = SCNVector3Zero
var max = SCNVector3Zero
referenceNode?.getBoundingBoxMin(&min, max: &max)
let referenceNodeHeight = max.y - min.y
referenceNode?.position = SCNVector3(x: 0, y: 0 - referenceNodeHeight, z: 0)
// Add reference node to <liveSphere>
liveSphere.addChildNode(referenceNode!)
// This value never changes no matter the scale value???
print(referenceNodeHeight)
Here's a Playground that adds a cube (in place of your reference node) as a child of the sphere. The cube responds to scaling (see the lines following "// Scale reference node").
//: Playground - noun: a place where people can play
import Cocoa
import SceneKit
import XCPlayground
public class GizmoNode: SCNNode {
required public override init() {
super.init()
let axisLength = CGFloat(3.0)
let offset = CGFloat(axisLength/2.0)
let axisSide = CGFloat(0.2)
let chamferRadius = CGFloat(axisSide)
let xBox = SCNBox(width: axisLength, height: axisSide, length: axisSide, chamferRadius: chamferRadius)
xBox.firstMaterial?.diffuse.contents = NSColor.redColor()
let yBox = SCNBox(width: axisSide, height: axisLength, length: axisSide, chamferRadius: chamferRadius)
yBox.firstMaterial?.diffuse.contents = NSColor.greenColor()
let zBox = SCNBox(width: axisSide, height: axisSide, length: axisLength, chamferRadius: chamferRadius)
zBox.firstMaterial?.diffuse.contents = NSColor.blueColor()
let xNode = SCNNode(geometry: xBox)
let yNode = SCNNode(geometry: yBox)
let zNode = SCNNode(geometry: zBox)
self.addChildNode(xNode)
self.addChildNode(yNode)
self.addChildNode(zNode)
print (xNode.position)
print (yNode.position)
print (zNode.position)
xNode.position.x = offset
yNode.position.y = offset
zNode.position.z = offset
print (xNode.pivot)
print (yNode.pivot)
print (zNode.pivot)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let scene = SCNScene()
sceneView.scene = scene
sceneView.backgroundColor = NSColor.blackColor()
sceneView.allowsCameraControl = true
let gizmo = GizmoNode()
scene.rootNode.addChildNode(gizmo)
XCPlaygroundPage.currentPage.liveView = sceneView
// Create reference node
let cubeSize = CGFloat(0.5)
let cubeGeometry = SCNBox(width: cubeSize, height: cubeSize, length: cubeSize, chamferRadius: 0.0)
let referenceNodeStandIn = SCNNode(geometry: cubeGeometry)
//referenceNodeStandIn?.load()
let cubeColor = NSColor.whiteColor().colorWithAlphaComponent(0.5)
cubeGeometry.firstMaterial!.diffuse.contents = cubeColor
// Scale reference node
let scale = CGFloat(8)
referenceNodeStandIn.scale = SCNVector3(x: scale, y: scale, z: scale)
// Create geometry for sphere
let sphereRadius = CGFloat(2.0)
let sphereGeometry = SCNSphere(radius: sphereRadius)
//sphereGeometry.materials = gridSphere.geometry!.materials
let gPurpleColor = NSColor.purpleColor().colorWithAlphaComponent(1.0)
sphereGeometry.firstMaterial!.diffuse.contents = gPurpleColor
// Create sphere to hold reference node, position at same place as <gridSphere>
let liveSphere = SCNNode(geometry: sphereGeometry)
//liveSphere.position = gridSphere.position
scene.rootNode.addChildNode(liveSphere)
// Center reference node inside <liveSphere>
var min = SCNVector3Zero
var max = SCNVector3Zero
referenceNodeStandIn.getBoundingBoxMin(&min, max: &max)
print("min: \(min) max: \(max)")
let referenceNodeHeight = max.y - min.y
//referenceNodeStandIn.position = SCNVector3(x: 0, y: 0 - referenceNodeHeight, z: 0)
// Add reference node to <liveSphere>
liveSphere.addChildNode(referenceNodeStandIn)
// This value never changes no matter the scale value because it's in local coordinate space
print("reference node height", referenceNodeHeight)

Adding an SKLabelNode to Scene Kit View

I am building a game using Scene Kit. In order to present the score I wanted to use an SKLabelNode on the screen, however, when I attach it to a SCNNode, it looks very blurry:
Here is the code that I have written to do this, please let me know if there is a better way to go about doing this without having the text be so blurry. Thank you so much!
func initHUD() {
let skScene = SKScene(size: CGSize(width: 100, height: 100))
skScene.backgroundColor = UIColor(white: 0.0, alpha: 0.0)
labelNode = SKLabelNode()
labelNode.fontSize = 20
labelNode.position.y = 50
labelNode.position.x = 50
skScene.addChild(labelNode)
let plane = SCNPlane(width: 1, height: 1)
let material = SCNMaterial()
material.lightingModelName = SCNLightingModelConstant
material.doubleSided = true
material.diffuse.contents = skScene
plane.materials = [material]
hudNode = SCNNode(geometry: plane)
hudNode.name = "HUD"
hudNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: 3.14159265)
hudNode.position = SCNVector3(x:0, y: 1, z: -5)
}
func updateHUD() {
labelNode.text = "\(score)"
}
The typical way to do a HUD for a SceneKit scene is to create a SpriteKit SKScene and set it as the overlaySKScene of your SceneKit view. Then it renders at full resolution and always at the same view-relative size and position.

Resources