Hi, I currently am having an issue with a tutorial posted on http://www.habrador.com/tutorials/unity-boat-tutorial/4-waves-endless-ocean with the endless ocean part. When I left the three scripts from the tutorial fully unedited, unity would freeze forcing me to quit and relaunch. But now I have fixed that error, and incountered a new one that is ("IndexOutOfRangeException: Array index is out of range.
WaterSquare.GenerateMesh () (at Assets/Scripts/WaterSquare.cs:130)
WaterSquare..ctor (UnityEngine.GameObject waterSquareObj, Single no, Single spacing) (at Assets/Scripts/WaterSquare.cs:56)
EndlessWaterSquare.AddWaterPlane (Single xCoord, Single zCoord, Single yPos, Single squareWidth, Single spacing) (at Assets/Scripts/EndlessWaterSquare.cs:211)
EndlessWaterSquare.CreateEndlessSea () (at Assets/Scripts/EndlessWaterSquare.cs:171)
EndlessWaterSquare.Start () (at Assets/Scripts/EndlessWaterSquare.cs:36)")
Here are my slightly edited Scripts:
WaterSquare.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//Generates a plane with a specific resolution and transforms the plane to make waves
public class WaterSquare
{
public Transform squareTransform;
//Add the wave mesh to the MeshFilter
public MeshFilter terrainMeshFilter;
//The total size in m
private float size = 5;
//Resolution = Width of one square
public float spacing = 3;
//The total number of vertices we need to generate based on size and spacing
private int width = 18;
//For the thread to update the water
//The local center position of this square to fake transformpoint in a thread
public Vector3 centerPos;
//The latest vertices that belong to this square
public Vector3[] vertices;
public WaterSquare(GameObject waterSquareObj, float no, float spacing)
{
this.squareTransform = waterSquareObj.transform;
size = 5;
this.spacing = spacing;
this.terrainMeshFilter = squareTransform.GetComponent();
//Calculate the data we need to generate the water mesh
width = (int)(size / spacing);
//Because each square is 2 vertices, so we need one more
width += 1;
//Center the sea
float offset = -((width - 1) * spacing) / 2;
Vector3 newPos = new Vector3(offset, squareTransform.position.y, offset);
squareTransform.position += newPos;
//Save the center position of the square
this.centerPos = waterSquareObj.transform.localPosition;
//Generate the sea
//To calculate the time it took to generate the terrain
float startTime = System.Environment.TickCount;
GenerateMesh();
//Calculate the time it took to generate the terrain in seconds
float timeToGenerateSea = (System.Environment.TickCount - startTime) / 1000f;
Debug.Log("Sea was generated in " + timeToGenerateSea.ToString() + " seconds");
//Save the vertices so we can update them in a thread
this.vertices = terrainMeshFilter.mesh.vertices;
}
//If we are updating the square from outside of a thread
public void MoveSea(Vector3 oceanPos, float timeSinceStart)
{
Vector3[] vertices = terrainMeshFilter.mesh.vertices;
for (int i = 0; i < vertices.Length; i++)
{
Vector3 vertex = vertices[i];
//From local to global
//Vector3 vertexGlobal = squareTransform.TransformPoint(vertex);
Vector3 vertexGlobal = vertex + centerPos + oceanPos;
//Unnecessary because no rotation nor scale
//Vector3 vertexGlobalTest2 = squareTransform.rotation * Vector3.Scale(vertex, squareTransform.localScale) + squareTransform.position;
//Debug
if (i == 0)
{
//Debug.Log(vertexGlobal + " " + vertexGlobalTest);
}
//Get the water height at this coordinate
vertex.y = WaterController.current.GetWaveYPos(vertexGlobal, timeSinceStart);
//From global to local - not needed if we use the saved local x,z position
//vertices[i] = transform.InverseTransformPoint(vertex);
//Don't need to go from global to local because the y pos is always at 0
vertices[i] = vertex;
}
terrainMeshFilter.mesh.vertices = vertices;
terrainMeshFilter.mesh.RecalculateNormals();
}
//Generate the water mesh
public void GenerateMesh()
{
//Vertices
List verts = new List();
//Triangles
List tris = new List();
//Texturing
//List uvs = new List();
for (int z = 0; z < 18; z++)
{
verts.Add(new Vector3[z]);
for (int x = 0; x < 1; x++)
{
Vector3 current_point = new Vector3();
//Get the corrdinates of the vertice
current_point.x = x * spacing;
current_point.z = z * spacing;
current_point.y = squareTransform.position.y;
verts[z][x] = current_point;
//uvs.Add(new Vector2(x,z));
//Don't generate a triangle the first coordinate on each row
//Because that's just one point
if (x <= 0 || z <= 0)
{
continue;
}
//Each square consists of 2 triangles
//The triangle south-west of the vertice
tris.Add(x + z * width);
tris.Add(x + (z-1) * width);
tris.Add((x-1) + (z-1) * width);
//The triangle west-south of the vertice
tris.Add(x + z * width);
tris.Add((x-1) + (z-1) * width);
tris.Add((x-1) + z * width);
}
}
//Unfold the 2d array of verticies into a 1d array.
Vector3[] unfolded_verts = new Vector3[width * width];
int i = 0;
foreach (Vector3[] v in verts)
{
//Copies all the elements of the current 1D-array to the specified 1D-array
v.CopyTo(unfolded_verts, i * width);
i++;
}
//Generate the mesh object
Mesh newMesh = new Mesh();
newMesh.vertices = unfolded_verts;
//newMesh.uv = uvs.ToArray();
newMesh.triangles = tris.ToArray();
//Ensure the bounding volume is correct
newMesh.RecalculateBounds();
//Update the normals to reflect the change
newMesh.RecalculateNormals();
//Add the generated mesh to this GameObject
terrainMeshFilter.mesh.Clear();
terrainMeshFilter.mesh = newMesh;
terrainMeshFilter.mesh.name = "Water Mesh";
Debug.Log(terrainMeshFilter.mesh.vertices.Length);
}
}
EndlessWaterSquare.cs using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
//Creates an endless water system with squares
public class EndlessWaterSquare : MonoBehaviour
{
//The object the water will follow
public GameObject boatObj;
//One water square
public GameObject waterSqrObj;
//Water square data
private float squareWidth = 3f;
private float innerSquareResolution =9f;
private float outerSquareResolution = 2f;
//The list with all water mesh squares == the entire ocean we can see
List waterSquares = new List();
//Stuff needed for the thread
//The timer that keeps track of seconds since start to update the water because we cant use Time.time in a thread
float secondsSinceStart;
//The position of the boat
Vector3 boatPos;
//The position of the ocean has to be updated in the thread because it follows the boat
//Is not the same as pos of boat because it moves with the same resolution as the smallest water square resolution
Vector3 oceanPos;
//Has the thread finished updating the water so we can add the stuff from the thread to the main thread
bool hasThreadUpdatedWater;
void Start()
{
//Create the sea
CreateEndlessSea();
//Init the time
secondsSinceStart = Time.time;
//Update the water in the thread
ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateWaterWithThreadPooling));
//Start the coroutine
StartCoroutine(UpdateWater());
}
void Update()
{
//UpdateWaterNoThread();
//Update these as often as possible because we don't know when the thread will run because of pooling
//and we always need the latest version
//Update the time since start to get correct wave height which depends on time since start
secondsSinceStart = Time.time;
//Update the position of the boat to see if we should move the water
boatPos = boatObj.transform.position;
}
//Update the water with no thread to compare
void UpdateWaterNoThread()
{
//Update the position of the boat
boatPos = boatObj.transform.position;
//Move the water to the boat
MoveWaterToBoat();
//Add the new position of the ocean to this transform
transform.position = oceanPos;
//Update the vertices
for (int i = 0; i < 5; i++)
{
waterSquares[i].MoveSea(oceanPos, Time.time);
}
}
//The loop that gives the updated vertices from the thread to the meshes
//which we can't do in its own thread
IEnumerator UpdateWater()
{
while (true)
{
//Has the thread finished updating the water?
if (hasThreadUpdatedWater)
{
//Move the water to the boat
transform.position = oceanPos;
//Add the updated vertices to the water meshes
for (int i = 0; i < 5; i++)
{
waterSquares[i].terrainMeshFilter.mesh.vertices = waterSquares[i].vertices;
waterSquares[i].terrainMeshFilter.mesh.RecalculateNormals();
}
//Stop looping until we have updated the water in the thread
hasThreadUpdatedWater = false;
//Update the water in the thread
ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateWaterWithThreadPooling));
}
//Don't need to update the water every frame
yield return new WaitForSeconds(Time.deltaTime * 3f);
}
}
//The thread that updates the water vertices
void UpdateWaterWithThreadPooling(object state)
{
//Move the water to the boat
MoveWaterToBoat();
//Loop through all water squares
for (int i = 0; i < 5; i++)
{
//The local center pos of this square
Vector3 centerPos = waterSquares[i].centerPos;
//All the vertices this square consists of
Vector3[] vertices = waterSquares[i].vertices;
//Update the vertices in this square
for (int j = 0; j < 5; j++)
{
//The local position of the vertex
Vector3 vertexPos = vertices[j];
//Can't use transformpoint in a thread, so to find the global position of the vertex
//we just add the position of the ocean and the square because rotation and scale is always 0 and 1
Vector3 vertexPosGlobal = vertexPos + centerPos + oceanPos;
//Get the water height
vertexPos.y = WaterController.current.GetWaveYPos(vertexPosGlobal, secondsSinceStart);
//Save the new y coordinate, but x and z are still in local position
vertices[j] = vertexPos;
}
}
hasThreadUpdatedWater = true;
Debug.Log("Thread finished");
}
//Move the endless water to the boat's position in steps that's the same as the water's resolution
void MoveWaterToBoat()
{
//Round to nearest resolution
float x = innerSquareResolution * (int)Mathf.Round(boatPos.x / innerSquareResolution);
float z = innerSquareResolution * (int)Mathf.Round(boatPos.z / innerSquareResolution);
//Should we move the water?
if (oceanPos.x != x || oceanPos.z != z)
{
Debug.Log("Moved sea");
oceanPos = new Vector3(x, oceanPos.y, z);
}
}
//Init the endless sea by creating all squares
void CreateEndlessSea()
{
//The center piece
AddWaterPlane(0f, 0f, 0f, squareWidth, innerSquareResolution);
//The 8 squares around the center square
for (int x = -1; x <= 1; x += 1)
{
for (int z = -1; z <= 1; z += 1)
{
//Ignore the center pos
if (x == 0 && z == 0)
{
continue;
}
//The y-Pos should be lower than the square with high resolution to avoid an ugly seam
float yPos = -0.5f;
AddWaterPlane(x * squareWidth, z * squareWidth, yPos, squareWidth, outerSquareResolution);
}
}
}
//Add one water plane
void AddWaterPlane(float xCoord, float zCoord, float yPos, float squareWidth, float spacing)
{
GameObject waterPlane = Instantiate(waterSqrObj, transform.position, transform.rotation) as GameObject;
waterPlane.SetActive(true);
//Change its position
Vector3 centerPos = transform.position;
centerPos.x += xCoord;
centerPos.y = yPos;
centerPos.z += zCoord;
waterPlane.transform.position = centerPos;
//Parent it
waterPlane.transform.parent = transform;
//Give it moving water properties and set its width and resolution to generate the water mesh
WaterSquare newWaterSquare = new WaterSquare(waterPlane, squareWidth, spacing);
waterSquares.Add(newWaterSquare);
}
}`
The last script was left unedited
Im pretty sure I know the cause to these errors, but cant fix them.
BTW: I'm running an expiremental build on Linux : 5.6.0xb8
↧