How can I make tilemap walls with an occluder show and cast shadows?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By uncleshelby

I’m working on a basic tilemap-based 2d roguelike game. Sort of. Anyway, I’m trying to use the a Light2D as the light source for the player character, with walls, floor, etc. in a tilemap. So I have a basic wall tile in my tileset which has an occluder covering the entire 32x32 tile. So if I enable Shadow in my Light2D, using a basic CanvasModulate as the fog of war, I get this:

field of view with black walls

So the shadows are working as I want them to, but the walls themselves aren’t visible. As an example, here’s what I want it to look like:

field of view with visible walls

So I guess I want some way of casting shadows from the back of the polygon, but for the light to still affect the wall tiles themselves. I’ve tried messing with the cull mode of the occluder polygon, but the best I get is this:

field of view with weird cull mode walls

I’ve also tried creating a second tile map and light source which will allow the wall tiles to be highlighted seperately, but then walls behind other walls that should be hidden are visible.

Any ideas? Is there any way I can make these walls show up and cast shadows? Would it be possible if I completely switch away from TileMap and place individual spirtes? I’m lost at this point. Any help would be greatly appreciated.

I had a problem with this once, I think I solved it with the Z coordinate.

mateusak | 2018-02-14 14:55

I am having this same issue. I was able to get the walls in my game to show up un-obscured by shadows when I re-created them as a scene/nodes with a Sprites and LightOccluder2Ds. However, going this route forgoes all the advantages for using a TileMap, which I’d prefer to avoid.

Additionally, I’m having the issue where the shadows cast by the TileMap walls are showing up grey rather than black, and I’ve been unable to find setting that actually changes the shadow color. (Dropbox link to screenshot.)

Does anyone have an idea of how to resolve these issues?

unwiseone | 2019-03-22 16:38

Having the exact same issue. Did anyone ever end up figuring this out?

Krydan | 2019-04-06 22:56

:bust_in_silhouette: Reply From: Wibbly

So, I was finally able to make it work. All I did was to set the LightOccluder2D into a different Light Mask than the Light2D.
:)

Hello! Can you provide an example?
It’s a very interesting question

mr.cobreen | 2019-09-27 21:33

Ok I got it. but there are still a problem…
transparency problem

As you can see, there are a problem with transparent tiles.
I see the same problem in your example)
also my demo project:
https://gitlab.com/mr.cobreen/godot-projects (too big labyrynth “initial commit”)

mr.cobreen | 2019-09-28 15:42

:bust_in_silhouette: Reply From: llasram

This is an old question, but I ran into the same issue and had the same incomplete understanding of the Godot 2D lighting system prompting it. And the one posted answer is wrong, or at least woefully incomplete.

The key concept is that the 2D light system is a physically-inspired model of light in a completely 2D space, optionally allowing line segments in the space (and hence polygons composed of them) to block light and cast shadows when the light enters from one or both sides of the line segment. Nothing in the light system has any concept of tiles, and every way of integrating tiles with the engine 2D light system – including built-in TileMap – must work in terms of tiling occlusion polygons, which necessarily results in walls covered by tile-sized occlusion polygons casting shadows on each other.

To see why there probably couldn’t be an engine-based solution within the existing light model, consider looking down a line of wall tiles. If the line of tiles is viewed obliquely, with all the tiles directly visible, then none of the tiles should cast shadows on each other. But if the line of tiles is viewed head-on, then the first tile should cast a shadow on all the subsequent tiles. I do not believe it is possible to implement this behavior using only the primitives provided by the Godot 2D light system and no additional code.

So, what I believe to be the best/real solution:

  • Implement tile-aware visibility behavior in your own game logic. Using 2D shadowcasting for the visibility algorithm allows the result to be integrated into the engine 2D light system by placing occlusion line segments at the boundaries between visible and non-visible tiles. I have managed to implement this and get exactly the effect I want.

Partial/alternative solutions, which may work for some people:

  • Work entirely within the engine 2D lighting system by designing your tiles to incorporate hard visibility barriers. For walls for example, this could involve designing wall tiles to have a visibility barrier down the center/cornering for every possible orientation; or splitting walls into two sets of tiles, with one including the majority of the visible portions and the other primarily/exclusively just blocking light.
  • Give the wall texture tiles a different light mask from their associated occluders, and set the shadow mask of all lights not to include the wall textures. This prevents any wall from casting shadows on any wall, which leaves all walls visible, but does cast shadows on other objects with the appropriate light mask. (This is what I believe Wibbly is describing in their posted answer.)
  • Use a CanvasItem shader with a light processor function to redirect undesired shadows. This one is purely speculative, as I haven’t spent enough time to see if could be made to work. Within CanvasItem shaders, the light() function can modify the value of SHADOW_VERTEX to redirect a shadow which would land on a given vertex to land on another vertex instead. The problem is knowing which shadows are undesired, which probably devolves once more to implementing tile-based visibility, only now either doing it in a shader or communicating it to a shader.

Hopefully that definitively answers this question!

Update: I’ve created a demo project demonstrating this technique – llasram/godot-visibility-demo.

Hey, would you mind if I ask you to please expand on your solution?
perhaps provide a demo because I’m having trouble implementing a visibility algorithm,
but you don’t have too if you don’t want.

FellowGamedev | 2020-09-17 02:18

I’ll try to put together a simple demo, but the visibility algorithm is described much better than I could in lots of places; e.g. What the Hero Sees and the FOV topic on RogueBasin.

To wire-in the occlusion for a single player light, you need to place culling occlusion line-segments on every edge of visible walls that isn’t shared with another visible wall. I hacked that together by having two more TileMap instances with transparent tiles and occlusion polygons together allowing each side to be set independently by selecting the right combination of tiles from the two (since there can only be one occlusion polygon per tile). The “right” way to do this is almost certainly to use the underlying visual server APIs, but the tile map hack worked for now.

I did discover that there’s a bit more work to be done in order to support having other lights; then there also needs to be something masking out where the player doesn’t have visibility. But that’s independent of the light system itself – it just beaks there being a strict relationship between “illuminated” and “player-visible.”

llasram | 2020-09-18 15:53

@FellowGamedev As I updated the answer to mention, I’ve created an example project demonstrating this technique – llasram/godot-visibility-demo.

llasram | 2021-03-03 02:17

:bust_in_silhouette: Reply From: WAT0078

I made a fully working example here: GitHub - walter1978/IsometricLights2DTest