Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by ckosmic · Aug 16, 2013 at 12:54 AM · terrainsavingvoxelhow to

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;
     }
         
 }
Comment
Add comment
10 |3000 characters needed characters left characters exceeded
â–¼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

2 Replies

· Add your reply
  • Sort: 
avatar image
3
Best Answer

Answer by QuantumCD · Aug 16, 2013 at 07:07 AM

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!

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
â–¼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
0

Answer by WizzDE · May 03, 2014 at 05:11 PM

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: https://www.assetstore.unity3d.com/#/content/17470

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
â–¼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

18 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Help with generating Chunks! 0 Answers

Cube World Terrain generation 2 Answers

Cube style terrain 1 Answer

how to manually make a voxel terrain? 1 Answer

when i create my terrain its so little that i cant even see it. how do i make it to look like the ones in the tutorials? 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges