Add glowing effect to an SKSpriteNode - ios

I have a moving black image on a dark screen, to make it easier to see I would like to add in a white glow to the image. This is my code for the moving image:
Ghost = SKSpriteNode(imageNamed: "Ghost1")
Ghost.size = CGSize(width: 50, height: 50)
Ghost.position = CGPoint(x: self.frame.width / 2 - Ghost.frame.width, y: self.frame.height / 2)
Ghost.physicsBody = SKPhysicsBody(circleOfRadius: Ghost.frame.height / 1.4)
Ghost.physicsBody?.categoryBitMask = PhysicsCatagory.Ghost
Ghost.physicsBody?.collisionBitMask = PhysicsCatagory.Ground | PhysicsCatagory.Wall
Ghost.physicsBody?.contactTestBitMask = PhysicsCatagory.Ground | PhysicsCatagory.Wall | PhysicsCatagory.Score
Ghost.physicsBody?.affectedByGravity = false
Ghost.physicsBody?.isDynamic = true
Ghost.zPosition = 2
self.addChild(Ghost)
I'm not sure how or what to use to add in a glow, if you need more information please ask.

I created this extension to add a glow effect to an SKSpriteNode
Just add this to your project
extension SKSpriteNode {
func addGlow(radius: Float = 30) {
let effectNode = SKEffectNode()
effectNode.shouldRasterize = true
addChild(effectNode)
let effect = SKSpriteNode(texture: texture)
effect.color = self.color
effect.colorBlendFactor = 1
effectNode.addChild(effect)
effectNode.filter = CIFilter(name: "CIGaussianBlur", parameters: ["inputRadius":radius])
}
}
Now given an SKSpriteNode
let sun = SKSpriteNode(imageNamed: "sun")
all you have to do it
sun.addGlow()

Just to add to this, you can perform this on any type of SKNode by first rendering its contents using the texture(from:SKNode) method available on an SKView instance.
Example:
extension SKNode
{
func addGlow(radius:CGFloat=30)
{
let view = SKView()
let effectNode = SKEffectNode()
let texture = view.texture(from: self)
effectNode.shouldRasterize = true
effectNode.filter = CIFilter(name: "CIGaussianBlur",withInputParameters: ["inputRadius":radius])
addChild(effectNode)
effectNode.addChild(SKSpriteNode(texture: texture))
}
}

Related

Issue with adding shadow to a SCNPlane with clear background

I am trying to add a shadow on SCNPlane, everything works fine but I cannot make SCNPlane transparent to show only the shadow not with the white background. here is the code:
let flourPlane = SCNPlane()
let groundPlane = SCNNode()
let clearMaterial = SCNMaterial()
clearMaterial.lightingModel = .constant
//clearMaterial.colorBufferWriteMask = []
clearMaterial.writesToDepthBuffer = true
clearMaterial.transparencyMode = .default
flourPlane.materials = [clearMaterial]
groundPlane.scale = SCNVector3(200, 200, 200)
groundPlane.geometry = flourPlane
groundPlane.castsShadow = false
groundPlane.eulerAngles = SCNVector3Make(-Float.pi/2, 0, 0)
groundPlane.position = SCNVector3(x: 0.0, y: shadowY, z: 0.0)
node.addChildNode(groundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.shadowMode = .deferred
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.position = SCNVector3(x: 0,y: 5,z: 0)
// Create a directional light node with shadow
let myNode = SCNNode()
myNode.light = SCNLight()
myNode.light?.type = .directional
myNode.light?.castsShadow = true
myNode.light?.automaticallyAdjustsShadowProjection = true
myNode.light?.shadowSampleCount = 80
myNode.light?.shadowBias = 1
myNode.light?.orthographicScale = 1
myNode.light?.shadowMode = .deferred
myNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
myNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.5)
myNode.light?.shadowRadius = 10.0
myNode.eulerAngles = SCNVector3Make(-Float.pi/2, 0, 0)
node.addChildNode(ambientLight)
node.addChildNode(myNode)
When I add clearMaterial.colorBufferWriteMask = [] shadow disappears! how can create a transparent material to show only the shadow.
The white area is SCNPlane and the red is the background.
You can set materials "color" to .clear like below:
extension SCNMaterial {
convenience init(color: UIColor) {
self.init()
diffuse.contents = color
}
convenience init(image: UIImage) {
self.init()
diffuse.contents = image
}
}
let clearColor = SCNMaterial(color: .clear)
flourPlane.materials = [clearColor]
I have found a trick in another SO answer.
Adjust the floor plane's blendMode to alter how its pixels are combined with the underlying pixels.
let clearMaterial = SCNMaterial()
// alter how pixels affect underlying pixels
clearMaterial.blendMode = .multiply
// use the simplest shading that shows a shadow (so not .constant!)
clearMaterial.lightingModel = .lambert
// unobstructed parts are render pure white and thus have no effect
clearMaterial.diffuse.contents = UIColor.white
floorPlane.firstMaterial = clearMaterial
You can see the effect in this image. The main plane is invisible, yet the shadow is visible. The cube, its shadow and the ball grid have the same XZ position. Notice how the "shadow" affects underlying geometries and the scene background.

Add plane nodes to ARKit scene vertically and horizontally

I want my app to lay the nodes on the surface, which can be vertical or horizontal. However, the node is always vertical. Here's a pic, these nodes aren't placed correctly.
#objc func didTapAddButton() {
let screenCentre = CGPoint(x: self.sceneView.bounds.midX, y: self.sceneView.bounds.midY)
let arHitTestResults: [ARHitTestResult] = sceneView.hitTest(screenCentre, types: [.featurePoint]) // Alternatively, we could use '.existingPlaneUsingExtent' for more grounded hit-test-points.
if let closestResult = arHitTestResults.first {
let transform: matrix_float4x4 = closestResult.worldTransform
let worldCoord: SCNVector3 = SCNVector3Make(transform.columns.3.x, transform.columns.3.y, transform.columns.3.z)
if let node = createNode() {
sceneView.scene.rootNode.addChildNode(node)
node.position = worldCoord
}
}
}
func createNode() -> SCNNode? {
guard let theView = myView else {
print("Failed to load view")
return nil
}
let plane = SCNPlane(width: 0.06, height: 0.06)
let imageMaterial = SCNMaterial()
imageMaterial.isDoubleSided = true
imageMaterial.diffuse.contents = theView.asImage()
plane.materials = [imageMaterial]
let node = SCNNode(geometry: plane)
return node
}
The app is able to see the ground but the nodes are still parallel to us. How can I fix this?
Edit: I figured I can use node.eulerAngles.x = -.pi / 2, this makes sure that the plane is laid down horizontally but it's still horizontal on vertical surfaces as well.
Solved! Here's how to make the view "parallel" to the camera at all times:
let yourNode = SCNNode()
let billboardConstraint = SCNBillboardConstraint()
billboardConstraint.freeAxes = [.X, .Y, .Z]
yourNode.constraints = [billboardConstraint]
Or
guard let currentFrame = sceneView.session.currentFrame else {return nil}
let camera = currentFrame.camera
let transform = camera.transform
var translationMatrix = matrix_identity_float4x4
translationMatrix.columns.3.z = -0.1
let modifiedMatrix = simd_mul(transform, translationMatrix)
let node = SCNNode(geometry: plane)
node.simdTransform = modifiedMatrix

SpriteKit: sprite looks blurry (with ghosting) at high velocity but fine at low velocity

When using high velocity (linear or angular) in SpriteKit, sprites look blurry as if there are "ghosts" trailing the sprite. The sprite looks fine at slow speeds.
Below is a screenshot and GIF illustrating the blurriness/ghosting problem with high linear velocity, but the problem also occurs with the angularVelocity property.
Ball Code (use SKScene below to reproduce blurriness):
let radius = CGFloat(8)
let body = SKPhysicsBody(circleOfRadius: radius)
body.isDynamic = true
body.affectedByGravity = false
body.allowsRotation = true
body.friction = 0
body.restitution = 0.0
body.linearDamping = 0.0
body.angularDamping = 0
body.categoryBitMask = categoryBitMask
let ball = SKShapeNode(circleOfRadius: radius)
ball.physicsBody = body
ball.physicsBody?.velocity.dx = 0
ball.physicsBody?.velocity.dy = -1200
Looks fine:
ball.physicsBody?.velocity.dy = -200
Looks blurry:
ball.physicsBody?.velocity.dy = -1200
Screenshot:
GIF:
SKScene (drop in project and present scene to see blurriness):
import Foundation
import SpriteKit
class TestScene : SKScene, SKPhysicsContactDelegate {
let BallBitMask : UInt32 = 0x1 << 1
let BottomWallBitMask : UInt32 = 0x1 << 3
let TopWallBitMask : UInt32 = 0x1 << 4
let RightWallBitMask : UInt32 = 0x1 << 5
let LeftWallBitMask : UInt32 = 0x1 << 6
let SceneBackgroundColor = UIColor(red: 58/255.0, green: 50/255.0, blue: 96/255.0, alpha: 1.0)
let HorizontalWallHeight = CGFloat(10)
let VerticallWallWidth = CGFloat(5)
override init() {
super.init()
}
override init(size: CGSize) {
super.init(size: size)
doInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func doInit() {
// Set background
backgroundColor = SceneBackgroundColor
// Set scale mode
scaleMode = .resizeFill
// Set anchor point to screen center
anchorPoint = CGPoint(x: 0.5, y: 0.5)
// Add walls
layoutWalls()
// Create ball
let radius = CGFloat(8)
let body = SKPhysicsBody(circleOfRadius: radius)
body.isDynamic = true
body.affectedByGravity = false
body.allowsRotation = true
body.friction = 0
body.restitution = 0.0
body.linearDamping = 0.0
body.angularDamping = 0
body.categoryBitMask = BallBitMask
body.collisionBitMask = TopWallBitMask | RightWallBitMask | BottomWallBitMask | LeftWallBitMask
let ball = SKShapeNode(circleOfRadius: radius)
ball.fillColor = UIColor.orange
ball.physicsBody = body
ball.physicsBody?.velocity.dx = 0
ball.physicsBody?.velocity.dy = -1200
// Add ball to scene
addChild(ball)
}
fileprivate func layoutWalls() {
// Set wall offset
let wallOffset = CGFloat(3)
// Layout bottom wall
let bottomWallSize = CGSize(width: UIScreen.main.bounds.width, height: HorizontalWallHeight)
let bottomWall = SKSpriteNode(color: UIColor.red, size: bottomWallSize)
bottomWall.position.y = -UIScreen.main.bounds.height/2 - bottomWallSize.height/2 - wallOffset
bottomWall.physicsBody = createWallPhysics(categoryBitMask: BottomWallBitMask, wallSize: bottomWallSize)
addChild(bottomWall)
// Layout top wall
let topWallSize = CGSize(width: UIScreen.main.bounds.width, height: HorizontalWallHeight)
let topWall = SKSpriteNode(color: UIColor.red, size: topWallSize)
topWall.position.y = UIScreen.main.bounds.height/2 + topWallSize.height/2 + wallOffset
topWall.physicsBody = createWallPhysics(categoryBitMask: TopWallBitMask, wallSize: topWallSize)
addChild(topWall)
// Layout right wall
let rightWallSize = CGSize(width: VerticallWallWidth, height: UIScreen.main.bounds.height)
let rightWall = SKSpriteNode(color: UIColor.blue, size: rightWallSize)
rightWall.position.x = UIScreen.main.bounds.width/2 + rightWallSize.width/2 + wallOffset
rightWall.physicsBody = createWallPhysics(categoryBitMask: RightWallBitMask, wallSize: rightWallSize)
addChild(rightWall)
// Layout left wall
let leftWallSize = CGSize(width: VerticallWallWidth, height: UIScreen.main.bounds.height)
let leftWall = SKSpriteNode(color: UIColor.blue, size: leftWallSize)
leftWall.position.x = -UIScreen.main.bounds.width/2 - leftWallSize.width/2 - wallOffset
leftWall.physicsBody = createWallPhysics(categoryBitMask: LeftWallBitMask, wallSize: leftWallSize)
addChild(leftWall)
}
fileprivate func createWallPhysics(categoryBitMask: UInt32, wallSize: CGSize) -> SKPhysicsBody {
// Create new physics body for wall
let physicsBody = SKPhysicsBody(edgeLoopFrom: CGRect(x: -wallSize.width/2, y: -wallSize.height/2, width: wallSize.width, height: wallSize.height))
physicsBody.isDynamic = true
physicsBody.friction = 0
physicsBody.restitution = 1.0
physicsBody.linearDamping = 0
physicsBody.angularDamping = 0.0
physicsBody.categoryBitMask = categoryBitMask
// Return body
return physicsBody
}
}
Which one of these looks more ghosty?
The "trick" is being performed by the eye. We're not equipped to deal with screens at a lowly 60fps with fast moving objects. We sustain an image on the screen and in position through a faux persistence of vision so our brains and consciousness can figure out how fast something is "moving" on the screen.
In real life we get a near infinite number of "frames" to process movement with, and depth and all sorts of other cues, so we rarely do this anywhere near as much.
We still do it, but it's much less perceptible because we've got that near infinite number of frames to call on.
The below three images do different things to reveal this in different ways.
The first one is linear speed, accelerates instantly to its velocity of rotation and stops instantly.
The second has a ramp up and ramp down to its rotational speed, which has a higher peak speed of rotation. This has an interesting effect on the brain that permits it to prepare for the velocity that's going to be achieved.
The final has a lot of fake motion blur (too much for real world motion graphics usage) that shows how effective blur is at solving the effect of this problem, and why slow shutter speeds are so incredibly important to movie making.
Linear rotation rate:
Accel and decel:
Heavily blurred:

How to Make an SKSriteNode Glow (Swift)? [duplicate]

I have a moving black image on a dark screen, to make it easier to see I would like to add in a white glow to the image. This is my code for the moving image:
Ghost = SKSpriteNode(imageNamed: "Ghost1")
Ghost.size = CGSize(width: 50, height: 50)
Ghost.position = CGPoint(x: self.frame.width / 2 - Ghost.frame.width, y: self.frame.height / 2)
Ghost.physicsBody = SKPhysicsBody(circleOfRadius: Ghost.frame.height / 1.4)
Ghost.physicsBody?.categoryBitMask = PhysicsCatagory.Ghost
Ghost.physicsBody?.collisionBitMask = PhysicsCatagory.Ground | PhysicsCatagory.Wall
Ghost.physicsBody?.contactTestBitMask = PhysicsCatagory.Ground | PhysicsCatagory.Wall | PhysicsCatagory.Score
Ghost.physicsBody?.affectedByGravity = false
Ghost.physicsBody?.isDynamic = true
Ghost.zPosition = 2
self.addChild(Ghost)
I'm not sure how or what to use to add in a glow, if you need more information please ask.
I created this extension to add a glow effect to an SKSpriteNode
Just add this to your project
extension SKSpriteNode {
func addGlow(radius: Float = 30) {
let effectNode = SKEffectNode()
effectNode.shouldRasterize = true
addChild(effectNode)
let effect = SKSpriteNode(texture: texture)
effect.color = self.color
effect.colorBlendFactor = 1
effectNode.addChild(effect)
effectNode.filter = CIFilter(name: "CIGaussianBlur", parameters: ["inputRadius":radius])
}
}
Now given an SKSpriteNode
let sun = SKSpriteNode(imageNamed: "sun")
all you have to do it
sun.addGlow()
Just to add to this, you can perform this on any type of SKNode by first rendering its contents using the texture(from:SKNode) method available on an SKView instance.
Example:
extension SKNode
{
func addGlow(radius:CGFloat=30)
{
let view = SKView()
let effectNode = SKEffectNode()
let texture = view.texture(from: self)
effectNode.shouldRasterize = true
effectNode.filter = CIFilter(name: "CIGaussianBlur",withInputParameters: ["inputRadius":radius])
addChild(effectNode)
effectNode.addChild(SKSpriteNode(texture: texture))
}
}

GKObstacleGraph not finding path when obstacle introduced

I'm working on a Gameplaykit pathfinding proof-of-concept and I can't get GKObstacleGraph to find paths correctly.
In the following code snippet (it should work in an Xcode 7.2 playground), path2 is always an empty array if there is an obstacle provided when the graph is created. If I create the obGraph object with an empty array of obstacles the findPathFromNode returns a correct path.
The obstacle created should be a simple U shaped polygon with the end point being inside the U.
import UIKit
import GameplayKit
let pts = [vector_float2(2,2),
vector_float2(3,2),
vector_float2(3,6),
vector_float2(7,6),
vector_float2(7,2),
vector_float2(8,3),
vector_float2(8,7),
vector_float2(2,7),
vector_float2(2,2)]
let obstacle1 = GKPolygonObstacle(points: UnsafeMutablePointer(pts) ,
count: pts.count)
let obGraph = GKObstacleGraph(obstacles: [obstacle1], bufferRadius: 0)
let startPt = GKGraphNode2D(point: vector_float2(5,9))
let endPt = GKGraphNode2D(point: vector_float2(5,5))
let pt3 = GKGraphNode2D(point: vector_float2(0,0))
let pt4 = GKGraphNode2D(point: vector_float2(0,9))
let pt5 = GKGraphNode2D(point: vector_float2(5,0))
let pt6 = GKGraphNode2D(point: vector_float2(10,0))
obGraph.connectNodeUsingObstacles(startPt)
obGraph.connectNodeUsingObstacles(endPt)
obGraph.connectNodeUsingObstacles(pt3)
obGraph.connectNodeUsingObstacles(pt4)
obGraph.connectNodeUsingObstacles(pt5)
obGraph.connectNodeUsingObstacles(pt6)
startPt.connectedNodes
endPt.connectedNodes
pt3.connectedNodes
let path2 = obGraph.findPathFromNode(startPt, toNode: endPt)
print(path2)
I was having the same problem as Jack. I started with Will's code example and translated it to Swift 5.0 in Xcode 10.3. I added it into Xcode's Game project template. I still had the same result: an empty array from findPath(from:to:).
After playing around with the code, I realized that anything physics-related is going to impact pathing. The only way to show code that will work for everyone is to include the creation of SKScene and all SKNode instances. Notice I set gravity to 0 in SKPhysicsWorld and I add no SKPhysicsBody to anything.
Run this in a Playground. You activate the animation by tapping anywhere in the scene.
import PlaygroundSupport
import SpriteKit
import GameKit
class GameScene: SKScene {
let nodeToMove:SKShapeNode = {
let n = SKShapeNode(circleOfRadius: 10)
n.lineWidth = 2
n.strokeColor = UIColor.orange
n.position = CGPoint(x: -200, y: 150)
return n
}()
override func sceneDidLoad() {
addChild(nodeToMove)
let nodeToFind = SKShapeNode(circleOfRadius: 5)
nodeToFind.lineWidth = 2
nodeToFind.strokeColor = UIColor.red
addChild(nodeToFind)
nodeToFind.position = CGPoint(x: 200, y: -150)
let nodeToAvoid = SKShapeNode(rectOf: CGSize(width: 100, height: 100))
nodeToAvoid.lineWidth = 4
nodeToAvoid.strokeColor = UIColor.blue
addChild(nodeToAvoid)
nodeToAvoid.position = CGPoint.zero
let polygonObstacles = SKNode.obstacles(fromNodeBounds: [nodeToAvoid])
let graph = GKObstacleGraph(obstacles: polygonObstacles, bufferRadius: 10.0)
let end = GKGraphNode2D(point: vector2(Float(nodeToMove.position.x), Float(nodeToMove.position.y)))
let start = GKGraphNode2D(point: vector2(Float(nodeToFind.position.x), Float(nodeToFind.position.y)))
graph.connectUsingObstacles(node: end)
graph.connectUsingObstacles(node: start)
graphNodes = graph.findPath(from: end, to: start) as! [GKGraphNode2D]
print("graphNodes = \(graphNodes)")
}
var graphNodes = [GKGraphNode2D]()
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touches.first.flatMap {_ in
let newActions: [SKAction] = graphNodes.map { n in
return SKAction.move(to: CGPoint(x: CGFloat(n.position.x), y: CGFloat(n.position.y)), duration: 2)
}
nodeToMove.run(SKAction.sequence(newActions))
}
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
let scene = GameScene(size: CGSize(width: 640, height: 480))
scene.scaleMode = .aspectFill
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
scene.backgroundColor = UIColor.purple
scene.physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
sceneView.presentScene(scene)
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
The output in the console is:
graphNodes = [GKGraphNode2D: {-200.00, 150.00}, GKGraphNode2D: {62.85, 62.85}, GKGraphNode2D: {200.00, -150.00}]
The start state:
The end state:
Warning: I do not know why it takes an entire second between the user tap and the start of the animation. Performance tuning is a separate topic.
(sorry in advance for obj-c not swift)
It is my impression that adding each vertex point as a connection is not necessary, merely telling the GKObstacleGraph about the GKPolygonObstacle will be enough for it to avoid the generated polgyon shape. I have used the following and received 3 nodes to create a pathway around my obstacle (with a buffer of 10.0f) seen here:
- (void)findPathWithNode:(SKNode *)nodeToFindPath {
NSMutableArray *obstaclesToAvoid = [NSMutableArray array];
for (SKNode *objectInScene in chapterScene.children) {
if ([objectInScene.name isEqualToString:#"innerMapBoundary"]) {
[obstaclesToAvoid addObject:objectInScene];
}
}
/* FYI: The objectInScene is just a black SKSpriteNode with a
square physics body 100 x 100 rotated at 45°
*/
NSArray *obstacles = [SKNode obstaclesFromNodePhysicsBodies:[NSArray arrayWithArray:obstaclesToAvoid]];
GKObstacleGraph *graph = [GKObstacleGraph graphWithObstacles:obstacles bufferRadius:10.0f];
GKGraphNode2D *end = [GKGraphNode2D nodeWithPoint:vector2((float)character.position.x, (float)character.position.y)];
GKGraphNode2D *start = [GKGraphNode2D nodeWithPoint:vector2((float)nodeToFindPath.position.x, (float)nodeToFindPath.position.y)];
[graph connectNodeUsingObstacles:end];
[graph connectNodeUsingObstacles:start];
NSArray *pathPointsFound = [graph findPathFromNode:enemy toNode:target];
NSLog(#"Path: %#", pathPointsFound);
GKPath *pathFound;
// Make sure that there were at least 2 points found before creating the path
if (pathPointsFound.count > 1) {
for (GKGraphNode2D *nodeFound in pathPointsFound) {
// This is just to create a visual for the points found
vector_float2 v = (vector_float2){(float)nodeFound.position.x, (float)nodeFound.position.y};
CGPoint p = CGPointMake(v.x, v.y);
SKShapeNode *shapetoadd = [SKShapeNode shapeNodeWithCircleOfRadius:4];
shapetoadd.name = #"shapeadded";
shapetoadd.fillColor = [UIColor redColor];
shapetoadd.position = p;
[chapterScene addChild:shapetoadd];
}
pathFound = [GKPath pathWithGraphNodes:pathPointsFound radius:10.0];
}
}
Hopefully this points you in the right direction!

Resources