Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
2d Water Line in Unity. With realtime mesh construction and deformation. Just a start, no physics. Thanks to http://forum.unity3d.com/threads/141925-2d-Water and http://games.deozaan.com/unity/MeshTutorial.pdf
// Author: Damien Mayance (http://dmayance.com)
// 2013 - Pixelnest Studio (http://pixelnest.io)
//
// This script simulates a simple 2d water line behavior, like you see in many 2d games.
// See http://dmayance.com/water-line-2d-unity/ for further explanations.
//
// Usage:
// - Attach it to an object
// - Then fill the "Material" parameter
// - Start the game (here it is only visible at runtime)
//
// The parameters can be modified during runtime, but for EDITOR only.
// The width and height should stay fixed during the game otherwise.
using UnityEngine;
using System.Collections;
public struct WaterLinePart
{
public float height;
public float velocity;
public GameObject gameObject;
public Mesh mesh;
public Vector2 boundsMin;
public Vector2 boundsMax;
}
public class WaterLine : MonoBehaviour
{
public float velocityDamping = 0.999999f; // Proportional velocity damping, must be less than or equal to 1.
public float timeScale = 25f;
public int Width = 50;
public float Height = 10f;
public Material material;
public Color color = Color.blue;
private WaterLinePart[] parts;
private int size;
private float currentHeight;
#if UNITY_EDITOR
private bool cleanRequested;
#endif
void Start()
{
#if UNITY_EDITOR
// Remove what we see from the editor
Clear();
#endif
Initialize();
}
private void Initialize()
{
size = Width;
currentHeight = Height;
material.color = color;
parts = new WaterLinePart[size];
// we'll use spheres to represent each vertex for demonstration purposes
for (int i = 0; i < size; i++)
{
// Create a game object
GameObject go = new GameObject("WavePart");
go.transform.parent = this.transform;
go.transform.localPosition = new Vector3(i - (size / 2), 0, 0);
parts[i].gameObject = go;
}
// Create the meshes
for (int i = 0; i < size; i++)
{
GameObject go = parts[i].gameObject;
// Except for the last point
if (i < size - 1)
{
Mesh mesh = new Mesh();
mesh.MarkDynamic();
parts[i].mesh = mesh;
go.AddComponent<MeshFilter>();
go.AddComponent<MeshRenderer>();
// Define vertices for the mesh (the points of the model)
UpdateMeshVertices(i);
// Define triangles and normals
InitializeTrianglesAndNormalsForMesh(i);
go.GetComponent<MeshFilter>().mesh = mesh;
go.GetComponent<MeshRenderer>().material = material;
}
}
// Small wave
Splash(size / 2, 10);
}
#if UNITY_EDITOR
/// <summary>
/// SUPER VIOLENT METHOD FOR EDITOR MODE
/// </summary>
private void Clear()
{
for (int i = 0; i < size; i++)
{
DestroyImmediate(parts[i].mesh);
DestroyImmediate(parts[i].gameObject);
}
parts = null;
}
#endif
private void UpdateMeshVertices(int i)
{
Mesh mesh = parts[i].mesh;
if (mesh == null) return;
Transform current = parts[i].gameObject.transform;
Transform next = current;
if (i < parts.Length - 1)
{
next = parts[i + 1].gameObject.transform;
}
Vector3 left = Vector3.zero;
Vector3 right = next.localPosition - current.localPosition;
// Get all parts of the mesh (it's just 2 planes, one on top and one on the front face)
Vector3 topLeftFront = new Vector3(left.x, left.y, 0);
Vector3 topRightFront = new Vector3(right.x, right.y, 0);
Vector3 topLeftBack = new Vector3(left.x, left.y, 1);
Vector3 topRightBack = new Vector3(right.x, right.y, 1);
Vector3 bottomLeftFront = new Vector3(left.x, left.y + (0 - Height), 0);
Vector3 bottomRightFront = new Vector3(right.x, right.y + (0 - Height), 0);
mesh.vertices = new Vector3[] { topLeftFront, topRightFront, topLeftBack, topRightBack, bottomLeftFront, bottomRightFront };
parts[i].boundsMin = topLeftFront + current.position;
parts[i].boundsMax = bottomRightFront + current.position;
}
private void InitializeTrianglesAndNormalsForMesh(int i)
{
Mesh mesh = parts[i].mesh;
if (mesh == null) return;
// Normals
var uvs = new Vector2[mesh.vertices.Length];
for (int i2 = 0; i2 < uvs.Length; i2++)
{
uvs[i2] = new Vector2(mesh.vertices[i2].x, mesh.vertices[i2].z);
}
mesh.uv = uvs;
// Triangles
mesh.triangles = new int[] { 5, 4, 0, 0, 1, 5, 0, 2, 3, 3, 1, 0 };
// For shader
mesh.RecalculateNormals();
}
void Update()
{
#if UNITY_EDITOR
// Size has been updated?
if (Width != size || Height != currentHeight)
{
cleanRequested = true;
}
// Recalculate everything!
// This should be for the editor only!
if (cleanRequested)
{
cleanRequested = false;
Debug.Log("Reinitializing water. Make sure we are in editor mode!");
Clear();
Initialize();
}
color = material.color;
#endif
// Water tension is simulated by a simple linear convolution over the height field.
for (int i = 1; i < size - 1; i++)
{
#if UNITY_EDITOR
// Objects deleted from editor
if (parts[i].gameObject == null)
{
cleanRequested = true;
return;
}
#endif
int j = i - 1;
int k = i + 1;
parts[i].height = (parts[i].gameObject.transform.localPosition.y + parts[j].gameObject.transform.localPosition.y + parts[k].gameObject.transform.localPosition.y) / 3.0f;
}
// Velocity and height are updated...
for (int i = 0; i < size; i++)
{
// update velocity and height
parts[i].velocity = (parts[i].velocity + (parts[i].height - parts[i].gameObject.transform.localPosition.y)) * velocityDamping;
float timeFactor = Time.deltaTime * timeScale;
if (timeFactor > 1f) timeFactor = 1f;
parts[i].height += parts[i].velocity * timeFactor;
// Update the dot position
Vector3 newPosition = new Vector3(
parts[i].gameObject.transform.localPosition.x,
parts[i].height,
parts[i].gameObject.transform.localPosition.z);
parts[i].gameObject.transform.localPosition = newPosition;
}
// Update meshes
for (int i = 0; i < size; i++)
{
UpdateMeshVertices(i);
}
}
#region Interaction
/// <summary>
/// Make waves from a point
/// </summary>
/// <param name="location"></param>
/// <param name="force"></param>
public void Splash(Vector3 location, int force)
{
// Find the touched part
for (int i = 0; i < (size - 1); i++)
{
if (location.x >= parts[i].boundsMin.x
&& location.x < parts[i].boundsMax.x)
{
if (location.y <= parts[i].boundsMin.y
&& location.y > parts[i].boundsMax.y)
{
Splash(i, force);
}
}
}
}
private void Splash(int i, int heightModifier)
{
parts[i].gameObject.transform.localPosition = new Vector3(
parts[i].gameObject.transform.localPosition.x,
parts[i].gameObject.transform.localPosition.y + heightModifier,
parts[i].gameObject.transform.localPosition.z
);
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment