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        # Camera
 40        self.cam: arcade.camera.Camera2D = None
 41
 42        # --- Light related ---
 43        # List of all the lights
 44        self.light_layer = None
 45        # Individual light we move with player, and turn on/off
 46        self.player_light = None
 47
 48    def setup(self):
 49        """ Create everything """
 50
 51        # Create camera
 52        self.cam = arcade.camera.Camera2D()
 53
 54        # Create sprite lists
 55        self.background_sprite_list = arcade.SpriteList()
 56        self.player_list = arcade.SpriteList()
 57        self.wall_list = arcade.SpriteList()
 58
 59        # Create player sprite
 60        self.player_sprite = arcade.Sprite(
 61            ":resources:images/animated_characters/female_person/femalePerson_idle.png",
 62            scale=0.4)
 63        self.player_sprite.center_x = 64
 64        self.player_sprite.center_y = 270
 65        self.player_list.append(self.player_sprite)
 66
 67        # --- Light related ---
 68        # Lights must shine on something. If there is no background sprite or color,
 69        # you will just see black. Therefore, we use a loop to create a whole bunch of brick tiles to go in the
 70        # background.
 71        for x in range(-128, 2000, 128):
 72            for y in range(-128, 1000, 128):
 73                sprite = arcade.Sprite(":resources:images/tiles/brickTextureWhite.png")
 74                sprite.position = x, y
 75                self.background_sprite_list.append(sprite)
 76
 77        # Create a light layer, used to render things to, then post-process and
 78        # add lights. This must match the screen size.
 79        self.light_layer = LightLayer(SCREEN_WIDTH, SCREEN_HEIGHT)
 80        # We can also set the background color that will be lit by lights,
 81        # but in this instance we just want a black background
 82        self.light_layer.set_background_color(arcade.color.BLACK)
 83
 84        # Here we create a bunch of lights.
 85
 86        # Create a small white light
 87        x = 100
 88        y = 200
 89        radius = 100
 90        mode = 'soft'
 91        color = arcade.csscolor.WHITE
 92        light = Light(x, y, radius, color, mode)
 93        self.light_layer.add(light)
 94
 95        # Create an overlapping, large white light
 96        x = 300
 97        y = 150
 98        radius = 200
 99        color = arcade.csscolor.WHITE
100        mode = 'soft'
101        light = Light(x, y, radius, color, mode)
102        self.light_layer.add(light)
103
104        # Create three, non-overlapping RGB lights
105        x = 50
106        y = 450
107        radius = 100
108        mode = 'soft'
109        color = arcade.csscolor.RED
110        light = Light(x, y, radius, color, mode)
111        self.light_layer.add(light)
112
113        x = 250
114        y = 450
115        radius = 100
116        mode = 'soft'
117        color = arcade.csscolor.GREEN
118        light = Light(x, y, radius, color, mode)
119        self.light_layer.add(light)
120
121        x = 450
122        y = 450
123        radius = 100
124        mode = 'soft'
125        color = arcade.csscolor.BLUE
126        light = Light(x, y, radius, color, mode)
127        self.light_layer.add(light)
128
129        # Create three, overlapping RGB lights
130        x = 650
131        y = 450
132        radius = 100
133        mode = 'soft'
134        color = arcade.csscolor.RED
135        light = Light(x, y, radius, color, mode)
136        self.light_layer.add(light)
137
138        x = 750
139        y = 450
140        radius = 100
141        mode = 'soft'
142        color = arcade.csscolor.GREEN
143        light = Light(x, y, radius, color, mode)
144        self.light_layer.add(light)
145
146        x = 850
147        y = 450
148        radius = 100
149        mode = 'soft'
150        color = arcade.csscolor.BLUE
151        light = Light(x, y, radius, color, mode)
152        self.light_layer.add(light)
153
154        # Create three, overlapping RGB lights
155        # But 'hard' lights that don't fade out.
156        x = 650
157        y = 150
158        radius = 100
159        mode = 'hard'
160        color = arcade.csscolor.RED
161        light = Light(x, y, radius, color, mode)
162        self.light_layer.add(light)
163
164        x = 750
165        y = 150
166        radius = 100
167        mode = 'hard'
168        color = arcade.csscolor.GREEN
169        light = Light(x, y, radius, color, mode)
170        self.light_layer.add(light)
171
172        x = 850
173        y = 150
174        radius = 100
175        mode = 'hard'
176        color = arcade.csscolor.BLUE
177        light = Light(x, y, radius, color, mode)
178        self.light_layer.add(light)
179
180        # Create a light to follow the player around.
181        # We'll position it later, when the player moves.
182        # We'll only add it to the light layer when the player turns the light
183        # on. We start with the light off.
184        radius = 150
185        mode = 'soft'
186        color = arcade.csscolor.WHITE
187        self.player_light = Light(0, 0, radius, color, mode)
188
189        # Create the physics engine
190        self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite, self.wall_list)
191
192    def on_draw(self):
193        """ Draw everything. """
194        self.clear()
195
196        # --- Light related ---
197        # Everything that should be affected by lights gets rendered inside this
198        # 'with' statement. Nothing is rendered to the screen yet, just the light
199        # layer.
200        with self.light_layer:
201            self.background_sprite_list.draw()
202            self.player_list.draw()
203
204        # Draw the light layer to the screen.
205        # This fills the entire screen with the lit version
206        # of what we drew into the light layer above.
207        self.light_layer.draw(ambient_color=AMBIENT_COLOR)
208
209        # Now draw anything that should NOT be affected by lighting.
210        left, bottom = self.cam.bottom_left
211        arcade.draw_text("Press SPACE to turn character light on/off.",
212                         10 + int(left), 10 + int(bottom),
213                         arcade.color.WHITE, 20)
214
215    def on_resize(self, width, height):
216        """ User resizes the screen. """
217
218        self.cam.viewport = 0, 0, width, height
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        # --- Manage Scrolling ---
259        pos = self.cam.position
260
261        top_left = self.cam.top_left
262        bottom_right = self.cam.bottom_right
263
264        # Scroll left
265        left_boundary = top_left[0] + VIEWPORT_MARGIN
266        if self.player_sprite.left < left_boundary:
267            pos = pos[0] + (self.player_sprite.left - left_boundary), pos[1]
268
269        # Scroll up
270        top_boundary = top_left[1] - VIEWPORT_MARGIN
271        if self.player_sprite.top > top_boundary:
272            pos = pos[0], pos[1] + (self.player_sprite.top - top_boundary)
273
274        # Scroll right
275        right_boundary = bottom_right[0] - VIEWPORT_MARGIN
276        if self.player_sprite.right > right_boundary:
277            pos = pos[0] + (self.player_sprite.right - right_boundary), pos[1]
278
279        # Scroll down
280        bottom_boundary = bottom_right[1] + VIEWPORT_MARGIN
281        if self.player_sprite.bottom < bottom_boundary:
282            pos = pos[0], pos[1] + (self.player_sprite.bottom - bottom_boundary)
283
284        self.cam.position = pos
285
286        # Make sure our boundaries are integer values. While the viewport does
287        # support floating point numbers, for this application we want every pixel
288        # in the view port to map directly onto a pixel on the screen. We don't want
289        # any rounding errors.
290        bottom_left = self.cam.bottom_left
291        self.cam.bottom_left = int(bottom_left[0]), int(bottom_left[1])
292
293        self.cam.use()
294
295    def on_update(self, delta_time):
296        """ Movement and game logic """
297
298        # Call update on all sprites (The sprites don't do much in this
299        # example though.)
300        self.physics_engine.update()
301
302        # --- Light related ---
303        # We can easily move the light by setting the position,
304        # or by center_x, center_y.
305        self.player_light.position = self.player_sprite.position
306
307        # Scroll the screen so we can see the player
308        self.scroll_screen()
309
310
311def main():
312    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
313    window.setup()
314    arcade.run()
315
316
317if __name__ == "__main__":
318    main()