How to save voxel (Minecraft) terrain?

This is my first post, so sorry if it isn’t good. I’m making a Minecraft-like game in Unity, and I’m happy with everything except that I can’t save any worlds. The worlds go on forever and the terrain is random too. I heard about arrays and each blocks being a byte. I’m very new to Unity, so I don’t really know what these mean or how to save them.
Here is my whole Chunk Script used in the game:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public enum TerrainType {
	Lowlands,
	Highlands,
	Mountains
}

public enum BiomeType {
	Grasslands,
	Forest,
	Desert,
	Ice
}


[RequireComponent (typeof(MeshFilter))]
[RequireComponent (typeof(MeshCollider))]
public class Chunk : MonoBehaviour {
	
	
	public int height = 40;
	public int groundHeight = 8;
	public byte[,,] map;
	
	protected Mesh mesh;
	
	protected List<Vector3> verts = new List<Vector3>();
	protected List<int> tris = new List<int>();
	protected List<Vector2> uv = new List<Vector2>();
	
	protected MeshCollider meshCollider;
	
	public List<GameObject> targets = new List<GameObject>();
	
	protected bool initialized = false;
	protected int width;
	
	
	// Use this for initialization
	void Start () {
		
		width = Terrain.activeTerrain.chunkSize;
		
		map = new byte[width, height, width];
		
		
		TerrainType terrainType = GetTerrainFor(transform.position.x, transform.position.z);
		BiomeType biomeType = GetBiomeFor(transform.position.x, transform.position.z);
		
		float myHeight = GetHeightOf(terrainType);
		
		byte dirt = Terrain.blocks.GetBlockByte("Dirt");
		byte grassydirt = Terrain.blocks.GetBlockByte("GrassyDirt");
		byte ice = Terrain.blocks.GetBlockByte("Ice");
		byte sand = Terrain.blocks.GetBlockByte("Sand");
		
		
		for (int x = 0; x < width; x++)
		{
			float percent = (float)x / (float)(width - 1);
			float xBalance = CurvePoint( 
				percent,
				(GetHeightOf(transform.position.x - width, transform.position.z) + myHeight) / 2,
				myHeight,
				(GetHeightOf(transform.position.x + width, transform.position.z) + myHeight) / 2);
			
			for (int z = 0; z < width; z++)
			{
				percent = (float)z / (float)(width - 1);
				float zBalance = CurvePoint( 
					percent,
					(GetHeightOf(transform.position.x, transform.position.z - width) + myHeight) / 2,
					myHeight,
					(GetHeightOf(transform.position.x, transform.position.z + width) + myHeight) / 2);
				
				
				float finalHeight = (xBalance + zBalance) / 2;
				
				for (int y = 0; y < finalHeight; y++)
				{
					switch (biomeType)
					{
					default:
						if (y >= finalHeight - 1)
							map[x, y, z] = grassydirt;
						else
							map[x, y, z] = dirt;
						break;
					case BiomeType.Ice:
						if (y >= finalHeight - 2)
							map[x, y, z] = ice;
						else
							map[x, y, z] = dirt;
						break;
					case BiomeType.Desert:
						if (y >= finalHeight - 7)
							map[x, y, z] = sand;
						else
							map[x, y, z] = dirt;
						break;
						
					}
				}
			}
		}
		
		for (int dx = -1; dx <= 1; dx++)
		{	
			for (int dz = -1; dz <= 1; dz++)
			{
				float myX = transform.position.x + dx * width;
				float myZ = transform.position.z + dz * width;
				
				List<LandBrush> brushes = GetBrushesFor(myX, myZ);
				for (int a = 0; a < brushes.Count; a++)
				{
					brushes[a].ApplyBrush(this);
				}
			}
		}
		
		initialized = true;
		Regenerate();
		
	}
	
	// Update is called once per frame
	void Update () {
	}
	
	public void CloseMeshTarget() {
		
		mesh.vertices = verts.ToArray();
		mesh.triangles = tris.ToArray();
		mesh.uv = uv.ToArray();
		mesh.RecalculateNormals();
		
		meshCollider.sharedMesh = mesh;
		
	}
	
	public void CreateMeshTarget(bool reset=false) {
		
		
		meshCollider = GetComponent<MeshCollider>();
		mesh = new Mesh();
		GetComponent<MeshFilter>().mesh = mesh;
		
		
		verts.Clear();
		tris.Clear();
		uv.Clear ();
		
	}
	
	public void DrawBrick(int x, int y, int z, byte block) 	{
		
		Vector3 start = new Vector3(x, y, z);
		Vector3 offset1, offset2;
		
		if (IsTransparent(x, y - 1, z))
		{
			offset1 = Vector3.left;
			offset2 = Vector3.back;
			DrawFace(start + Vector3.right, offset1, offset2, block);
		}
		
		if (IsTransparent(x, y + 1, z))
		{
			offset1 = Vector3.right;
			offset2 = Vector3.back;
			DrawFace(start + Vector3.up, offset1, offset2, block);
		}
		
		if (IsTransparent(x - 1, y, z))
		{
			offset1 = Vector3.back;
			offset2 = Vector3.down;
			DrawFace(start + Vector3.up, offset1, offset2, block);
		}
		
		if (IsTransparent(x + 1, y, z))
		{
			offset1 = Vector3.forward;
			offset2 = Vector3.down;
			DrawFace(start + Vector3.right + Vector3.up + Vector3.back, offset1, offset2, block);
		}
		
		
		if (IsTransparent(x, y, z - 1))
		{
			offset1 = Vector3.right;
			offset2 = Vector3.down;
			DrawFace(start + Vector3.back + Vector3.up, offset1, offset2, block);
		}
		
		
		if (IsTransparent(x, y, z + 1))
		{
			offset1 = Vector3.left;
			offset2 = Vector3.down;
			DrawFace(start + Vector3.up + Vector3.right, offset1, offset2, block);
		}
		
		
	}
	
	
	public void DrawFace(Vector3 start, Vector3 offset1, Vector3 offset2, byte block)
	{
		int index = verts.Count;
		
		verts.Add (start);
		verts.Add (start + offset1);
		verts.Add (start + offset2);
		verts.Add (start + offset1 + offset2);
		
		BlockType blockType = Terrain.blocks.GetBlock(block);
		Vector2 uvBase = blockType.UVCoord();
		
		float uvOff = 64f / 1024f;
		
		
		if ((offset1 == Vector3.right) && (offset2 == Vector3.back))
		{
			uv.Add (uvBase);
			uv.Add (uvBase + new Vector2(uvOff, 0));
			uv.Add (uvBase + new Vector2(0, -uvOff));
			uv.Add (uvBase + new Vector2(uvOff, -uvOff));
		}
		else
		{		
			uv.Add (uvBase + new Vector2(0, -uvOff));
			uv.Add (uvBase + new Vector2(uvOff, -uvOff));
			uv.Add (uvBase + new Vector2(0, -uvOff * 2));
			uv.Add (uvBase + new Vector2(uvOff, -uvOff * 2));
		}
		
		
		
		tris.Add (index + 0);
		tris.Add (index + 1);
		tris.Add (index + 2);
		tris.Add (index + 3);
		tris.Add (index + 2);
		tris.Add (index + 1);
	}
	
	public bool IsTransparent(int x, int y, int z)
	{
		if ((x< 0) || (y < 0) || (z < 0) || (x >= width) || (y >= height) || (z >= width)) return true;
		return map[x,y,z] == 0;
	}
	
	public void Regenerate() {
		if (! initialized) return;
		
		CreateMeshTarget(true);
		
		mesh.triangles = tris.ToArray();
		
		for (int y = 0; y < height; y++)
		{
		
			for (int x = 0; x < width; x++)
			{
				for (int z = 0; z < width; z++)
				{
					
					byte block = map[x,y,z];
					if (block == 0) continue;
					
					DrawBrick(x, y, z, block);
					
				}
			}
		}
		
		
		CloseMeshTarget();
	}
	public void SetBrick(int x, int y , int z, byte block)
	{
		if (y == 0) return;
		x -= Mathf.RoundToInt(transform.position.x);
		y -= Mathf.RoundToInt(transform.position.y);
		z -= Mathf.RoundToInt(transform.position.z);
		
		if ((x< 0) || (y < 0) || (z < 0) || (x >= width) || (y >= height) || (z >= width)) return;
		
		if (map[x,y,z] != block)
		{
			map[x,y,z] = block;
			Regenerate();
		}
	}
	
	public static List<LandBrush> GetBrushesFor(float x, float z) {
		List<LandBrush> brushes = new List<LandBrush>();
		
		Terrain terrain = Terrain.activeTerrain;
		
		TerrainType terrainType = GetTerrainFor(x, z);
		BiomeType biomeType = GetBiomeFor (x, z);
		
		Random.seed = terrain.seed + Mathf.FloorToInt(x * 7 + z * 13);
		
		
		float numBrushes = 8;
		if (terrainType == TerrainType.Mountains)
			numBrushes = 18;
		while (numBrushes > 0)
		{
			numBrushes--;
			brushes.Add (new LandBrush(x, z, terrain.chunkSize, terrainType, biomeType));
		}
		
		return brushes;
	}
	
	public static TerrainType GetTerrainFor(float x, float z) {
		
		Random.seed = Terrain.activeTerrain.seed + Mathf.FloorToInt(x * 7 + z * 13);
		
		return (TerrainType)Mathf.FloorToInt(Random.value * 3);
		
	}
	public static BiomeType GetBiomeFor (float x, float z) {
		
		x = Mathf.FloorToInt(x / 160);
		z = Mathf.FloorToInt(x / 160);
		
		Random.seed = Terrain.activeTerrain.seed + Mathf.FloorToInt(x * 7 + z * 13);
		
		return (BiomeType)Mathf.FloorToInt(Random.value * 4);
		
	}
	
	public static float GetHeightOf(float x, float z) {
		return GetHeightOf (GetTerrainFor(x, z));
	}
			
	public static float GetHeightOf(TerrainType terrainType) {
		switch (terrainType)
		{
		default: return 8;
		case TerrainType.Highlands:
			return 13;
		case TerrainType.Mountains:
			return 30;
		}
	}
	
	public static float CurvePoint(float percent, float val1, float val2, float val3)
	{
		float p1 = (1 - percent) * val1 + percent * val2;
		float p2 = (1 - percent) * val2 + percent * val3;
		return (1 - percent) * p1 + percent * p2;
	}
		
}

I would look into making a list/array of all your chunk objects in a World class or something of the like. Then you would need to write this data to disk with a serializer. Binary would probably be the best in this case. You can find many excellent serializers such as the one found here. Alternatively, you can look into lower-level .NET serializers and figure out storing the bytes and restoring them from a file.

If I remember correctly, Mojang stores their world data in a custom binary format which basically represents 32x32 chunks stored across several files. Also note that Minecraft stores other data in a NBT format, and not the chunk regions.

If you are new, however, I would seriously consider looking into that serializer by WhyDoIDoIt.com. It’s on the Unity Asset Store. For more control and a greater learning experience, you can always resort to writing a custom serializer through .NET methods/classes. This would probably be easier than you think if you just store a byte value for your block. The harder part would be modifying your code to iterate through all the values in your chunks and restore them. You could probably get away with using most of your existing generation code, just modifying the parts where you use random seeding and replace it with actual serialized data.

Best of luck!

The easiest way to save voxel terrain is by only saving the seed for the level generator. Another way would be to give each block a byte index (number between 0-255) and save them into a file. You can save all your chunks like that and create an aditional file that holds the position of each chunk. You can check out my voxel engine at the unity assetstore which already comes with a save/load system: Unity Asset Store - The Best Assets for Game Making