Who: Percy Legendre, developer at Half Human Games
My name is Percy Legendre and 2 years ago, Peter Milko and I founded Half Human Games, the small indie studio working on Dwerve, a Zelda-like action RPG with tower defense combat. As indies, we juggle multiple responsibilities. I do programming, game design, writing, and even art at times.
With a small team of just 2 full-time developers, we had to come up with creative ways to make our game look and feel unique without investing too much time.
What: Dynamic lights and shadows for 2D games
We knew from the very beginning that we wanted to push the boundaries of SNES-style pixel graphics with Dwerve. We wanted the player to dungeon crawl through dimly lit dungeons to further immerse them in our fantasy world.
When it came to lighting, a combination of technical and creative skills helped us add beautiful 2D lighting to Dwerve using a Unity Asset called Smart Lighting 2D. Hopefully you can still take away insight from this article even if you use other game engines or lighting systems.
Create a new Unity project using the 2D template and import Smart Lighting 2D into the project. Make a new scene and add a Unity Tilemap by right clicking the Hierarchy window and selecting
2D Object/Tilemap. Make or find a dungeon tileset. Itch.io has a ton of free top-down tilesets. For this article, I am using Pita's Dungeon Tileset which we used as a base for Dwerve assets early on in development. I will recreate the scene below with lighting:
In the sprite import settings, set the
Sprite Mode to
Pixels Per Unit to
16, and the
Filter Mode to
Point (no filter). Then open the
Sprite Editor and slice the grid into 16 x 16 cells.
You will notice that many sprites are larger than 16 x 16 pixels and are cut into 2 or 4 separate sprite slices. Fix each one by deleting the extra sprite slices and resizing the other to encompass the sprite entirely. Make sure to keep the length and width a multiple of 16. In the image below, the red outline shows the generated sprites, and the blue outline shows what you need to make it look like.
Pivot Unit Mode to
Custom Pivot to
(8, 8). This makes sure the sprite will align properly to the tilemap grid.
Don't forget to adjust the positions and pivots of the torch sprites and any other large sprites. When you are done, click the
Apply button to save the changes.
Next we need to generate tiles from the Tile Palette window. Open the window from the top toolbar by selecting
Window/2D/Tile Palette. Then create a new tile palette called
Dungeon Palette and save the tile palette to a new folder
To automatically generate tiles from sprites, just drag the sprites from
Assets onto the Tile Palette window. When the
Select Folder dialog pops up, create a new folder
Assets/Tiles and generate the tiles into that folder.
Your Tile Palette window should now look like this:
Before we start painting our tilemap, let's duplicate the
Tilemap game object in the Hierarchy window. Rename the first tilemap game object to
Ground Tilemap and rename the second one to
Obstacle Tilemap. For the
Obstacle Tilemap, set the
Order in Layer property of the
Tilemap Renderer component to
10 so it renders over the
Now paint your tilemap. Make sure to paint the ground tiles on the
Ground Tilemap and the walls, pillars, and props on the
Obstacles Tilemap. Skip the torches for now, we will add these later. I am going to paint the tilemap shown at the beginning of this article, but feel free to paint your own.
In the image above, take a look at the furniture by the wall in the bottom right. Notice how the wall is rendering over the furniture. We can fix this in the Project Settings. Open the Project Settings window from the top toolbar by selecting
Edit/Project Settings.... Then select
Graphics. Under the
Camera Settings section, set
Transparency Sort Mode to
Custom Axis and
Transparent Sort Axis to
(0, 1, 0). Now the furniture tiles should renderer over the wall tiles.
Next we want to add a collider to our tilemap. Add a
Tilemap Collider 2D component to the
Obstacle Tilemap game object, and tick the
Used by Composite checkbox. Then add a
Composite Collider 2D component. This will also automatically add a
Rigidbody 2D component. Change the rigidbody
Body Type to
Static. Select the
Obstacle Tilemap game object. Your collider should look similar to the image below.
You might notice a problem with this collider. We want the collider to only be at the base of the objects, not entirely encompass the sprites. Open the Sprite Editor window from the texture import settings and edit the custom physics shapes for the sprites by selecting
Sprite Editor/Custom Physics Shape in the top right. Edit the custom physics shapes to include the base of the objects as shown in the image below.
Obstacle Tilemap in the hierarchy. If the colliders do not update automatically, disable and re-enable the
Tilemap Collider 2D component and the colliders will update. Your colliders should now look like this.
Next let's add the torch tiles. Select the
Grid game object and create a new tilemap by right clicking and selecting
2D Object/Tilemap. Rename the tilemap
Torch Tilemap and set the
Order in Layer property of the
Tilemap Renderer component to
Now create a new animated tile by right clicking in the Project window and selecting
Create/2D/Tiles/Animated Tile. Rename the tile
Pillar Torch Tile and select it. In the Inspector, click the lock icon to lock the Inspector. This prevents it from inspecting a sprite when we select the sprite to drag it into the box.
Drag the torch sprites into the box. Now set both
Minimum Speed and
Maximum Speed to
10. Finally, click the lock icon to unlock the Inspector. Repeat these last couple of steps to create the
Wall Torch Tile. Do not forget to unlock the Inspector when you are done.
Now drag the newly created torch tiles onto the Tile Palette window like we did earlier with the sprites. Note that we will use these new animated torch tiles and not the ones that got auto-generated earlier.
Now paint a couple of torches on the
Torch Tilemap. Keep in mind the torch on the left that sits higher is for pillars while the one on the right that sits lower is for walls. When you are done, enter Play Mode so you can see the animated torches.
Now we are ready to add lighting. Create the
Lighting Manager 2D by right clicking the Hierarchy and selecting
2D Light/Light Manager. If you enter Play Mode, the entire screen will be black since there are no lights.
Add a light source by right clicking the Hierarchy and selecting
2D Light/Light Source. Rename the game object to
Torch Light and drag it into a
Assets/Prefabs to create a prefab. Now duplicate the
Torch Light and drag it over each torch. Place it over the ground just in front of the pillar or wall, so that the light source itself is not located inside any obstacle.
Enter Play Mode to check out the lighting.
You will notice that walls and obstacles do not block the light. Exit Play Mode and add a
Lighting Tilemap Collider 2D component to the
Obstacle Tilemap game object. Enter Play Mode to see how it looks.
Not exactly what we want. The shadows casts around the tile instead of the physics shapes. By default, the
Collision Type is set to
Tile. Since we want shadows to cast around the base of the obstacles, set
Collision Type to
Sprite Custom Physics Shape. Enter Play Mode. It should now like this this:
These shadows also look a bit too dark. Let's add more transparency. Open the Lighting 2D window from the top toolbar by selecting
Tools/Lighting 2D. Click the
Preferences tab if it is not already selected. Set
Shadow Darkness to
.85. Let's also give our light a warmer tint and make the light size smaller. Open the
Torch Light prefab and set the
Color property to a light tan color. I decided to use
#FFAF64. Next set the
Size property to
3. Enter to Play Mode to check out the changes.
Next let's update our torch light to include a nice glow and particles. Open the
Torch Light prefab and make the root game object an empty game object, and move the
Lighting Source 2D component to a child game object called
Light Source. Create two more game objects under
Torch Light called
Light Glow and
Ember Particles. Your Hierarchy should look like the image below.
The light source is positioned on the ground, but we want the glow and embers over the torch. Set the
Position of the
Light Glow and
Ember Particles game objects to
(0, .5, 0). Next add a
SpriteRenderer component to the
Light Glow game object. Set the
Color to a light yellow color. I decided to go with
#CD7319. Also set the
Order in Layer to
20 so it renders over any obstacles. Then set the
Sprite to a soft circle texture. I made a custom texture that looks like a donut. Feel free to download and use it.
Next create a new material called
Additive Particle Material in
Assets/Materials and set the Shader to
Legacy Shaders/Particles/Additive. Set the
Material property to use this new material. Let's also add a script to make the glow flicker. Download SpriteAlphaFlicker.cs and put it in
Assets/Scripts. Then add the component to the
Light Glow game object. Enter Play Mode to check it out.
Next let make ember particles. Add a
Particle System component to the
Ember Particles game object. Here are the settings I used:
Now the torch has little embers that float upwards. Enter Play Mode to check out the flickering glow and the ember particles.
Let's also add a vignette to darken the edges of the screen. Open the Package Manager window from the top toolbar by selecting
Window/Package Manager. Then select
Post Processing from the package list and click
Install. Then create a new layer called
Next add the
Post Process Layer component to the
Main Camera game object. Under the
Volume blending section, set
Create a game object called
Post Process Volume and set the game object layer to
Post Processing. Add the
Post Process Volume component to it and tick the
Is Global checkbox. Then press the
New button to create a new profile. Click
Add effect.../Unity/Vignette. Here are the settings I decided to use.
I also decided to bump the brightness and contrast a bit. Click
Add effect.../Unity/Color Grading. Here are the settings I decided to use.
Now enter Play Mode to see how it looks with the vignette and color grading.
Next let's add a light source that follows the mouse around so we can see the dynamic lighting in action. Download FollowMouse.cs and put it in
Assets/Scripts. Duplicate one of your torch game objects and rename it
Mouse Torch. Add the
Follow Mouse component to it. Finally, disable the
Light Glow and
Ember Particles child objects. Enter Play Mode and move the mouse around so see the dynamic lighting in action.
Dungeon crawling through dimly lit dungeons with dynamic lighting sets the mood and makes the environments come to life. It further immerses the player in the various cave biomes that exist in the game world. Our goal was never to stick to the strict "old-school pixel art" with pixel perfect cameras and limited color palettes. Our goal, in addition to invoking nostalgia with SNES-inspired pixel graphics, is to immerse the player in a deep fantasy world. Adding dynamic lighting helped us a accomplish this.
It took us a long time to find a lighting system that had all of the features we wanted for Dwerve: tilemap support, soft shadows, deep customization, and high performance. When we discovered Smart Lighting 2D, it had just been released and lacked minor features and had mild performance bottlenecks. Neck deep in development with still a long road ahead, we took a risk and decided to stick with the asset. And we are glad we did!
It took a lot of experimentation to get the aesthetic we wanted. The developer FunkyCode provided immense support, added missing features, and optimized performance bottlenecks over time. Smart Lighting 2D has since evolved into a mature asset with a stable codebase, deep customization, and high performance. Even still, FunkyCode continues to develop, improve, and support the asset. The various examples, thorough documentation, and active community also provide additional support.
I really hope this modest overview of how we use Smart Lighting 2D to add dynamic lighting to Dwerve will help you improve your next game, and inspire you to find other creative ways to push your aesthetics further.