- Home /
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 :)
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
{
}
}
}
}
"Rerendering chunks" where are you calculating these chunks? (I don't see that you are doing any chunking like say what $$anonymous$$inecraft does).
I would advise you against it unless you truly understand the benefits, no offense intended but I don't think you do. Just draw each block individually, forget chunking.
If you really do want to create a chunking system then create a very simple example (maybe just 1 block type with no rounded corners) get chunking working for that and then make it more complex.
I had a chunk generated map, but i deleted the code because it wouldnt work in the future. It would render 16 across and all the way down when i go too far to the left or right.
It was working complex but i now want the ability to 'un-render".
I had a system where the following happened:
mapData [,] was written to.
mapData [,] was read.
A chunk container prefab named, for example, for the lower left most chunk, chunk 1-1, was spawned.
mapData [,] was used to spawn different blocks.
As each block spawned, it was assigned the chunk as it's parent.
Ok but from looking at what you're saying I don't think that you are using chunks correctly. I$$anonymous$$O chunking is useful for the following (no specific order and I'm sure I'm missing points, I'm no chunking expert):
Allowing you to load only part of your terrain into memory at any one time (you don't do this, your terrain is quite small anyway 512, 256)
Allowing you to optimise the rendering by creating optimised macro objects for rendering ins$$anonymous$$d of rendering the micro data (you have not started to do this yet at all, all you have is the micro data in the form of your mapData, assigning this data to game objects that you call chunks is not chunking. In your case though I don't see this as a problem, it's 2D and not an infinite map; if it was a 3D game I would certainly be telling you to render chunked data but in your case I don't think the complexity is worth the effort, hence why I recommend you avoid chunking the map)
Allowing you to make changes to your terrain without having to recalculate the whole terrain data (as far as I can tell all you are doing, or intending to do, is creating chunks that contain a subset of the mapData data, this is not what $$anonymous$$inecraft does for instance, $$anonymous$$inecraft takes the data and creates an optimised mesh for rendering that chunk, when you make a change in that chunk the whole chunk mesh is recalculated and switched with the old one, $$anonymous$$inecraft does not draw each block in the chunk individually, which is essentially what you are saying you want to do which is not I$$anonymous$$O using chunks properly)
What I am saying is... if you can convince me that you know what you are trying to achieve with chunking the data then I might be inclined to help you If you created a new question which was smaller more concise and aimed at an actual problem you are having with chunking rather than just saying: I want to achieve some complex functionality and I want someone to show me how its done.
Answer by Neilos · Jul 13, 2015 at 06:03 PM
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
}
}
Sorry, just got home from a college course. I was taking a class on Autodesk $$anonymous$$aya for youth.
Since I waited for your answer, I have come a long way, and now am just experiencing slight issues with $$anonymous$$athf.RoundToInt. Don't worry though, I'll accept the answer.
If you want to check out the question, here it is.
http://answers.unity3d.com/questions/1005703/render-area-not-working-correctly.html
Your answer
Follow this Question
Related Questions
2D Efficient Realtime Terrain Generation 0 Answers
Animator trigger stuck 4 Answers
Freezing on destruction of terrain. 1 Answer
2D Shader that bends vertices? 3 Answers