[Tutorial] Particle Sea in Unity3D

When I saw it, I was totally shocked. Firstly — it looked amazing, Secondly — it was playing smoothly in my browser on a non-high-end PC. That was my reaction to this: http://i-remember.fr/en. I immediately fell in love with that and the three.js library. I knew I had to recreate the same effect in Unity3d.

Hi,

Today I decided to share with you my spare time project — Particle Sea and also show you step by step how I managed to create such an effect.

This is how it looks in action! Feel free to play around with parameters, share your results!

In short, what we need to do:

  1. Create Particle System and array of particles
  2. Assign particles positions & create Perlin Noise
  3. Make everything move nice and smoothly — put in loop
  4. Color!

Let’s Start!

I assume that you have already created a new, empty project and scene. If not, do so right now, I’ll wait for you. As soon as you’ve done that, create a new empty game object and place it at the origin. This will be the base of our “sea”. Then create a new C# script named ParticleSea and add it to the object. We’ll also a need ParticleSystem component so go ahead and add one.

If you don’t know how to do that: Go To Components->Effects->Particle System

That’s how it should look like

Project Setup

As I said before, at first we need to create a particle system (we’ve done that already) and create an array of particles. Simply open the ParticleSea class you’ve just created and paste following code. Don’t worry, I’ll explain it all thoroughly.

using UnityEngine; 
using System.Collections;
public class ParticleSea : MonoBehaviour {
public ParticleSystem particleSystem;
private ParticleSystem.Particle[] particlesArray;
public int seaResolution = 25;
void Start() {
particlesArray = new ParticleSystem.Particle[seaResolution * seaResolution];
particleSystem.maxParticles = seaResolution * seaResolution;
particleSystem.Emit(seaResolution * seaResolution);
particleSystem.GetParticles(particlesArray);
}
}

There is really no magic behind this code. We simply we create 3 variables: a particle system, an array of our particles and a number responsible for resolution of our sea. Then, as soon as the game starts, we create set up a new array with space suitable for all the particles. Then we set a maximum particle count to seaResolution squared. In the last two instructions we spawn the desired amount of particles and assign them to an array.

Don’t forget to assign ParticleSystem

The first point — done!

Assigning particles’ position

Right now we have our system creating particles. We also have them assigned in particlesArray, so we can start playing with them. What we would like to do is iterate through each one of them and assign a unique position. First we would like to create a simple grid/plane made of these. Let’s start with a new variable:

public float spacing = 0.25f;

and then paste this code below GetParticles(…) function:

for(int i = 0; i < seaResolution; i++) { 
for(int j = 0; j < seaResolution; j++) {
particlesArray[i * seaResolution + j].position = new Vector3(i * spacing, j * spacing, 0);
}
}
particleSystem.SetParticles(particlesArray, particlesArray.Length);

What’s happening here is simple. We are looping through all of the particles and place each one based on its position. In this case, we place 25 particles in one row and every n-th 25 particles in the next row. When all particles are set, we assign our array of particles back to the particle system.

We are almost ready to see our script in action, last thing we need to do is change a bit values of ParticleSystem.

Looping: False, Prewarm: False, Start Lifetime: 100, Play on Awake: False, Emission Rate: 0

If you have followed instructions correctly, as soon as you press the play button you should see something like this:

Great! We have a grid, now we have to skew it only a bit. For that, we’ll need…

Perlin Noise

I can honestly say it’s great tool/algorithm and I can guarantee you’ll use it many times in your Unity projects. In this tutorial I won’t cover what precisely Perlin Noise is but if you would like to broaden your knowledge in that topic check out Wikipedia or Unity Docs.
In our case the only thing you need to know about this noise is that it will produce nice Z-axis output basing on X-Axis and Y-Axis. In order to apply Perlin Noise we’ll need to introduce two new public variables:

public float noiseScale = 0.2f; 
public float heightScale = 3f;

and change a bit our existing nested loops:

for(int i = 0; i < seaResolution; i++) { 
for(int j = 0; j < seaResolution; j++) {
float zPos = Mathf.PerlinNoise(i * noiseScale, j * noiseScale) * heightScale;
particlesArray[i * seaResolution + j].position = new Vector3(i * spacing, zPos, j * spacing);
}
}

Notice that I switched Y value with Z value and pasted zPos in place of Y value.

Here I introducted a new variable — zPos. zPos is calculated by Mathf.PerlinNoise function complemented with the position of the currently processed particle. You multiply that value by heightScale just to make effect more visible.

If you change the position of ParticleSea prefab to (-12.5,-5,0) and change the parameters of ParticleSea class to Resolution: 50, Spacing: 0.5 you should see something like this:

Static Particle Sea — so far so good

Animating Sea

The most exciting part I guess. And it’s all pretty simple. We only need to move some code to Update() function so it is be executed in every frame and leave some in start function and add two or three new variables which will be changing with time. The code that is executed every frame should be something like this:

void Update() { 
for(int i = 0; i < seaResolution; i++) {
for(int j = 0; j < seaResolution; j++) {
float zPos = Mathf.PerlinNoise(i * noiseScale + perlinNoiseAnimX, j * noiseScale + perlinNoiseAnimY) * heightScale;
particlesArray[i * seaResolution + j].position = new Vector3(i * spacing, zPos, j * spacing);
}
}
perlinNoiseAnimX += 0.01f; perlinNoiseAnimY += 0.01f;
particleSystem.SetParticles(particlesArray, particlesArray.Length);
}

We also need to create two new global variables:

private float perlinNoiseAnimX = 0.01f; 
private float perlinNoiseAnimY = 0.01f;

What happened here? Perlin Noise parameters have been extended by perlinNoiseAnimX and perlinNoiseAnimY parameters. These two guys change value with time so perlin noise function result is also changing. Feel free to change these values a bit. We also need to remove some code from the Start() function so it should look like:

void Start() { 
particlesArray = new ParticleSystem.Particle[seaResolution * seaResolution];
particleSystem.maxParticles = seaResolution * seaResolution;
particleSystem.Emit(seaResolution * seaResolution);
particleSystem.GetParticles(particlesArray);
}

If you’ve done everything correctly now you should now see something like this:

Feel free to play with values in inspector

Animating particles color

We can make the whole thing even more awesome just by making valleys transparent and changing the colour of the hills. here, the Gradient class is very handy. Simply add another variable:

public Gradient colorGradient;

And play with it a bit in inspector:

Finally, we need to change the particle color according to the gradient based on particle Y position in space. In order to do that I slightly modified the body of our second, nested loop:

float zPos = Mathf.PerlinNoise(i * noiseScale + perlinNoiseAnimX, j * noiseScale + perlinNoiseAnimY); particlesArray[i * seaResolution + j].color = colorGradient.Evaluate(zPos); particlesArray[i * seaResolution + j].position = new Vector3(i * spacing, zPos * heightScale, j * spacing);

I think this change is self-explanatory. Basically, I assign the color of a particle by evaluating gradient by zPos variable.

If you play a bit with gradient and other variables you should easily receive a result like this:

Black camera background, Resolution: 100, Spacing: 0.3, Noise Scale: 0.05, Height Scale: 3

At the end of the day, ParticleSea class should look like this: PasteBin

I hope you enjoyed this tutorial, Don’t hesitate to ask questions, I more than happy to answer them.

Feel free to download the whole project a bit pimped-up from GitHub: https://github.com/Precel/Particle-Sea/tree/master

Sadly I had to remove some cool postpro effects because I wasn’t the author.
You can purchase these awesome effects here:
https://www.assetstore.unity3d.com/en/#!/content/22217

--

--

--

Founder of https://dynobase.dev, AWS Certified Architect, love minimalism and procedural content generation.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Rafal Wilinski

Rafal Wilinski

Founder of https://dynobase.dev, AWS Certified Architect, love minimalism and procedural content generation.

More from Medium

Anticipate and Adjust: Cultivating Access in Human-Centered Methods

Diagram with 5 boxes; bidirectional arrows connect each. 1. Identify stakeholders: list who will be involved e.g., participants, collaborators. 2. Define tasks for this stage. 3. Assign tasks to relevant stakeholders. Consider access needs, the benefits/costs of familiarity, other constraints. 4. Plan accommodations: Think about how to meet everyone’s access needs. Consider communication, materials, space, time. 5. Reflect: Review the plan. Consider access synergies or conflicts, power dynamics.

Setting up my UI & adding player’s Score

Simple 2D Player Movement In Unity: Ideal For SHMUPs

Automating your test states: an Introduction