Adding an SKLabelNode to Scene Kit View - ios

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.

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

Cannot Display Sprites Above SK3DNode

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)
...

How to use a SKScene as a SCNMaterial?

I am having a SKScene as a SCNMaterial that I want to apply on a SCNSphere. I want to add a point on the surface of the sphere, that should be partially visible even when it is on the other side of the sphere. So I want a double sided texture. But when I use spherematerial.isDoubleSided = true I get a whitish layer on top of my sphere. Not sure why this happens.
Here is my code:
let surfacematerial = SKScene(size: CGSize(width: 800, height: 400))
surfacematerial.backgroundColor = SKColor.blue
self.point.fillColor = SKColor.red
self.point.lineWidth = 0
self.point.position = CGPoint(x: 200, y: 200)
surfacematerial.addChild(point)
let spherematerial = SCNMaterial()
spherematerial.isDoubleSided = true
spherematerial.diffuse.contents = surfacematerial
let sphere = SCNSphere(radius: CGFloat(2))
sphere.materials = [spherematerial]
spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0, y: 0, z: 0)
spherenode.opacity = CGFloat(0.9)

Circular SKShapeNode is displayed as square when used in SceneKit overlaySKScene when using fillColor

I'm trying to build an overlay for my SceneKit scene by using overlaySKScene. But for some reason a circular SKShapeNode will show up as squares as soon as I set a fillColor.
The same node will show up correctly when used with a SKView.
Simple Playground example (needs "Run in Full Simulator"):
import UIKit
import SpriteKit
import SceneKit
import XCPlayground
let skScene = SKScene(size: CGSizeMake(500, 300))
skScene.scaleMode = .AspectFit
let node = SKShapeNode(circleOfRadius: 50)
node.position = CGPoint(x: 250, y: 150)
node.fillColor = SKColor.redColor()
node.strokeColor = SKColor.grayColor()
skScene.addChild(node)
let scnScene = SCNScene()
let scnView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
scnView.scene = scnScene
scnView.overlaySKScene = skScene
XCPShowView("Live View 1", scnView)
let skView = SKView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
skView.presentScene(skScene)
XCPShowView("Live View 2", skView)
While writing this I found the solution. I create the SKScene programmatically and I have to explicitly set shouldEnableEffects, backgroundColor and blendMode.
skScene.shouldEnableEffects = true
skScene.backgroundColor = SKColor.clearColor()
skScene.blendMode = .Alpha

Invisible circle stroke color in SKSpriteNode (iOS)

I'm trying to paint the stroke color in black, but it doesn't appear any line...
This is how it's displayed in the iOS simulator:
BUT, if I run the same code in the Xcode playground, the circle is perfect
My scene code:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.backgroundColor = UIColor.whiteColor()
let midcir = self.mainCircle()
self.addChild(midcir)
}
func mainCircle()->SKSpriteNode{
let node = SKSpriteNode()
node.anchorPoint=CGPoint(x: 0.5, y: 0.5)
let outsideNode = SKShapeNode(circleOfRadius: 127.5)
let insideNode = SKShapeNode(circleOfRadius: 1)
outsideNode.strokeColor = UIColor.blackColor()
outsideNode.fillColor = UIColor.blueColor()
outsideNode.lineWidth = 5
insideNode.strokeColor = UIColor.clearColor()
insideNode.fillColor = UIColor.clearColor()
node.addChild(outsideNode)
node.addChild(insideNode)
node.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
return node
}
}
And the playground code, it's for OS X for live view incompatibilities (live view):
It only changes the UIColor for NSColor
import XCPlayground
import Cocoa
import SpriteKit
var myView = SKView(frame: NSRect(x: 0, y: 0, width: 400, height: 400))
var myScene = SKScene(size: CGSize(width: 400, height: 400))
myScene.backgroundColor = NSColor.whiteColor()
myView.presentScene(myScene)
func maincircle()->SKSpriteNode{
let node = SKSpriteNode()
node.anchorPoint=CGPoint(x: 0.5, y: 0.5)
let outsideNode = SKShapeNode(circleOfRadius: 127.5)
let insideNode = SKShapeNode(circleOfRadius: 1)
outsideNode.strokeColor = NSColor.blackColor()
outsideNode.fillColor = NSColor.blueColor()
outsideNode.lineWidth = 5
insideNode.strokeColor = NSColor.clearColor()
insideNode.fillColor = NSColor.clearColor()
node.addChild(insideNode)
node.addChild(outsideNode)
node.position = CGPoint(x: 200, y: 200)
return node
}
let node = maincircle()
myScene.addChild(node)
XCPShowView("MainCir", myView)
I don't know that is happening, I'll post the answer if I find it. Thank you!

Resources