== Lesson 25: Particles image::learn/25-particles.png[The finished lesson, 480] By the end of this lesson a ship will fly across the bottom of the screen trailing fire from its engine, and a button will blow it into a hundred and forty spinning sparks that bounce off the floor. You will not write a single line that draws a spark. That is the whole idea of this lesson. You describe the fire; the engine makes it. === Why You Do Not Draw the Sparks You could do it the other way. You know enough: a table of sparks from lesson six, each with an x, a y, and a speed; a loop in `onOverlayUpdate` that moves every one of them, fades it, drops it when it dies, and draws it. It works. It is also two hundred lines of bookkeeping that every game rewrites, and it runs in Lua, one spark at a time, sixty times a second. Singe does it for you instead. You hand it a *recipe* -- how many sparks a second, how long each one lives, how fast and which way they set off, what pulls on them, what they look like, and what colour they fade to -- and it keeps the sparks itself. The thing that holds the recipe and runs it is called an *emitter*. An emitter is not a picture and it is not an object in your game. It is a description of a kind of fire, kept somewhere you can point at, and it can make that fire anywhere you ask. === Start with Something That Moves Particles are more convincing when they come off something, so start with a ship. Make a folder, copy the book's `art` folder into it, and put this in `particles.singe`: [source,lua] ---- dofile("Singe/Framework.singe") local SHIP_WIDTH = 32 local SHIP_HEIGHT = 24 local SHIP_SPEED = 120 local shipSprite = spriteLoad(DIR .. "art/ship.png") local shipX = (overlayGetWidth() - SHIP_WIDTH) / 2 local shipY = overlayGetHeight() - 56 local movingLeft = false local movingRight = false local lastTicks = singeGetTicks() function onInputPressed(what) if what == SWITCH_LEFT then movingLeft = true elseif what == SWITCH_RIGHT then movingRight = true end end function onInputReleased(what) if what == SWITCH_LEFT then movingLeft = false elseif what == SWITCH_RIGHT then movingRight = false end end function onOverlayUpdate() local now = singeGetTicks() local seconds = (now - lastTicks) / 1000 lastTicks = now if movingLeft then shipX = shipX - SHIP_SPEED * seconds end if movingRight then shipX = shipX + SHIP_SPEED * seconds end overlayClear() spriteDraw(shipSprite, shipX, shipY) return OVERLAY_UPDATED end ---- Run it with `Singe -R particles` and steer with the left and right arrow keys. There is nothing new here: a sprite from lesson nine, the switches from lesson three, and `singeGetTicks` so the ship moves at the same speed whatever the frame rate is. The one line worth pointing at is `dofile("Singe/Framework.singe")`. That is what defines `DIR`, the folder your script is in, so `DIR .. "art/ship.png"` finds the picture no matter which folder you started Singe from. === Give It an Exhaust Now the fire. Add this above the callbacks, after the ship's variables: [source,lua] ---- local exhaust = emitterNew() emitterSetBlend(exhaust, PARTICLE_ADD) emitterSetRate(exhaust, 90) emitterSetLife(exhaust, 0.15, 0.40) emitterSetDirection(exhaust, 0, 1) emitterSetSpread(exhaust, 12) emitterSetSpeed(exhaust, 40, 90) emitterSetSize(exhaust, 7, 1) emitterSetColor(exhaust, 255, 240, 180, 255, 255, 60, 0, 0) emitterSetMax(exhaust, 120) emitterStart(exhaust) ---- And two lines inside `onOverlayUpdate`. The first goes just before `overlayClear`, the second just after `spriteDraw`: [source,lua] ---- emitterSetPosition(exhaust, shipX + SHIP_WIDTH / 2, shipY + SHIP_HEIGHT - 2) ---- [source,lua] ---- emitterDraw(exhaust) ---- Save. The ship now sits on a short tongue of yellow flame, and the flame goes with it when you steer. === Blow It Up An exhaust runs forever. An explosion happens once, which the engine treats as a different kind of request from the same emitter. Two more files and one more number first. These go beside the ship sprite at the top: [source,lua] ---- local sparkSprite = spriteLoad(DIR .. "art/star.png") local boomSound = soundLoad(DIR .. "art/boom.wav") ---- and this goes beside `shipX` and `shipY`, twenty units up from the bottom of the screen: [source,lua] ---- local ground = overlayGetHeight() - 20 ---- Now the second recipe, under the first: [source,lua] ---- local boom = emitterNew() emitterSetTexture(boom, sparkSprite) emitterSetBlend(boom, PARTICLE_ADD) emitterSetLife(boom, 0.4, 1.1) emitterSetSpeed(boom, 40, 220) emitterSetSpread(boom, 180) emitterSetGravity(boom, 0, 160) emitterSetDrag(boom, 0.9) emitterSetSize(boom, 10, 2, 0.4) emitterSetSpin(boom, -240, 240) emitterSetColor(boom, 255, 255, 220, 255, 255, 70, 20, 0) emitterSetCollide(boom, COLLIDE_FLOOR, 0.35, 0.3, ground) emitterSetTrail(boom, 4, 2) emitterSetMax(boom, 400) ---- Notice that `emitterStart` is missing. This one is never going to stream. Add a function to set it off, above `onInputPressed`: [source,lua] ---- function explode() if not flying then return end flying = false emitterStop(exhaust) emitterSetPosition(boom, shipX + SHIP_WIDTH / 2, shipY + SHIP_HEIGHT / 2) emitterBurst(boom, 140) soundPlay(boomSound) timerAfter(RESPAWN_MS, function() flying = true emitterStart(exhaust) end) end ---- That needs three more things: `local RESPAWN_MS = 1400` with the other constants, `local flying = true` with the other variables, and a branch in `onInputPressed`: [source,lua] ---- elseif what == SWITCH_BUTTON1 then explode() ---- Finally, the ship should not be there while it is in pieces. Wrap the steering and the drawing in `onOverlayUpdate` in `if flying then ... end`, and add `emitterDraw(boom)` beside the other draw call. The finished script is `learn/25-particles.singe`. Press the *space bar* -- button one by default -- and the ship comes apart into a cloud of tumbling stars that arc down, strike the floor, and bounce. === What Just Happened There are a great many new calls in this lesson, and they are all the same shape: the emitter first, then the thing you are setting. Take them in groups. [source,lua] ---- local exhaust = emitterNew() ---- Makes an emitter and hands back a *handle*: a number the engine uses to find it again, exactly like the number `spriteLoad` gives you. With no argument you get a two dimensional emitter, which lives in overlay coordinates and is drawn from `onOverlayUpdate`. Give `emitterNew` a node instead and you get a three dimensional one, which lives in the scene and draws itself; everything else in this lesson is the same for both. A new emitter already has a complete recipe -- fifty particles a second, white, a second or two of life, drifting up the screen -- so every call after this one is you disagreeing with a default. The manual's entry for `emitterNew` lists them all. [source,lua] ---- emitterSetRate(exhaust, 90) emitterSetLife(exhaust, 0.15, 0.40) ---- Ninety particles a second, each living between fifteen hundredths and four tenths of a second. Those two numbers together decide how much fire there is: ninety a second that live for a third of a second means about thirty on screen at any moment. Anywhere an emitter call takes a smallest and a largest, every particle gets its own value picked at random between the two. That is where the raggedness comes from. A fire where every flame lived exactly as long as every other would look like a machine. [source,lua] ---- emitterSetDirection(exhaust, 0, 1) emitterSetSpread(exhaust, 12) emitterSetSpeed(exhaust, 40, 90) ---- Which way they set off, how wide the fan is, and how fast. Overlay Y grows *downward*, as it has since lesson one, so `(0, 1)` is down the screen -- out of the back of a ship that points up. `emitterSetSpread` is half the angle of the fan: twelve degrees is a tight jet, ninety opens it to half the circle, and one hundred and eighty is every direction at once. [source,lua] ---- emitterSetSize(exhaust, 7, 1) emitterSetColor(exhaust, 255, 240, 180, 255, 255, 60, 0, 0) ---- These two are why the flame looks like a flame. A particle does not have a size and a colour; it has a size and a colour *at birth* and another *at death*, and it slides evenly from one to the other over whatever life it drew. So each spark starts seven overlay units across and pale yellow, and ends one unit across and dark red with an alpha of zero, which is invisible. It shrinks and fades out because you said where it ends, not because anything faded it. `emitterSetColor` takes eight numbers, which is a lot to read: red, green, blue, and alpha at the start, then red, green, blue, and alpha at the end. [source,lua] ---- emitterSetBlend(exhaust, PARTICLE_ADD) ---- How a particle combines with what is behind it. `PARTICLE_ALPHA` is normal painting and is the default: use it for smoke, dust, rain, and rubble. `PARTICLE_ADD` adds its light to the picture instead, so two sparks on top of each other are brighter than one and nothing ever gets darker. Fire, sparks, lasers, and magic are additive. Smoke is not, and smoke drawn additively looks like steam lit from inside. [source,lua] ---- emitterSetMax(exhaust, 120) emitterStart(exhaust) ---- `emitterSetMax` is the size of the pool: how many of this emitter's particles may exist at once. When the pool is full nothing new is born until something dies. Ninety a second living up to four tenths of a second needs about thirty-six, so a hundred and twenty is comfortable. Set it before you start, because changing it later throws away every live particle. `emitterStart` opens the tap. From then on the emitter makes particles every frame, whether or not you draw it, until `emitterStop` closes it again. Note what `emitterStop` does *not* do: it stops new particles, and the ones already alive finish their lives and fade out normally. That is why the exhaust trails away instead of vanishing when the ship explodes. If you want them gone this instant, `emitterClear` kills them. [source,lua] ---- emitterSetPosition(exhaust, shipX + SHIP_WIDTH / 2, shipY + SHIP_HEIGHT - 2) ---- This is how an emitter follows something. A two dimensional emitter is born at one point, and that point is wherever you last put it, so you set it every frame from whatever is moving: a sprite's position, a physics body's node, the mouse. New particles appear at the new place; the ones already in the air stay where they were born, which is exactly right for exhaust, because exhaust is left behind. A three dimensional emitter is made on a node and follows that node on its own, with no call in your update at all. And when you *do* want the whole cloud to travel with the thing -- a shield, an aura, a thruster plume seen from outside -- `emitterSetLocal` switches that on. [source,lua] ---- emitterDraw(exhaust) ---- Two dimensional emitters are drawn when you ask, once per frame, from `onOverlayUpdate`. The order of your `emitterDraw` calls is the order they stack in. Forgetting this call is the mistake everyone makes once: the emitter keeps running, keeps spending its pool, and shows nothing. Now the explosion's own calls. [source,lua] ---- emitterSetTexture(boom, sparkSprite) ---- Without a texture every particle is a soft round blob the engine draws itself, which is what the exhaust uses and is right for flame. With one, each particle wears that picture -- here `star.png`, eight pixels by eight. The emitter takes its own copy of the pixels, so you may unload the sprite afterward and the particles keep working. [source,lua] ---- emitterSetGravity(boom, 0, 160) emitterSetDrag(boom, 0.9) ---- Gravity is a steady pull, in overlay units per second per second, and it is positive downward for the same reason the exhaust fires with a positive Y. Drag is the opposite: it takes speed away, a little every frame, so the sparks fly out hard and then hang. Sparks with gravity and no drag rain straight down like a firework; drag is what makes them look like they are moving through air. [source,lua] ---- emitterSetSpin(boom, -240, 240) emitterSetSize(boom, 10, 2, 0.4) ---- Spin is degrees a second, and a range that crosses zero gives you some spinning each way. It shows on a texture and does nothing at all to the built-in blob, which is round. The third number on `emitterSetSize` is variation. It scales both sizes of each particle by one random factor -- here anywhere from six tenths to one and four tenths -- so the sparks are not all the same star at the same size. [source,lua] ---- emitterSetCollide(boom, COLLIDE_FLOOR, 0.35, 0.3, ground) emitterSetTrail(boom, 4, 2) ---- `COLLIDE_FLOOR` puts a flat, invisible floor across the world at the height you name, and particles bounce off it. The two numbers before it are how much speed survives a bounce and how much sideways speed is scrubbed off. It costs nothing, because it is one comparison per particle, and it makes debris look like it landed somewhere rather than falling through the world. In a three dimensional game `COLLIDE_SCENE` bounces particles off the real geometry instead, which costs a great deal more. A trail keeps the last few places a particle has been and draws a fading ribbon through them. Four positions and two units wide turns each star into a short streak. [source,lua] ---- emitterBurst(boom, 140) ---- And this is the other way to make particles. `emitterStart` is a tap; `emitterBurst` is a handful thrown at once. The emitter does not have to be streaming, and this one never is. Everything else -- the recipe, the pool, the position -- works the same. That distinction is worth keeping. Anything continuous is a stream you start and stop: exhaust, smoke, rain, a torch, a waterfall. Anything that happens at a moment is a burst: an explosion, a splash, a footfall in dust, a bullet hit, a muzzle flash. Plenty of effects are both, from one emitter or two: a shell bursts and then smokes. === How Many Is Too Many The honest answer is that particles are cheap to move and expensive to look at. Moving ten thousand particles is arithmetic, and a computer that can run a game at all can do it without noticing. What costs is the drawing, and what the drawing costs is roughly the number of screen pixels the particles cover, counting overlaps. Two hundred sparks eight pixels across cover less of the screen than one smoke puff that fills it. That is the rule: small and many is nearly free, and big and overlapping is not. Additive blending is the expensive kind, because nothing it draws can be skipped -- every layer adds to what is underneath, so the machine paints all of them. A dozen full-screen additive flashes on top of each other will slow a Raspberry Pi 4 to a crawl while ten thousand tiny bouncing sparks do not trouble it. So: a few thousand small particles is nothing to worry about. Before you go past that, put the count on screen and watch it. [source,lua] ---- overlayPrint(2, 4, "Particles alive: " .. (emitterGetCount(exhaust) + emitterGetCount(boom))) ---- `emitterGetCount` is how you size a pool honestly rather than by guessing. If the number sits at exactly your `emitterSetMax`, your pool is full and the engine is quietly refusing to make particles you asked for. If it never gets near, you are reserving memory for nothing. === Try It . *Turn the exhaust into smoke.* Change `emitterSetBlend(exhaust, PARTICLE_ADD)` to `PARTICLE_ALPHA`, set the colour to grey fading to transparent grey, and make it grow instead of shrink with `emitterSetSize(exhaust, 3, 14)`. Two lines have made a different machine. . *Take the drag off the explosion.* Delete the `emitterSetDrag` line and compare. Then put drag back and take the gravity off instead. One of those looks like fireworks and the other looks like an accident. . *Widen the exhaust.* Set `emitterSetSpread(exhaust, 180)`. The ship stops looking like it has an engine. Spread is doing more work than you think. . *Burst a thousand.* Change `emitterBurst(boom, 140)` to `emitterBurst(boom, 1000)` and watch the count on screen. It will not reach a thousand. Work out from `emitterSetMax` why not, then raise the pool and make the sparks four times bigger, and see what that does to the frame rate. . *Fire the explosion where the ship is not.* Put `emitterSetPosition(boom, 40, 60)` in `explode` instead of the ship's position. An emitter is a description of a fire, not a thing that lives anywhere, and you can burst the same one in ten places in the same frame. === Break It on Purpose The constants -- `PARTICLE_ADD`, `COLLIDE_FLOOR`, `OVERLAY_UPDATED` -- are names Singe has given to numbers. Get one slightly wrong and Lua does not complain, because in Lua a name that was never given a value is not an error: it is `nil`, the value that means "nothing here". Change the exhaust's blend line to a name that sounds just as likely: [source,lua] ---- emitterSetBlend(exhaust, PARTICLE_ADDITIVE) ---- Singe stops before the window opens, naming the line that call is on in your file: ---- 23:emitterSetBlend: Argument 2 must be a number. ---- This error has a different shape from the Lua errors in lesson eight, because it comes from the engine rather than from Lua. It is the line number, then the call that objected, then the complaint. Argument two of `emitterSetBlend` is the blend mode, and what arrived was `nil`, because `PARTICLE_ADDITIVE` is not a name anything ever defined. "Must be a number" nearly always means this: a misspelled constant, or a variable you thought you had set. The fix is to check the name against the manual, which spells it `PARTICLE_ADD`. === What You Learned * An emitter holds a recipe for a kind of particle and keeps the particles itself, so your script never draws one. * `emitterNew` with no argument makes a two dimensional emitter in overlay coordinates; with a node it makes a three dimensional one in the scene. * Every emitter starts with a complete recipe, and each `emitterSet...` call changes one part of it. * Anywhere a call takes a smallest and a largest, every particle draws its own value at random between them, and that is where the raggedness comes from. * Size and colour are set at birth and at death, and the particle slides from one to the other over its life. * `emitterStart` streams until `emitterStop`; `emitterBurst` throws a handful at once. Continuous effects are streams, momentary ones are bursts. * `emitterStop` lets the live particles finish; `emitterClear` kills them now. * A two dimensional emitter is moved with `emitterSetPosition` every frame and drawn with `emitterDraw` every frame. A three dimensional one follows its node and draws itself. * Particles cost what they cover on screen, not what they cost to move, and additive blending is the expensive kind. * `emitterSetMax` caps the pool, and `emitterGetCount` tells you whether you guessed the size right. === Next Time The explosion moved a hundred and forty things without being told where any of them should go, but they were sparks, and sparks do not care what they run into. Lesson twenty-six is about something that does: a character who has to get from here to there, and a wall in the way.