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