Lighting Demo

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