Particle Systems

Particle Systems
particle_systems.py
  1"""
  2Particle Systems
  3
  4Demonstrate how to use the Emitter and Particle classes to create particle systems.
  5
  6Demonstrate the different effects possible with Emitter's and Particle's by showing
  7a number of different emitters in sequence, with each example often varying just one
  8setting from the previous example.
  9
 10If Python and Arcade are installed, this example can be run from the command line with:
 11python -m arcade.examples.particle_systems
 12"""
 13import arcade
 14import pyglet
 15import os
 16import random
 17import math
 18
 19SCREEN_WIDTH = 800
 20SCREEN_HEIGHT = 600
 21SCREEN_TITLE = "Particle System Examples"
 22QUIET_BETWEEN_SPAWNS = 0.25  # time between spawning another particle system
 23EMITTER_TIMEOUT = 10 * 60
 24CENTER_POS = (SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)
 25BURST_PARTICLE_COUNT = 500
 26TEXTURE = ":resources:images/pinball/pool_cue_ball.png"
 27TEXTURE2 = ":resources:images/space_shooter/playerShip3_orange.png"
 28TEXTURE3 = ":resources:images/pinball/bumper.png"
 29TEXTURE4 = ":resources:images/enemies/wormGreen.png"
 30TEXTURE5 = ":resources:images/space_shooter/meteorGrey_med1.png"
 31TEXTURE6 = ":resources:images/animated_characters/female_person/femalePerson_idle.png"
 32TEXTURE7 = ":resources:images/tiles/boxCrate_double.png"
 33DEFAULT_SCALE = 0.3
 34DEFAULT_ALPHA = 32
 35DEFAULT_PARTICLE_LIFETIME = 3.0
 36PARTICLE_SPEED_FAST = 1.0
 37PARTICLE_SPEED_SLOW = 0.3
 38DEFAULT_EMIT_INTERVAL = 0.003
 39DEFAULT_EMIT_DURATION = 1.5
 40
 41
 42# Utils
 43def sine_wave(t, min_x, max_x, wavelength):
 44    spread = max_x - min_x
 45    mid = (max_x + min_x) / 2
 46    return (spread / 2) * math.sin(2 * math.pi * t / wavelength) + mid
 47
 48
 49# Example emitters
 50def emitter_0():
 51    """Burst, emit from center, particle with lifetime"""
 52    e = arcade.Emitter(
 53        center_xy=CENTER_POS,
 54        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
 55        particle_factory=lambda emitter: arcade.LifetimeParticle(
 56            filename_or_texture=TEXTURE,
 57            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
 58            lifetime=DEFAULT_PARTICLE_LIFETIME,
 59            scale=DEFAULT_SCALE,
 60            alpha=DEFAULT_ALPHA
 61        )
 62    )
 63    return emitter_0.__doc__, e
 64
 65
 66def emitter_1():
 67    """Burst, emit from center, particle lifetime 1.0 seconds"""
 68    e = arcade.Emitter(
 69        center_xy=CENTER_POS,
 70        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
 71        particle_factory=lambda emitter: arcade.LifetimeParticle(
 72            filename_or_texture=TEXTURE,
 73            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
 74            lifetime=1.0,
 75            scale=DEFAULT_SCALE,
 76            alpha=DEFAULT_ALPHA
 77        )
 78    )
 79    return emitter_1.__doc__, e
 80
 81
 82def emitter_2():
 83    """Burst, emit from center, particle lifetime random in range"""
 84    e = arcade.Emitter(
 85        center_xy=CENTER_POS,
 86        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
 87        particle_factory=lambda emitter: arcade.LifetimeParticle(
 88            filename_or_texture=TEXTURE,
 89            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
 90            lifetime=random.uniform(DEFAULT_PARTICLE_LIFETIME - 1.0, DEFAULT_PARTICLE_LIFETIME),
 91            scale=DEFAULT_SCALE,
 92            alpha=DEFAULT_ALPHA
 93        )
 94    )
 95    return emitter_2.__doc__, e
 96
 97
 98def emitter_3():
 99    """Burst, emit in circle"""
100    e = arcade.Emitter(
101        center_xy=CENTER_POS,
102        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
103        particle_factory=lambda emitter: arcade.LifetimeParticle(
104            filename_or_texture=TEXTURE,
105            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
106            lifetime=DEFAULT_PARTICLE_LIFETIME,
107            center_xy=arcade.rand_in_circle((0.0, 0.0), 100),
108            scale=DEFAULT_SCALE,
109            alpha=DEFAULT_ALPHA
110        )
111    )
112    return emitter_3.__doc__, e
113
114
115def emitter_4():
116    """Burst, emit on circle"""
117    e = arcade.Emitter(
118        center_xy=CENTER_POS,
119        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
120        particle_factory=lambda emitter: arcade.LifetimeParticle(
121            filename_or_texture=TEXTURE,
122            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
123            lifetime=DEFAULT_PARTICLE_LIFETIME,
124            center_xy=arcade.rand_on_circle((0.0, 0.0), 100),
125            scale=DEFAULT_SCALE,
126            alpha=DEFAULT_ALPHA
127        )
128    )
129    return emitter_4.__doc__, e
130
131
132def emitter_5():
133    """Burst, emit in rectangle"""
134    width, height = 200, 100
135    centering_offset = (-width / 2, -height / 2)
136    e = arcade.Emitter(
137        center_xy=CENTER_POS,
138        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
139        particle_factory=lambda emitter: arcade.LifetimeParticle(
140            filename_or_texture=TEXTURE,
141            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
142            lifetime=DEFAULT_PARTICLE_LIFETIME,
143            center_xy=arcade.rand_in_rect(centering_offset, width, height),
144            scale=DEFAULT_SCALE,
145            alpha=DEFAULT_ALPHA
146        )
147    )
148    return emitter_5.__doc__, e
149
150
151def emitter_6():
152    """Burst, emit on line"""
153    e = arcade.Emitter(
154        center_xy=CENTER_POS,
155        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
156        particle_factory=lambda emitter: arcade.LifetimeParticle(
157            filename_or_texture=TEXTURE,
158            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
159            lifetime=DEFAULT_PARTICLE_LIFETIME,
160            center_xy=arcade.rand_on_line((0.0, 0.0), (SCREEN_WIDTH, SCREEN_HEIGHT)),
161            scale=DEFAULT_SCALE,
162            alpha=DEFAULT_ALPHA
163        )
164    )
165    return emitter_6.__doc__, e
166
167
168def emitter_7():
169    """Burst, emit from center, velocity fixed speed around 360 degrees"""
170    e = arcade.Emitter(
171        center_xy=CENTER_POS,
172        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT // 4),
173        particle_factory=lambda emitter: arcade.LifetimeParticle(
174            filename_or_texture=TEXTURE,
175            change_xy=arcade.rand_on_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
176            lifetime=DEFAULT_PARTICLE_LIFETIME,
177            scale=DEFAULT_SCALE,
178            alpha=DEFAULT_ALPHA
179        )
180    )
181    return emitter_7.__doc__, e
182
183
184def emitter_8():
185    """Burst, emit from center, velocity in rectangle"""
186    e = arcade.Emitter(
187        center_xy=CENTER_POS,
188        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
189        particle_factory=lambda emitter: arcade.LifetimeParticle(
190            filename_or_texture=TEXTURE,
191            change_xy=arcade.rand_in_rect((-2.0, -2.0), 4.0, 4.0),
192            lifetime=DEFAULT_PARTICLE_LIFETIME,
193            scale=DEFAULT_SCALE,
194            alpha=DEFAULT_ALPHA
195        )
196    )
197    return emitter_8.__doc__, e
198
199
200def emitter_9():
201    """Burst, emit from center, velocity in fixed angle and random speed"""
202    e = arcade.Emitter(
203        center_xy=CENTER_POS,
204        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT // 4),
205        particle_factory=lambda emitter: arcade.LifetimeParticle(
206            filename_or_texture=TEXTURE,
207            change_xy=arcade.rand_vec_magnitude(45, 1.0, 4.0),
208            lifetime=DEFAULT_PARTICLE_LIFETIME,
209            scale=DEFAULT_SCALE,
210            alpha=DEFAULT_ALPHA
211        )
212    )
213    return emitter_9.__doc__, e
214
215
216def emitter_10():
217    """Burst, emit from center, velocity from angle with spread"""
218    e = arcade.Emitter(
219        center_xy=CENTER_POS,
220        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT // 4),
221        particle_factory=lambda emitter: arcade.LifetimeParticle(
222            filename_or_texture=TEXTURE,
223            change_xy=arcade.rand_vec_spread_deg(90, 45, 2.0),
224            lifetime=DEFAULT_PARTICLE_LIFETIME,
225            scale=DEFAULT_SCALE,
226            alpha=DEFAULT_ALPHA
227        )
228    )
229    return emitter_10.__doc__, e
230
231
232def emitter_11():
233    """Burst, emit from center, velocity along a line"""
234    e = arcade.Emitter(
235        center_xy=CENTER_POS,
236        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT // 4),
237        particle_factory=lambda emitter: arcade.LifetimeParticle(
238            filename_or_texture=TEXTURE,
239            change_xy=arcade.rand_on_line((-2, 1), (2, 1)),
240            lifetime=DEFAULT_PARTICLE_LIFETIME,
241            scale=DEFAULT_SCALE,
242            alpha=DEFAULT_ALPHA
243        )
244    )
245    return emitter_11.__doc__, e
246
247
248def emitter_12():
249    """Infinite emitting w/ eternal particle"""
250    e = arcade.Emitter(
251        center_xy=CENTER_POS,
252        emit_controller=arcade.EmitInterval(0.02),
253        particle_factory=lambda emitter: arcade.EternalParticle(
254            filename_or_texture=TEXTURE,
255            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
256            scale=DEFAULT_SCALE,
257            alpha=DEFAULT_ALPHA
258        )
259    )
260    return emitter_12.__doc__, e
261
262
263def emitter_13():
264    """Interval, emit particle every 0.01 seconds for one second"""
265    e = arcade.Emitter(
266        center_xy=CENTER_POS,
267        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
268        particle_factory=lambda emitter: arcade.LifetimeParticle(
269            filename_or_texture=TEXTURE,
270            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
271            lifetime=DEFAULT_PARTICLE_LIFETIME,
272            scale=DEFAULT_SCALE,
273            alpha=DEFAULT_ALPHA
274        )
275    )
276    return emitter_13.__doc__, e
277
278
279def emitter_14():
280    """Interval, emit from center, particle lifetime 1.0 seconds"""
281    e = arcade.Emitter(
282        center_xy=CENTER_POS,
283        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
284        particle_factory=lambda emitter: arcade.LifetimeParticle(
285            filename_or_texture=TEXTURE,
286            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
287            lifetime=1.0,
288            scale=DEFAULT_SCALE,
289            alpha=DEFAULT_ALPHA
290        )
291    )
292    return emitter_14.__doc__, e
293
294
295def emitter_15():
296    """Interval, emit from center, particle lifetime random in range"""
297    e = arcade.Emitter(
298        center_xy=CENTER_POS,
299        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
300        particle_factory=lambda emitter: arcade.LifetimeParticle(
301            filename_or_texture=TEXTURE,
302            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
303            lifetime=random.uniform(DEFAULT_PARTICLE_LIFETIME - 1.0, DEFAULT_PARTICLE_LIFETIME),
304            scale=DEFAULT_SCALE,
305            alpha=DEFAULT_ALPHA
306        )
307    )
308    return emitter_15.__doc__, e
309
310
311def emitter_16():
312    """Interval, emit in circle"""
313    e = arcade.Emitter(
314        center_xy=CENTER_POS,
315        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
316        particle_factory=lambda emitter: arcade.LifetimeParticle(
317            filename_or_texture=TEXTURE,
318            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
319            lifetime=DEFAULT_PARTICLE_LIFETIME,
320            center_xy=arcade.rand_in_circle((0.0, 0.0), 100),
321            scale=DEFAULT_SCALE,
322            alpha=DEFAULT_ALPHA
323        )
324    )
325    return emitter_16.__doc__, e
326
327
328def emitter_17():
329    """Interval, emit on circle"""
330    e = arcade.Emitter(
331        center_xy=CENTER_POS,
332        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
333        particle_factory=lambda emitter: arcade.LifetimeParticle(
334            filename_or_texture=TEXTURE,
335            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
336            lifetime=DEFAULT_PARTICLE_LIFETIME,
337            center_xy=arcade.rand_on_circle((0.0, 0.0), 100),
338            scale=DEFAULT_SCALE,
339            alpha=DEFAULT_ALPHA
340        )
341    )
342    return emitter_17.__doc__, e
343
344
345def emitter_18():
346    """Interval, emit in rectangle"""
347    width, height = 200, 100
348    centering_offset = (-width / 2, -height / 2)
349    e = arcade.Emitter(
350        center_xy=CENTER_POS,
351        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
352        particle_factory=lambda emitter: arcade.LifetimeParticle(
353            filename_or_texture=TEXTURE,
354            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
355            lifetime=DEFAULT_PARTICLE_LIFETIME,
356            center_xy=arcade.rand_in_rect(centering_offset, width, height),
357            scale=DEFAULT_SCALE,
358            alpha=DEFAULT_ALPHA
359        )
360    )
361    return emitter_18.__doc__, e
362
363
364def emitter_19():
365    """Interval, emit on line"""
366    e = arcade.Emitter(
367        center_xy=(0.0, 0.0),
368        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
369        particle_factory=lambda emitter: arcade.LifetimeParticle(
370            filename_or_texture=TEXTURE,
371            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_SLOW),
372            lifetime=DEFAULT_PARTICLE_LIFETIME,
373            center_xy=arcade.rand_on_line((0.0, 0.0), (SCREEN_WIDTH, SCREEN_HEIGHT)),
374            scale=DEFAULT_SCALE,
375            alpha=DEFAULT_ALPHA
376        )
377    )
378    return emitter_19.__doc__, e
379
380
381def emitter_20():
382    """Interval, emit from center, velocity fixed speed around 360 degrees"""
383    e = arcade.Emitter(
384        center_xy=CENTER_POS,
385        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
386        particle_factory=lambda emitter: arcade.LifetimeParticle(
387            filename_or_texture=TEXTURE,
388            change_xy=arcade.rand_on_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
389            lifetime=DEFAULT_PARTICLE_LIFETIME,
390            scale=DEFAULT_SCALE,
391            alpha=DEFAULT_ALPHA
392        )
393    )
394    return emitter_20.__doc__, e
395
396
397def emitter_21():
398    """Interval, emit from center, velocity in rectangle"""
399    e = arcade.Emitter(
400        center_xy=CENTER_POS,
401        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
402        particle_factory=lambda emitter: arcade.LifetimeParticle(
403            filename_or_texture=TEXTURE,
404            change_xy=arcade.rand_in_rect((-2.0, -2.0), 4.0, 4.0),
405            lifetime=DEFAULT_PARTICLE_LIFETIME,
406            scale=DEFAULT_SCALE,
407            alpha=DEFAULT_ALPHA
408        )
409    )
410    return emitter_21.__doc__, e
411
412
413def emitter_22():
414    """Interval, emit from center, velocity in fixed angle and speed"""
415    e = arcade.Emitter(
416        center_xy=CENTER_POS,
417        emit_controller=arcade.EmitterIntervalWithTime(0.2, DEFAULT_EMIT_DURATION),
418        particle_factory=lambda emitter: arcade.LifetimeParticle(
419            filename_or_texture=TEXTURE,
420            change_xy=(1.0, 1.0),
421            lifetime=DEFAULT_PARTICLE_LIFETIME,
422            scale=DEFAULT_SCALE,
423            alpha=128
424        )
425    )
426    return emitter_22.__doc__, e
427
428
429def emitter_23():
430    """Interval, emit from center, velocity in fixed angle and random speed"""
431    e = arcade.Emitter(
432        center_xy=CENTER_POS,
433        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 8, DEFAULT_EMIT_DURATION),
434        particle_factory=lambda emitter: arcade.LifetimeParticle(
435            filename_or_texture=TEXTURE,
436            change_xy=arcade.rand_vec_magnitude(45, 1.0, 4.0),
437            lifetime=DEFAULT_PARTICLE_LIFETIME,
438            scale=DEFAULT_SCALE,
439            alpha=DEFAULT_ALPHA
440        )
441    )
442    return emitter_23.__doc__, e
443
444
445def emitter_24():
446    """Interval, emit from center, velocity from angle with spread"""
447    e = arcade.Emitter(
448        center_xy=CENTER_POS,
449        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
450        particle_factory=lambda emitter: arcade.LifetimeParticle(
451            filename_or_texture=TEXTURE,
452            change_xy=arcade.rand_vec_spread_deg(90, 45, 2.0),
453            lifetime=DEFAULT_PARTICLE_LIFETIME,
454            scale=DEFAULT_SCALE,
455            alpha=DEFAULT_ALPHA
456        )
457    )
458    return emitter_24.__doc__, e
459
460
461def emitter_25():
462    """Interval, emit from center, velocity along a line"""
463    e = arcade.Emitter(
464        center_xy=CENTER_POS,
465        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
466        particle_factory=lambda emitter: arcade.LifetimeParticle(
467            filename_or_texture=TEXTURE,
468            change_xy=arcade.rand_on_line((-2, 1), (2, 1)),
469            lifetime=DEFAULT_PARTICLE_LIFETIME,
470            scale=DEFAULT_SCALE,
471            alpha=DEFAULT_ALPHA
472        )
473    )
474    return emitter_25.__doc__, e
475
476
477def emitter_26():
478    """Interval, emit particles every 0.4 seconds and stop after emitting 5"""
479    e = arcade.Emitter(
480        center_xy=CENTER_POS,
481        emit_controller=arcade.EmitterIntervalWithCount(0.4, 5),
482        particle_factory=lambda emitter: arcade.LifetimeParticle(
483            filename_or_texture=TEXTURE,
484            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
485            lifetime=DEFAULT_PARTICLE_LIFETIME,
486            scale=0.6,
487            alpha=128
488        )
489    )
490    return emitter_26.__doc__, e
491
492
493def emitter_27():
494    """Maintain a steady count of particles"""
495    e = arcade.Emitter(
496        center_xy=CENTER_POS,
497        emit_controller=arcade.EmitMaintainCount(3),
498        particle_factory=lambda emitter: arcade.LifetimeParticle(
499            filename_or_texture=TEXTURE,
500            change_xy=arcade.rand_on_circle((0.0, 0.0), 2.0),
501            lifetime=random.uniform(1.0, 3.0),
502        )
503    )
504    return emitter_27.__doc__, e
505
506
507def emitter_28():
508    """random particle textures"""
509    e = arcade.Emitter(
510        center_xy=CENTER_POS,
511        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
512        particle_factory=lambda emitter: arcade.LifetimeParticle(
513            filename_or_texture=random.choice((TEXTURE, TEXTURE2, TEXTURE3)),
514            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
515            lifetime=DEFAULT_PARTICLE_LIFETIME,
516            scale=DEFAULT_SCALE
517        )
518    )
519    return emitter_28.__doc__, e
520
521
522def emitter_29():
523    """random particle scale"""
524    e = arcade.Emitter(
525        center_xy=CENTER_POS,
526        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
527        particle_factory=lambda emitter: arcade.LifetimeParticle(
528            filename_or_texture=TEXTURE,
529            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
530            lifetime=DEFAULT_PARTICLE_LIFETIME,
531            scale=random.uniform(0.1, 0.8),
532            alpha=DEFAULT_ALPHA
533        )
534    )
535    return emitter_29.__doc__, e
536
537
538def emitter_30():
539    """random particle alpha"""
540    e = arcade.Emitter(
541        center_xy=CENTER_POS,
542        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
543        particle_factory=lambda emitter: arcade.LifetimeParticle(
544            filename_or_texture=TEXTURE,
545            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
546            lifetime=DEFAULT_PARTICLE_LIFETIME,
547            scale=DEFAULT_SCALE,
548            alpha=random.uniform(32, 128)
549        )
550    )
551    return emitter_30.__doc__, e
552
553
554def emitter_31():
555    """Constant particle angle"""
556    e = arcade.Emitter(
557        center_xy=CENTER_POS,
558        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
559        particle_factory=lambda emitter: arcade.LifetimeParticle(
560            filename_or_texture=TEXTURE2,
561            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
562            lifetime=DEFAULT_PARTICLE_LIFETIME,
563            angle=45,
564            scale=DEFAULT_SCALE
565        )
566    )
567    return emitter_31.__doc__, e
568
569
570def emitter_32():
571    """animate particle angle"""
572    e = arcade.Emitter(
573        center_xy=CENTER_POS,
574        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL * 5, DEFAULT_EMIT_DURATION),
575        particle_factory=lambda emitter: arcade.LifetimeParticle(
576            filename_or_texture=TEXTURE2,
577            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
578            lifetime=DEFAULT_PARTICLE_LIFETIME,
579            change_angle=2,
580            scale=DEFAULT_SCALE
581        )
582    )
583    return emitter_32.__doc__, e
584
585
586def emitter_33():
587    """Particles that fade over time"""
588    e = arcade.Emitter(
589        center_xy=CENTER_POS,
590        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
591        particle_factory=lambda emitter: arcade.FadeParticle(
592            filename_or_texture=TEXTURE,
593            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
594            lifetime=DEFAULT_PARTICLE_LIFETIME,
595            scale=DEFAULT_SCALE
596        )
597    )
598    return emitter_33.__doc__, e
599
600
601def emitter_34():
602    """Dynamically generated textures, burst emitting, fading particles"""
603    textures = [arcade.make_soft_circle_texture(48, p) for p in (arcade.color.GREEN, arcade.color.BLUE_GREEN)]
604    e = arcade.Emitter(
605        center_xy=CENTER_POS,
606        emit_controller=arcade.EmitBurst(BURST_PARTICLE_COUNT),
607        particle_factory=lambda emitter: arcade.FadeParticle(
608            filename_or_texture=random.choice(textures),
609            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST),
610            lifetime=DEFAULT_PARTICLE_LIFETIME,
611            scale=DEFAULT_SCALE
612        )
613    )
614    return emitter_34.__doc__, e
615
616
617def emitter_35():
618    """Use most features"""
619    soft_circle = arcade.make_soft_circle_texture(80, (255, 64, 64))
620    textures = (TEXTURE, TEXTURE2, TEXTURE3, TEXTURE4, TEXTURE5, TEXTURE6, TEXTURE7, soft_circle)
621    e = arcade.Emitter(
622        center_xy=CENTER_POS,
623        emit_controller=arcade.EmitterIntervalWithTime(0.01, 1.0),
624        particle_factory=lambda emitter: arcade.FadeParticle(
625            filename_or_texture=random.choice(textures),
626            change_xy=arcade.rand_in_circle((0.0, 0.0), PARTICLE_SPEED_FAST * 2),
627            lifetime=random.uniform(1.0, 3.5),
628            angle=random.uniform(0, 360),
629            change_angle=random.uniform(-3, 3),
630            scale=random.uniform(0.1, 0.8)
631        )
632    )
633    return emitter_35.__doc__, e
634
635
636def emitter_36():
637    """Moving emitter. Particles spawn relative to emitter."""
638
639    class MovingEmitter(arcade.Emitter):
640        def __init__(self, *args, **kwargs):
641            super().__init__(*args, **kwargs)
642            self.elapsed = 0.0
643
644        def update(self):
645            super().update()
646            self.elapsed += 1 / 60
647            self.center_x = sine_wave(self.elapsed, 0, SCREEN_WIDTH, SCREEN_WIDTH / 100)
648            self.center_y = sine_wave(self.elapsed, 0, SCREEN_HEIGHT, SCREEN_HEIGHT / 100)
649
650    e = MovingEmitter(
651        center_xy=CENTER_POS,
652        emit_controller=arcade.EmitInterval(0.005),
653        particle_factory=lambda emitter: arcade.FadeParticle(
654            filename_or_texture=TEXTURE,
655            change_xy=arcade.rand_in_circle((0.0, 0.0), 0.1),
656            lifetime=random.uniform(1.5, 5.5),
657            scale=random.uniform(0.05, 0.2)
658        )
659    )
660    return emitter_36.__doc__, e
661
662
663def emitter_37():
664    """Rotating emitter. Particles initial velocity is relative to emitter's angle."""
665    e = arcade.Emitter(
666        center_xy=CENTER_POS,
667        emit_controller=arcade.EmitterIntervalWithTime(DEFAULT_EMIT_INTERVAL, DEFAULT_EMIT_DURATION),
668        particle_factory=lambda emitter: arcade.LifetimeParticle(
669            filename_or_texture=TEXTURE,
670            change_xy=(0.0, 2.0),
671            lifetime=2.0,
672            scale=DEFAULT_SCALE
673        )
674    )
675    e.change_angle = 10.0
676    return emitter_37.__doc__, e
677
678
679def emitter_38():
680    """Use simple emitter interface to create a burst emitter"""
681    e = arcade.make_burst_emitter(
682        center_xy=CENTER_POS,
683        filenames_and_textures=(TEXTURE, TEXTURE3, TEXTURE4),
684        particle_count=50,
685        particle_speed=2.5,
686        particle_lifetime_min=1.0,
687        particle_lifetime_max=2.5,
688        particle_scale=0.3,
689        fade_particles=True
690    )
691    return emitter_38.__doc__, e
692
693
694def emitter_39():
695    """Use simple emitter interface to create an interval emitter"""
696    e = arcade.make_interval_emitter(
697        center_xy=CENTER_POS,
698        filenames_and_textures=(TEXTURE, TEXTURE3, TEXTURE4),
699        emit_interval=0.01,
700        emit_duration=2.0,
701        particle_speed=1.5,
702        particle_lifetime_min=1.0,
703        particle_lifetime_max=3.0,
704        particle_scale=0.2,
705        fade_particles=True
706    )
707    return emitter_39.__doc__, e
708
709
710class MyGame(arcade.Window):
711    def __init__(self):
712        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
713
714        # Set the working directory (where we expect to find files) to the same
715        # directory this .py file is in. You can leave this out of your own
716        # code, but it is needed to easily run the examples using "python -m"
717        # as mentioned at the top of this program.
718        file_path = os.path.dirname(os.path.abspath(__file__))
719        os.chdir(file_path)
720
721        arcade.set_background_color(arcade.color.BLACK)
722
723        # collect particle factory functions
724        self.factories = [v for k, v in globals().items() if k.startswith("emitter_")]
725
726        self.emitter_factory_id = -1
727        self.label = None
728        self.emitter = None
729        self.emitter_timeout = 0
730        self.obj = arcade.Sprite(":resources:images/pinball/bumper.png", 0.2, center_x=0, center_y=15)
731        self.obj.change_x = 3
732        pyglet.clock.schedule_once(self.next_emitter, QUIET_BETWEEN_SPAWNS)
733
734    def next_emitter(self, _time_delta):
735        self.emitter_factory_id = (self.emitter_factory_id + 1) % len(self.factories)
736        print("Changing emitter to {}".format(self.emitter_factory_id))
737        self.emitter_timeout = 0
738        self.label, self.emitter = self.factories[self.emitter_factory_id]()
739
740    def update(self, delta_time):
741        if self.emitter:
742            self.emitter_timeout += 1
743            self.emitter.update()
744            if self.emitter.can_reap() or self.emitter_timeout > EMITTER_TIMEOUT:
745                pyglet.clock.schedule_once(self.next_emitter, QUIET_BETWEEN_SPAWNS)
746                self.emitter = None
747        self.obj.update()
748        if self.obj.center_x > SCREEN_WIDTH:
749            self.obj.center_x = 0
750
751    def on_draw(self):
752        arcade.start_render()
753        self.obj.draw()
754        if self.label:
755            arcade.draw_text("#{} {}".format(self.emitter_factory_id, self.label),
756                             SCREEN_WIDTH / 2, SCREEN_HEIGHT - 20,
757                             arcade.color.PALE_GOLD, 20, width=SCREEN_WIDTH, align="center",
758                             anchor_x="center", anchor_y="center")
759        if self.emitter:
760            self.emitter.draw()
761            arcade.draw_text("Particles: " + str(self.emitter.get_count()), 10, 30, arcade.color.PALE_GOLD, 12)
762
763    def on_key_press(self, key, modifiers):
764        if key == arcade.key.ESCAPE:
765            arcade.close_window()
766
767
768if __name__ == "__main__":
769    game = MyGame()
770    arcade.run()