Tutorial: Creating awesome water with LiquidFun, snapshots, and filters

Tutorial: Creating awesome water with LiquidFun, snapshots, and filters

This tutorial comes to you courtesy of Andreas von Lepel of Frozen Gun Studios. Following the worldwide success of “Freeze! – The Escape” (iOS/Android) with more than 11 million freemium downloads, Frozen Gun Studios has just released the sequel, “Freeze! 2 – Brothers” (iOS/Android), built entirely in Corona SDK and featuring LiquidFun-based water effects.


Overview

Very early in the development, I decided that it would be really interesting for the gameplay if I could have water and poisonous liquids like rocket fuel sloshing around in the rotating levels. Have a look at the trailer to see what we did:

As you can see, both our heroes can swim in the water, but in later stages the task for the player is to first collect all the deadly rocket fuel into a barrel and then to pick it up to open the level exit.

Project code

Setting up a scene with basic water using LiquidFun is really easy and there are some nice examples included in the Corona SampleCode repository. However, creating really nice, transparent water with a visible “surface” is a little harder and you need to use snapshots and filters. Fortunately, as usual with Corona SDK, even complex stuff is mostly done for you by the engine.

As we step through this tutorial, I encourage you to download the LiquidFun-Transparency project in the GitHub repository, also bundled into recent builds of Corona SDK:

CoronaSDK-XXXXSampleCodePhysicsLiquidFun-Transparency

Within the project folder, open the main.lua file. We’ll start with the basic world setup:

Liquid “container”

In this block, I use three static rectangles to build a container outside of the screen boundaries. The container is open at the top and it will later hold the water inside of the screen.

Scrolling background

I want even demos to look nice, so in the next block, I add an endless scrolling background by placing two identical background images adjacent to each other, one fully visible in the middle of the screen, the other to the right of it.

Note that both images are moved slowly to the left and then, via endless iterations, are set back to their original positions and moved again, all using simple transition.to() calls.

The hero

Next, I add our eye hero into the scene as a dynamic physics object that will swim on the water. It can also be touched and dragged around by the user. I will not dive into the touch-drag code, about which a lot has been written and demonstrated previously.

LiquidFun particle system and water

In the next block, the particle system for the water is created, and a big rectangle of water particles is placed into the scene.

And this is the result if you start the code now:

LiquidFunDemo01
The water is flowing, but it’s more like oil, because there is no transparency yet.

Adding transparency (first attempt)

Now things get a little more complex. I want to render the water semi-transparent, e.g. with an alpha value of 0.3 (30%) so that the background is visible through the water.

If I change the last value (alpha) of the the color property on line 111, there will be some transparency, but not exactly what I’m looking for:

LiquidFunDemo02
Each particle now has an alpha value of 30%.
If you look closely at the image, you can see that each round particle now has an alpha value of 0.3, and this gives a chaotic rendering of the water because all of the particles overlap slightly. While this could be nice effect for certain cases, it’s definitely not what I am trying to achieve.

Adding transparency using a snapshot

The solution is to render all the particles into a snapshot texture on each frame and then apply transparency to the entire texture. Here are the relevant lines:

Essentially, a snapshot and its group are created, positioned in the center of the content area. Note that the width and height of the snapshot are adjusted by the letterboxWidth and letterboxHeight variables calculated earlier in main.lua — this ensures that, when running in "letterbox" scale mode, the snapshot occupies the entire screen on devices of various aspect ratios.

On line 123, I set the alpha value of the entire snapshot to 0.3, effectively setting the entire snapshot texture to 30% opacity. Following this, the particle system is inserted into the snapshot group and finally, with the help of an "enterFrame" listener, the snapshot is invalidated and rendered anew each frame.

With this addition, you can see that the water now has realistic transparency:

LiquidFunDemo03
Transparency! At last!

Go for pretty and add a filter

After I got this far, I was quite happy. But the cool engineers at Corona Labs added filter support, so I started to experiment a lot with all of the filters and filter options in my quest to add a nice visible surface to the water. It took some time to achieve the correct look, but in the end it was really easy:

And that’s it, we now have water with transparency and a nice water surface!

LiquidFunDemo04
The water “surface” shows as a visible line on top of the water
(the edge of the rendered water enhanced by the sobel filter).

Where to go from here

From here, there are a lot of experiments you can do, e.g. play around with the density of the hero eye (with this you can influence whether it should be very light and always float on top of the water, or whether it should be heavy and sink below).

Another option is to use different filters for the water and tune their respective filter settings:

  • snapshot.fill.effect = "filter.emboss"
  • snapshot.fill.effect = "filter.frostedGlass"
  • snapshot.fill.effect = "filter.crystallize"
  • snapshot.fill.effect = "filter.scatter"

Finally, don’t forget the many different “flags” you can use for the LiquidFun group — e.g. add "staticPressure" and the water particles at the bottom of the container will not be compressed.

Performance issues

Not all devices are powerful enough to display water with these added special effects while maintaining an acceptable frame rate. Because of this, I take the following precautions:

  • On iOS, I use the snapshot on all supported devices (iPhone 4S and better; iPad 2 and better). However, the additional sobel filter is added only on more powerful devices like the iPad Air or iPad mini 2 (iPad4,*), iPhone 5S (iPhone6,1), or iPod Touch 5G (iPod5,1) and better.
  • For Android, I set the minSdkVersion to "16" (Android 4.1) to exclude many older devices. Also, I use the sobel filter only on devices that support high precision shaders, as determined by this:
    system.getInfo( "gpuSupportsHighPrecisionFragmentShaders" )
  • Finally, I test the system.getInfo( "androidDisplayDensityName" ) for "xhdpi", "xxhdpi", and "xxxhdpi" — only these devices will get the sobel filter, because they are quite modern and should hopefully be fast enough.

Conclusion

As you can see, it is quite easy to create nice-looking water and other liquids, and at last the technology is good enough to make all of this useable on modern mobile devices. I’m looking forward to seeing more games which make use of this, as liquids are great fun for players and developers alike.

If you want to learn more about “Freeze! 2 – Brothers,” visit www.freeze2.com, or download it for either iOS or Android. I hope you enjoy the game!


Brent Sorrentino
[email protected]

Brent Sorrentino is a full-time Developer Evangelist and technical writer who assists others with game development, code, and in overcoming other challenges to help them bring their app dreams to life.

8 Comments
  • Dave Haynes
    Posted at 11:34h, 25 September

    I’ve been thinking about adding some kind of water-based puzzle to Puzzlewood Quests for a long time, so this post is perfect. Now I just have to find the time to actually do it…

    • Andreas
      Posted at 02:35h, 27 September

      Hi Dave,

      Please take the time – would love to see more games using LiquidFun. 🙂

      I’m thinking about adding another world to “Freeze! 2 – Brothers” where you actually can freeze the water and turn it to ice, do things with and to the frozen water and then un-freeze it again.

      I will make the time to test some stuff in the next weeks, I guess there will be no rest for me as long as I’m able to hack some code into my keyboard. And if I can’t do that anymore I will start to talk like “Cortana, please help Siri to add a loop where you iterate through all my LiquidFun particles …”

      Whatever. Please do it. And then don’t forget to tell us about it.

      Best from Munich, Germany, home of the Oktoberfest,
      Andreas

      • Steven Johnson (Star Crunch)
        Posted at 13:57h, 28 September

        This looks (and sounds) really nice!

        In case it’s interesting, I’ve got an ice effect, more or less what I did here but in Corona proper. I’ve lately considered putting together some sort of “materials” package, comprising different texture-ish effects and their variations. Naturally, I’d like to know what needs would come up in real world use…

        “I will make the time to test some stuff in the next weeks…” / “…home of the Oktoberfest” Ah, the tension! 🙂

        • Andreas
          Posted at 13:03h, 29 September

          Hi Steven,

          Looks awesome! It would be my pleasure to steal this fine example of code and adapt it for my own purposes. Evil me!

          But I guess the old problem is still there, that I cannot use my snapshot of the LiquidFun particles in a custom shader? Or is this already possible, I didn’t look at the state of affairs regarding shaders for some month.

          Best
          Andreas

          • Steven Johnson (Star Crunch)
            Posted at 15:50h, 29 September

            Sounds good.

            I should point out that this is actually only a very reduced version of the effect described in ShaderX6, which had transparency and incorporated the back side geometry into reflection and refraction, so there’s plenty of room for improvement! (It also included things like cracks and gas that, aside from the cost and complexity they bring, would probably be too subtle on mobile.)

            My Lua implementation might look a little imposing since it drags in extra utilities to cram more than four inputs into the shader. In practice, though, you could just hard-code several shaders, each with values for particular situations, e.g. “thick ice” or “dimly-lit ice”, which would free up some inputs. This would be easy enough to do just by working from the Shader Playground version.

            (If you could still use the full generality, drop me a mail or PM if you need any help.)

            I’ve not had any problems using shaders on snapshots. Snapshots can’t yet be passed through composite paints, though. This means you’ll have to synthesize the bump map in the shader, but with dynamic particles that might have been inevitable anyway.

  • Carlos Montesino
    Posted at 23:23h, 26 September

    Thanks for this tutorial guys! 😀

  • Andreas
    Posted at 01:07h, 30 September

    Hi Steven,

    thanks for all the input!

    Sadly I’ve got tons of other stuff to investigate before I tackle the actual shader (that’s eye candy after all), things like freezing the LiquidFun particles in place (already got – almost – a solution for that) etc.

    But really good to know that I can use a snapshot as a source for the shader.

    No if only I could use a snapshot as a source for graphics.newOutline …

    But I guess with all the complex shapes that would be created by water snapshots newOutline would make too many problems.

    Lots of stuff to think about.

    Thanks & best
    Andreas

    BTW:
    I was wondering how narrow the “reply” column would get, and now I know it – there was no “reply” button offered anymore at the end of your last answer. 🙂

  • Ed Maurina
    Posted at 23:35h, 01 October

    Thanks so much for writing this article. Its hard for many of us to get started experimenting with the more advanced visual elements. This gives us a leg up and motivation to try our hand at it.