Key Ideas
1The Particle Class. A single particle needs just enough state to move and fade: position, velocity, a lifetime that counts down, and a color/radius that changes as it ages.
2The Particle System Manager. Holds a list of every active particle, updates them all each frame, removes dead ones, and offers emit() methods that spawn new bursts.
3Configuring Different Effects. Smoke, fire, rain, snow, sparks and explosions all reuse the exact same Particle/ParticleSystem classes — only the emit parameters (velocity, color, lifetime) change.
4Aging & Fading. life_pct = 1 - (age / lifetime) drives the alpha value, so a particle visibly fades out as it approaches the end of its life.
5Drag / Friction. Multiplying velocity by a value like 0.98 every frame is a simple approximation of drag, making particles decelerate naturally.
6Performance at Scale. A list comprehension rebuilding self.particles each frame is fine for hundreds of particles; for thousands, an object pool that reuses dead instances avoids constant allocation.