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