Search Unity

Global Game Jam 2017 recently ended. It was a mad weekend where 36,000 game makers gathered in more than 700 venues around the world, and made 7000 games based on a common theme: “Waves”.

Instead of organising in Rome, this year I flew to Prague and made a game with a graphic artist named Jana Kilianová. Our game is called Splash Clash, and it’s a 2-player brawler on a tiny island, where two pixel characters jump to produce waves and bump each other out. This final concept is just one of the many we got during brainstorming: for instance, we toyed for a while with the idea of having players produce waves by making a sound in the microphone, but after some experiments we settled for this one – the one that seemed achievable in 48 hours.

The wave effect

From the very start we knew we wanted a cool and somehow believable water effect, so we didn’t want to use any approximation like 3D wave objects spreading in a circle, or particles. Instead, I thought we could use a circle texture on a Displacement shader, so the wave would be created from the surface (a simple plane) and if we then scaled this circle, we could have an animated wave.

Disclaimer: This post is intended to show you how I created the effect quickly during GGJ, not how it should be done! There are probably better ways in regards to performance! (perhaps doing it all with shaders?)

Before setting off, I made a mockup by creating circle textures in Photoshop and using the first sprite found on the web, Batman:

Being no shader wizard, I found a very simple free Displacement shader in the actual Unity Manual, which also supports tesselation (so I wouldn’t have to worry about the geometry). Once applied to the water, the Material looks like this:

As you can see it has a tessellation amount (that I pumped to the max!) – which controls how many times the surface is divided – and a displacement slider – which controls the height of the extrusion.

First thing, I wanted to verify that what I wanted to do was actually feasible. So I started with a single white circle texture (with transparency), then wrote a function to stamp it on top of a black texture. Once applied as a displacement map, it looks like this:

Humble beginnings

After that, I expanded the function to allow stamping more circles.

Multiple still circles

I had to composite multiple white circles together, to achieve having multiple waves moving on the surface. As you can see from the image above, the circles were quite high resolution at this point (I think they were 256×256 on a 512 black texture).

Since each wave is independent, the whole texture has to be deleted each frame and recalculated with the scaled-up circles. Once animated, they looked like this:

Random animated waves on a temporary colour texture

At this point, I found out that I had to reduce the texture size, A LOT. The method I was using is not optimised at all: after all for a 512×512 bump texture it means 262,144 read and write pixel operations, for each wave! (remember: the waves were 256×256 but they would scale in time, needing to rewrite the whole thing each frame) This brings us to a grand total of 1,048,576 pixel operations every frame with just 4 waves on screen!

So I scaled down everything, settling on a mere 32×32 for the ring textures and 64×64 for the whole bump. After a few tests, I ran into a problem: having a small texture, scaling it down (since the ring starts very small) and then scaling it up again destroys it until it’s not a circle anymore.

So what I did is cache the original ring texture, and use that to re-generate the rescaled circle every frame, instead of just transforming one texture all the time (which would make it useless after a couple of scale-ups). This means that each wave has a reference to the original ring texture, but instead of using that it just copies the pixels onto the rescaled one.

It worked fine on a plane, but when I applied the material to a circular disc shape I got this weird effect, with the sides of the disc extruding outwards:

See the edge extruding out on the left, leaving holes in the geometry?

I just had to apply one small modification to the shader: instead of extruding along the pixel normals, I would just extrude up. I changed this line:

… to this:

Voilá, I had my waves finally working!

Bump done, but waves are not really visible…

Once the bump was done, it was time to take care of the colour texture. As you can see from the animation above, at first I had a shadow from the Directional light to highlight the waves’ profile, but it wasn’t enough at all. I needed something visible, something that the players could use as a feedback to time their jumps properly.

At first I basically applied the same trick again: white texture rings printed on top of the colour texture, exactly in the same position as the ones in the bump/displacement:

In the above animation you can clearly see how the white circles display the low resolution of the textures used, especially when the circle is big.

This is when I decided to modify the shader to paint the pixels at the top of the wave in white. After all it makes everything faster, since I cut half of the texture readings each frame.

I just changed the line that calculates the pixel color in the surf function of the shader, to something like:

By using IN.worldPos.y, I make sure to take into account the height of the pixel in world space. The higher the pixel, the brighter it will be, tending towards white. The result is:

Physics

Once I had the graphics in place, it was time to make sure that the two characters got pushed by the wave. This was an easy one: I made each wave a gameObject with a Sphere Collider, marked as trigger. As the texture is scaled up, the Collider is scaled up as well.

The collider is scaled to match the wave on the displacement map

Once it collides with one of the players (OnTriggerEnter), I just verify check the y coordinate of the character’s gameObject, and if it’s below a certain threshold (which is connected with the wave strength – a float that decreases in time) the character is pushed back by applying a force to its Rigidbody proportional – again – to the wave’s strength.

I then filter the collisions by tag, so waves from player 1 only hit character 2, and vice versa.

Just a small mention regarding Drag. When going for a fully-physical approach, usually you apply a strong force (AddForce) to characters to make them accelerate quickly, then use Drag to balance that out so they don’t drift. But adding too much Drag will make the character descend slowly when jumping, creating some terribly floaty physics.

In this case I implemented my own fake drag, by multiplying only the x and z components of the Rigidbodies’ velocity by an arbitrary factor, to keep the y component (the gravity) intact:

The scene setup

Finally, as you can see from the animation below, the game is a mix of 2D and 3D, with the characters being Sprites rotated 30º on the x axis to match the camera rotation, as are the backdrop and the rocks below the scenario. The disc of water is basically the only proper 3D object in the game, as I needed it to get complex and believable waves.

The characters have a Capsule Collider, and a Rigidbody whose rotation is locked so they just more around without tipping.

Particles

I have added particles to the jump to provide feedback on the jump, and to provide the so-called juicyness (remember Martin and Petri’s talk “Juice it or lose it”?):

For the waterfall particles, I just used a big Circle emitter and I separated the front particles from the back ones (which are spawned by another particle system) by putting it on 180º in the Shape module. The back ones don’t change in colour and have a way shorter life span, since they are never going to be seen.

As usual I randomize every parameter I can (lifetime, speed, gravity, etc.) as long as I’m using it, but in this case I kept the size fixed because I want the particles to have a pixel art feel. For the same reason I used a Fixed gradient in the Color over Lifetime volume instead of a Blend one, so they change colour instantly, instead of fading away as I would usually do with realistic particles.

Closing

As you can see, most of the solutions I adopted this weekend feel dirty, unoptimised, and almost like a hack. That’s fine! Jam games ARE meant to be quick and dirty, they don’t need to be performing well.

In fact when I have to decide what to spend my time on at a jam, I prefer to spend it on code quality instead of optimisation. I like to have a relatively solid approach at coding (say, less spaghetti code and more logical class structures and information flow) to avoid getting bugs at the last minute. This usually keeps me away from nasty, incomprehensible, last-minute bugs!

14 评论

订阅评论

评论被关闭。

  1. Cool game! I’ve downloaded your project and been studying the code. Just a note: on Mac tessellation doesn’t work because mac seems to have not support tessellation yet. Therefore I have to make a circle plane with thousands of dense vertices to replace the water plane.

  2. Very cool game! I downloaded the project and try to open the scene, but every time I click the scene Unity crashes. Which version of Unity did you use to create the project?

    1. It was made in Unity 5.5.0, any 5.x should work fine!

      1. Hey! Your game is so COOL! When I plan to open and study the project, I meet the same question and the unity crashes too. The version of unity I used is 5.3.4f1. And when I just open the project without open the scenes, it shows there are some errors with the prefabs and the shaders. So could you update a new link with the source code. Thanks very much.

  3. Executable is not playable in Win 10. Nothing happens after you click on Start.

    1. Ciro Continisio

      二月 4, 2017 10:01 下午

      Argh! It was compiled on Mac in a hurry (you know, GGJ…) and now I can’t edit it. I can try to put up an updated working version on another site if your interested!

  4. Arnaud Mukenge Kim

    二月 2, 2017 9:16 下午

    Excellent work. Thanks for sharing

  5. Beautiful! Great work- love the wave effects

  6. Nice work! i like your displacement map approach. Seems very simple to use. I actually did some water mesh warping for the Global Game Jam myself. And, I actually just published today! We’re using sin waves for our water. I want to add some water reaction soon though. Maybe I’ll add in some displacement maps myself!

    Cheers!

    1. My link went a little haywire there and I don’t see a way to edit. Whoops!

    2. Ciro Continisio

      二月 3, 2017 5:49 下午

      Hey Michael, your game looks cool too! It really gives a sense of a huge water mass swaying up and down.

      Also: “In the end, it was a frantic mess of coding and art that made me want to cry. So I would say… it was a successful jamly time.” SO TRUE :D

  7. Very cool article! Want moar like this!

  8. Super amazing, would love to see it in a tutorial style with more information how to do it step by step.

    Well done :)

    1. Ciro Continisio

      二月 3, 2017 5:51 下午

      Cheers Flito! I don’t think I’m going to go more in-depth than this on this specific game, but if you want you can get the whole project from the GGJ website (link at the beginning of the article, on the game’s name) and explore the code and the assets!