Overview
In 2D games made with Godot Engine, lighting plays a role beyond mere decoration. Properly configured light and shadow give scenes depth and atmosphere, guide player attention, and dramatically enhance the game's world.
This article focuses on the PointLight2D node, the core of Godot's 2D lighting system, and explains shadow setup methods, while addressing common stumbling points for beginners to intermediate developers and introducing practical best practices.
By reading this article, you'll learn:
- Basic structure and mechanics of Godot 2D lighting
- Basic usage and main properties of PointLight2D node
- Dynamic shadow generation using LightOccluder2D node
- Common lighting setup mistakes and their solutions
Core Concepts
Godot's 2D lighting system consists mainly of three elements:
| Element | Node | Role |
|---|---|---|
| Light source | PointLight2D / DirectionalLight2D | Emits light and illuminates the scene. PointLight2D acts as a point light, DirectionalLight2D as parallel light like sunlight. |
| Light-blocking object | LightOccluder2D | Defines boundaries that block light and generate shadows. |
| Lit object | Nodes inheriting CanvasItem (Sprite2D, TileMap, etc.) | Objects affected by light sources, casting shadows or hidden in shadows. |
How PointLight2D Works
PointLight2D is a node that radiates light in all directions from a specific point. The light's shape is defined by the texture set in the Texture property. Typically, you use a gradient texture that's bright in the center and darker toward the edges.
Tip: You don't need external tools to prepare images. Select
New GradientTexture2Dfrom theTextureproperty dropdown and setFillto Radial to create beautiful light gradients within the editor.
Main Properties:
- Texture: Texture defining light shape.
- Color: Light color.
- Energy: Light intensity.
- Mode: How light blends with scene (Add, Sub, Mix, Mask). Default
Addis most common.
Shadow Generation: LightOccluder2D
To generate shadows, you need a light source (PointLight2D) and a LightOccluder2D node to block light.
- PointLight2D settings: Turn on
Shadow > Enabled. - LightOccluder2D settings: Add a
LightOccluder2Dnode to objects you want to cast shadows (walls, obstacles, etc.) and set an OccluderPolygon2D resource to theOccluderproperty. This polygon becomes the light-blocking boundary.
Common Pitfalls
Here we explain representative mistakes and misconceptions that beginners often encounter with 2D lighting.
Shadows Not Showing at All
If shadows don't appear even after turning on Shadow > Enabled in PointLight2D, the most common cause is missing LightOccluder2D nodes.
- Mistake: Thinking shadows will automatically generate just by placing
PointLight2Dand enablingShadow. - Truth: In Godot's 2D lighting system, you need to explicitly define boundaries to block light with
LightOccluder2Dnodes. This is separate from collision shapes (CollisionShape2D).
Entire Scene Goes Dark
If the entire scene darkens the moment you place PointLight2D, or areas not hit by light become pitch black, the cause may be CanvasModulate node or WorldEnvironment node settings.
- Solutions:
- CanvasModulate: If there's a
CanvasModulatenode at the scene root, check if itsColoris black (#000000). If black, the entire scene darkens. - LightOccluder2D placement error: A
LightOccluder2Dmay be unintentionally set on an object blocking light covering the entire scene.
- CanvasModulate: If there's a
Note:
WorldEnvironmentnode is mainly for 3D scenes. If mixing 2D/3D, ambient light settings may affect things, but for pure 2D games, first checkCanvasModulatecolor and each sprite'sModulateproperty.
Light Source Texture Looks Unnatural
The default PointLight2D texture may look unnatural due to abrupt light falloff.
- Solution: Set a custom gradient texture with smoother falloff to the
PointLight2D'sTextureproperty. Also, adjust theTexture Scaleproperty to widen or narrow the light range for a more natural look.
Shadow Shape is Wrong or Inverted
Even after setting up LightOccluder2D, shadows may appear in strange directions or the inside of objects may become shadowed.
- Solution: The vertex order (clockwise/counter-clockwise) when drawing OccluderPolygon2D may be affecting this. Try changing the
Cull Modein the inspector, or change the direction you draw polygon vertices.
Best Practices and Examples
Here we introduce practical techniques for achieving more professional 2D lighting.
Efficient LightOccluder2D Setup
When using TileMaps, setting LightOccluder2D individually on all tiles is inefficient.
Recommended method:
- Set Occluders in TileSet: Set
Occlusion Layerfor tiles that should block light (walls, etc.) within theTileSetresource. This makes the entireTileMapnode function as a singleLightOccluder2D, improving performance. - OccluderPolygon2D optimization: When creating
OccluderPolygon2D, keep vertex count to the minimum necessary. Overly complex polygons increase rendering cost and impact performance.
Dynamic Light Source Control with GDScript
Dynamically controlling light sources based on player movement or game events can add tension and realism to gameplay.
For example, if the player holds a flashlight, make that light source follow the player with energy that decreases over time.
# Player.gd (assumes PointLight2D node as child)
extends CharacterBody2D
@export var max_light_energy: float = 1.5
@export var energy_drain_rate: float = 0.05 # Drain per second
@onready var flashlight: PointLight2D = $PointLight2D
func _process(delta: float) -> void:
# Optionally rotate light source based on player direction
# var mouse_pos = get_global_mouse_position()
# flashlight.rotation = (mouse_pos - global_position).angle()
# Gradually decrease energy
if flashlight.energy > 0.0:
flashlight.energy -= energy_drain_rate * delta
flashlight.energy = max(0.0, flashlight.energy)
# Toggle flashlight on/off
if Input.is_action_just_pressed("toggle_light"):
flashlight.enabled = not flashlight.enabled
# Auto-off when energy reaches zero
if flashlight.energy == 0.0:
flashlight.enabled = false
# Function to restore energy with items, etc.
func restore_energy(amount: float) -> void:
flashlight.energy += amount
flashlight.energy = min(max_light_energy, flashlight.energy)
if not flashlight.enabled:
flashlight.enabled = true
Note: The example above uses an input action called
toggle_light. Addtoggle_lightin "Project Settings" → "Input Map" and assign a keyboard or gamepad button.
Using Light Occlusion Mask Layers
In Godot 4, combining Light2D node's Item Cull Mask and CanvasItem node's Light Mask properties allows fine control over which light sources illuminate which objects.
For example, to separate "normal light" and "special magic light", configure as follows:
- Normal light (
PointLight2D): SetRange > Item Cull Maskto Layer 1. - Magic light (
PointLight2D): SetRange > Item Cull Maskto Layer 2. - Normal objects (
Sprite2D, etc.): Turn on Layer 1 inVisibility > Light Mask. - Objects responding only to magic light: Turn on Layer 2 in
Visibility > Light Mask.
This allows logical separation of lighting scope even in complex scenes and contributes to performance optimization.
Note: Property names have changed in Godot 4. Light source side is
Range > Item Cull Mask, lit object side isVisibility > Light Mask.
Summary
Understanding the combination of PointLight2D and LightOccluder2D is the key to success with Godot's 2D lighting.
- PointLight2D: The light source itself. Adjust light appearance with texture and energy.
- LightOccluder2D: Boundary for casting shadows. Essential node for generating shadows.
Once you master these basics, your 2D game will evolve from flat expression to a world rich with depth woven by light and shadow. For next steps, we recommend challenging Normal Maps for more realistic lighting expression or extending light expression with custom shaders.