Lighting Demo#

Screen shot of a Defender clone with a bloom/glow effect.
light_demo.py#
  1"""
  2Show how to use lights.
  3
  4Artwork from https://kenney.nl
  5
  6If Python and Arcade are installed, this example can be run from the command line with:
  7python -m arcade.examples.light_demo
  8"""
  9import arcade
 10from arcade.experimental.lights import Light, LightLayer
 11
 12SCREEN_WIDTH = 1024
 13SCREEN_HEIGHT = 768
 14SCREEN_TITLE = "Lighting Demo"
 15VIEWPORT_MARGIN = 200
 16MOVEMENT_SPEED = 5
 17
 18# This is the color used for 'ambient light'. If you don't want any
 19# ambient light, set it to black.
 20AMBIENT_COLOR = (10, 10, 10, 255)
 21
 22
 23class MyGame(arcade.Window):
 24    """ Main Game Window """
 25
 26    def __init__(self, width, height, title):
 27        """ Set up the class. """
 28        super().__init__(width, height, title, resizable=True)
 29
 30        # Sprite lists
 31        self.background_sprite_list = None
 32        self.player_list = None
 33        self.wall_list = None
 34        self.player_sprite = None
 35
 36        # Physics engine
 37        self.physics_engine = None
 38
 39        # Used for scrolling
 40        self.view_left = 0
 41        self.view_bottom = 0
 42
 43        # --- Light related ---
 44        # List of all the lights
 45        self.light_layer = None
 46        # Individual light we move with player, and turn on/off
 47        self.player_light = None
 48
 49    def setup(self):
 50        """ Create everything """
 51
 52        # Create sprite lists
 53        self.background_sprite_list = arcade.SpriteList()
 54        self.player_list = arcade.SpriteList()
 55        self.wall_list = arcade.SpriteList()
 56
 57        # Create player sprite
 58        self.player_sprite = arcade.Sprite(
 59            ":resources:images/animated_characters/female_person/femalePerson_idle.png",
 60            scale=0.4)
 61        self.player_sprite.center_x = 64
 62        self.player_sprite.center_y = 270
 63        self.player_list.append(self.player_sprite)
 64
 65        # --- Light related ---
 66        # Lights must shine on something. If there is no background sprite or color,
 67        # you will just see black. Therefore, we use a loop to create a whole bunch of brick tiles to go in the
 68        # background.
 69        for x in range(-128, 2000, 128):
 70            for y in range(-128, 1000, 128):
 71                sprite = arcade.Sprite(":resources:images/tiles/brickTextureWhite.png")
 72                sprite.position = x, y
 73                self.background_sprite_list.append(sprite)
 74
 75        # Create a light layer, used to render things to, then post-process and
 76        # add lights. This must match the screen size.
 77        self.light_layer = LightLayer(SCREEN_WIDTH, SCREEN_HEIGHT)
 78        # We can also set the background color that will be lit by lights,
 79        # but in this instance we just want a black background
 80        self.light_layer.set_background_color(arcade.color.BLACK)
 81
 82        # Here we create a bunch of lights.
 83
 84        # Create a small white light
 85        x = 100
 86        y = 200
 87        radius = 100
 88        mode = 'soft'
 89        color = arcade.csscolor.WHITE
 90        light = Light(x, y, radius, color, mode)
 91        self.light_layer.add(light)
 92
 93        # Create an overlapping, large white light
 94        x = 300
 95        y = 150
 96        radius = 200
 97        color = arcade.csscolor.WHITE
 98        mode = 'soft'
 99        light = Light(x, y, radius, color, mode)
100        self.light_layer.add(light)
101
102        # Create three, non-overlapping RGB lights
103        x = 50
104        y = 450
105        radius = 100
106        mode = 'soft'
107        color = arcade.csscolor.RED
108        light = Light(x, y, radius, color, mode)
109        self.light_layer.add(light)
110
111        x = 250
112        y = 450
113        radius = 100
114        mode = 'soft'
115        color = arcade.csscolor.GREEN
116        light = Light(x, y, radius, color, mode)
117        self.light_layer.add(light)
118
119        x = 450
120        y = 450
121        radius = 100
122        mode = 'soft'
123        color = arcade.csscolor.BLUE
124        light = Light(x, y, radius, color, mode)
125        self.light_layer.add(light)
126
127        # Create three, overlapping RGB lights
128        x = 650
129        y = 450
130        radius = 100
131        mode = 'soft'
132        color = arcade.csscolor.RED
133        light = Light(x, y, radius, color, mode)
134        self.light_layer.add(light)
135
136        x = 750
137        y = 450
138        radius = 100
139        mode = 'soft'
140        color = arcade.csscolor.GREEN
141        light = Light(x, y, radius, color, mode)
142        self.light_layer.add(light)
143
144        x = 850
145        y = 450
146        radius = 100
147        mode = 'soft'
148        color = arcade.csscolor.BLUE
149        light = Light(x, y, radius, color, mode)
150        self.light_layer.add(light)
151
152        # Create three, overlapping RGB lights
153        # But 'hard' lights that don't fade out.
154        x = 650
155        y = 150
156        radius = 100
157        mode = 'hard'
158        color = arcade.csscolor.RED
159        light = Light(x, y, radius, color, mode)
160        self.light_layer.add(light)
161
162        x = 750
163        y = 150
164        radius = 100
165        mode = 'hard'
166        color = arcade.csscolor.GREEN
167        light = Light(x, y, radius, color, mode)
168        self.light_layer.add(light)
169
170        x = 850
171        y = 150
172        radius = 100
173        mode = 'hard'
174        color = arcade.csscolor.BLUE
175        light = Light(x, y, radius, color, mode)
176        self.light_layer.add(light)
177
178        # Create a light to follow the player around.
179        # We'll position it later, when the player moves.
180        # We'll only add it to the light layer when the player turns the light
181        # on. We start with the light off.
182        radius = 150
183        mode = 'soft'
184        color = arcade.csscolor.WHITE
185        self.player_light = Light(0, 0, radius, color, mode)
186
187        # Create the physics engine
188        self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
189
190        # Set the viewport boundaries
191        # These numbers set where we have 'scrolled' to.
192        self.view_left = 0
193        self.view_bottom = 0
194
195    def on_draw(self):
196        """ Draw everything. """
197        self.clear()
198
199        # --- Light related ---
200        # Everything that should be affected by lights gets rendered inside this
201        # 'with' statement. Nothing is rendered to the screen yet, just the light
202        # layer.
203        with self.light_layer:
204            self.background_sprite_list.draw()
205            self.player_list.draw()
206
207        # Draw the light layer to the screen.
208        # This fills the entire screen with the lit version
209        # of what we drew into the light layer above.
210        self.light_layer.draw(ambient_color=AMBIENT_COLOR)
211
212        # Now draw anything that should NOT be affected by lighting.
213        arcade.draw_text("Press SPACE to turn character light on/off.",
214                         10 + self.view_left, 10 + self.view_bottom,
215                         arcade.color.WHITE, 20)
216
217    def on_resize(self, width, height):
218        """ User resizes the screen. """
219
220        # --- Light related ---
221        # We need to resize the light layer to
222        self.light_layer.resize(width, height)
223
224        # Scroll the screen so the user is visible
225        self.scroll_screen()
226
227    def on_key_press(self, key, _):
228        """Called whenever a key is pressed. """
229
230        if key == arcade.key.UP:
231            self.player_sprite.change_y = MOVEMENT_SPEED
232        elif key == arcade.key.DOWN:
233            self.player_sprite.change_y = -MOVEMENT_SPEED
234        elif key == arcade.key.LEFT:
235            self.player_sprite.change_x = -MOVEMENT_SPEED
236        elif key == arcade.key.RIGHT:
237            self.player_sprite.change_x = MOVEMENT_SPEED
238        elif key == arcade.key.SPACE:
239            # --- Light related ---
240            # We can add/remove lights from the light layer. If they aren't
241            # in the light layer, the light is off.
242            if self.player_light in self.light_layer:
243                self.light_layer.remove(self.player_light)
244            else:
245                self.light_layer.add(self.player_light)
246
247    def on_key_release(self, key, _):
248        """Called when the user releases a key. """
249
250        if key == arcade.key.UP or key == arcade.key.DOWN:
251            self.player_sprite.change_y = 0
252        elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
253            self.player_sprite.change_x = 0
254
255    def scroll_screen(self):
256        """ Manage Scrolling """
257
258        # Scroll left
259        left_boundary = self.view_left + VIEWPORT_MARGIN
260        if self.player_sprite.left < left_boundary:
261            self.view_left -= left_boundary - self.player_sprite.left
262
263        # Scroll right
264        right_boundary = self.view_left + self.width - VIEWPORT_MARGIN
265        if self.player_sprite.right > right_boundary:
266            self.view_left += self.player_sprite.right - right_boundary
267
268        # Scroll up
269        top_boundary = self.view_bottom + self.height - VIEWPORT_MARGIN
270        if self.player_sprite.top > top_boundary:
271            self.view_bottom += self.player_sprite.top - top_boundary
272
273        # Scroll down
274        bottom_boundary = self.view_bottom + VIEWPORT_MARGIN
275        if self.player_sprite.bottom < bottom_boundary:
276            self.view_bottom -= bottom_boundary - self.player_sprite.bottom
277
278        # Make sure our boundaries are integer values. While the viewport does
279        # support floating point numbers, for this application we want every pixel
280        # in the view port to map directly onto a pixel on the screen. We don't want
281        # any rounding errors.
282        self.view_left = int(self.view_left)
283        self.view_bottom = int(self.view_bottom)
284
285        arcade.set_viewport(self.view_left,
286                            self.width + self.view_left,
287                            self.view_bottom,
288                            self.height + self.view_bottom)
289
290    def on_update(self, delta_time):
291        """ Movement and game logic """
292
293        # Call update on all sprites (The sprites don't do much in this
294        # example though.)
295        self.physics_engine.update()
296
297        # --- Light related ---
298        # We can easily move the light by setting the position,
299        # or by center_x, center_y.
300        self.player_light.position = self.player_sprite.position
301
302        # Scroll the screen so we can see the player
303        self.scroll_screen()
304
305
306if __name__ == "__main__":
307    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
308    window.setup()
309    arcade.run()