Mini-Map Defender¶
This example shows how to create a ‘mini-map’ using frame buffers. Frame buffers allow us to draw off-screen. We can then take that image and draw it elsewhere.
Section 1 - Define Constants¶
Lines 26-32
At the start of our code, define some constants that specify how large your mini-map is. In this case, our mini-map is at the top of the screen, and the main playing field is below.
Section 2 - Create Frame Buffer¶
Lines 172-189
As our window opens up, we need to define a frame buffer that will hold our mini-map.
Figure out the size and position.
Create an OpenGL shader program. This is a simple pre-defined shader that just passes through vertex and texture data.
Create a color attachment, which holds what color each pixel is.
Create a frame buffer, and tell it to use the color attachment to hold data.
Create a rectangle that defines where we will draw the mini-map.
Section 3 - Draw to Mini-Map¶
Lines 226-238
Any time we want to draw to the frame buffer instead of the screen, there’s a
use
method in the frame buffer. So in this case, we can do:
self.mini_map_screen.use()
To switch back to the screen, use the windows use
method:
self.use()
Once we select the frame buffer, we set the viewport to encompass our entire playing field. Then we draw the sprites we want in the mini-map. Note that this example doesn’t draw the background stars or bullets to the mini-map. The program can easily select what should appear in the mini-map this way.
Section 4 - Draw Mini-Map to Screen¶
Lines 263-280
While we’ve drawn the mini-map, it is off-screen and we can’t see it. In this case, we render it to the pre-defined location with:
self.mini_map_color_attachment.use(0)
self.mini_map_rect.render(self.program)
The rest of the code in this section calculates a rectangle on the mini-map that outlines what the user can see on the mini-map.
1"""
2Defender Clone.
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
8This example shows how to create a mini-map
9
10If Python and Arcade are installed, this example can be run from the command line with:
11python -m arcade.examples.minimap_defender
12"""
13
14import arcade
15import random
16
17# --- Minimap Related ---
18from arcade.gl import geometry
19
20# Size/title of the window
21SCREEN_WIDTH = 1280
22SCREEN_HEIGHT = 800
23SCREEN_TITLE = "Defender Clone"
24
25# Size of the playing field
26PLAYING_FIELD_WIDTH = 5000
27PLAYING_FIELD_HEIGHT = 1000
28
29# --- Mini-map related ---
30# Size of the minimap
31MINIMAP_HEIGHT = 200
32
33# Size of the playing field.
34# This, plus the mini-map height, should add up to the height of the screen.
35MAIN_SCREEN_HEIGHT = SCREEN_HEIGHT - MINIMAP_HEIGHT
36
37# How far away from the edges do we get before scrolling?
38VIEWPORT_MARGIN = SCREEN_WIDTH / 2 - 50
39TOP_VIEWPORT_MARGIN = 30
40DEFAULT_BOTTOM_VIEWPORT = -10
41
42# Control the physics of how the player moves
43MAX_HORIZONTAL_MOVEMENT_SPEED = 10
44MAX_VERTICAL_MOVEMENT_SPEED = 5
45HORIZONTAL_ACCELERATION = 0.5
46VERTICAL_ACCELERATION = 0.2
47MOVEMENT_DRAG = 0.08
48
49# How far the bullet travels before disappearing
50BULLET_MAX_DISTANCE = SCREEN_WIDTH * 0.75
51
52class Player(arcade.SpriteSolidColor):
53 """ Player ship """
54 def __init__(self):
55 """ Set up player """
56 super().__init__(40, 10, arcade.color.SLATE_GRAY)
57 self.face_right = True
58
59 def accelerate_up(self):
60 """ Accelerate player up """
61 self.change_y += VERTICAL_ACCELERATION
62 if self.change_y > MAX_VERTICAL_MOVEMENT_SPEED:
63 self.change_y = MAX_VERTICAL_MOVEMENT_SPEED
64
65 def accelerate_down(self):
66 """ Accelerate player down """
67 self.change_y -= VERTICAL_ACCELERATION
68 if self.change_y < -MAX_VERTICAL_MOVEMENT_SPEED:
69 self.change_y = -MAX_VERTICAL_MOVEMENT_SPEED
70
71 def accelerate_right(self):
72 """ Accelerate player right """
73 self.face_right = True
74 self.change_x += HORIZONTAL_ACCELERATION
75 if self.change_x > MAX_HORIZONTAL_MOVEMENT_SPEED:
76 self.change_x = MAX_HORIZONTAL_MOVEMENT_SPEED
77
78 def accelerate_left(self):
79 """ Accelerate player left """
80 self.face_right = False
81 self.change_x -= HORIZONTAL_ACCELERATION
82 if self.change_x < -MAX_HORIZONTAL_MOVEMENT_SPEED:
83 self.change_x = -MAX_HORIZONTAL_MOVEMENT_SPEED
84
85 def update(self):
86 """ Move the player """
87 # Move
88 self.center_x += self.change_x
89 self.center_y += self.change_y
90
91 # Drag
92 if self.change_x > 0:
93 self.change_x -= MOVEMENT_DRAG
94 if self.change_x < 0:
95 self.change_x += MOVEMENT_DRAG
96 if abs(self.change_x) < MOVEMENT_DRAG:
97 self.change_x = 0
98
99 if self.change_y > 0:
100 self.change_y -= MOVEMENT_DRAG
101 if self.change_y < 0:
102 self.change_y += MOVEMENT_DRAG
103 if abs(self.change_y) < MOVEMENT_DRAG:
104 self.change_y = 0
105
106 # Check bounds
107 if self.left < 0:
108 self.left = 0
109 elif self.right > PLAYING_FIELD_WIDTH - 1:
110 self.right = PLAYING_FIELD_WIDTH - 1
111
112 if self.bottom < 0:
113 self.bottom = 0
114 elif self.top > PLAYING_FIELD_HEIGHT - 1:
115 self.top = PLAYING_FIELD_HEIGHT - 1
116
117class Bullet(arcade.SpriteSolidColor):
118 """ Bullet """
119
120 def __init__(self, width, height, color):
121 super().__init__(width, height, color)
122 self.distance = 0
123
124 def update(self):
125 """ Move the particle, and fade out """
126 # Move
127 self.center_x += self.change_x
128 self.center_y += self.change_y
129 self.distance += self.change_x
130 if self.distance > BULLET_MAX_DISTANCE:
131 self.remove_from_sprite_lists()
132
133class Particle(arcade.SpriteSolidColor):
134 """ Particle from explosion """
135 def update(self):
136 """ Move the particle, and fade out """
137 # Move
138 self.center_x += self.change_x
139 self.center_y += self.change_y
140 # Fade
141 self.alpha -= 5
142 if self.alpha <= 0:
143 self.remove_from_sprite_lists()
144
145class MyGame(arcade.Window):
146 """ Main application class. """
147
148 def __init__(self, width, height, title):
149 """ Initializer """
150
151 # Call the parent class initializer
152 super().__init__(width, height, title)
153
154 # Variables that will hold sprite lists
155 self.player_list = None
156 self.star_sprite_list = None
157 self.enemy_sprite_list = None
158 self.bullet_sprite_list = None
159
160 # Set up the player info
161 self.player_sprite = None
162
163 # Track the current state of what key is pressed
164 self.left_pressed = False
165 self.right_pressed = False
166 self.up_pressed = False
167 self.down_pressed = False
168
169 self.view_bottom = 0
170 self.view_left = 0
171
172 # Set the background color
173 arcade.set_background_color(arcade.color.BLACK)
174
175 # --- Mini-map related ---
176 # How big is our screen?
177 screen_size = (SCREEN_WIDTH, SCREEN_HEIGHT)
178 # How big is the mini-map?
179 mini_map_size = (SCREEN_WIDTH, MINIMAP_HEIGHT)
180 # Where is the mini-map to be drawn?
181 mini_map_pos = (SCREEN_WIDTH / 2, SCREEN_HEIGHT - MINIMAP_HEIGHT / 2)
182 # Load a vertex and fragment shader
183 self.program = self.ctx.load_program(
184 vertex_shader=arcade.resources.shaders.vertex.default_projection,
185 fragment_shader=arcade.resources.shaders.fragment.texture)
186 # Add a color attachment to store pixel colors
187 self.mini_map_color_attachment = self.ctx.texture(screen_size)
188 # Create a frame buffer with the needed color attachment
189 self.mini_map_screen = self.ctx.framebuffer(color_attachments=[self.mini_map_color_attachment])
190 # Create a rectangle that will hold where the mini-map goes
191 self.mini_map_rect = geometry.screen_rectangle(0, SCREEN_WIDTH, MINIMAP_HEIGHT, SCREEN_HEIGHT)
192
193 def setup(self):
194 """ Set up the game and initialize the variables. """
195
196 # Sprite lists
197 self.player_list = arcade.SpriteList()
198 self.star_sprite_list = arcade.SpriteList()
199 self.enemy_sprite_list = arcade.SpriteList()
200 self.bullet_sprite_list = arcade.SpriteList()
201
202 # Set up the player
203 self.player_sprite = Player()
204 self.player_sprite.center_x = 50
205 self.player_sprite.center_y = 50
206 self.player_list.append(self.player_sprite)
207
208 # Add stars
209 for i in range(100):
210 sprite = arcade.SpriteSolidColor(4, 4, arcade.color.WHITE)
211 sprite.center_x = random.randrange(PLAYING_FIELD_WIDTH)
212 sprite.center_y = random.randrange(PLAYING_FIELD_HEIGHT)
213 self.star_sprite_list.append(sprite)
214
215 # Add enemies
216 for i in range(30):
217 sprite = arcade.SpriteSolidColor(20, 20, arcade.csscolor.LIGHT_SALMON)
218 sprite.center_x = random.randrange(PLAYING_FIELD_WIDTH)
219 sprite.center_y = random.randrange(PLAYING_FIELD_HEIGHT)
220 self.enemy_sprite_list.append(sprite)
221
222 def on_draw(self):
223 """ Render the screen. """
224 # This command has to happen before we start drawing
225 arcade.start_render()
226
227 # --- Mini-map related ---
228
229 # Draw to the frame buffer used in the mini-map
230 self.mini_map_screen.use()
231 self.mini_map_screen.clear()
232
233 arcade.set_viewport(0,
234 PLAYING_FIELD_WIDTH,
235 0,
236 PLAYING_FIELD_HEIGHT)
237
238 self.enemy_sprite_list.draw()
239 self.player_list.draw()
240
241 # Now draw to the actual screen
242 self.use()
243
244 arcade.set_viewport(self.view_left,
245 SCREEN_WIDTH + self.view_left,
246 self.view_bottom,
247 SCREEN_HEIGHT + self.view_bottom)
248
249 self.star_sprite_list.draw()
250 self.enemy_sprite_list.draw()
251 self.bullet_sprite_list.draw()
252 self.player_list.draw()
253
254 # Draw the ground
255 arcade.draw_line(0, 0, PLAYING_FIELD_WIDTH, 0, arcade.color.WHITE)
256
257 # Draw a background for the minimap
258 arcade.draw_rectangle_filled(SCREEN_WIDTH - SCREEN_WIDTH / 2 + self.view_left,
259 SCREEN_HEIGHT - MINIMAP_HEIGHT + MINIMAP_HEIGHT / 2 + self.view_bottom,
260 SCREEN_WIDTH,
261 MINIMAP_HEIGHT,
262 arcade.color.DARK_GREEN)
263
264 # --- Mini-map related ---
265
266 # Draw the minimap
267 self.mini_map_color_attachment.use(0)
268 self.mini_map_rect.render(self.program)
269
270 # Draw a rectangle showing where the screen is
271 width_ratio = SCREEN_WIDTH / PLAYING_FIELD_WIDTH
272 height_ratio = MINIMAP_HEIGHT / PLAYING_FIELD_HEIGHT
273 width = width_ratio * SCREEN_WIDTH
274 height = height_ratio * MAIN_SCREEN_HEIGHT
275
276 x = (self.view_left + SCREEN_WIDTH / 2) * width_ratio + self.view_left
277 y = (SCREEN_HEIGHT - MINIMAP_HEIGHT) + self.view_bottom + height / 2 + (MAIN_SCREEN_HEIGHT / PLAYING_FIELD_HEIGHT) * self.view_bottom
278
279 arcade.draw_rectangle_outline(center_x=x, center_y=y,
280 width=width, height=height,
281 color=arcade.color.WHITE)
282
283 def on_update(self, delta_time):
284 """ Movement and game logic """
285
286 # Calculate speed based on the keys pressed
287 if self.up_pressed and not self.down_pressed:
288 self.player_sprite.accelerate_up()
289 elif self.down_pressed and not self.up_pressed:
290 self.player_sprite.accelerate_down()
291
292 if self.left_pressed and not self.right_pressed:
293 self.player_sprite.accelerate_left()
294 elif self.right_pressed and not self.left_pressed:
295 self.player_sprite.accelerate_right()
296
297 # Call update to move the sprite
298 self.player_list.update()
299 self.bullet_sprite_list.update()
300
301 for bullet in self.bullet_sprite_list:
302 enemy_hit_list = arcade.check_for_collision_with_list(bullet, self.enemy_sprite_list)
303 for enemy in enemy_hit_list:
304 enemy.remove_from_sprite_lists()
305 for i in range(10):
306 particle = Particle(4, 4, arcade.color.RED)
307 while particle.change_y == 0 and particle.change_x == 0:
308 particle.change_y = random.randrange(-2, 3)
309 particle.change_x = random.randrange(-2, 3)
310 particle.center_x = enemy.center_x
311 particle.center_y = enemy.center_y
312 self.bullet_sprite_list.append(particle)
313
314 # Scroll left
315 left_boundary = self.view_left + VIEWPORT_MARGIN
316 if self.player_sprite.left < left_boundary:
317 self.view_left -= left_boundary - self.player_sprite.left
318
319 # Scroll right
320 right_boundary = self.view_left + SCREEN_WIDTH - VIEWPORT_MARGIN
321 if self.player_sprite.right > right_boundary:
322 self.view_left += self.player_sprite.right - right_boundary
323
324 # Scroll up
325 self.view_bottom = DEFAULT_BOTTOM_VIEWPORT
326 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN - MINIMAP_HEIGHT
327 if self.player_sprite.top > top_boundary:
328 self.view_bottom += self.player_sprite.top - top_boundary
329
330 self.view_left = int(self.view_left)
331 self.view_bottom = int(self.view_bottom)
332
333 def on_key_press(self, key, modifiers):
334 """Called whenever a key is pressed. """
335
336 if key == arcade.key.UP:
337 self.up_pressed = True
338 elif key == arcade.key.DOWN:
339 self.down_pressed = True
340 elif key == arcade.key.LEFT:
341 self.left_pressed = True
342 elif key == arcade.key.RIGHT:
343 self.right_pressed = True
344 elif key == arcade.key.SPACE:
345 # Shoot out a bullet/laser
346 bullet = arcade.SpriteSolidColor(35, 3, arcade.color.WHITE)
347 bullet.center_x = self.player_sprite.center_x
348 bullet.center_y = self.player_sprite.center_y
349 bullet.change_x = max(12, abs(self.player_sprite.change_x) + 10)
350
351 if not self.player_sprite.face_right:
352 bullet.change_x *= -1
353
354 self.bullet_sprite_list.append(bullet)
355
356 def on_key_release(self, key, modifiers):
357 """Called when the user releases a key. """
358
359 if key == arcade.key.UP:
360 self.up_pressed = False
361 elif key == arcade.key.DOWN:
362 self.down_pressed = False
363 elif key == arcade.key.LEFT:
364 self.left_pressed = False
365 elif key == arcade.key.RIGHT:
366 self.right_pressed = False
367
368
369def main():
370 """ Main method """
371 window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
372 window.setup()
373 arcade.run()
374
375
376if __name__ == "__main__":
377 main()