As of Daily Build #2322, Corona Pro and Enterprise users can begin implementing the LiquidFun physics system into their apps. This allows you to simulate faucets, pools, waves, streams, and other amazing physics-based effects similar to those featured in games like Where’s My Water? or other games where the physics world is not composed entirely of rigid objects.

In addition to basic liquid effects, you can implement semi-liquid elastic/gelatin objects, create liquids that color mix and blend together when they come in contact, detect and manipulate “regions” of the liquids, and more.

Particle Systems

In LiquidFun, all liquids are represented by a particle system, meaning that a stream or pool is made up of many “particles” which interact together to simulate the desired effect. Each particle is round and they are the minimal unit of matter in a particle system. By default, a particle behaves as a liquid, but you can change the behavior of individual particles or groups of particles. You can also set particle properties like position, velocity, and color.

Note that particles defined by the LiquidFun framework are not related to emitter-based “particles” that are generated by display.newEmitter(). In other words, these particle types are distinct objects from different libraries and they will not interact with each other physically.

In Corona, you create a particleSystem object via the physics.newParticleSystem() function:

physics.newParticleSystem( params )

In this call, params is a table of parameters with only one required element: the filename of the particle image to render for each particle instance:

local physics = require( "physics" )
physics.start()

local testParticleSystem = physics.newParticleSystem(
   {
      filename = "particle.png",
      radius = 3,
      imageRadius = 4
   }
)

In addition to the filename parameter, all other parameters are optional, but you will need to specify at least a few of them to make the system behave as intended.

At the minimum, you’ll typically want to define the particle radius to control the size of the particles which constitute the liquid effect.

Another useful parameter is imageRadius which, if defined, will instruct Corona to render the particle image at a different size than the physical body defined by radius. If imageRadius is slightly larger than radius, the particles will overlap somewhat, resulting in a more cohesive liquid-like appearance.

The complete list of other parameters and their purpose is beyond the scope of this tutorial, so please refer to the documentation or the LiquidFun Programmer’s Guide for more information.

Creating Particles

Once you have an established a particle system, new particles are generated using the ParticleSystem:createParticle() function:

ParticleSystem:createParticle( params )

In this function, params is a table of optional parameters which control the behavior of each generated particle. For example:

testParticleSystem:createParticle(
   {
      flags = { "water", "colorMixing" },
      x = 0,
      y = 0,
      velocityX = 256,
      velocityY = 480,
      color = { 1, 0.2, 0.4, 1 },
      lifetime = 32.0
   }
)

Most of these parameters are self-explanatory. x and y determine where the new particle is generated in screen content space. velocityX and velocityY specify the starting velocity values for the new particle, color is a table of RGB+A values for the particle, and lifetime defines how many seconds the particle remains on screen before it dies. The flags parameter can be either a string or a table of strings which dictate various behavioral aspects of the particle. These options are beyond the scope of this tutorial, so please refer to the LiquidFun Programmer’s Guide for more information.

It’s important to note that this function creates just one particle in the overall system, so to create a useful scenario, you’ll need to generate numerous particles by calling this function on a repeating timer or via some other repeating method. For example:

local physics = require( "physics" )
physics.start()

local testParticleSystem = physics.newParticleSystem(
   {
      filename = "particle.png",
      radius = 3
   }
)

local function onTimer( event )

   testParticleSystem:createParticle(
      {
         flags = "water",
         velocityX = 256,
         velocityY = 480,
         color = { 1, 0, 0.1, 1 },
         x = 0,
         y = 0,
         lifetime = 32.0
      }
   )
end

timer.performWithDelay( 20, onTimer, 0 )

Creating Particle Groups

In addition to creating single particles in a system, you can create groups of related particles using the ParticleSystem:createGroup() function:

ParticleSystem:createGroup( params )

Essentially, this “fills” a defined region with multiple particles in one command, and the group can be assigned various behavioral and visual properties. For example:

local physics = require( "physics" )
physics.start()

local testParticleSystem = physics.newParticleSystem(
   {
      filename = "particle.png",
      radius = 3
   }
)

testParticleSystem:createGroup(
   {
      flags = { "water", "colorMixing" },
      x = 0,
      y = 0,
      color = { 0, 0.3, 1, 1 },
      halfWidth = 64,
      halfHeight = 32
   }
)

The code above generates a rectangular particle group, but you can also create circular groups, arbitrary shape groups, and even shapes defined by graphics.newOutline(). Please refer to the documentation for information on creating the different group types.

Region Querying

While generating individual particles and even groups of particles can provide for some amazing effects, at some point you may need the ability to detect and manipulate particles within a defined region. This is accomplished via the ParticleSystem:queryRegion() function:

ParticleSystem:queryRegion( upperLeftX, upperLeftY, lowerRightX, lowerRightY, hitProperties )

The defined region must be rectangular, and it’s defined by the first four parameters — simply specify the upper-left corner (upperLeftX,upperleftY) and the lower-right corner (lowerRightX,lowerRightY) to query in a rectangular region created by these coordinates.

The last parameter, hitProperties, is a table of optional properties which can be applied to each particle in the region. Valid properties include:

  • deltaX — The x position delta to apply to each particle.
  • deltaY — The y position delta to apply to each particle.
  • velocityX — The x velocity delta to apply to each particle.
  • velocityY — The y velocity delta to apply to each particle.

So, if you want to apply an upward delta y velocity to all particles in a region, the code may look like this:

local hits = ParticleSystem:queryRegion(
    10, 40,  --upper-left coordinate for the region
    100, 160,  --lower-right coordinate for the region
    { velocityY = -40 }  --apply delta Y velocity of -40 to each particle in region
)

Note that all of the values applied in the hitProperties table are delta values that will be applied to each particle’s current associated value(s). For example, if you apply a velocityY of -40 to the particles in the region, it does not set -40 as the actual y velocity on them, but rather it applies that velocity in delta relation to any existing velocity the particles already have.

In Summary

This tutorial is meant to be a brief introduction to LiquidFun physics in Corona, but the capabilities of the framework extend far beyond this scope. Please post your test results, questions, and comments below.

  1. Dear Corona-Team,

    I waited a long time for this, I was checking the daily builds every day to watch for the nice “Physics maintenance” note and I was hoping that it meant you are working on the integration of Liquid Fun. And now today – what a nice surprise!

    But after I checked the examples and the docs at a first glance I don’t see how I can check collisions between “normal” physics objects and the particles.

    For example: My HERO is afraid of the poison lake (done with liquid fun particles), and when he drops into the lake – or some poison drops hit him – he should die a horrible death.

    Of course I can use a rectangular region with almost the size and at the position of my HERO and then check

    particleSystem:queryRegion()

    for hits, but this would be kind of crude and makes a problem, if the HERO e.g. is in the shape of a circle (like my eye in “Freeze!”).

    Is there a possibility to add a collision listener between the particles and normal physics objects?

    Thanks for the great work you do & best,
    Andreas

    • [EDIT]

      After playing around a bit with an example and after I added

      Runtime:addEventListener( “collision”, onCollision )

      I saw that like I guessed right now no collision events are fired for the particles when they collide with an normal physics object.

      Do you plan to implement this, too, e.g. using the “Collision Module” and the “Contact Filtering” of LiquidFun (if it’s possible, I am a little bit in the dark here).

      And yes, it’s always the same – you’re giving us a great feature and we just want more. So let me state again, it’s awesome what you did and do all the time, collisions would just add some more awesomeness (and would be extremely helpful).

      Thanks & best,
      Andreas

  2. I did a quick copy and paste (with slight mod) and created a 4×4 blue square named particle.png, but nothing appears in the simulator. Obviously, I have no idea what I’m doing.

    display.setStatusBar( display.HiddenStatusBar )
    local physics = require( “physics” )
    physics.start()

    local bg = display.newRect( 0, 0, display.viewableContentWidth, display.viewableContentHeight )
    bg.x = display.contentCenterX
    bg.y = display.contentCenterY
    bg:setFillColor(255, 255, 255)

    local testParticleSystem = physics.newParticleSystem(
    {
    filename = “particle.png”,
    radius = 3
    }
    )

    local function onTimer( event )
    testParticleSystem:createParticle(
    {
    flags = “water”,
    velocityX = 256,
    velocityY = 480,
    color = { 1, 0, 0.1, 1 },
    x = 0,
    y = 0,
    lifetime = 32.0
    }
    )
    end

    timer.performWithDelay( 20, onTimer, 0 )

    • Brent Sorrentino says:

      Hi Greg,
      You may have already discovered this by looking at the examples Andreas cites, but you should add the “forceRender” line to your code:

      display.setDrawMode( “forceRender” )

      I’ve updated the code snippets in this tutorial to reflect that. Sorry for not including it from the start. :)

      Brent

    • Ahhh, nice video – but I think you are torturing the engine, you’re using an incredible amount ob particles.

      I guess can do lots of nice things with LiquidFun by using less than 1/10 of the amount of particles you used.

      If you want to do a real test for all the Corona users maybe implement a FPS counter and then provide a small list like this:

      iPad 3:

      10 Particles = x FPS
      50 Particles = x FPS
      100 Particles = x FPS
      200 Particles = x FPS
      500 Particles = x FPS
      1000 Particles = x FPS
      2000 Particles = x FPS
      5000 Particles = x FPS

      Provide the code for downloading, then others can pitch in an provide this table for some other devices, like
      iPhone 4, iPhone 4S, iPhone 5, iPad 2, iPad 4, iPad Air, Google NEXUS 7, Samsung S2 + S3 + S4 + S5, Kindle HDX 7″, etc. – this would be a cool community effort and incredibly useful.

      Best, Andreas

    • Albert Yale says:

      We’ll make speed optimizations over time. The short-term solution is to reduce the number of particles.

      • Hi Albert,

        to be on par with current games already out there (that show liquid simulation) we need to

        * put the particles into a snapshot (to make e.g. seamless semitransparent water with a cool surface) and
        * put the snapshot into a display group

        As soon as this can be done I can enhance our Hall-Of-Fame-Game “Freeze!” (at this time 6 million downloads, thanks to the great Corona Labs SDK and all the stuff you integrated over the years like Game Center/Google Play Games support etc.):
        http://coronalabs.com/halloffame/

        Imagine deadly poisonous water sloshing around inside of the rotating levels (the objects are all in a rotating image group, and the direction of the world gravity is adapted to the angle of the group), this would be really cool. :-)

        Do you think you can make this happen?

        Thanks & best,
        Andreas

          • Hi Albert,

            any news on LiquidFun & Groups?

            I played around with the LiquidFun particles and I really love it, but without the ability to put them in groups I cannot use LiquidFun for our games.

            Thanks & best,
            Andreas

  3. I got excited about this, but realized I think that collisions are NOT working yet?

    The problem I usually find with particles is grouping and removing them between scenes. It’s probably due to I’m not that fancy, but I always end up with loose particles on screen when changing scenes.

    Either way, I’ll keep an eye on this, I have a game from 2011 that got shelved as I didn’t have an optimized way to do liquid without making the engine scream and die and bleed all over.

  4. Hi,

    thanks for adding the collision and the examples – this addresses all my collision needs. :-)

    Now one more feature (to put the particles into display groups etc.) and I can put LiquidFun functionality into the first prototype of “Freeze 2″. Without this it’s not possible, because in “Freeze!” I rotate the complete world and I then compensate the changed gravity direction by updating the gravity vector each frame so gravity pulls down, as it should (like Newton told us). And for this the LiquidFun particles need to be integrated into the whole display group thing.

    I’m really anxious to continue here – most of the other game engine code for “Freeze 2″ is already done.

    Thanks & best,
    Andreas

    • [EDIT]

      Since 2359 the LiquidFun particles are now fully integrated into the display groups, I already experimented with this and I love it! Thanks Corona for listening and delivering so timely!

      Best,
      Andreas

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>