내일배움캠프 Unity 59일차 TIL - 팀 9와 4분의 3 - 개발일지

Wooooo·2024년 1월 18일
0

내일배움캠프Unity

목록 보기
61/94

[오늘의 키워드]

  1. Chunk.cs 리팩토링
  2. 초간단 맵 에디터 설계

1. [Chunk.cs 리팩토링]

기존 코드

	if (_world.VoxelMap.TryGetValue(new(posX, y, posZ), out var value))
		AddVoxelDataToChunk(new(posX, y, posZ), value);
    private void AddVoxelDataToChunk(Vector3Int pos, BlockType block)
    {
        if (block is NormalBlockType normalBlock)
        {
            for (int i = 0; i < 6; i++)
            {
                if (_world.CheckVoxel(pos + _data.faceChecks[i]))
                    continue;

                for (int j = 0; j < 4; j++)
                    _vertices.Add(pos + _data.voxelVerts[_data.voxelTris[i][j]]);

                AddTextureUV(normalBlock.GetTextureID(i));
                _triangles.Add(_vertexIdx);
                _triangles.Add(_vertexIdx + 1);
                _triangles.Add(_vertexIdx + 2);
                _triangles.Add(_vertexIdx + 2);
                _triangles.Add(_vertexIdx + 1);
                _triangles.Add(_vertexIdx + 3);
                _vertexIdx += 4;
            }
        }
        else if (block is SlideBlockType slideBlock)
        {
            var obj = Managers.Resource.GetCache<GameObject>("Slide Block.prefab");
            obj = UnityEngine.Object.Instantiate(obj, pos, Quaternion.identity);
            var slide = obj.GetComponent<SlideBlock>();
            slide.Forward = slideBlock.Forward;
            slide.FrontMaterial = slideBlock.FrontMaterial;
            slide.SideMaterial = slideBlock.SideMaterial;
            slide.transform.SetParent(_chunkObject.transform);
        }
    }

미끄럼틀처럼 생긴 Slide 블럭을 추가하면서 BlockType을 NormalBlock과 SlideBlock 두 가지로 파생시키게 됐다.
때문에 블럭 데이터를 Chunk에 추가할 때, Map에서 불러온 BlockType이 어떤 타입인지 if문으로 걸러내서 각각 블럭에 맞는 로직으로 데이터를 추가하고 있었다.

이 구조는 전혀 객체지향적이지 않아서, 부모 클래스인 BlockType를 추상클래스로 바꾸고, AddVoxelDataToChunk를 Chunk가 아닌 BlockType에서 진행하도록 리팩토링해봤다.

리팩토링 후 코드

    if (_world.VoxelMap.TryGetValue(new(posX, y, posZ), out var value))
        value.type.AddVoxelDataToChunk(this, new(posX, y, posZ), value.forward);
[System.Serializable]
public abstract class BlockType
{
	...
    
    public abstract void AddVoxelDataToChunk(Chunk chunk, Vector3Int pos, Vector3 dir);
}

[System.Serializable]
public class NormalBlockType : BlockType
{
	...
    
    public override void AddVoxelDataToChunk(Chunk chunk, Vector3Int pos, Vector3 dir)
    {
        for (int i = 0; i < 6; i++)
        {
            if (chunk.World.CheckVoxel(pos + chunk.Data.faceChecks[i]))
                continue;

            for (int j = 0; j < 4; j++)
                chunk.Vertices.Add(pos + chunk.Data.voxelVerts[chunk.Data.voxelTris[i][j]]);

            chunk.AddTextureUV(GetTextureID(i));
            chunk.Triangles.Add(chunk.VertexIdx);
            chunk.Triangles.Add(chunk.VertexIdx + 1);
            chunk.Triangles.Add(chunk.VertexIdx + 2);
            chunk.Triangles.Add(chunk.VertexIdx + 2);
            chunk.Triangles.Add(chunk.VertexIdx + 1);
            chunk.Triangles.Add(chunk.VertexIdx + 3);
            chunk.VertexIdx += 4;
        }
    }
}

[System.Serializable]
public class SlideBlockType : BlockType
{
	...
    
    public override void AddVoxelDataToChunk(Chunk chunk, Vector3Int pos, Vector3 dir)
    {
        var obj = Managers.Resource.GetCache<GameObject>("Slide Block.prefab");
        obj = UnityEngine.Object.Instantiate(obj, pos, Quaternion.identity);
        var slide = obj.GetComponent<SlideBlock>();
        slide.Forward = dir;
        slide.FrontMaterial = FrontMaterial;
        slide.SideMaterial = SideMaterial;
        slide.transform.SetParent(chunk.InstanceBlocksParent);
        slide.name = $"{obj.name} ({pos})";
    }
}

Chunk의 AddVoxelDataToChunk 메서드는 아예 지웠다.
대신, BlockType의 추상메서드로 바꿔줬다.

이제, 새로운 블럭의 타입이 더 파생된다해도 Chunk.cs는 건드릴 필요가 없어졌다.
특히, 새로운 블럭 타입마다 else if를 더 늘리는 짓은 안해도 된다.


2. [초간단 맵 에디터 설계]

현재 진행중인 프로젝트는 복셀 맵을 사용중이다.
하지만 MagicaVoxel같은 완벽한 에디터라기보단, 유니티의 기본 3D 오브젝트 (프리미티브)를 이용해서 간단하게 맵 제작을 할 수 있도록 계획중이다.

  1. 유니티 에디터의 Edit 모드에서 씬에 프리미티브 Cube 배치 작업
  2. Play 모드로 실행 시 프리미티브 Cube들의 Transform 정보로 VoxelMap 데이터 생성
    • Position -> 중앙 위치
    • Scale -> 2로 나눠서 음의 방향, 양의 방향으로 for문을 돌며 Map을 채움.
    • Forward -> 블럭이 바라보는 방향 (미끄럼틀 블럭에 필요)
  3. 해당 프리미티브 Cube로 생성될 블럭 타입을 정해줄 컴포넌트 추가

맵 정보를 생성하게 되면, World 클래스와 WorldEditor(가명) 클래스의 기능도 분리해야한다.

  1. 현재 World 클래스에서 반복문으로 대충 맵의 형태를 띈 무언가를 생성 중인데, 이 기능을 빼고 저장된 VoxelMap 정보를 읽어와서 생성하는 방식으로 바꿔야한다.
  2. WorldEditor(가명) 클래스는 MapEdit 씬에 있는 프리미티브 Cube들의 정보를 가져와서 VoxelMap 데이터로 변환한다. 그 후 데이터를 JSON으로 저장한다.

Map Data Edit Source 컴포넌트 추가

using UnityEngine;

public class MapEditDataSource : MonoBehaviour
{
    public enum Inherits
    {
        Normal,
        Slide,
    }

    public enum Direction
    {
        Forward,
        Back,
        Up,
        Down,
        Left,
        Right,
    }

    [SerializeField] private WorldData data;
    [SerializeField] private Vector3Int position = Vector3Int.zero;
    [SerializeField] private Vector3Int size = Vector3Int.one;
    [SerializeField] private Direction forward = Direction.Forward;
    [SerializeField] private Inherits type;
    [SerializeField] private int index;
    public BlockType BlockType;

    private void OnValidate()
    {
        transform.position = position - new Vector3(0f, -0.5f, 0f);
        transform.localScale = size;
        switch (forward)
        {
            case Direction.Forward: transform.forward = Vector3Int.forward; break;
            case Direction.Back: transform.forward = Vector3Int.down; break;
            case Direction.Up: transform.forward = Vector3Int.up; break;
            case Direction.Down: transform.forward = Vector3Int.down; break;
            case Direction.Left: transform.forward = Vector3Int.left; break;
            case Direction.Right: transform.forward = Vector3Int.right; break;
        }
    }
}

Edit Mode에서 인스펙터 수치 딸깍으로 Transform 정보가 반영되게 OnValidate 메서드를 사용했다.
또, 복셀 맵 정보를 생성할 것이기 때문에 위치, 사이즈 정보는 Vector3Int를 사용해서 소수점은 사용하지 않도록 했다.

profile
game developer

0개의 댓글