Copy the assets from to the GitHub project into your project folder. The sprites are located in LightingDemo/LightingDemo/Sprites.
In the GitHub repository, you'll find a file called spriteilluminator-project.sip. It contains the animation and background images.
The project also contains pre-built normal maps for this tutorial.
Simply create a new empty SpriteKit project for iOS devices.
Drag & drop the Sprites folder onto the project.
This code determines the screen size and derives a content scale factor _scale. The scale factor allows showing 1 background tile (1920px) on the screen.
We are only using 1 set of graphics at the highest resolution that we scale down to match the different devices. We are doing this to keep the tutorial as short as possible. For a real project it is highly recommended that you use pre-scaled versions of your graphics that match your devices resolution. This will improve the frame rate.
If you start this project you'll simply see … nothing.
Let's now add our sprite animation. This is a bit more tricky since SpriteKit does not have a way to animate normal mapped sprites out of the box. You have to create a callback action that applies the new frame and the normal map.
Create the initSprite function. This first block creates 2 arrays, with SKTexture objects for the animations phases of the sprite and the normal map. It simply iterates over the animation frames building the file names from the string format. The character animation currently has 8 frames: 1..8.
The important line is sprite.lightingBitMask = 1 which assigns the light to a light source. The lights have a property called lightCategoryBitMask. A light shines on a sprite if at least one bit is set in both — the lightCategoryBitMask and lightingBitMask.
Compile and run. You should now see our character walking in front of a gray screen. Nice - but where is the light?
A light is implemented using an SKLightNode. The node itself is invisible. This is why you'll add a sprite with a light symbol to have a better reference where it is positioned.
Now create an SKLightNode and attach it to the sprite. Note that if you don't want the light bulb icon you can simply add the light to the scene instead. It's derived from SKNode so it contains all the functions for positioning etc.
The light has it's own color - lightColor which you can change to create different effects.
ambientColor is a background light - it does not have any direction and shines on all pixels of a sprite.
falloff changes how far the light is visible.
Compile and run - way better! Now you see the light's effect.
Let's finally add some background. A scrolling background fits this scene best. To give the scene some more depth, you'll add 2 layers: A foreground which scrolls with the character and a background.
The background is far away — so the influence of the light source should not be visible. This is easily done by adding a background sprite that is tinted by our ambient light. Set the ambient color on the sprite's color property and set the colorBlendFactor depending on how intense the tinting should be.
No need to apply any light source or normal map.
The foreground gets a normal map to react on the light. Don't forget to set lightingBitMask to match the light.
iOS simulator is slow — the emulation with shaders takes a huge amount of performance. This is not only with SpriteKit but also in cocos2d and other frameworks.
The performance on the real hardware is ok - so don't worry too much about this.
We use the high-quality images and scale them down at runtime. If you pre-scale the images to match the devices performance will be way better.
x: _scale*((backgroundOffset < -1920) ?
x: _scale*((foregroundOffset < -1920) ?
The function called update is called before the next frame is rendered. It gets the currentTime as parameter. The function simply uses the time to update the position of the sprites. The speed factor for the foreground is 250, the factor for the background is 100 — this is why the foreground moves faster than the background — giving the impression of depth.
In case you are wondering what the ?: operator is doing in _backgroundSprite1 and _foregroundSprite1: It places the sprite before or after the second sprite depending on the scroll position.