2021. 5. 31. 11:27ㆍ[Unity] Game Programing/유니티를 활용한 마인크래프트 청크 시스템 구현하기
저번시간에는 마인크래프트 청크를 구현하기 위한 필수 방법에 대해서 알아 봤습니다.
이번에는 직접적으로 청크를 생성해봅시다.
1. Chunk 스크립트 생성하기
우선 3D 공간상에서 Chunk을 표현하기 위해서 3차원 배열을 사용해야 하는데요.
우리가 기존에 만든 Block 스크립트를 3차원 배열로 선언해 준후 월드 크기에 맞게 초기화를 해줘야 합니다.
하지만 아직 World를 관리하는 스크립트가 없기 때문에 임시적으로 8*8*8 크기의 Chunk로 초기화 합니다.
그리고, 3중 for문을 이용하여 블럭의 정보를 대입해 줍니다.
또한 Block 스크립트를 직접적으로 사용하지 않을거기 때문에 Chunk 스크립트를 이용해서 Material을 받는 식으로 변경해야 합니다.
- Chunk 스크립트 안에 있는 BuildChunk() 함수 -
public Material cube_material;
public Block[,,] chunk_data
public GameObject chunk;
// 청크의 값을 생성하는 함수.
void BuildChunk()
{
chunk_data = new Block[8, 8, 8];
// 3중 for문을 이용하여 3차원 좌표를 표현
for(int z = 0; z < 8; z++)
{
for(int x = 0; x < 8; x++)
{
for(int y = 0; y < 8; y++)
{
Vector3 pos = new Vector3(x, y, z);
chunk_data[x, y, z] = new Block(Block.BlockType.DIRT, pos, this.gameObject, cube_material);
}
}
}
}
그리고, Chunk들의 정보를 이용하여 Block을 그릴 수 있게 DrawChunk()라는 함수를 추가해 줍니다.
- Chunk 스크립트 안에 있는 DrawChunk() 함수 -
// 청크의 값을 기반으로 오브젝트를 그리는 함수
IEnumerator DrawChunk()
{
// 3중 for문을 이용하여 3차원 좌표를 표현
for(int z = 0; z < 8; z++)
{
for(int x = 0; x < 8; x++)
{
for(int y = 0; y < 8; y++)
{
// 각 블럭을 그리기
chunk_data[x, y, z].Draw();
yield return new WaitForSeconds(0.1f);
}
}
}
}
여기서 코루틴(Coroutine)을 사용한 이유는 단순히 블럭이 생성되는 과정을 보기 위해서 입니다.
마지막으로 Start구문에 지금까지의 함수들을 순서에 맞게 입력해 주세요.
- Chunk 스크립트 안에 있는 Start() 함수 -
public void Start()
{
BuildChunk();
StartCoroutine(DrawChunk());
}
저장후 실행시켜 주시면 다음과 같은 결과물이 나옵니다.
- Chunk 스크립트 -
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Chunk : MonoBehaviour
{
public Material cube_material;
public Block[,,] chunk_data;
public GameObject chunk;
private void Start()
{
BuildChunk();
StartCoroutine(DrawChunk());
}
// 청크의 값을 생성하는 함수.
void BuildChunk()
{
chunk_data = new Block[8, 8, 8];
// 3중 for문을 이용하여 3차원 좌표를 표현
for(int z = 0; z < 8; z++)
{
for(int x = 0; x < 8; x++)
{
for(int y = 0; y < 8; y++)
{
Vector3 pos = new Vector3(x, y, z);
chunk_data[x, y, z] = new Block(Block.BlockType.DIRT, pos, this.gameObject, cube_material);
}
}
}
}
// 청크의 값을 기반으로 오브젝트를 그리는 함수
IEnumerator DrawChunk()
{
// 3중 for문을 이용하여 3차원 좌표를 표현
for(int z = 0; z < 8; z++)
{
for(int x = 0; x < 8; x++)
{
for(int y = 0; y < 8; y++)
{
// 각 블럭을 그리기
chunk_data[x, y, z].Draw();
yield return new WaitForSeconds(0.1f);
}
}
}
}
}
하지만 아직 보이지 않는 곳에 Quad를 그린다거나 오브젝트가 많다는 등등 해결해야 할 문제가 많습니다.
3. CombineMeshes 사용하기
우선 CombineMeshes를 사용하여 하나의 오브젝트로 만들어 볼까요?
CombineMeshes를 사용하는 방법은 그리 어렵지 않습니다.
단순히 Unity공식 레퍼런스 에도 나와있기도 하고, 다른 사람들이 올린 코드도 많기 때문입니다.
물론 이해를 하려면 시간이 걸릴 지도 모르겠지만 단순히 사용하기 위해서라면 간단합니다.
우선 Chunk 스크립트에 CombineQuads() 함수를 추가해 주세요.
- Chunk 스크립트 안에 있는 CombineQuads() 함수 -
// 자식 오브젝트의 메쉬를 하나의 오브젝트로 합치는 함수
public void CombineQuads(GameObject chunk, Material cube_material)
{
//1. 모든 자식 메쉬 결합
MeshFilter[] meshFilters = chunk.GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length)
{
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
i++;
}
MeshFilter meshFilter = chunk.gameObject.GetComponent<MeshFilter>();
if (meshFilter == null)
{
//2. 부모 오브젝트에 새로운 Mesh만들기
MeshFilter mf = chunk.gameObject.AddComponent<MeshFilter>();
mf.mesh = new Mesh();
//3. 자식 오브젝트의 결합된 Mesh를 부모 오브젝트의 Mesh로 추가
mf.mesh.CombineMeshes(combine);
//4. 부모 오브젝트의 MeshRendere 만들기
MeshRenderer renderer = chunk.gameObject.AddComponent<MeshRenderer>();
renderer.material = cube_material;
}
else
{
//3. 자식 오브젝트의 결합된 Mesh를 부모 오브젝트의 Mesh로 추가
meshFilter.mesh.Clear();
meshFilter.mesh = new Mesh();
meshFilter.mesh.CombineMeshes(combine);
}
//5. 결합되지 않은 모든 하위 항목 삭제
foreach (Transform quad in chunk.transform)
{
GameObject.Destroy(quad.gameObject);
}
return;
}
그리고 DrawChunk() 함수를 다음과 같이 수정해 줍니다.
- Chunk 스크립트 안에 있는 DrawChunk() 함수 -
// 청크의 값을 기반으로 오브젝트를 그리는 함수
public void DrawChunk()
{
// 3중 for문을 이용하여 3차원 좌표를 표현
for (int z = 0; z < 8; z++)
{
for(int x = 0; x < 8; x++)
{
for(int y = 0; y < 8; y++)
{
// 각 블럭을 그리기
chunkData[x, y, z].Draw();
}
}
}
// 자식 오브젝트의 메쉬를 하나의 오브젝트로 합치기
CombineQuads(chunk, cube_material);
MeshCollider meshCollider = chunk.gameObject.GetComponent<MeshCollider>();
// MeshCollider가 없을 경우
if (meshCollider == null)
{
// MeshCollider컴포넌트 추가
meshCollider = chunk.AddComponent<MeshCollider>();
// meshCollider의 Mesh에 MeshFilter의 Mesh 대입
meshCollider.sharedMesh = chunk.GetComponent<MeshFilter>().mesh;
} // 아닐경우
else
{
// 변경한 Mesh의 맞게 MeshCollider 새로고침
meshCollider.sharedMesh = chunk.GetComponent<MeshFilter>().mesh;
}
}
이렇게 Combine을 활용하여 자식오브젝트들을 하나의 오브젝트로 합쳐 주었습니다.
Chunk오브젝트에 MeshCollider 컴포넌트가 있는지 확인하고 MeshCollider가 없으면 새로운 컴포넌트를 추가해주고, .shaderMesh에 MeshFilter 컴포넌트에 있는 mesh를 대입해 줍니다.
이제 CombineQuads() 함수에서 Material을 입혀주므로 Block 스크립트에서 필요없는 Material관련된 구문들은 전부 삭제해 주시고, CreateQuad() 함수에 있는 MeshRendere와 관련된 구문도 없애주세요.
Block 스크립트와 Chunk 스크립트를 다음과 같이 수정해 줍니다.
- Block 스크립트 -
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Block
{
public enum Cubeside { BOTTOM, TOP, LEFT, RIGHT, FRONT, BACK };
public enum BlockType
{
AIR, GRASS, DIRT
};
public BlockType bType;
public bool isSolid;
public Chunk owner;
public GameObject parent;
public Vector3 position;
// 블럭에 따른 UV들
Vector2[,] blockUVs =
{
/* GRASS TOP */ {new Vector2(0.2125f, 0.9875f) , new Vector2(0.2250f, 0.9875f) , new Vector2(0.2125f, 1f) , new Vector2(0.2250f, 1f)},
/* GRASS SIDE */ {new Vector2(0.2250f, 0.9875f) , new Vector2(0.2375f, 0.9875f) , new Vector2(0.2250f, 1f) , new Vector2(0.2375f, 1f)},
/* DIRT */ {new Vector2(0.2000f, 0.9875f) , new Vector2(0.2125f, 0.9875f) , new Vector2(0.2000f, 1f) , new Vector2(0.2125f, 1f)},
};
public Block(BlockType b, Vector3 pos, GameObject p, Chunk o)
{
bType = b;
owner = o;
parent = p;
position = pos;
if (bType == BlockType.AIR) isSolid = false;
else isSolid = true;
}
void CreateQuad(Cubeside side)
{
Mesh mesh = new Mesh();
mesh.name = "ScriptedMesh";
Vector2[] uvs = new Vector2[4];
Vector3[] normals = new Vector3[4];
Vector3[] vertices = new Vector3[4];
int[] triangles = new int[6];
Vector2 uv00, uv10, uv01, uv11;
Vector3 p0, p1, p2, p3, p4, p5, p6, p7;
p0 = new Vector3(-0.5f, -0.5f, 0.5f);
p1 = new Vector3(0.5f, -0.5f, 0.5f);
p2 = new Vector3(0.5f, -0.5f, -0.5f);
p3 = new Vector3(-0.5f, -0.5f, -0.5f);
p4 = new Vector3(-0.5f, 0.5f, 0.5f);
p5 = new Vector3(0.5f, 0.5f, 0.5f);
p6 = new Vector3(0.5f, 0.5f, -0.5f);
p7 = new Vector3(-0.5f, 0.5f, -0.5f);
// 블럭의 종류가 GRASS이고 Top을 그릴 경우
if (bType == BlockType.GRASS && side == Cubeside.TOP)
{
uv00 = blockUVs[0, 0];
uv10 = blockUVs[0, 1];
uv01 = blockUVs[0, 2];
uv11 = blockUVs[0, 3];
} // BOTTOM을 그릴 경우
else if (bType == BlockType.GRASS && side == Cubeside.BOTTOM)
{
uv00 = blockUVs[(int)(BlockType.DIRT), 0];
uv10 = blockUVs[(int)(BlockType.DIRT), 1];
uv01 = blockUVs[(int)(BlockType.DIRT), 2];
uv11 = blockUVs[(int)(BlockType.DIRT), 3];
}
else
{
uv00 = blockUVs[(int)(bType), 0];
uv10 = blockUVs[(int)(bType), 1];
uv01 = blockUVs[(int)(bType), 2];
uv11 = blockUVs[(int)(bType), 3];
}
switch (side)
{
case Cubeside.BOTTOM:
vertices = new Vector3[] { p0, p1, p2, p3 };
normals = new Vector3[] { Vector3.down, Vector3.down, Vector3.down, Vector3.down };
break;
case Cubeside.TOP:
vertices = new Vector3[] { p7, p6, p5, p4 };
normals = new Vector3[] { Vector3.up, Vector3.up, Vector3.up, Vector3.up };
break;
case Cubeside.LEFT:
vertices = new Vector3[] { p7, p4, p0, p3 };
normals = new Vector3[] { Vector3.left, Vector3.left, Vector3.left, Vector3.left };
break;
case Cubeside.RIGHT:
vertices = new Vector3[] { p5, p6, p2, p1 };
normals = new Vector3[] { Vector3.right, Vector3.right, Vector3.right, Vector3.right };
break;
case Cubeside.FRONT:
vertices = new Vector3[] { p4, p5, p1, p0 };
normals = new Vector3[] { Vector3.forward, Vector3.forward, Vector3.forward, Vector3.forward };
break;
case Cubeside.BACK:
vertices = new Vector3[] { p6, p7, p3, p2 };
normals = new Vector3[] { Vector3.back, Vector3.back, Vector3.back, Vector3.back };
break;
}
uvs = new Vector2[] { uv11, uv01, uv00, uv10 };
triangles = new int[] { 3, 1, 0, 3, 2, 1 };
mesh.vertices = vertices;
mesh.normals = normals;
mesh.uv = uvs;
mesh.triangles = triangles;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
GameObject quad = new GameObject("Quad");
quad.transform.position = position;
quad.transform.parent = parent.transform;
MeshFilter meshFilter = quad.AddComponent<MeshFilter>();
meshFilter.mesh = mesh;
}
}
- Chunk 스크립트 안에 있는 BuildChunk() 함수 -
void BuildChunk()
{
chunkData = new Block[8, 8, 8];
for(int z = 0; z < 8; z++)
{
for(int x = 0; x < 8; x++)
{
for(int y = 0; y < 8; y++)
{
Vector3 pos = new Vector3(x, y, z);
chunkData[x, y, z] = new Block(Block.BlockType.DIRT, pos, chunk, this);
}
}
}
}
4. Chunk를 효율 적으로 관리하기
하지만 아직 Chunk를 1개만 사용할 뿐입니다. 많은 양의 블럭을 다루려면, Chunk의 사이즈를 늘려서 사용할 수 도 있겠지만 그렇게 되면 아주 멀리 있는 필요하지 않는 블럭이 그려지는 불상사가 일어나게 됩니다.
그래서 차라리 Chunk의 개수를 늘리고 필요할때 보이게 설정할 수 있습니다.
그러기 위해 저는 Chunk를 관리하는 World라는 스크립트의 필요성을 느꼈습니다.
우선 World스크립트에서 사용할 수 있게 Chunk 스크립트에 ' : MonoBehaviour'를 없애주시고, 생성자를 생성, Start() 함수 삭제, DrawChunk() 함수에 코루틴을 없애주세요.
Chunk스크립트를 다음과 같이 만들어야 합니다.
- Chunk 스크립트 -
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Chunk
{
public Material cube_material;
public Block[,,] chunkData;
public GameObject chunk;
public Chunk(Vector3 position)
{
chunk = new GameObject(World.BuildChunkName(position));
chunk.transform.position = position;
cube_material = World.ChunksObject.GetComponent<World>().cube_material;
BuildChunk();
}
// 청크의 값을 생성하는 함수.
void BuildChunk()
{
// ChunkSize의 사이즈로 초기화
chunkData = new Block[World.chunkSize, World.chunkSize, World.chunkSize];
// 3중 for문을 이용하여 3차원 좌표를 표현
for(int z = 0; z < World.chunkSize; z++)
{
for(int x = 0; x < World.chunkSize; x++)
{
for(int y = 0; y < World.chunkSize; y++)
{
Vector3 pos = new Vector3(x, y, z);
// 위치가 0,0,0인 청크의 바닥만 GRASS로 생성
if (pos.y == 0 && chunk.transform.position.y == 0 && chunk.transform.position == Vector3.zero)
{
chunkData[x, y, z] = new Block(Block.BlockType.GRASS, pos, chunk, this);
} // 그 외의 모든 것은 AIR로 생성
else chunkData[x, y, z] = new Block(Block.BlockType.AIR, pos, chunk, this);
}
}
}
}
// 청크의 값을 기반으로 오브젝트를 그리는 함수
public void DrawChunk()
{
// 3중 for문을 이용하여 3차원 좌표를 표현
for (int z = 0; z < World.chunkSize; z++)
{
for(int x = 0; x < World.chunkSize; x++)
{
for(int y = 0; y < World.chunkSize; y++)
{
// 각 블럭을 그리기
chunkData[x, y, z].Draw();
}
}
}
// 자식 오브젝트의 메쉬를 하나의 오브젝트로 합치기
CombineQuads(chunk, cube_material);
MeshCollider meshCollider = chunk.gameObject.GetComponent<MeshCollider>();
// MeshCollider가 없을 경우
if (meshCollider == null)
{
// MeshCollider컴포넌트 추가
meshCollider = chunk.AddComponent<MeshCollider>();
// meshCollider의 Mesh에 MeshFilter의 Mesh 대입
meshCollider.sharedMesh = chunk.GetComponent<MeshFilter>().mesh;
} // 아닐경우
else
{
// 변경한 Mesh의 맞게 MeshCollider 새로고침
meshCollider.sharedMesh = chunk.GetComponent<MeshFilter>().mesh;
}
}
// 자식 오브젝트의 메쉬를 하나의 오브젝트로 합치는 함수
public void CombineQuads(GameObject chunk, Material cube_material)
{
//1. 모든 자식 메쉬 결합
MeshFilter[] meshFilters = chunk.GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length)
{
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
i++;
}
MeshFilter meshFilter = chunk.gameObject.GetComponent<MeshFilter>();
if (meshFilter == null)
{
//2. 부모 오브젝트에 새로운 Mesh만들기
MeshFilter mf = chunk.gameObject.AddComponent<MeshFilter>();
mf.mesh = new Mesh();
//3. 자식 오브젝트의 결합된 Mesh를 부모 오브젝트의 Mesh로 추가
mf.mesh.CombineMeshes(combine);
//4. 부모 오브젝트의 MeshRendere 만들기
MeshRenderer renderer = chunk.gameObject.AddComponent<MeshRenderer>();
renderer.material = cube_material;
}
else
{
//3. 자식 오브젝트의 결합된 Mesh를 부모 오브젝트의 Mesh로 추가
meshFilter.mesh.Clear();
meshFilter.mesh = new Mesh();
meshFilter.mesh.CombineMeshes(combine);
}
//5. 결합되지 않은 모든 하위 항목 삭제
foreach (Transform quad in chunk.transform)
{
GameObject.Destroy(quad.gameObject);
}
return;
}
}
이후 World스크립트를 만들어서 청크의 생성과, 그리기 역할을 수행할 수 있도록 해줘야 합니다.
- World 스크립트 -
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class World : MonoBehaviour
{
public static int chunkSize = 8;
public static GameObject ChunksObject;
public Material cube_material;
public static Dictionary<string, Chunk> chunks;
// 위치값을 기반으로 청크 이름 생성
public static string BuildChunkName(Vector3 v)
{
return (int)v.x + "_" + (int)v.y + "_" + (int)v.z;
}
// 청크들을 생성하는 함수
IEnumerator BuildWorld()
{
// 청크 생성
CreateChunk(0, 0, 0);
// Dictionary안에 있는 모든 Chunk 그리기.
foreach (KeyValuePair<string, Chunk> c in chunks)
{
c.Value.DrawChunk();
}
yield return null;
}
void Start()
{
ChunksObject = this.gameObject;
chunks = new Dictionary<string, Chunk>();
this.transform.position = Vector3.zero;
this.transform.rotation = Quaternion.identity;
StartCoroutine(BuildWorld());
}
// 청크를 생성하는 함수
public static void CreateChunk(int x, int y, int z)
{
// 새로운 청크를 생성
Chunk c = new Chunk(new Vector3(x, y, z));
// 생성한 청크를 자식 오브젝트로 설정
c.chunk.transform.SetParent(ChunksObject.transform);
// Dictionary에 값 추가
chunks.Add(c.chunk.name, c);
}
}
여기서 Chunk 스크립트의 public 형식은 유니티에서 직접적인 접근이 불가능 합니다( : MonoBehaviour 가 없기 때문에) 그래서 때문에 World 스크립트에 Material을 추가해 주고 World에 있는 Material을 Chunk에서 사용할 수 있게 만들었습니다. (다른 방법으로 싱글톤을 만들어 그 안에 있는 Material을 이용하거나, Resources를 사용할 수 있겠네요.)
5. 보이지 않는 Quad 삭제
임시로 만든 커스텀 쉐이더로 내부가 잘 보이게 만들었는데, 보시면 안쪽까지 Quad가 그려져 있습니다.
다음과 같은 문제를 해결하기 위해서는 Block 스크립트에서 Quad를 그릴때 주변 블럭들을 참조해서 그릴지 말지를 결정해야 합니다.
- Block 스크립트 안에 있는 ConvertBlockIndexToLocal() 함수 -
// ChunkSize에 맞게 Local좌표를 계산하는 함수
int ConvertBlockIndexToLocal(int i)
{
if (i == -1) i = World.chunkSize - 1;
else if (i == World.chunkSize) i = 0;
return i;
}
- Block 스크립트 안에 있는 HasSolidNeighbour() 함수 -
// 해당 블럭이 고체인지 판단하는 함수
public bool HasSolidNeighbour(int x, int y, int z)
{
Block[,,] chunks;
// ChunkSize를 벗어날 경우
if (x < 0 || x >= World.chunkSize || y < 0 || y >= World.chunkSize || z < 0 || z >= World.chunkSize)
{
// 해당 블럭에 맞는 다른 청크의 위치를 구함
Vector3 neightbourChunkPos = this.parent.transform.position + new Vector3((x - (int)position.x) * World.chunkSize,
(y - (int)position.y) * World.chunkSize, (z - (int)position.z) * World.chunkSize);
// 새로 구한 청크의 위치값을 이용해 청크의 이름을 구함
string nName = World.BuildChunkName(neightbourChunkPos);
// 새로운 청크에 맞는 로컬좌표 값 계산
x = ConvertBlockIndexToLocal(x);
y = ConvertBlockIndexToLocal(y);
z = ConvertBlockIndexToLocal(z);
Chunk nChunk;
// 값 계산으로 구한 청크의 이름으로 Chunk변수를 구함.
if (World.chunks.TryGetValue(nName, out nChunk))
{
// 해당 청크의 블럭 값을 대입
chunks = nChunk.chunkData;
}
else return false;
} // 아닐경우 기존 청크의 블럭 값을 대입
else chunks = owner.chunkData;
try
{
// 위치에 맞는 블럭의 고체 여부 확인
return chunks[x, y, z].isSolid;
}
catch (System.IndexOutOfRangeException ex) { Debug.Log(ex); }
return false;
}
HasSolidNeighbour() 함수는 다음과 같은 역할을 합니다.
1. 우선적으로 그려야 하는 해당 쿼드의 Local좌표가 다른 Chunk에 있는지 없는지 확인해 줍니다.
2. 만약 Local좌표가 chunksize의 값보다 크거나 작다면(해당 청크의 Local좌표를 벗어난 경우) 그에 맞는 해당 블럭 좌표의 Chunk의 위치를 찾고 TryGetValue함수를 이용하여 Chunk의 블럭 정보를 가져오는데, 해당 청크가 없다면 false를 반환합니다.
3. try/catch 를 이용하여 그려야 하는 해당 블럭의 isSolid를 반환해 줍니다. (고체일 경우 true를 반환)
그리고 Draw() 함수를 다음과 같이 수정해 줍니다.
- Block 스크립트 안에 있는 Draw() 함수 -
public void Draw()
{
if (bType.Equals(BlockType.AIR)) return;
// 해당 면의 옆 블럭이 고체가 아닐경우 Quad생성
if (!HasSolidNeighbour((int)position.x, (int)position.y, (int)position.z + 1))
CreateQuad(Cubeside.FRONT);
if (!HasSolidNeighbour((int)position.x, (int)position.y, (int)position.z - 1))
CreateQuad(Cubeside.BACK);
if (!HasSolidNeighbour((int)position.x, (int)position.y + 1, (int)position.z))
CreateQuad(Cubeside.TOP);
if (!HasSolidNeighbour((int)position.x, (int)position.y - 1, (int)position.z))
CreateQuad(Cubeside.BOTTOM);
if (!HasSolidNeighbour((int)position.x - 1, (int)position.y, (int)position.z))
CreateQuad(Cubeside.LEFT);
if (!HasSolidNeighbour((int)position.x + 1, (int)position.y, (int)position.z))
CreateQuad(Cubeside.RIGHT);
}
여기서 각 방향의 쿼드마다 맞닿은 블럭이 다르기 때문에 위와 같이 작성해 주었습니다.
이로써 Wolrd 스크립트를 이용하여 Chunk를 효율적으로 관리하고, Chunk의 보이지 않는 곳의 Quad는 그려지지 않게 만들어 줬습니다.
'[Unity] Game Programing > 유니티를 활용한 마인크래프트 청크 시스템 구현하기' 카테고리의 다른 글
4. 블록 생성 및 제거 (0) | 2021.06.22 |
---|---|
2. UV및 텍스쳐 아틀라스 (0) | 2021.05.27 |
1. Quad를 이용한 Cube구축 (0) | 2021.05.26 |
Cube오브젝트로 마인크래프트를 만들 수 없는 이유. (0) | 2021.05.25 |