PujolDeveloper
PujolDeveloper

Reputation: 85

SpriteKit progressive darkness in the Scene

Does anyone know how to bring progressive darkness on a SKScene using SpriteKit?

I tried using a SKLightNode, which works great to have "full" darkness in the scene and some light sources.

I also tried to have a Node in front of everything else where I adjust the alpha to get darker and darker. which works great if there is on light source

But the 2 solutions doesn't work together.

In my example, the blue bar on the buttom control the alpha of the "darkness" node (left = 0 so fully transparent, right = 1 so fully dark), and the white part on the buttom left is to swift on and off the light.

My goal would be to use the bar on the buttom to go from light on to light off, with a gradual transition.

class GameScene: SKScene {

override func didMove(to view: SKView) {
    let background = SKSpriteNode(color: .lightGray, size: view.frame.size) //imageNamed: "background")
    background.zPosition = 1
    background.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
    background.lightingBitMask = 0b0001
    addChild(background)
    
    let character = SKSpriteNode(color: .blue, size: CGSize(width: 100, height: 100))//imageNamed: "character")
    character.zPosition = 2
    character.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
    character.lightingBitMask = 0b0001
    character.shadowCastBitMask = 0b0001
    addChild(character)
    
    let lightNode = SKLightNode()
    lightNode.name = "SKLightNode"
    lightNode.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
    lightNode.categoryBitMask = 0b0001
    lightNode.lightColor = .white
    //lightNode.ambientColor = .white
    lightNode.isEnabled = false
    addChild(lightNode)

    // Control
    let elementSize: CGFloat = 40
    
    let lightToggleSize = CGSize(width: elementSize, height: elementSize)
    let lightToggle = SKSpriteNode(color: .white, size: lightToggleSize)
    lightToggle.name = "LightToggle"
    lightToggle.zPosition = 10000
    lightToggle.position = CGPoint(x: view.frame.width - (elementSize / 2), y: elementSize / 2)
    addChild(lightToggle)
    
    let sideBarSize = CGSize(width: view.frame.width - elementSize, height: elementSize)
    let sideBar = SKSpriteNode(color: .blue, size: sideBarSize)
    sideBar.name = "SideBar"
    sideBar.position = CGPoint(x:(view.frame.width - elementSize) / 2, y: elementSize / 2)
    sideBar.zPosition = lightToggle.zPosition
    addChild(sideBar)

    
    let darknessNodeSize = CGSize(width: view.frame.width , height: view.frame.height)
    let darknessNode = SKSpriteNode(color: .black, size: darknessNodeSize)
    darknessNode.name = "DarknessNode"
    darknessNode.position = CGPoint(x: view.frame.width / 2, y: view.frame.height / 2)
    darknessNode.alpha = 0
    darknessNode.zPosition = lightToggle.zPosition - 1
    addChild(darknessNode)
}

func handleTouches(_ point: CGPoint) {
    let darknessNode = childNode(withName: "DarknessNode") as! SKSpriteNode
    let lightNode = childNode(withName: "SKLightNode") as! SKLightNode
    let sideBar = childNode(withName: "SideBar")!
    let lightToggle = childNode(withName: "LightToggle") as! SKSpriteNode

    if lightToggle.contains(point) {
        lightNode.isEnabled = !lightNode.isEnabled
        lightNode.position = CGPoint(x: lightNode.position.x + 1, y: lightNode.position.y)
    } else if sideBar.contains(point) {
        darknessNode.alpha = point.x / sideBar.frame.width
    } else {
        lightNode.position = point
    }
}

override func touchesBegan(_ touches: Set<UITouch>,
                           with event: UIEvent?) {}

override func touchesMoved(_ touches: Set<UITouch>,
                           with event: UIEvent?) {
    handleTouches(touches.first?.location(in: self))
}

override func touchesEnded(_ touches: Set<UITouch>,
                           with event: UIEvent?) {
    handleTouches(touches.first?.location(in: self))
}
}

Upvotes: 2

Views: 86

Answers (1)

PujolDeveloper
PujolDeveloper

Reputation: 85

If anyone is interested by this topic, here is the solution I found: change the alpha of the source.

It works well on a Playground project but on my real one the local light is having a weird flat shape (I posted a question here

This is what it looks like.

enter image description here

And the solution is bellow.

  • There is a green background and blue box on the scene.

  • There are 2 white boxes that are switches for the Background light (that simulate the sunlight, which is off screen) and the "local" light (that simulate a light source)

  • The white bar is to control the alpha of the background light (on the left the alpha is 0 and there is no light and the right the alpha is 1 and the light is full on). In the same time as I change the alpha of the background light, I also change the alpha of the local light for better look.

    import Foundation import SpriteKit

    class GameScene: SKScene {

      var background: SKSpriteNode!
      var object: SKSpriteNode!
      var backgroundLight: SKLightNode!
      var localLight: SKLightNode!
      var backgroundLightSwitch: SKSpriteNode!
      var backgroundLightBar: SKSpriteNode!
      var localLightSwitch: SKSpriteNode!
      var isBackgroundLightOn = false
      var isLocalLightOn = false
      var selectedElement: SKSpriteNode? = nil
    
      class func newGameScene() -> GameScene {
          let scene = GameScene()
          scene.scaleMode = .resizeFill
          return scene
      }
    
      override func didMove(to view: SKView) {
          super.didMove(to: view)
    
          let objectSize = CGSize(width: 100, height: 100)
    
          background = createSpriteNode(color: .green, x: view.frame.width / 2, y: view.frame.height / 2, width: view.frame.width, height: view.frame.height, lightMask: 1)
          object = createSpriteNode(color: .blue, x: background.position.x, y: background.position.y, width: objectSize.width, height: objectSize.height, lightMask: 1)
          backgroundLightSwitch = createSpriteNode(color: .white, x: view.frame.width / 4, y: view.frame.height / 4, width: objectSize.width / 2, height: objectSize.height / 2, lightMask: 2)
          backgroundLightBar = createSpriteNode(color: .white, x:view.frame.width * 2 / 3, y: backgroundLightSwitch.position.y, width: view.frame.width / 2, height: objectSize.height / 2, lightMask: 2)
          localLightSwitch = createSpriteNode(color: .white, x: backgroundLightSwitch.position.x, y: view.frame.height / 8, width: objectSize.width / 2, height: objectSize.height / 2, lightMask: 2)
    
          let color = NSColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
          backgroundLight = createLightNode(color: nil, ambiantColor: color, x: -view.frame.width * 2, y: -view.frame.height * 2)
          localLight = createLightNode(color: color, ambiantColor: nil, x:view.frame.width * 2 / 3, y: view.frame.height * 2 / 3)
      }
    
      func createSpriteNode(color: NSColor, x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat, lightMask: UInt32) -> SKSpriteNode {
          let nodePosition = CGPoint(x: x, y: y)
          let nodeSize = CGSize(width: width, height: height)
          let node = SKSpriteNode(color: color, size: nodeSize)
          node.zPosition = 1
          node.position = nodePosition
          node.lightingBitMask = lightMask
          addChild(node)
          return node
      }
    
      func createLightNode(color: NSColor?, ambiantColor: NSColor? , x: CGFloat, y: CGFloat) -> SKLightNode {
          let light = SKLightNode()
          light.falloff = 1.5
          light.position = CGPoint(x:x, y: y)
          light.isEnabled = false
          light.categoryBitMask = 1
    
          if color != nil {
              light.lightColor = color!
          }
          if ambiantColor != nil {
              light.ambientColor = ambiantColor!
          }
          addChild(light)
          return light
      }
    
      func getSelectElement(location: CGPoint) -> SKSpriteNode? {
          let elements: [SKSpriteNode] = [backgroundLightSwitch, localLightSwitch, backgroundLightBar]
          for element in elements {
              if element.contains(location) {
                  return element
              }
          }
          return nil
      }
    
      func switchLight(_ light: SKLightNode, selected: Bool) -> Bool {
          light.isEnabled = selected
          light.position.x += selected ? 1 : -1
          return selected
      }
    
      func getAlpha(node: SKSpriteNode, location: CGPoint) -> CGFloat {
          if location.x < node.frame.minX {
              return 0
          } else if location.x > node.frame.maxX {
              return 1
          } else {
              return (location.x - node.frame.minX) / node.frame.width
          }
      }
    
      func changeAlpha(_ alpha: CGFloat) {
          backgroundLight.ambientColor = NSColor(red: 1.0, green: 1.0, blue: 1.0, alpha: alpha)
          localLight.lightColor = NSColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1 - alpha)
    
          backgroundLight.position.x -= 1
      }
    
      override func mouseDown(with event: NSEvent) {
          let location = event.location(in: self)
          selectedElement = getSelectElement(location: location)
      }
    
      override func mouseDragged(with event: NSEvent) {
          if selectedElement == backgroundLightBar {
              let location = event.location(in: self)
              let newAlpha = getAlpha(node: backgroundLightBar, location: location)
              changeAlpha(newAlpha)
          }
      }
    
      override func mouseUp(with event: NSEvent) {
          let location = event.location(in: self)
          let newSelectedElement = getSelectElement(location: location)
    
          if selectedElement == newSelectedElement {
              if selectedElement == backgroundLightSwitch {
                  isBackgroundLightOn = switchLight(backgroundLight, selected: !isBackgroundLightOn)
              } else if selectedElement == localLightSwitch {
                  isLocalLightOn = switchLight(localLight, selected: !isLocalLightOn)
              }
          }
      }
    

    }

Upvotes: 1

Related Questions