布卢姆效应捍卫者#

具有光晕/发光效果的防御者克隆人的屏幕截图。

创造一种“发光”的效果可以增强2D游戏。这个例子展示了如何做到这一点。

创建帧缓冲区和后处理器#

第176-202行

这里我们创建了帧缓冲区,并添加了一个颜色附件来存储像素数据。

它还创建了一个后处理器,将对渲染的内容执行高斯模糊。模糊有很多参数,这取决于您希望它看起来是什么样子。

渲染到帧缓冲区#

239-252号线

绘制时,我们将希望模糊的对象渲染到帧缓冲区,然后运行后处理器进行模糊处理。

注意:此缓冲区不是透明的!它背后的任何东西都会被隐藏起来。所以在这个时候多层发光是不可能的,你也不能把任何东西放在发光后面。

将帧缓冲区渲染到屏幕#

第264-265行

最后,我们将该缓冲区呈现到屏幕上。

mini_map_defender.py#
  1"""
  2Defender Clone.
  3
  4This example shows how to create a 'bloom' or 'glow' effect.
  5
  6If Python and Arcade are installed, this example can be run from the command line with:
  7python -m arcade.examples.bloom_defender
  8"""
  9
 10import arcade
 11import random
 12
 13# --- Bloom related ---
 14from arcade.experimental import postprocessing
 15
 16# Size/title of the window
 17SCREEN_WIDTH = 1280
 18SCREEN_HEIGHT = 720
 19SCREEN_TITLE = "Defender Clone"
 20
 21# Size of the playing field
 22PLAYING_FIELD_WIDTH = 5000
 23PLAYING_FIELD_HEIGHT = 1000
 24
 25# Size of the playing field.
 26MAIN_SCREEN_HEIGHT = SCREEN_HEIGHT
 27
 28# How far away from the edges do we get before scrolling?
 29VIEWPORT_MARGIN = SCREEN_WIDTH / 2 - 50
 30TOP_VIEWPORT_MARGIN = 30
 31DEFAULT_BOTTOM_VIEWPORT = -10
 32
 33# Control the physics of how the player moves
 34MAX_HORIZONTAL_MOVEMENT_SPEED = 10
 35MAX_VERTICAL_MOVEMENT_SPEED = 5
 36HORIZONTAL_ACCELERATION = 0.5
 37VERTICAL_ACCELERATION = 0.2
 38MOVEMENT_DRAG = 0.08
 39
 40# How far the bullet travels before disappearing
 41BULLET_MAX_DISTANCE = SCREEN_WIDTH * 0.75
 42
 43
 44class Player(arcade.SpriteSolidColor):
 45    """ Player ship """
 46    def __init__(self):
 47        """ Set up player """
 48        super().__init__(40, 10, color=arcade.color.SLATE_GRAY)
 49        self.face_right = True
 50
 51    def accelerate_up(self):
 52        """ Accelerate player up """
 53        self.change_y += VERTICAL_ACCELERATION
 54        if self.change_y > MAX_VERTICAL_MOVEMENT_SPEED:
 55            self.change_y = MAX_VERTICAL_MOVEMENT_SPEED
 56
 57    def accelerate_down(self):
 58        """ Accelerate player down """
 59        self.change_y -= VERTICAL_ACCELERATION
 60        if self.change_y < -MAX_VERTICAL_MOVEMENT_SPEED:
 61            self.change_y = -MAX_VERTICAL_MOVEMENT_SPEED
 62
 63    def accelerate_right(self):
 64        """ Accelerate player right """
 65        self.face_right = True
 66        self.change_x += HORIZONTAL_ACCELERATION
 67        if self.change_x > MAX_HORIZONTAL_MOVEMENT_SPEED:
 68            self.change_x = MAX_HORIZONTAL_MOVEMENT_SPEED
 69
 70    def accelerate_left(self):
 71        """ Accelerate player left """
 72        self.face_right = False
 73        self.change_x -= HORIZONTAL_ACCELERATION
 74        if self.change_x < -MAX_HORIZONTAL_MOVEMENT_SPEED:
 75            self.change_x = -MAX_HORIZONTAL_MOVEMENT_SPEED
 76
 77    def update(self):
 78        """ Move the player """
 79        # Move
 80        self.center_x += self.change_x
 81        self.center_y += self.change_y
 82
 83        # Drag
 84        if self.change_x > 0:
 85            self.change_x -= MOVEMENT_DRAG
 86        if self.change_x < 0:
 87            self.change_x += MOVEMENT_DRAG
 88        if abs(self.change_x) < MOVEMENT_DRAG:
 89            self.change_x = 0
 90
 91        if self.change_y > 0:
 92            self.change_y -= MOVEMENT_DRAG
 93        if self.change_y < 0:
 94            self.change_y += MOVEMENT_DRAG
 95        if abs(self.change_y) < MOVEMENT_DRAG:
 96            self.change_y = 0
 97
 98        # Check bounds
 99        if self.left < 0:
100            self.left = 0
101        elif self.right > PLAYING_FIELD_WIDTH - 1:
102            self.right = PLAYING_FIELD_WIDTH - 1
103
104        if self.bottom < 0:
105            self.bottom = 0
106        elif self.top > SCREEN_HEIGHT - 1:
107            self.top = SCREEN_HEIGHT - 1
108
109
110class Bullet(arcade.SpriteSolidColor):
111    """ Bullet """
112
113    def __init__(self, width, height, color):
114        super().__init__(width, height, color)
115        self.distance = 0
116
117    def update(self):
118        """ Move the particle, and fade out """
119        # Move
120        self.center_x += self.change_x
121        self.center_y += self.change_y
122        self.distance += self.change_x
123        if self.distance > BULLET_MAX_DISTANCE:
124            self.remove_from_sprite_lists()
125
126
127class Particle(arcade.SpriteSolidColor):
128    """ Particle from explosion """
129    def update(self):
130        """ Move the particle, and fade out """
131        # Move
132        self.center_x += self.change_x
133        self.center_y += self.change_y
134        # Fade
135        self.alpha -= 5
136        if self.alpha <= 0:
137            self.remove_from_sprite_lists()
138
139
140class MyGame(arcade.Window):
141    """ Main application class. """
142
143    def __init__(self, width, height, title):
144        """ Initializer """
145
146        # Call the parent class initializer
147        super().__init__(width, height, title)
148
149        # Variables that will hold sprite lists
150        self.player_list = None
151        self.star_sprite_list = None
152        self.enemy_sprite_list = None
153        self.bullet_sprite_list = None
154
155        # Set up the player info
156        self.player_sprite = None
157
158        # Track the current state of what key is pressed
159        self.left_pressed = False
160        self.right_pressed = False
161        self.up_pressed = False
162        self.down_pressed = False
163
164        self.view_bottom = 0
165        self.view_left = 0
166
167        # Set the background color of the window
168        self.background_color = arcade.color.BLACK
169
170        # --- Bloom related ---
171
172        # Frame to receive the glow, and color attachment to store each pixel's
173        # color data
174        self.bloom_color_attachment = self.ctx.texture((SCREEN_WIDTH, SCREEN_HEIGHT))
175        self.bloom_screen = self.ctx.framebuffer(
176            color_attachments=[self.bloom_color_attachment]
177        )
178
179        # Down-sampling helps improve the blur.
180        # Note: Any item with a size less than the down-sampling size may get missed in
181        # the blur process. Down-sampling by 8 and having an item of 4x4 size, the item
182        # will get missed 50% of the time in the x direction, and 50% of the time in the
183        # y direction for a total of being missed 75% of the time.
184        down_sampling = 4
185        # Size of the screen we are glowing onto
186        size = (SCREEN_WIDTH // down_sampling, SCREEN_HEIGHT // down_sampling)
187        # Gaussian blur parameters.
188        # To preview different values, see:
189        # https://observablehq.com/@jobleonard/gaussian-kernel-calculater
190        kernel_size = 21
191        sigma = 4
192        mu = 0
193        step = 1
194        # Control the intensity
195        multiplier = 2
196
197        # Create a post-processor to create a bloom
198        self.bloom_postprocessing = postprocessing.BloomEffect(size,
199                                                               kernel_size,
200                                                               sigma,
201                                                               mu,
202                                                               multiplier,
203                                                               step)
204
205    def setup(self):
206        """ Set up the game and initialize the variables. """
207
208        # Sprite lists
209        self.player_list = arcade.SpriteList()
210        self.star_sprite_list = arcade.SpriteList()
211        self.enemy_sprite_list = arcade.SpriteList()
212        self.bullet_sprite_list = arcade.SpriteList()
213
214        # Set up the player
215        self.player_sprite = Player()
216        self.player_sprite.center_x = 50
217        self.player_sprite.center_y = 50
218        self.player_list.append(self.player_sprite)
219
220        # Add stars
221        for i in range(80):
222            sprite = arcade.SpriteSolidColor(4, 4, color=arcade.color.WHITE)
223            sprite.center_x = random.randrange(PLAYING_FIELD_WIDTH)
224            sprite.center_y = random.randrange(PLAYING_FIELD_HEIGHT)
225            self.star_sprite_list.append(sprite)
226
227        # Add enemies
228        for i in range(20):
229            sprite = arcade.SpriteSolidColor(20, 20, color=arcade.csscolor.LIGHT_SALMON)
230            sprite.center_x = random.randrange(PLAYING_FIELD_WIDTH)
231            sprite.center_y = random.randrange(PLAYING_FIELD_HEIGHT)
232            self.enemy_sprite_list.append(sprite)
233
234    def on_draw(self):
235        """ Render the screen. """
236        # This command has to happen before we start drawing
237        self.clear()
238
239        # --- Bloom related ---
240
241        # Draw to the 'bloom' layer
242        self.bloom_screen.use()
243        self.bloom_screen.clear(arcade.color.TRANSPARENT_BLACK)
244
245        arcade.set_viewport(self.view_left,
246                            SCREEN_WIDTH + self.view_left,
247                            self.view_bottom,
248                            SCREEN_HEIGHT + self.view_bottom)
249
250        # Draw all the sprites on the screen that should have a bloom
251        self.star_sprite_list.draw()
252        self.bullet_sprite_list.draw()
253
254        # Now draw to the actual screen
255        self.use()
256
257        arcade.set_viewport(self.view_left,
258                            SCREEN_WIDTH + self.view_left,
259                            self.view_bottom,
260                            SCREEN_HEIGHT + self.view_bottom)
261
262        # --- Bloom related ---
263
264        # Draw the bloom layers
265        self.bloom_postprocessing.render(self.bloom_color_attachment, self)
266
267        # Draw the sprites / items that have no bloom
268        self.enemy_sprite_list.draw()
269        self.player_list.draw()
270
271        # Draw the ground
272        arcade.draw_line(0, 0, PLAYING_FIELD_WIDTH, 0, arcade.color.WHITE)
273
274    def on_update(self, delta_time):
275        """ Movement and game logic """
276
277        # Calculate speed based on the keys pressed
278        if self.up_pressed and not self.down_pressed:
279            self.player_sprite.accelerate_up()
280        elif self.down_pressed and not self.up_pressed:
281            self.player_sprite.accelerate_down()
282
283        if self.left_pressed and not self.right_pressed:
284            self.player_sprite.accelerate_left()
285        elif self.right_pressed and not self.left_pressed:
286            self.player_sprite.accelerate_right()
287
288        # Call update to move the sprite
289        self.player_list.update()
290        self.bullet_sprite_list.update()
291
292        for bullet in self.bullet_sprite_list:
293            enemy_hit_list = arcade.check_for_collision_with_list(bullet,
294                                                                  self.enemy_sprite_list)
295            for enemy in enemy_hit_list:
296                enemy.remove_from_sprite_lists()
297                for i in range(10):
298                    particle = Particle(4, 4, arcade.color.RED)
299                    while particle.change_y == 0 and particle.change_x == 0:
300                        particle.change_y = random.randrange(-2, 3)
301                        particle.change_x = random.randrange(-2, 3)
302                    particle.center_x = enemy.center_x
303                    particle.center_y = enemy.center_y
304                    self.bullet_sprite_list.append(particle)
305
306        # Scroll left
307        left_boundary = self.view_left + VIEWPORT_MARGIN
308        if self.player_sprite.left < left_boundary:
309            self.view_left -= left_boundary - self.player_sprite.left
310
311        # Scroll right
312        right_boundary = self.view_left + SCREEN_WIDTH - VIEWPORT_MARGIN
313        if self.player_sprite.right > right_boundary:
314            self.view_left += self.player_sprite.right - right_boundary
315
316        # Scroll up
317        self.view_bottom = DEFAULT_BOTTOM_VIEWPORT
318        top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
319        if self.player_sprite.top > top_boundary:
320            self.view_bottom += self.player_sprite.top - top_boundary
321
322        self.view_left = int(self.view_left)
323        self.view_bottom = int(self.view_bottom)
324
325    def on_key_press(self, key, modifiers):
326        """Called whenever a key is pressed. """
327
328        if key == arcade.key.UP:
329            self.up_pressed = True
330        elif key == arcade.key.DOWN:
331            self.down_pressed = True
332        elif key == arcade.key.LEFT:
333            self.left_pressed = True
334        elif key == arcade.key.RIGHT:
335            self.right_pressed = True
336        elif key == arcade.key.SPACE:
337            # Shoot out a bullet/laser
338            bullet = arcade.SpriteSolidColor(35, 3, arcade.color.WHITE)
339            bullet.center_x = self.player_sprite.center_x
340            bullet.center_y = self.player_sprite.center_y
341            bullet.change_x = max(12, abs(self.player_sprite.change_x) + 10)
342
343            if not self.player_sprite.face_right:
344                bullet.change_x *= -1
345
346            self.bullet_sprite_list.append(bullet)
347
348    def on_key_release(self, key, modifiers):
349        """Called when the user releases a key. """
350
351        if key == arcade.key.UP:
352            self.up_pressed = False
353        elif key == arcade.key.DOWN:
354            self.down_pressed = False
355        elif key == arcade.key.LEFT:
356            self.left_pressed = False
357        elif key == arcade.key.RIGHT:
358            self.right_pressed = False
359
360
361def main():
362    """ Main function """
363    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
364    window.setup()
365    arcade.run()
366
367
368if __name__ == "__main__":
369    main()