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"""
9from __future__ import annotations
10
11import arcade
12from arcade.experimental.lights import Light, LightLayer
13
14SCREEN_WIDTH = 1024
15SCREEN_HEIGHT = 768
16SCREEN_TITLE = "Lighting Demo"
17VIEWPORT_MARGIN = 200
18MOVEMENT_SPEED = 5
19
20# This is the color used for 'ambient light'. If you don't want any
21# ambient light, set it to black.
22AMBIENT_COLOR = (10, 10, 10, 255)
23
24
25class MyGame(arcade.Window):
26 """ Main Game Window """
27
28 def __init__(self, width, height, title):
29 """ Set up the class. """
30 super().__init__(width, height, title, resizable=True)
31
32 # Sprite lists
33 self.background_sprite_list = None
34 self.player_list = None
35 self.wall_list = None
36 self.player_sprite = None
37
38 # Physics engine
39 self.physics_engine = None
40
41 # Used for scrolling
42 self.view_left = 0
43 self.view_bottom = 0
44
45 # --- Light related ---
46 # List of all the lights
47 self.light_layer = None
48 # Individual light we move with player, and turn on/off
49 self.player_light = None
50
51 def setup(self):
52 """ Create everything """
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 # Set the viewport boundaries
193 # These numbers set where we have 'scrolled' to.
194 self.view_left = 0
195 self.view_bottom = 0
196
197 def on_draw(self):
198 """ Draw everything. """
199 self.clear()
200
201 # --- Light related ---
202 # Everything that should be affected by lights gets rendered inside this
203 # 'with' statement. Nothing is rendered to the screen yet, just the light
204 # layer.
205 with self.light_layer:
206 self.background_sprite_list.draw()
207 self.player_list.draw()
208
209 # Draw the light layer to the screen.
210 # This fills the entire screen with the lit version
211 # of what we drew into the light layer above.
212 self.light_layer.draw(ambient_color=AMBIENT_COLOR)
213
214 # Now draw anything that should NOT be affected by lighting.
215 arcade.draw_text("Press SPACE to turn character light on/off.",
216 10 + self.view_left, 10 + self.view_bottom,
217 arcade.color.WHITE, 20)
218
219 def on_resize(self, width, height):
220 """ User resizes the screen. """
221
222 # --- Light related ---
223 # We need to resize the light layer to
224 self.light_layer.resize(width, height)
225
226 # Scroll the screen so the user is visible
227 self.scroll_screen()
228
229 def on_key_press(self, key, _):
230 """Called whenever a key is pressed. """
231
232 if key == arcade.key.UP:
233 self.player_sprite.change_y = MOVEMENT_SPEED
234 elif key == arcade.key.DOWN:
235 self.player_sprite.change_y = -MOVEMENT_SPEED
236 elif key == arcade.key.LEFT:
237 self.player_sprite.change_x = -MOVEMENT_SPEED
238 elif key == arcade.key.RIGHT:
239 self.player_sprite.change_x = MOVEMENT_SPEED
240 elif key == arcade.key.SPACE:
241 # --- Light related ---
242 # We can add/remove lights from the light layer. If they aren't
243 # in the light layer, the light is off.
244 if self.player_light in self.light_layer:
245 self.light_layer.remove(self.player_light)
246 else:
247 self.light_layer.add(self.player_light)
248
249 def on_key_release(self, key, _):
250 """Called when the user releases a key. """
251
252 if key == arcade.key.UP or key == arcade.key.DOWN:
253 self.player_sprite.change_y = 0
254 elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
255 self.player_sprite.change_x = 0
256
257 def scroll_screen(self):
258 """ Manage Scrolling """
259
260 # Scroll left
261 left_boundary = self.view_left + VIEWPORT_MARGIN
262 if self.player_sprite.left < left_boundary:
263 self.view_left -= left_boundary - self.player_sprite.left
264
265 # Scroll right
266 right_boundary = self.view_left + self.width - VIEWPORT_MARGIN
267 if self.player_sprite.right > right_boundary:
268 self.view_left += self.player_sprite.right - right_boundary
269
270 # Scroll up
271 top_boundary = self.view_bottom + self.height - VIEWPORT_MARGIN
272 if self.player_sprite.top > top_boundary:
273 self.view_bottom += self.player_sprite.top - top_boundary
274
275 # Scroll down
276 bottom_boundary = self.view_bottom + VIEWPORT_MARGIN
277 if self.player_sprite.bottom < bottom_boundary:
278 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
279
280 # Make sure our boundaries are integer values. While the viewport does
281 # support floating point numbers, for this application we want every pixel
282 # in the view port to map directly onto a pixel on the screen. We don't want
283 # any rounding errors.
284 self.view_left = int(self.view_left)
285 self.view_bottom = int(self.view_bottom)
286
287 arcade.set_viewport(self.view_left,
288 self.width + self.view_left,
289 self.view_bottom,
290 self.height + self.view_bottom)
291
292 def on_update(self, delta_time):
293 """ Movement and game logic """
294
295 # Call update on all sprites (The sprites don't do much in this
296 # example though.)
297 self.physics_engine.update()
298
299 # --- Light related ---
300 # We can easily move the light by setting the position,
301 # or by center_x, center_y.
302 self.player_light.position = self.player_sprite.position
303
304 # Scroll the screen so we can see the player
305 self.scroll_screen()
306
307
308if __name__ == "__main__":
309 window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
310 window.setup()
311 arcade.run()