Source code for core.components.light

"""2D Light components for point lights, spot lights, and light occluders.

Attach a ``PointLight2D`` or ``SpotLight2D`` to an entity with a Transform
to create a light source. Add ``LightOccluder2D`` to entities that should
block light and cast shadows.

The ``LightingSystem`` renders an ambient + light overlay each frame.

Usage::

    entity = world.create_entity("torch")
    entity.add_component(Transform(x=100, y=200))
    entity.add_component(PointLight2D(color=(255, 200, 100), radius=300, intensity=1.0))

    wall = world.create_entity("wall")
    wall.add_component(Transform(x=300, y=200))
    wall.add_component(LightOccluder2D(shape="box", width=50, height=200))
"""
from __future__ import annotations
from core.ecs import Component


[docs] class PointLight2D(Component): """Omnidirectional 2D point light.""" def __init__(self, color: tuple = (255, 255, 255), radius: float = 200.0, intensity: float = 1.0, falloff: float = 2.0): self.color = color # RGB self.radius = max(1.0, float(radius)) self.intensity = max(0.0, float(intensity)) self.falloff = max(0.1, float(falloff)) # Exponent for attenuation curve
[docs] class SpotLight2D(Component): """Directional 2D spot light with a cone angle.""" def __init__(self, color: tuple = (255, 255, 255), radius: float = 300.0, intensity: float = 1.0, falloff: float = 2.0, angle: float = 0.0, cone_angle: float = 45.0, offset_x: float = 0.0, offset_y: float = 0.0): self.color = color self.radius = max(1.0, float(radius)) self.intensity = max(0.0, float(intensity)) self.falloff = max(0.1, float(falloff)) self.angle = float(angle) # Direction in degrees (0 = right) self.cone_angle = max(1.0, min(180.0, float(cone_angle))) # Half-angle of cone self.offset_x = float(offset_x) self.offset_y = float(offset_y)
[docs] class LightOccluder2D(Component): """Light occluder that blocks light and casts shadows. Supports three shape types: - ``"box"`` — axis-aligned rectangle (width × height) - ``"circle"`` — circle defined by radius - ``"polygon"`` — arbitrary convex/concave polygon (list of Vector2 points) When added via the inspector with shape ``"box"`` or ``"circle"``, the size is automatically derived from the entity's ``SpriteRenderer`` if present. """ SHAPE_BOX = "box" SHAPE_CIRCLE = "circle" SHAPE_POLYGON = "polygon" def __init__(self, shape: str = "box", width: float = 50.0, height: float = 50.0, radius: float = 25.0, points: list | None = None, offset_x: float = 0.0, offset_y: float = 0.0, receive_light: bool = False, receive_shadow: bool = False, rotation: float = 0.0): self.shape = shape if shape in ("box", "circle", "polygon") else "box" self.width = max(1.0, float(width)) self.height = max(1.0, float(height)) self.radius = max(1.0, float(radius)) self.offset_x = float(offset_x) self.offset_y = float(offset_y) self.receive_light = bool(receive_light) self.receive_shadow = bool(receive_shadow) self.rotation = float(rotation) # Polygon points (local space, relative to entity transform + offset) self._points: list = [] self.points = points @property def points(self): return self._points @points.setter def points(self, value): from core.vector import Vector2 raw = value or [] converted = [] for p in raw: if isinstance(p, Vector2): converted.append(Vector2(float(p.x), float(p.y))) elif isinstance(p, (list, tuple)) and len(p) >= 2: converted.append(Vector2(float(p[0]), float(p[1]))) if self.shape == "polygon" and len(converted) < 3: converted = [ Vector2(-25.0, -25.0), Vector2(25.0, -25.0), Vector2(25.0, 25.0), Vector2(-25.0, 25.0), ] self._points = converted