PICO-8


Particle Effects



Follow along to make these effects and more yourself!


This lesson focuses on FUNCTIONS and TABLES!


Challenges!

1. Change the SFX of an effect!

2. Change the settings of the fire to make it huge.

3. Change the colors of the trail.

4. Make your own visual effect function that uses our core functions!

How to make Particle Effects!


[ Video Coming Soon ]


Explanation of Code!


  • 1. Overview and Variables

      function _init()
          --particles
          effects = {}
      
          --effects settings
          trail_width = 1.5
          trail_colors = {12,13,1}
          trail_amount = 2
      
          fire_width = 3
          fire_colors = {8,9,10,5}
          fire_amount = 3
      
          explode_size = 5
          explode_colors = {8,9,6,5}
          explode_amount = 5
      
          --sfx
          trail_sfx = 0
          explode_sfx = 1
          fire_sfx = 2
      
          --player
          player = {x=53, y=53, r=2, c=7}
      end
      

      As usual, we set most of the variables we will use inside of the main _init() function, or if you are using multiple game states such as a main menu, game play, game over, etc. then you should put these variables inside of the init function for the state that will use them.


      effects = {} creates an empty table that will be used to hold all of the particle effects data.

      Each particle (could be a single circle or a single pixel) will have data attached to it such as the screen position, size, force and direction of movement, color, and lifetime. Each particle's data will be held in a smaller table and then all of the particle tables will be added to this effects table.






      It is a good idea to organize your code so that variables that you may need to adjust many times, are all grouped together near the beginning of your game file. This way, when you are play testing your game and you want to tweak things, you can simply go to one place in your code and change the values of the correct variables instead of hunting them down in your code every time.

      This is why we created an --effects settings section to organize the particle effects variables that can be quickly changed depending on the feel of the game. It's like having a settings menu inside of our code.

      Want to increase the fire effect size? No need to find the fire effects function, simply find the fire effects variables in these "effects settings" section and try different amounts quickly and easily.


      In this tutorial, we will create 3 effects:

      trail = usually a line or several pixels that constantly follow behind something moving.

      fire = some kind of pixel effect that looks like fire and/or smoke.

      explode = usually circles or pixels that spread outward in every direction at the same time.

      But there are many other types of effects that you could create from this code and we'll show how easy that is at the end.


      Each effect has 3 settings variables:

      width or size = a number of pixels that controls the area of space that the effect is created in.

      colors = a table of up to 4 color numbers (you could add more) that each particle will cycle through as its lifetime runs out.

      amount = a number of particles that will be created at the same time.


      Each effect also has a sound effect (SFX) number and we grouped those together as variables here as well. It is a good idea to create and name these variables appropriately so that your code is highly readable for yourself and others.

      --sfx
      trail_sfx = 0
      explode_sfx = 1
      fire_sfx = 2
      

      If I see sfx(0) in your code, all I know is that you are playing the sound effect #0. Then I have to go to the SFX editor, find #0, and listen to it to know what sound effect it actually is.

      If I see sfx(explode_sfx), then I know you are playing an explosion sound effect. Done. And if you or I want to change the sound effect number, then we only need to change one variable setting instead of searching all of the code for sfx(0) and change each one.






      player = {x=53, y=53, r=2, c=7} = For this demo, we needed to have a controllable "player", which is just a circle.

      It is a table that has four keys:

      x = X-axis position (number of pixels from the left)

      y = Y-axis position (number of pixels from the top)

      r = (radius) size

      c = color number






  • 2. Core Effects Functions

      There are 3 core functions that we will build to create and run every particle effect from tiny sparkles to giant explosions. First we need a function that will take data about one particle, put that data together into a table, then add that particle table to the larger effects table.

      Second, we will need a function that will update all the particles in the effects table, controlling how they move and when they change size or colors.

      Lastly, we will need a function that will draw all the particles in the effects table to the game screen.

    • A. Add Effects Function


        Here is the first function that adds each particle data to the effects table, so we named it "add_fx":

        function add_fx(x,y,die,dx,dy,grav,grow,shrink,r,c_table)
            local fx={
                x=x,
                y=y,
                t=0,
                die=die,
                dx=dx,
                dy=dy,
                grav=grav,
                grow=grow,
                shrink=shrink,
                r=r,
                c=0,
                c_table=c_table
            }
            add(effects,fx)
        end
        

        function creates a new custom function. Then we write the name add_fx. Then inside of parentheses we name all the variables that we will need to know about a particle in order to build the particle table. Here is what those variable names are and what they are for:

        x = X-axis position (number of pixels from the left)

        y = Y-axis position (number of pixels from the top)

        t = (time) starts at 0 and used for counting the particle's lifetime.

        die = a number used for how long the particle should "live" (when it should "die")

        dx = (change in X) for moving the particle left or right.

        dy = (change in Y) for moving the particle up or down.

        grav = a boolean (true or false) variable for whether to apply gravity to the particle or not.

        grow = a boolean (true or false) variable for whether to increase the particle size or not.

        shrink = a boolean (true or false) variable for whether to decrease the particle size or not.

        r = (radius) a number used for a particle's size. 1 = a single pixel.

        c = (color) the current color of the particle. It is first set as 0 but will be updated later depending on the color table.

        c_table = (color table) a simple list of color numbers in the order of what color the particle should be through its lifetime.


        We take all of those variables and put them inside a local table named fx.

        It might look funny that each entry in the fx table looks like x = x but understand that the first "x" there is what the key will be named inside the table. Then the second "x" is the value of the variable x that we will pass in to this function from inside the parentheses. We just decided to keep our variable naming consistent which is good practice, but it's unfortunate that it sometimes leads to this code that can look a bit confusing at first.


        To finish this function we need to take that local fx table and add it to the larger effects table.

        add(effects,fx) will do that and now effects is a table of tables!

        What is a table of tables?




    • B. Update Effects Function

        The next core function is for updating the effects, so we appropriately name it update_fx

        Here is the code overview:

        function update_fx()
            for fx in all(effects) do
                --lifetime
                ...
        
                --color depends on lifetime
                ...
        
                --physics
                ...
        
                --move
                ...
            end
        end
        

        Notice the first line creates the function, names the function, and then has empty parentheses. That's because this function simply needs to run the same way every time and does not need to be given any variables like the add_fx function did.

        More honestly, this function does run differently every time, but that's because it will use the effects table and check each particle's data to know how to change things with that particle.



        The first thing we do inside this update function is access and loop through the effects table. To do that we use a special table loop: FOR entry IN ALL(table) do

        for fx in all(effects) do

        We name a variable fx there to become the local variable of each entry in the effects table.

        Remember that the effects table is a table of tables, so each entry is a smaller table of its own. So each particle table inside the effects table will be able to be accessed as a new local variable named fx. And anything we want to change about that particle, we can simply change fx while we are inside of this table loop.



        --lifetime
        fx.t+=1
        if fx.t>fx.die then del(effects,fx) end
        

        This next bit of code handles the lifetime of the particle. Inside each particle table (now named "fx") we already added a key named t for tracking the amount of time the particle has been "alive".

        So every time the game update runs, we will add to each particle's t to increase their lifetime counter.

        Now that the lifetime counter is working, we can check if the particle's life runs past its time to "die", which is another variable we already set inside of each particle. So if the lifetime is more than when the particle should die, then we should delete the particle and remove it from the effects table completely.

        That is eactly what this code will check and do: if fx.t>fx.die then del(effects,fx) end






        In this next part, we will change the color of the particle based on the lifetime.

        For example, in the fire particle effect, we will want to create many particles as single pixels that start off red, move upwards and change to orange, then yellow, then gray as smoke. Then the particle can die and be erased.

        So first we need to figure out a way to know where the particle is in its lifetime.

        Did the particle just start? Is it half way through its life? Or is it close to the die time?


        We know how long it has been alive because of each particle's lifetime counter: t

        We also know how long it will live because of each particle's death variable: die

        So if we create a fraction that is lifetime counter over death time, then that will tell us how close the particle's life is getting to its end.

        Fractions are simply division, so we divide t by die and we will get a decimal that is somewhere between 0 and 1. Zero would be the start of a particle's life, and one would be the particle's death. So now we have a decimal to tell us how near or far the particle is to die.


        So we could simply check for something like this:

        --color depends on lifetime
        if fx.t/fx.die < .5 then
            --particle is younger than half its life
        else
            --particle is older than half its life
        end
        

        That's nice a simple but it would only let us set 2 colors: one color for when the particle is young, and one color when it is old.

        But we already thought about a fire effect that would be 4 colors: red, orange, yellow, and finally gray. So we definitely want more than just 2 colors, and we also want the freedom to use only 2 or 3 colors too.

        That just takes a little bit more math like when we compared lifetime with death time as a fraction.


        This time we want to compare the number of colors in each particle's color table. That is saved in c_table. And remember we access that from the new local variable for each particle that we named FX. So to get the color table we write: fx.c_table

        We can check the total number of colors in the color table by getting the count of entries in the table. To do that we use the # symbol before the table. So this will get the total count of colors: #fx.c_table


        Now we can create more fractions using the total color count.

        If the color count is 2. Then 1/color_count = 1/2. So that would be half.

        If the color count is 2. Then 2/color_count = 2/2. So that would be full, or the end.


        Imagine the same thing but with a different total color count.

        If the color count is 4. Then 1/color_count = 1/4. And that would be a quarter.

        If the color count is 4. Then 2/color_count = 2/4 = 1/2. And that would be only half.


        So notice we are doing the same math there but depending on the total number of colors in the color table, the result will change to show a different result. And again, by doing the actual division it will give us a decimal between 0 and 1.

        Now we have something to compare the lifetime decimal with!


        Before we write the code, lets just think it through first.

        If a particle's life compared to its death is less than the first color compared to the total number of colors, then the particle should be the first color.

        If a particle's life compared to its death is more than the first but less than the second color compared to the total number of colors, then the particle should be the second color.

        And so on. Here's the actual code:

        --color depends on lifetime
        if fx.t/fx.die < 1/#fx.c_table then
            fx.c=fx.c_table[1]
        
        elseif fx.t/fx.die < 2/#fx.c_table then
            fx.c=fx.c_table[2]
        
        elseif fx.t/fx.die < 3/#fx.c_table then
            fx.c=fx.c_table[3]
        
        else
            fx.c=fx.c_table[4]
        end
        

        The if and all elseif here are checking Algebra really. So to help you understand what's happening try plugging in different numbers and doing the division yourself to see how those compare with each other. Then you should see that the math works out.

        This is built for a maximum of 4 colors. As it is, you could add 5 or 6 colors to a particle's color table, but this code that updates the actual color will simply ignore any higher than 4.

        But it's easy to continue the elseif pattern to allow for 5, 6, 7, or more colors.


        If any of those checks are true, then we want to set the particle color (fx.c) using the correct color from the color table (fx.c_table[#]).

        That's why we do: fx.c = fx.c_table[1-4]

        The square brackets [ ], is another way to select a value from inside a table.


        Hang on, look what that means! Let's just take a step back for a moment to appreciate the fact that we are looping through the effects table and accessing each particle as an fx table, AND NOW we are accessing each color from inside the particle's c_table color table!

        We have just successfully jumped 3 levels of table inception! And if you are following along without a nose bleed, well done!






        The next code we update for each particle will be adding physics, but only if that particle has those physics variables set to true.

        --physics
        if fx.grav then fx.dy+=.5 end
        if fx.grow then fx.r+=.1 end
        if fx.shrink then fx.r-=.1 end
        

        if fx.grav then checks if the gravity variable on the particle is true.

        If so, then we add .5 to the particle's dy so that it moves down a little faster.


        if fx.grow then checks if the growing variable on the particle is true.

        If so, then we add .1 to the particle's r (radius) so that it gets bigger.


        if fx.shrink then checks if the shrinking variable on the particle is true.

        If so, then we subtract .1 from the particle's r (radius) so that it gets smaller.


        These three are basically settings for you to create many different types of effects yourself.






        The last bit of code we update for each particle is changing the particle's position, creating movement.

        --move
        fx.x+=fx.dx
        fx.y+=fx.dy
        

        fx.x plus fx.dx takes the current X-position and adds the X-momentum variable.

        fx.y plus fx.dy takes the current Y-position and adds the Y-momentum variable.

        To understand how the momentum variables work, check out our Advanced Movement tutorial.




    • C. Draw Effects Function

        The last core function is for drawing the effects, so we appropriately name it draw_fx.

        function draw_fx()
            for fx in all(effects) do
                --draw pixel for size 1, draw circle for larger
                if fx.r<=1 then
                    pset(fx.x,fx.y,fx.c)
                else
                    circfill(fx.x,fx.y,fx.r,fx.c)
                end
            end
        end
        

        Just like the update_fx function, we want to cycle through every particle in the effects table. So we use the same table loop: for fx in all(effects) do


        Inside of that we simply want to draw the particle with the correct position (X,Y), size (R), and color (C).

        There are two different ways we want to draw particles.

        Small particles we want to draw as single pixels, so we use a pixel drawing function: pset()

        Larger particles we want to draw as a circle, so we use a circle drawing function: circfill()


        To figure out which of those to use, we first check the currect size of the particle.

        if fx.r<=1 then checks the size (R for radius) as less than or equal to 1.

        So if we want single pixel particles, then we just set the r to 1. Or if we want a shrinking particle, then as its r size gets smaller, and becomes 1 or less, then it will be drawn as a single pixel.

        pset(fx.x,fx.y,fx.c) is how we take the position (x, y) and color (c) from the particle table (fx).


        Then we just write else so that it catches any particles that come back as false from the first check.

        So if any particles size is not less than or equal to 1, that means it must be more than 1! So let's draw a circle instead.

        circfill(fx.x,fx.y,fx.r,fx.c) will draw a filled in circle all the same color. We take the variables out of the particle table (fx) again, but this time we also use the size variable (r) to set the correct size of the circle.


        That's it for drawing each particle!






  • 3. Example Effects

      Creating your own visual effects using the above 3 functions is surprisingly easy!

      All you need to do is set up a new function, name it whatever fits for the effect you want to create, and then use the add_fx() function to create particles with the settings that you want, to create the effect you want.


      Let's take a look at a few examples we've written to show you how to set up different effects functions and change the settings in little ways to create wildly different results.

      We are going to use two words often in the follow explanations: "parameter" and "argument".

      parameter = a local variable name set inside of the parentheses when a function is declared.

      Example: function shoot(x,y,z)

      X, Y, and Z are "parameters" of this shoot function.


      argument = a variable given inside of parentheses when a function is called.

      Example: shoot(player_x, player_y, player_z)

      It's the same function, but somewhere in the game's update code when we want to run the shoot function. Notice it does not have the word "function" before the name, so we are asking it to run, not creating a new function.

      The variables inside of the parentheses already hold data about the player and want to pass the data to the shoot function, so they are "arguments".


      We will be creating effects functions and think ahead about what parameters we should set them up with.

    • A. Trail

        Overview

        The motion trail effect will create particles behind a moving object that change color and disappear slowly.

        Here is the full function for setting trail effect particles and sending them to the effects table using our add_fx( ) function.

        -- motion trail effect
        function trail(x,y,w,c_table,num)
        
            for i=0, num do
                --settings
                add_fx(
                    x+rnd(w)-w/2,  -- x
                    y+rnd(w)-w/2,  -- y
                    40+rnd(30),  -- die
                    0,         -- dx
                    0,         -- dy
                    false,     -- gravity
                    false,     -- grow
                    false,     -- shrink
                    1,         -- radius
                    c_table    -- color_table
                )
            end
        end
        

        It looks long because we expanded it out and added comments for easy editing. But this is what it looks like if we condense it down.

        -- motion trail effect
        function trail(x,y,w,c_table,num)
            for i=0, num do
                add_fx( ... )
            end
        end
        

        So now we can see there are really only 3 parts to this effect function.

        1. The parameters for being able to customize the effect.

        2. Repeatedly create a certain number (num) of particles.

        3. Send each particle's data to the add_fx function so it can be added to the effects table.






        Parameters

        function trail(...) creates the function and names it "trail".

        ( x,y,w,c_table,num ) are all the parameters for customizing the trail.


        x,y are for the starting position of the particle.

        In your game, you may want a trail behind a player when it jumps, so you can tell this trail function to run, and pass the player's X and Y position so the trail starts creating particles at the player's position.


        w is for the width of the trail.

        A large width will create a wider or narrower trail. It is the size of the spawn zone for each particle in the trail. A smaller spawn zone will make sure all of the particles are close together and create a thin trail. Balance this with the number (num) of particles spawned together to create wildly different effects.


        c_table is a table of color numbers.

        If you think your game will only have one trail and always the same color, then simply remove this as a parameter and hard set the color table inside of this function instead. But this is a parameter so that you can create trails with different colors. For example, if you have a multiplayer game, then each player can have a trail color to help set them apart. Or the player's bullets can be a blue trail, and the enemy bullets can be red. So many ways to use this same effect and simply change the colors used in the effect with this parameter!


        num is a number of particles that will be created together at the same time.

        So it will control the amount or volume of particles in the effect. A low number will create a sparse effect to make something like a glitter trail. A high number will create a densely packed effect to make something like a beam trail. Balance this with width (w) to create wildly different trail effects. We created the settings variables named "..._amount" to control this up in the _init() function.






        Loop for Bursts of Particles

        We will want to be able to create more than one particle at a time to make a dense trail. So we start with a For Loop: for i=0, num do

        What is a For Loop?

        That will count a number (i) starting at 0 and stop when it reaches whatever number we decide to set in the amount of particles (num). And it will run the code we put inside of this For Loop that many times.

        We will just create one particle each time this loop runs, and num will control how many should be created in each burst.






        Add Each Particle to Effects Table

        Next we will use the add_fx function to build each particle.

        We already wrote the function with parameters, so this tells us not only what data we need to pass, but also the order we need to pass it in. Here's a reminder:

        add_fx(x,y,die,dx,dy,grav,grow,shrink,r,c_table)

        The data we pass are the arguments and they must match what the function is expecting to receive, the parameters.

        So compare the parameters (above) with the arguments (below) when we call this function from inside the trail effect function.

        --settings
        add_fx(
            x+rnd(w)-w/2,  -- x
            y+rnd(w)-w/2,  -- y
            40+rnd(30),  -- die
            0,         -- dx
            0,         -- dy
            false,     -- gravity
            false,     -- grow
            false,     -- shrink
            1,         -- radius
            c_table    -- color_table
        )





        Arguments (Particle Settings)

        Let's take a closer look now at the arguments to figure out why these specific arguments create the particle settings for a trail effect.

        --settings
        add_fx(
            x+rnd(w)-w/2,  -- x
            y+rnd(w)-w/2,  -- y
            40+rnd(30),  -- die
            0,         -- dx
            0,         -- dy
            false,     -- gravity
            false,     -- grow
            false,     -- shrink
            1,         -- radius
            c_table    -- color_table
        )
        Argument 1: x

        = x+rnd(w)-w/2

        Argument 2: y

        = y+rnd(w)-w/2

        This takes the x and y that was passed to this trail function, adds a random number between 0 and the width (w) that was passed to this function, then subtracts half of the width. Subtracting half of the random number's max value, shifts the range of random numbers to a positive and negative range with 0 being in the middle. This allows the X and Y coordinates to be set somewhere inside of a spawn zone created by the random range with W.


        Argument 3: die

        = 40+rnd(30)

        This sets the lifetime of the particle to be at least 40, and a maximum of 30 more than that.

        How? Well rnd( ) can be a number between 0 and the number inside the parentheses. So if the random function comes out at 0, then the lifetime of this particle will be 40, and if random comes out at 30, then 40 + 30 = 70. So each particle in this trail will die (get deleted) at unique times. This creates the end appearance of the trail where if it looks like it fades away.

        Change the length of the trail = change the number 40.

        Change the length of the trail's fadeout = change the number 30.


        Argument 4: dx

        = 0

        Argument 5: dy

        = 0

        The DX and DY are the momentum variables. Together they will create the direction and speed of each particle's movement. In a trail effect, we don't want each particle to move. So we set them both to 0. Each particle will be created at a specific location, change color as it gets older, then die. If this trail is created based on the position of a moving object, then each particle of the trail will be placed and remain at a location where the moving object once was, and together, all of those particles will appear as a trail.


        Argument 6: gravity

        = false

        Argument 7: grow

        = false

        Argument 8: shrink

        = false

        These arguments are all set to false. You could try turning them on to experiment with different trail effects that you could make. The names are pretty obvious for what they do.


        Argument 9: radius

        = 1

        This is the size of the particle. 1 means a single pixel while anything above 1 will be drawn as a circle, or whatever you set up the draw_fx to do.


        Argument 10: color_table

        = c_table

        This uses the parameter passed to this trail function: c_table

        It should be a table of numbers as a simple list, like this: { 1,2,3,4 }

        That determines the sequence of colors that the particle should be drawn in based on its lifetime.







    • B. Explosion

        Overview

        For a detailed explanation on each piece, see the above Trail Effect. Only the unique differences will be explained here.

        -- explosion effect
        function explode(x,y,r,c_table,num)
            for i=0, num do
                --settings
                add_fx(
                    x,         -- x
                    y,         -- y
                    30+rnd(25),-- die
                    rnd(2)-1,  -- dx
                    rnd(2)-1,  -- dy
                    false,     -- gravity
                    false,     -- grow
                    true,      -- shrink
                    r,         -- radius
                    c_table    -- color_table
                )
            end
        end
        
        Parameters
        function explode(x,y,r,c_table,num)

        Almost exactly like the Trail Effect above, except for:

        r = the starting radius of each particle.

        1 is a single pixel, while anything above 1 is a circle, depending on how you set up the draw_fx function.

        Loop

        The same FOR LOOP is used to create a burst of particles all at once, like the Trail Effect. Except the Trail Effect, you want to run multiple times to continue creating particles as an object moves. But with an Explosion Effect, you only want to create a large number of particles at one time, when something major happens like an important collision.

        Arguments
        add_fx(
            x,         -- x
            y,         -- y
            30+rnd(25),-- die
            rnd(2)-1,  -- dx
            rnd(2)-1,  -- dy
            false,     -- gravity
            false,     -- grow
            true,      -- shrink
            r,         -- radius
            c_table    -- color_table
        )
        
        Argument 1: x

        = x

        Argument 2: y

        = y

        This takes the x and y that was passed to this explosion function, and sets all of the particles in this effect to the same position to be the center point of the explosion.


        Argument 3: die

        = 30+rnd(25)

        This sets the lifetime of the particle to be at least 30, and a maximum of 25 more than that, for a range of 30 to 55.

        How? Check out the Trail Effect explanation above.

        Change the duration of the explosion = change the number 30.

        Change the duration of the fadeout = change the number 25.


        Argument 4: dx

        = rnd(2)-1

        Argument 5: dy

        = rnd(2)-1

        The DX and DY are the momentum variables. Together they will create the direction and speed of each particle's movement. In an explosion, we want each particle to move away from the center point in any direction. So we randomly choose a number between 0 and 2, then take away 1 for a range between -1 and +1.


        Argument 6: gravity

        = false

        Argument 7: grow

        = false

        Argument 8: shrink

        = true

        We don't want the explosion to fall, so gravity is turned off (false).

        We don't want the explosion to get bigger, so grow is turned off (false).

        We do want the explosion to get smaller, so shrink is turned on (true).


        You might prefer an explosion that grows instead of shrinks, so flip those and try it out!


        Argument 9: radius

        = r

        This is the size of the particle. We use the parameter passed to this explosion function, so that it can be set differently inside of your game to create small explosions for small collisions, and large explosions for important collisions.


        Argument 10: color_table

        = c_table

        Same as the Trail Effect. It uses the parameter passed to this function: c_table

        It should be a table of numbers as a simple list, like this: { 1,2,3,4 }

        That determines the sequence of colors that the particle should be drawn in based on its lifetime.







    • C. Fire

        Overview

        For a detailed explanation on each piece, see the above Trail Effect. Only the unique differences will be explained here.

        -- fire effect
        function fire(x,y,w,c_table,num)
            for i=0, num do
                --settings
                add_fx(
                    x+rnd(w)-w/2,  -- x
                    y+rnd(w)-w/2,  -- y
                    30+rnd(10),-- die
                    0,         -- dx
                    -.5,       -- dy
                    false,     -- gravity
                    false,     -- grow
                    true,      -- shrink
                    2,         -- radius
                    c_table    -- color_table
                )
            end
        end
        
        Parameters
        function fire(x,y,w,c_table,num)

        Exactly the same parameters as the Trail Effect above.

        Loop

        The same FOR LOOP is used to create a burst of particles each time, just like the Trail Effect.

        Arguments
        add_fx(
            x+rnd(w)-w/2,  -- x
            y+rnd(w)-w/2,  -- y
            30+rnd(10),-- die
            0,         -- dx
            -.5,       -- dy
            false,     -- gravity
            false,     -- grow
            true,      -- shrink
            2,         -- radius
            c_table    -- color_table
        )
        
        Argument 1: x

        = x+rnd(w)-w/2

        Argument 2: y

        = y+rnd(w)-w/2

        This takes the x and y that was passed to this fire function, within a spawn zone set by w, just like the Trail Effect.


        Argument 3: die

        = 30+rnd(10)

        This sets the lifetime of the particle to be at least 30, and a maximum of 10 more than that, for a range of 30 to 40.

        How? Check out the Trail Effect explanation above.

        Change the height of the fire = change the number 30.

        Change the length of the fadeout = change the number 10.


        Argument 4: dx

        = 0

        Argument 5: dy

        = -.5

        The DX and DY are the momentum variables. Together they will create the direction and speed of each particle's movement.

        In a fire effect, we want each particle to move upwards from the starting point. So we set X (left and right movement) to 0. And we set Y (up and down) to a negative number, so that the particles move up the screen.


        Argument 6: gravity

        = false

        Argument 7: grow

        = false

        Argument 8: shrink

        = true

        We don't want the fire to fall, so gravity is turned off (false).

        We don't want the fire to get bigger, so grow is turned off (false).

        We do want the fire to get smaller, so shrink is turned on (true).



        Argument 9: radius

        = 2

        This is the size of the starting particle. We hard-set this to 2 here in the effect function because we don't think we will create different size fires. To create larger fires, we could have multiple of these effects happening near each other.


        Argument 10: color_table

        = c_table

        Same as the Trail Effect. It uses the parameter passed to this function: c_table

        It should be a table of numbers as a simple list, like this: { 1,2,3,4 }

        That determines the sequence of colors that the particle should be drawn in based on its lifetime.







  • 4. Sound Effects (SFX)

      These are simply the sounds we created in the demo below. They are based on Gruber Music's suggestions and lessons in our Music section, so go there to understand how to use the SFX editor and all of the sound options to create just the right sound effects to match your particle effects!

      Here are just examples to get you started:

      Trail
      Explosion
      Fire
  • Full Code!

      function _init()
          --particles
          effects = {}
      
          --effects settings
          trail_width = 1.5
          trail_colors = {12,13,1}
          trail_amount = 2
      
          fire_width = 3
          fire_colors = {8,9,10,5}
          fire_amount = 3
      
          explode_size = 5
          explode_colors = {8,9,6,5}
          explode_amount = 5
      
          --sfx
          trail_sfx = 0
          explode_sfx = 1
          fire_sfx = 2
      
          --player
          player = {x=53, y=53, r=2, c=7}
      end
      
      function _update60()
          --update particles
          update_fx()
      
          --player controls
          if btn(0) then player.x-=1 end
          if btn(1) then player.x+=1 end
          if btn(2) then player.y-=1 end
          if btn(3) then player.y+=1 end
      
          if btn(4) then
              fire(player.x,player.y,fire_width,fire_colors,fire_amount)
              sfx(fire_sfx)
          end
          if btnp(5) then
              explode(player.x,player.y,explode_size,explode_colors,explode_amount)
              sfx(explode_sfx)
          end
      
          if btn(0) or btn(1) or btn(2) or btn(3) then
              trail(player.x,player.y,trail_width,trail_colors,trail_amount)
              sfx(trail_sfx)
          end
      end
      
      function _draw()
          cls()
          --draw particles
          draw_fx()
      
          --player
          circfill(player.x,player.y,player.r,player.c)
      end
      
      -->8
      -- core particle functions
      
      function add_fx(x,y,die,dx,dy,grav,grow,shrink,r,c_table)
          local fx={
              x=x,
              y=y,
              t=0,
              die=die,
              dx=dx,
              dy=dy,
              grav=grav,
              grow=grow,
              shrink=shrink,
              r=r,
              c=0,
              c_table=c_table
          }
          add(effects,fx)
      end
      
      function update_fx()
          for fx in all(effects) do
              --lifetime
              fx.t+=1
              if fx.t>fx.die then del(effects,fx) end
      
              --color depends on lifetime
              if fx.t/fx.die < 1/#fx.c_table then
                  fx.c=fx.c_table[1]
      
              elseif fx.t/fx.die < 2/#fx.c_table then
                  fx.c=fx.c_table[2]
      
              elseif fx.t/fx.die < 3/#fx.c_table then
                  fx.c=fx.c_table[3]
      
              else
                  fx.c=fx.c_table[4]
              end
      
              --physics
              if fx.grav then fx.dy+=.5 end
              if fx.grow then fx.r+=.1 end
              if fx.shrink then fx.r-=.1 end
      
              --move
              fx.x+=fx.dx
              fx.y+=fx.dy
          end
      end
      
      function draw_fx()
          for fx in all(effects) do
              --draw pixel for size 1, draw circle for larger
              if fx.r<=1 then
                  pset(fx.x,fx.y,fx.c)
              else
                  circfill(fx.x,fx.y,fx.r,fx.c)
              end
          end
      end
      
      -->8
      --example particle effects
      
      -- motion trail effect
      function trail(x,y,w,c_table,num)
      
          for i=0, num do
              --settings
              add_fx(
                  x+rnd(w)-w/2,  -- x
                  y+rnd(w)-w/2,  -- y
                  40+rnd(30),  -- die
                  0,         -- dx
                  0,         -- dy
                  false,     -- gravity
                  false,     -- grow
                  false,     -- shrink
                  1,         -- radius
                  c_table    -- color_table
              )
          end
      end
      
      -- explosion effect
      function explode(x,y,r,c_table,num)
          for i=0, num do
      
              --settings
              add_fx(
                  x,         -- x
                  y,         -- y
                  30+rnd(25),-- die
                  rnd(2)-1,  -- dx
                  rnd(2)-1,  -- dy
                  false,     -- gravity
                  false,     -- grow
                  true,      -- shrink
                  r,         -- radius
                  c_table    -- color_table
              )
          end
      end
      
      -- fire effect
      function fire(x,y,w,c_table,num)
          for i=0, num do
              --settings
              add_fx(
                  x+rnd(w)-w/2,  -- x
                  y+rnd(w)-w/2,  -- y
                  30+rnd(10),-- die
                  0,         -- dx
                  -.5,       -- dy
                  false,     -- gravity
                  false,     -- grow
                  true,      -- shrink
                  2,         -- radius
                  c_table    -- color_table
              )
          end
      end
      






  • Play the Demo!


font