LiquidFun Essentials

The LiquidFun physics system allows you to simulate faucets, pools, waves, streams, and other amazing physics-based effects similar to those featured in Corona-powered games like Freeze! 2 – Brothers 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.

Important

Particles defined by the LiquidFun framework are not related to emitter-based particles that are generated by display.newEmitter(). In other words, these two particle types are distinct objects from different libraries and they will not interact with each other physically — in fact, emitter-based particles do not support physical collisions whatsoever.

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

physics.newParticleSystem( params )

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

local physics = require( "physics" )
physics.start()
 
local testParticleSystem = physics.newParticleSystem(
{
    filename = "particle.png"
})

However, to make your particle system behave better, you should specify a couple additional properties. At minimum, you should 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. Essentially, if imageRadius is slightly larger than radius, the particles will overlap somewhat, resulting in a more cohesive liquid-like appearance.

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

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 particle system, new particles are generated using the object:createParticle() method. This accepts a table of optional parameters which control the behavior of each generated particle, for example:

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

Most of these parameters are self-explanatory:

It’s important to note that object:createParticle() 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 instance:

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

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

local function onTimer( event )

    testParticleSystem:createParticle(
    {
        x = 0,
        y = 0,
        velocityX = 256,
        velocityY = 480,
        color = { 1, 0.2, 0.4, 1 },
        lifetime = 32.0,
        flags = { "water", "colorMixing" }
    })
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 object:createGroup() function. 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 = 2,
    imageRadius = 4
})

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

This code generates a rectangular particle group, but you can also create circular groups and arbitrary shape groups. Please refer to the documentation for information on creating different group types.

Applying Force/Impulse

Linear force can be applied to the center of all particles in a particle system via the object:applyForce() API. This method accepts two numerical values indicating the amount of force to apply in the x and y directions respectively. For instance:

testParticleSystem:applyForce( 0, -9.8 * testParticleSystem.particleMass )

Alternatively, you can apply a linear impulse to all particles with the object:applyLinearImpulse() API. This is similar to the above method except that an impulse is a single momentary jolt in newton-seconds. For example:

testParticleSystem:applyLinearImpulse( 0, -9.8 * testParticleSystem.particleMass )

Region Querying

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

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:

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

local hits = testParticleSystem:queryRegion( 10, 40, 100, 160, { velocityY=-40 } )
Note

All of the values potentially 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 directly 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.

Particle Collisions

Detecting collision events between LiquidFun particles and other physical objects is an in-depth topic beyond the scope of this tutorial. Please see the LiquidFun Particle Collisions tutorial for more details on this topic.

Conclusion

This tutorial is meant to be a brief introduction to LiquidFun physics in Corona, but the capabilities of the framework extend beyond this scope. Please consult the documentation or the LiquidFun Programmer’s Guide for more information.