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