- Home /
Help saving Voxel Terrain between scenes
Hi I was wondering if anyone could help me figure out a script to save my voxel terrain between scene changes. I've tried using unity serializer but Since theyre generated on the fly It doesn't recognize the chunk clones or their positions. I'm new to unity so any help would be greatly appreciated. here are the scripts I'M working with.
Chunk Script
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using SimplexNoise;
public enum BrickType {
None,
RoughStone,
SmoothStone,
DarkStone,
PowderStone,
Granite,
Dirt,
Sand,
GreenLattice,
Taint,
Dust,
Rust,
Ice,
Snow,
DirtyIce,
Streaks,
Lava
}
[RequireComponent (typeof(MeshRenderer))]
[RequireComponent (typeof(MeshCollider))]
[RequireComponent (typeof(MeshFilter))]
public class Chunk : MonoBehaviour {
public static List<Chunk> chunks = new List<Chunk>();
public static int width {
get { return World.currentWorld.chunkWidth; }
}
public static int height {
get { return World.currentWorld.chunkHeight; }
}
public static float brickHeight {
get { return World.currentWorld.brickHeight; }
}
public byte[,,] map;
public Mesh visualMesh;
protected MeshRenderer meshRenderer;
protected MeshCollider meshCollider;
protected MeshFilter meshFilter;
// Use this for initialization
void Start () {
chunks.Add(this);
meshRenderer = GetComponent<MeshRenderer>();
meshCollider = GetComponent<MeshCollider>();
meshFilter = GetComponent<MeshFilter>();
CalculateMapFromScratch();
StartCoroutine(CreateVisualMesh());
}
// Update is called once per frame
void Update () {
}
public static byte GetTheoreticalByte(Vector3 pos) {
Random.seed = World.currentWorld.seed;
Vector3 grain0Offset = new Vector3(Random.value * 10000, Random.value * 10000, Random.value * 10000);
Vector3 grain1Offset = new Vector3(Random.value * 10000, Random.value * 10000, Random.value * 10000);
Vector3 grain2Offset = new Vector3(Random.value * 10000, Random.value * 10000, Random.value * 10000);
return GetTheoreticalByte(pos, grain0Offset, grain1Offset, grain2Offset);
}
public static byte GetTheoreticalByte(Vector3 pos, Vector3 offset0, Vector3 offset1, Vector3 offset2)
{
float clusterValue = CalculateNoiseValue(pos, offset2, 0.02f);
int biomeIndex = Mathf.FloorToInt(clusterValue * World.currentWorld.biomes.Length);
Biome biome = World.currentWorld.biomes[biomeIndex];
float heightBase = biome.minHeight;
float maxHeight = biome.maxHeight;
float heightSwing = maxHeight - heightBase;
float blobValue = CalculateNoiseValue(pos, offset1, 0.05f);
float mountainValue = CalculateNoiseValue(pos, offset0, 0.009f);
mountainValue += biome.mountainPowerBonus;
if (mountainValue < 0) mountainValue = 0;
mountainValue = Mathf.Pow(mountainValue, biome.mountainPower); //Mathf.Sqrt(mountainValue);
byte brick = biome.GetBrick(Mathf.FloorToInt(pos.y), mountainValue, blobValue);
mountainValue *= heightSwing;
mountainValue += heightBase;
mountainValue += (blobValue * 10) - 5f;
if (mountainValue >= pos.y)
return brick;
return 0;
}
public virtual void CalculateMapFromScratch() {
map = new byte[width, height, width];
Random.seed = World.currentWorld.seed;
Vector3 grain0Offset = new Vector3(Random.value * 10000, Random.value * 10000, Random.value * 10000);
Vector3 grain1Offset = new Vector3(Random.value * 10000, Random.value * 10000, Random.value * 10000);
Vector3 grain2Offset = new Vector3(Random.value * 10000, Random.value * 10000, Random.value * 10000);
for (int x = 0; x < World.currentWorld.chunkWidth; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < width; z++)
{
map[x, y, z] = GetTheoreticalByte(new Vector3(x, y, z) + transform.position, grain0Offset, grain1Offset, grain2Offset);
}
}
}
}
public static float CalculateNoiseValue(Vector3 pos, Vector3 offset, float scale)
{
float noiseX = Mathf.Abs((pos.x + offset.x) * scale);
float noiseY = Mathf.Abs((pos.y + offset.y) * scale);
float noiseZ = Mathf.Abs((pos.z + offset.z) * scale);
return Mathf.Max(0, Noise.Generate(noiseX, noiseY, noiseZ));
}
public virtual IEnumerator CreateVisualMesh() {
visualMesh = new Mesh();
List<Vector3> verts = new List<Vector3>();
List<Vector2> uvs = new List<Vector2>();
List<int> tris = new List<int>();
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < width; z++)
{
if (map[x,y,z] == 0) continue;
byte brick = map[x,y,z];
// Left wall
if (IsTransparent(x - 1, y, z))
BuildFace (brick, new Vector3(x, y, z), Vector3.up, Vector3.forward, false, verts, uvs, tris);
// Right wall
if (IsTransparent(x + 1, y , z))
BuildFace (brick, new Vector3(x + 1, y, z), Vector3.up, Vector3.forward, true, verts, uvs, tris);
// Bottom wall
if (IsTransparent(x, y - 1 , z))
BuildFace (brick, new Vector3(x, y, z), Vector3.forward, Vector3.right, false, verts, uvs, tris);
// Top wall
if (IsTransparent(x, y + 1, z))
BuildFace (brick, new Vector3(x, y + 1, z), Vector3.forward, Vector3.right, true, verts, uvs, tris);
// Back
if (IsTransparent(x, y, z - 1))
BuildFace (brick, new Vector3(x, y, z), Vector3.up, Vector3.right, true, verts, uvs, tris);
// Front
if (IsTransparent(x, y, z + 1))
BuildFace (brick, new Vector3(x, y, z + 1), Vector3.up, Vector3.right, false, verts, uvs, tris);
}
}
}
visualMesh.vertices = verts.ToArray();
visualMesh.uv = uvs.ToArray();
visualMesh.triangles = tris.ToArray();
visualMesh.RecalculateBounds();
visualMesh.RecalculateNormals();
meshFilter.mesh = visualMesh;
meshCollider.sharedMesh = null;
meshCollider.sharedMesh = visualMesh;
yield return 0;
}
public virtual void BuildFace(byte brick, Vector3 corner, Vector3 up, Vector3 right, bool reversed, List<Vector3> verts, List<Vector2> uvs, List<int> tris)
{
int index = verts.Count;
float uvRow = ((corner.y + up.y) % 7);
if (uvRow >= 4) uvRow = 7 - uvRow;
uvRow /= 4f;
Vector2 uvCorner = new Vector2(0.00f, uvRow);
if (brick < 8)
uvCorner.y += 0.125f;
corner.y *= brickHeight;
up.y *= brickHeight;
right.y *= brickHeight;
verts.Add (corner);
verts.Add (corner + up);
verts.Add (corner + up + right);
verts.Add (corner + right);
Vector2 uvWidth = new Vector2(0.125f, 0.125f);
uvCorner.x += (float)((brick - 1) % 8) / 8f;
uvs.Add(uvCorner);
uvs.Add(new Vector2(uvCorner.x, uvCorner.y + uvWidth.y));
uvs.Add(new Vector2(uvCorner.x + uvWidth.x, uvCorner.y + uvWidth.y));
uvs.Add(new Vector2(uvCorner.x + uvWidth.x, uvCorner.y));
if (reversed)
{
tris.Add(index + 0);
tris.Add(index + 1);
tris.Add(index + 2);
tris.Add(index + 2);
tris.Add(index + 3);
tris.Add(index + 0);
}
else
{
tris.Add(index + 1);
tris.Add(index + 0);
tris.Add(index + 2);
tris.Add(index + 3);
tris.Add(index + 2);
tris.Add(index + 0);
}
}
public virtual bool IsTransparent (int x, int y, int z)
{
if ( y < 0) return false;
byte brick = GetByte(x,y,z);
switch (brick)
{
case 0:
return true;
default:
return false;
}
}
public virtual byte GetByte (int x, int y , int z)
{
if ((y < 0) || (y >= height))
return 0;
if ( (x < 0) || (z < 0) || (x >= width) || (z >= width))
{
Vector3 worldPos = new Vector3(x, y, z) + transform.position;
Chunk chunk = Chunk.FindChunk(worldPos);
if (chunk == this) return 0;
if (chunk == null)
{
return GetTheoreticalByte(worldPos);
}
return chunk.GetByte (worldPos);
}
return map[x,y,z];
}
public virtual byte GetByte(Vector3 worldPos) {
worldPos -= transform.position;
int x = Mathf.FloorToInt(worldPos.x);
int y = Mathf.FloorToInt(worldPos.y);
int z = Mathf.FloorToInt(worldPos.z);
return GetByte (x, y, z);
}
public static Chunk FindChunk(Vector3 pos) {
for (int a = 0; a < chunks.Count; a++)
{
Vector3 cpos = chunks[a].transform.position;
if ( ( pos.x < cpos.x) || (pos.z < cpos.z) || (pos.x >= cpos.x + width) || (pos.z >= cpos.z + width) ) continue;
return chunks[a];
}
return null;
}
public bool SetBrick (byte brick, Vector3 worldPos)
{
worldPos -= transform.position;
return SetBrick(brick, Mathf.FloorToInt(worldPos.x),Mathf.FloorToInt(worldPos.y),Mathf.FloorToInt(worldPos.z));
}
public bool SetBrick (byte brick, int x, int y, int z)
{
if ( ( x < 0) || (y < 0) || (z < 0) || (x >= width) || (y >= height) || (z >= width) )
{
return false;
}
if (map[x,y,z] == brick) return false;
map[x,y,z] = brick;
StartCoroutine(CreateVisualMesh());
if (x == 0)
{
Chunk chunk = FindChunk( new Vector3(x - 2, y, z) + transform.position);
if (chunk != null)
StartCoroutine(chunk.CreateVisualMesh());
}
if (x == width - 1)
{
Chunk chunk = FindChunk( new Vector3(x + 2, y, z) + transform.position);
if (chunk != null)
StartCoroutine(chunk.CreateVisualMesh());
}
if (z == 0)
{
Chunk chunk = FindChunk( new Vector3(x, y, z - 2) + transform.position);
if (chunk != null)
StartCoroutine(chunk.CreateVisualMesh());
}
if (z == width - 1)
{
Chunk chunk = FindChunk( new Vector3(x, y, z + 2) + transform.position);
if (chunk != null)
StartCoroutine(chunk.CreateVisualMesh());
}
return true;
}
}
world script
using UnityEngine; using System.Collections;
public class World : MonoBehaviour {
public Biome[] biomes;
public static World currentWorld;
public int chunkWidth = 20, chunkHeight = 20, seed = 0;
public float viewRange = 30;
public float brickHeight = 1;
public Chunk chunkFab;
// Use this for initialization
void Awake () {
currentWorld = this;
if (seed == 0)
seed = Random.Range(0, int.MaxValue);
}
// Update is called once per frame
void Update () {
for (float x = transform.position.x - viewRange; x < transform.position.x + viewRange; x+= chunkWidth)
{
for (float z = transform.position.z - viewRange; z < transform.position.z + viewRange; z+= chunkWidth)
{
Vector3 pos = new Vector3(x, 0, z);
pos.x = Mathf.Floor(pos.x / (float)chunkWidth) * chunkWidth;
pos.z = Mathf.Floor(pos.z / (float)chunkWidth) * chunkWidth;
Chunk chunk = Chunk.FindChunk(pos);
if (chunk != null) continue;
chunk = (Chunk)Instantiate(chunkFab, pos, Quaternion.identity);
}
}
}
}
Thank you in advance in a noobie.
Wow, well, that's a lot of code. But if all you need is persistence across scenes ins$$anonymous$$d of full save/load functionality, wouldn't DontDestroyOnLoad work?
Answer by PaulOrac · Aug 16, 2014 at 05:48 PM
I added this answer some minutes ago but It didn't show I'll do it again...
http://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
I hope this helps you my friend, this will keep your GameObject alive between the scenes but it won't be saved if you quit the application
@PaulOrac - until you have 15 $$anonymous$$arma points, all your questions go through moderation. It can be $$anonymous$$utes or hours depending what moderators are online before a question from the moderation queue is published, so reposting questions just makes work for the moderators.
oh Sorry about that robertbu, I got worried as I didn't even see the answer in my dashboard. Thankyou!!
THAN$$anonymous$$ YOU!!!!!!!!!! I feel dumb lol but it worked!!
Answer by Cherno · Aug 17, 2014 at 01:37 AM
If you want to saveto file, you can use Unity Serializer or your own Serializer to just save the information from your 3-dimension array and then load it when loading the saved file again, and then rebuilding your chunks/meshes.
Be aware that you can not save multi-dimensional arrays though, so you have to put the block info into one long array and when loading, use the xyz size values to put that long array back into 3-dimensional form.
int x = 0;
int y = 0;
int z = 0;
for(int i = 0; i < yourFlattenedArray; i ++) {
yourWorld[x,y,z].fillData = loadedGame.fillDataList[i];
z++;
if (z == zSize) {
z = 0;
if(y < ySize - 1) {
y++;
}
else {
x++;
y = 0;
}
}
}
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Serialize loads of different data 0 Answers
Scriptable Object's Data Gets Lost After Re-opening Unity!!! 1 Answer
Fatal Error: Database or Disk is Full. 0 Answers
How to get update from database? 1 Answer