Stumped! I have an engine that chooses what cube is where, but I need a way to render them.

I need:

Chunk-based rendering

Being able to deactivate chunks when too far from the player

-Rerendering chunks that have been created and deactivated

I put plenty of comments beside variables and methods, and the part I am stuck on is at the bottom. Thanks :slight_smile:

using UnityEngine;
using System;
using System.Collections;

public class TerrainGeneration : MonoBehaviour {

	public int width, height; // The original width and height of the map. I have it on 512, 256.
	[Range (0, 100)]
	public int fillPercent; // How much block there should be, and how much cave.
	private bool [,] map; // Whether or not there is a cube in each spot.
	private enum CubeTypes // The different blocks so far.
	{
		Dirt,
		Grass,
		Cobblestone,
		DirtMoss,
		CobblestoneMoss
	};
	private CubeTypes [,] mapData;
	private enum CubeRounding // Types of cubes that determine what sides of the cubes to round.
	{
		BottomLeft,
		BottomMiddle,
		BottomRight,
		MiddleLeft,
		Middle,
		MiddleRight,
		TopLeft,
		TopMiddle,
		TopRight,
		Bottom,
		Left,
		Right,
		Top,
		LeftRight,
		TopBottom,
		Single
	};
	private CubeRounding [,] roundingData; // What the rounding visual of each block is.
	public string seed;
	public bool useRandomSeed; // Does the map rely on a random seed or an input?
	public int smoothness; // How many times the SmoothMap method runs.
	public int edgeThickness; // When this is a higher value, caves stay closer to the middle of the map.
	public GameObject player;
	public int chunkSize; // The diameter of a chunk, x and y is the same.
	public int leftmostChunk, rightmostChunk; // Basically I want these to be used to know how far it has rendered to the left, right, top, bottom etc.
	public int bottommostChunk, topmostChunk; // For example, the chunk in the bottom left would be called "Chunk 1-1" or something like that.
	public int hillStrength; // How many times BuildHills is called.
	public int hillHeight; // The max hill height.
	public int hillSample; // this decided how high a hill can potentially be every time BuildHills is called.
	private int naturalHeight; // This equals height + hillHeight.
	private int totalHeight; // This equals height + hillHeight + buildingHeight.
	public int buildingHeight; // This will give a little bit of extra room in the map array to build above ground level.
	public int mossLevel; // When moss is found when going underground.
	public int cobblestoneLevel; // Same as mossLevel but with cobblestone.
	public GameObject chunkContainer;
	public int renderDistance; // Intended to effect the chunk rendering. I want a box around the player of rendered chunks with a radius of renderDistance.
	
	// Ignore the fact that I have variants of each block, I can implement the instantiation of that after I am helped.
	public GameObject [] dirt; // Dirt and all of it's rounded variants.
	public GameObject [] grass;
	public GameObject [] cobblestone;
	public GameObject moss; // Moss object that spawns occasionally on dirt/cobblestone in deep areas and lights up.
	public int cobblestoneIncrease; // How much more cobblestone there is when descending.
	public int cobblestoneIncreaseDistance; // How much space there is in beween levels where cobblestone increases.
	
	public GameObject [] chunks;
	
	// This constructor checks blocks around the block being plugged in at x, y.
	int GetNeighbors (int x, int y) { 
		int neighbors = 0;
		for (int x2 = x - 1; x2 < x + 2; x2 ++)
		{	
			for (int y2 = y - 1; y2 < y + 2; y2 ++)
			{
				if (x2 != x || y2 != y)
				{
					if (x2 > -1 && x2 < width && y2 > -1 && y2 < naturalHeight)
					{
						if (map [x2, y2] == true)
						{
							neighbors ++;
						}
					}
				}
			}
		}
		return neighbors;
	}
	
	// This constructor checks above the block until going up h times.
	bool CheckAboveBlock (int x, int y, int h) { 
		if (naturalHeight - y > h)
		{
			for (int y2 = y + 1; y2 < y + h + 1; y2 ++)	
			{
				if (map [x, y2] == true)
				{
					return true;
				}
			}
		}
		else
		{
			for (int y2 = y + 1; y2 < naturalHeight; y2 ++)	
			{
				if (map [x, y2] == true)
				{
					return true;
				}
			}
		}
		return false;
	}
	
	// This makes a visual effect of the block depending on it's neighbors. e.g. if there is a block missing an upper neighbor, it will be rounded on the top a little.
	CubeRounding RoundBlock (int x, int y) {
		if (y > 0)
		{
			switch (map [x, y - 1])
			{
			case true:
				if (x > 0)
				{
					switch (map [x - 1, y])
					{
					case true:
						if (x < width - 1)
						{
							switch (map [x + 1, y])
							{
							case true:
								if (y < naturalHeight - 1)
								{
									switch (map [x, y + 1])
									{
									case true:
										return CubeRounding.Middle;
										
									case false:
										return CubeRounding.TopMiddle;
									}
									break;
								}
								break;
								
							case false:
								if (y < naturalHeight - 1)
								{
									switch (map [x, y + 1])
									{
									case true:
										return CubeRounding.MiddleRight;
										
									case false:
										return CubeRounding.TopRight;
									}
									break;
								}
								break;
							}
							break;
						}
						break;
						
					case false:
						if (x < width - 1)
						{
							switch (map [x + 1, y])
							{
							case true:
								if (y < naturalHeight - 1)
								{
									switch (map [x, y + 1])
									{
									case true:
										return CubeRounding.MiddleLeft;
										
									case false:
										return CubeRounding.TopLeft;
									}
									break;
								}
								break;
								
							case false:
								if (y < naturalHeight - 1)
								{
									switch (map [x, y + 1])
									{
									case true:
										return CubeRounding.LeftRight;
										
									case false:
										return CubeRounding.Top;
									}
									break;
								}
								break;
							}
							break;
						}
						break;
					}
					break;
				}
				break;
				
			case false:
				if (x > 0)
				{
					switch (map [x - 1, y])
					{
					case true:
						if (x < width - 1)
						{
							switch (map [x + 1, y])
							{
							case true:
								if (y < naturalHeight - 1)
								{
									switch (map [x, y + 1])
									{
									case true:
										return CubeRounding.BottomMiddle;
										
									case false:
										return CubeRounding.TopBottom;
									}
									break;
								}
								break;
								
							case false:
								if (y < naturalHeight - 1)
								{
									switch (map [x, y + 1])
									{
									case true:
										return CubeRounding.BottomRight;
										
									case false:
										return CubeRounding.Right;
									}
									break;		
								}
								break;
							}
							break;
						}
						break;
						
					case false:
						if (x < width - 1)
						{
							switch (map [x + 1, y])
							{
							case true:
								if (y < naturalHeight - 1)
								{
									switch (map [x, y + 1])
									{
									case true:
										return CubeRounding.BottomLeft;
										
									case false:
										return CubeRounding.Left;
									}
									break;
								}
								break;
								
							case false:
								if (y < naturalHeight - 1)
								{
									switch (map [x, y + 1])
									{
									case true:
										return CubeRounding.Bottom;
										
									case false:
										return CubeRounding.Single;
									}
									break;
								}
								break;
							}
							break;
						}
						break;
					}
				break;
				}
				break;
			}
		}
		return CubeRounding.Middle;
	}
	
	void Start () {
		CreateGrid ();
		// Renders the first chunks. An explanation is in LateUpdate, as the code is reused.
		for (int i = 0; i < renderDistance; i ++)
		{
			if (player.transform.position.x < leftmostChunk * chunkSize / 4 + renderDistance * chunkSize / 4)
			{
				if (leftmostChunk - 1 > 0)
				{
					if (player.transform.position.y < bottommostChunk * chunkSize / 4 + renderDistance * chunkSize / 4)
					{
						if (bottommostChunk - 1 > 0)
						{
							Render (-1, -1);
						}
					}
					else if (player.transform.position.y > topmostChunk * chunkSize / 4 - renderDistance * chunkSize / 4)
					{
						if (topmostChunk + 1 <= naturalHeight / chunkSize)
						{
							Render (-1, 1);
						}
					}
					else
					{
						Render (-1, 0);
					}
				}
			}
			else if (player.transform.position.x > rightmostChunk * chunkSize / 4 - renderDistance * chunkSize / 4)
			{
				if (rightmostChunk + 1 > width / chunkSize)
				{	
					if (player.transform.position.y < bottommostChunk * chunkSize / 4 + renderDistance * chunkSize / 4)
					{
						if (bottommostChunk - 1 > 0)
						{
							Render (1, -1);
						}
					}
					else if (player.transform.position.y > topmostChunk * chunkSize / 4 - renderDistance * chunkSize / 4)
					{
						if (topmostChunk + 1 <= naturalHeight / chunkSize)
						{
							Render (1, 1);
						}
					}
					else
					{
						Render (1, 0);
					}
				}
			}
			else
			{
				if (player.transform.position.y < bottommostChunk * chunkSize / 4 + renderDistance * chunkSize / 4)
				{
					if (bottommostChunk - 1 > 0)
					{
						Render (0, -1);
					}
				}
				else if (player.transform.position.y > topmostChunk * chunkSize / 4 - renderDistance * chunkSize / 4)
				{
					if (topmostChunk + 1 <= naturalHeight / chunkSize)
					{
						Render (0, 1);
					}
				}
			}
		}
	}
	
	void LateUpdate () {
		// Checks if the player is near an area that has not been rendered, and if so, renders in that direction. This part is connected to a broken part at the moment.
		if (player.transform.position.x < leftmostChunk * chunkSize / 4 + renderDistance * chunkSize / 4)
		{
			if (leftmostChunk - 1 > 0)
			{
				if (player.transform.position.y < bottommostChunk * chunkSize / 4 + renderDistance * chunkSize / 4)
				{
					if (bottommostChunk - 1 > 0)
					{
						Render (-1, -1, true);
					}
				}
				else if (player.transform.position.y > topmostChunk * chunkSize / 4 - renderDistance * chunkSize / 4)
				{
					if (topmostChunk + 1 <= naturalHeight / chunkSize)
					{
						Render (-1, 1, true);
					}
				}
				else
				{
					Render (-1, 0, true);
				}
			}
		}
		else if (player.transform.position.x > rightmostChunk * chunkSize / 4 - renderDistance * chunkSize / 4)
		{
			if (rightmostChunk + 1 > width / chunkSize)
			{	
				if (player.transform.position.y < bottommostChunk * chunkSize / 4 + renderDistance * chunkSize / 4)
				{
					if (bottommostChunk - 1 > 0)
					{
						Render (1, -1, true);
					}
				}
				else if (player.transform.position.y > topmostChunk * chunkSize / 4 - renderDistance * chunkSize / 4)
				{
					if (topmostChunk + 1 <= naturalHeight / chunkSize)
					{
						Render (1, 1, true);
					}
				}
				else
				{
					Render (1, 0, true);
				}
			}
		}
		else
		{
			if (player.transform.position.y < bottommostChunk * chunkSize / 4 + renderDistance * chunkSize / 4)
			{
				if (bottommostChunk - 1 > 0)
				{
					Render (0, -1, true);
				}
			}
			else if (player.transform.position.y > topmostChunk * chunkSize / 4 - renderDistance * chunkSize / 4)
			{
				if (topmostChunk + 1 <= naturalHeight / chunkSize)
				{
					Render (0, 1, true);
				}
			}
		}
	}
	
	void CreateGrid () { // Creates all data arrays that are necessary and moves on to the next method.
		naturalHeight = height + hillHeight;
		totalHeight = naturalHeight + buildingHeight;
		map = new bool [width, totalHeight];
		mapData = new CubeTypes [width, totalHeight];
		roundingData = new CubeRounding [width, totalHeight];
		chunks = new GameObject [width / chunkSize, totalHeight / chunkSize];
		Randomize ();
	}
	
	// Based on a seed, this places blocks and empty spaces based on chance of fill percentage and edge thickness.
	void Randomize () { 
		if (useRandomSeed == true)
		{
			seed = Time.time.ToString ();
		}
		System.Random prng = new System.Random (seed.GetHashCode());
		for (int x = 0; x < width; x ++)
		{
			for (int y = 0; y < height; y ++)
			{
				if (x < edgeThickness || x > width - edgeThickness - 1 || y < edgeThickness)
				{
					map [x, y] = true;
				}
				else if (prng.Next (0, 100) < fillPercent)
				{
					map [x, y] = true;
				}
			}
		}	
		// Hills are pulled out of the ground s amount of times
		for (int s = 0; s < hillStrength; s ++) 
		{	
			int additiveHeight = prng.Next (hillSample, hillSample + 2);
			int newHeight = height + additiveHeight;
			BuildHills (newHeight);
		}
		// Smoothing occurs s2 amount of times to give more public flexibility
		for (int s2 = 0; s2 < smoothness; s2 ++) 
		{
			SmoothMap ();
		}
		DetermineCubes ();
		Render (-1, 0, true);
		Render (1, 0, true);
	}
	
	// If a block has mroe than 4 neighbors, it becomes a definite cube. If it has exactly 4, it is left alone. Less than 4, it is destroyed.
	void SmoothMap () {
		for (int x = 0; x < width; x ++)
		{
			for (int y = 0; y < naturalHeight; y ++)
			{	
				int neighbors = GetNeighbors (x, y);
				if (neighbors > 4)
				{
					map [x, y] = true;
				}
				else if (neighbors < 4)
				{
					map [x, y] = false;
				}
			}
		}
	}
	
	// This places some blocks above the ground that are later smoothed out. This is just an early implementation that will be changed later
	void BuildHills (int n) {
		System.Random prng = new System.Random (seed.GetHashCode ());
		for (int x = 0; x < width; x ++)
		{
			if (map [x, height - 1] == true)
			{
				if (prng.Next (1, 4) == 1)
				{
					for (int y = height - 1; y < naturalHeight; y ++)
					{
						if (y < n)
						{
							map [x, y] = true;
						}
					}
				}
			}
		}
		for (int x = 0; x < width; x ++)
		{
			for (int y = height - 1; y < naturalHeight; y ++)
			{
				int neighbors = GetNeighbors (x, y);
				if (neighbors == 0)
				{
					map [x, y] = false;
				}
			}
		}
	}
	
	// Based on the environment and circumstances of each cube, each space is put into an array of enums that says what each block is
	void DetermineCubes () {
		System.Random prng = new System.Random (seed.GetHashCode ());
		for (int x = 0; x < width; x ++)
		{
			for (int y = 0; y < naturalHeight; y ++)
			{
				int neighbors = GetNeighbors (x, y);
				if (neighbors >= 5 || y == 0)
				{
					int section = 0;
					for (int s = 0; s < (naturalHeight) / cobblestoneIncreaseDistance; s ++)
					{
						if (s < y / cobblestoneIncreaseDistance && s >= y / cobblestoneIncreaseDistance - cobblestoneIncreaseDistance)
						{
							section = s;
							break;
						}
					}
					if (y < height && prng.Next (0, 100) > cobblestoneIncrease * section)
					{
						mapData [x, y] = CubeTypes.Cobblestone;
					}
				}
				else if (y < cobblestoneLevel && prng.Next (1, 4) == 1)
				{
					mapData [x, y] = CubeTypes.Cobblestone;
				}
				if (mapData [x, y] == CubeTypes.Dirt)
				{
					roundingData [x, y] = RoundBlock (x, y);
				}
				bool checkAbove = CheckAboveBlock (x, y, 5);
				if (checkAbove == false)
				{
					if (y < mossLevel)
					{
						if (mapData [x, y] == CubeTypes.Dirt)
						{
							if (roundingData [x, y] == CubeRounding.TopLeft || roundingData [x, y] == CubeRounding.TopMiddle || roundingData [x, y] == CubeRounding.TopRight)
							{
								if (prng.Next (1, 32) == 1)
								{
									mapData [x, y] = CubeTypes.DirtMoss;
								}
							}
						}
						else if (mapData [x, y] == CubeTypes.Cobblestone)
						{
							if (prng.Next (1, 32) == 1)
							{
								mapData [x, y] = CubeTypes.CobblestoneMoss;
							}
						}
					}
					else
					{
						if (roundingData [x, y] == CubeRounding.TopLeft || roundingData [x, y] == CubeRounding.TopMiddle || roundingData [x, y] == CubeRounding.TopRight)
						{
							mapData [x, y] = CubeTypes.Grass;
						}
					}
				}
			}
		}
	}
	
	void Render (int directionX, int directionY) { // This is where cubes would actually be rendered.
		// I attempted this before, but the way I did it the chunks would never go away.
		// This is where I am stuck.
		if (directionX == -1)
		{
			if (directionY == -1)
			{
			}
			else if (directionY == 1)
			{
			}
			else
			{
			}
		}
		else if (directionX == 1)
		{
			if (directionY == -1)
			{
			}
			else if (directionY == 1)
			{
			}
			else
			{
			}
		}
	}
}

Ok I feel I should offer you some kind of an answer.

I recommend you forget about chunking your terrain for the following reasons…

Chunking terrain should be done to increase load efficiency, you are loading the terrain entirely into memory anyway.

Chunking terrain should be done to enable you to make changes within a chunk without having to recalculate the entire terrain. You aren’t making any optimisations within a chunk, you just intend to store the tiles in a chunk so that each tile within a chunk is rendered individually (this is exactly the same as not using chunks at all).

What I suggest you do is store the block data in a 2d array. Then you will want to determine how the viewport relates to your world, you will get that it starts at x1 and goes to x2 and starts at y1 and goes to y2. Then just iterate over that area of the 2d array using something like:

for (int i = x1; i < x2; i++) {
    for (int j = y1; j < y2; j++) {
        // render the blocks in this range
    }
}