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