Godot 2D Lighting Basics: Adding Depth with PointLight2D and Shadows

Created: 2025-12-10Last updated: 2026-07-08

How to implement 2D lighting and shadows in Godot with PointLight2D and LightOccluder2D: the three elements of light source, occluder, and subject, how shadows work, when to use DirectionalLight2D instead, and building a flashlight.

Compare a 2D screen that is uniformly, flatly lit with one where a torch glows in a small pool of light and the wall casts darkness behind it. The same map reads as an actual world in the second case. Light and shadow aren't decoration. They create depth and atmosphere and steer the player's attention.

2D lighting in Godot is built on combining PointLight2D (the light) with LightOccluder2D (the occluder that creates shadows). This article covers emitting light, casting shadows, and lighting only specific objects, then builds a flashlight at the end.

Atmospheric lighting in a dark 2D scene: a point light illuminates a character and a wall, and the wall casts a shadow

What You'll Learn

  • The roles of the three elements: light, occluder, and subject
  • Creating light with PointLight2D's Texture (a radial gradient) and Energy
  • How LightOccluder2D casts shadows
  • When to use PointLight2D versus DirectionalLight2D, and building a flashlight

Sponsored

The Three Elements of 2D Lighting

2D lighting in Godot comes from three kinds of nodes working together.

The three elements of 2D lighting. A light (PointLight2D), an occluder (LightOccluder2D), and a subject (Sprite2D) combine into a scene with light and shadow
ElementTypical NodeRole
LightPointLight2D / DirectionalLight2DEmits light. Determines the type, such as a point light or a directional light
OccluderLightOccluder2DThe shape that blocks light and creates shadows. Separate from physics collision
SubjectSprite2D, TileMap, and so onAny node inheriting CanvasItem. Gets lit or hidden in shadow

Thinking of it as three roles (something that emits light, something that blocks it, and something that receives it) makes it easy to keep straight.

Creating Light with PointLight2D

PointLight2D is the basic light that radiates in all directions from a single point. Adding one doesn't produce light on its own. The first step is giving Texture a shape for the light.

Assigning a radial GradientTexture2D (Fill: Radial) to a PointLight2D's Texture makes that gradient the shape of the light

In the Inspector, click the empty Texture slot, choose "New GradientTexture2D," and set its Fill to Radial. That gives you a radial gradient that is bright at the center and fades outward, which is your basic light. From there, tune the following.

  • Energy: the strength of the light (higher is brighter)
  • Color: the color of the light (white by default)
  • Shadow > Enabled: whether this light casts shadows (it's expensive, so enable it only where needed)

Casting Shadows with LightOccluder2D

Shadows don't appear automatically. You have to tell Godot what blocks the light, and that's what LightOccluder2D is for.

How a LightOccluder2D's shape blocks light and creates a shadow on the opposite side of the light source. The blocking shape becomes the shadow

Add a LightOccluder2D as a child node of whatever should cast a shadow (a wall, a pillar, a character), create a new OccluderPolygon2D for its Occluder, and draw the blocking polygon. Three things must line up for a shadow to appear.

  1. Shadow > Enabled is on for the PointLight2D
  2. The occluding object has a LightOccluder2D plus an OccluderPolygon2D
  3. The light's Shadow > Item Cull Mask matches the occluder's mask

When shadows don't show up, check those three first (the FAQ covers this too).

Sponsored

PointLight2D vs DirectionalLight2D

Where PointLight2D radiates outward from a point, DirectionalLight2D pours parallel light across the entire scene from one direction, like the sun or the moon.

A comparison: PointLight2D radiates from a point with shadows stretching in every direction, while DirectionalLight2D is parallel across the scene with shadows stretching one way
TraitPointLight2DDirectionalLight2D
Light shapeRadiates from a pointParallel across the whole scene
Main useTorches, lamps, magic, and other light at a specific positionSun, moon, outdoor ambient light
Shadow directionStretches outward from the light's positionStretches one way based on the light angle
CostGets expensive with many lightsUsually one or two. Relatively cheap

Outdoors, a good combination is setting the overall brightness and shadow direction with DirectionalLight2D first, then adding PointLight2D at cave entrances and campfires.

Hands-On: Building a Flashlight

Now that the three elements are clear, let's build a flashlight that follows the player, points toward the mouse, and drains its battery as it's used. This is how you get "you can only see what's right in front of you" in a horror game, an exploration action game, or a survival game.

A flashlight in action. A cone of light stretches from the player toward the mouse, the battery gauge drains, and brightness changes with energy

Place a PointLight2D named Flashlight as a child of the player.

extends CharacterBody2D

@onready var flashlight: PointLight2D = $Flashlight
@export var max_energy := 1.0
@export var drain_rate := 0.02   # Drain per second
var energy := 1.0

func _ready() -> void:
    energy = max_energy
    flashlight.enabled = false

func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("toggle_light"):
        flashlight.enabled = not flashlight.enabled   # Toggle on/off
        get_viewport().set_input_as_handled()

func _process(delta: float) -> void:
    if not flashlight.enabled:
        return
    energy = maxf(energy - drain_rate * delta, 0.0)   # Drain the battery
    if energy == 0.0:
        flashlight.enabled = false                    # Turn off when it dies
    flashlight.energy = lerpf(0.5, 1.5, energy / max_energy)   # Brightness follows the charge left
    # Aim toward the mouse (for a cone, use a cone-shaped image as the Texture)
    flashlight.rotation = (get_global_mouse_position() - global_position).angle()

func restore_energy(amount: float) -> void:
    energy = minf(energy + amount, max_energy)         # Recharge when a battery is picked up
    if energy > 0.0:
        flashlight.enabled = true

There are two points to take away.

  • Light is just properties: touching flashlight.enabled (on/off), flashlight.energy (brightness), and flashlight.rotation (direction) each frame is all it takes to get a flashlight that dims as the battery runs down.
  • For a cone, swap the Texture: PointLight2D radiates outward, so rotating it leaves the light a circle. When you want a cone of light, use a cone-shaped gradient image as the Texture. For fancier light shapes, Fragment Shader Basics is where you build them.
Sponsored

Common Mistakes and Best Practices

A comparison showing that a pure black CanvasModulate leaves nothing visible, while a slightly lighter gray keeps minimal silhouettes readable
Common MistakeBest Practice
No shadows because there's no LightOccluder2DAdd a LightOccluder2D plus OccluderPolygon2D to the shadow-casting object (or its parent)
Making CanvasModulate pure black so nothing is visibleUse a slightly lighter gray (say #202020) instead of full black to keep minimal visibility
An OccluderPolygon2D with too many vertices, hurting performanceUse the fewest vertices that express the shape. For a TileMap, optimize with the TileSet's Occlusion layer
Enabling shadows on every light and tanking performanceEnable shadows only on the lights that matter for gameplay. Leave them off for atmospheric fill lights
Unintended lighting because Light Mask isn't usedSeparate layers with the light's Item Cull Mask and the subject's Light Mask

Bonus: Good to Know for Later

  • Normal maps add realism: assigning a normal map to a Sprite2D gives the lighting surface detail, making a flat sprite look three-dimensional.
  • Make night with CanvasModulate: place a single CanvasModulate to darken the whole scene, then add lights on top, and you get a night or cave atmosphere instantly.
  • TileMap shadows belong in the TileSet: for the shadows of map walls, use the TileSet's Occlusion layer instead of setting up each tile individually. It's much cheaper.
  • Elaborate light belongs in shaders: flickering flames, chromatic aberration, and the like are the domain of Fragment Shader Basics.

Summary

  • The three elements: light (PointLight2D), occluder (LightOccluder2D), and subject (Sprite2D and friends)
  • Making light: put a radial gradient in PointLight2D's Texture and tune the strength with Energy
  • Making shadows: add LightOccluder2D plus OccluderPolygon2D to the occluder, and enable shadows on the light
  • Choosing between them: DirectionalLight2D for the whole scene, PointLight2D for specific positions

Just one pair of PointLight2D and LightOccluder2D turns a flat 2D scene into a world with light and shadow. Start by lighting a single torch in a dark scene. Next, add sparks with GPUParticles2D Effects or expand your lighting vocabulary with Fragment Shader Basics, and the screen gets richer still.