교수님이 내주신 과제를 졸업작품때문에 미루고 미루다 어제 새벽에 완성하였다. 유니티의 버그(?)를 잠시 느껴본 시간이었는데 지금 생각하니 너무 억울하다..흑흑

일단 설명을 해 두겠다.

과제는 Unity의 MeshFilter와 Mesh클레스를 이용해 마인크래프트의 기본 캐릭터인 Steve 캐릭터를 렌더링 하는 것이었다.

기본적으로 렌더링을 위해 Vertex 좌표, Vertex와 1:1 매칭하는 UV 좌표 그리고 법선 벡터를 위해 Winding Order를 정의할 필요가 있었다.

MineCraftCharacter.cs

Gist 코드상에서 [SeriallizeField]를 사용해서 배열을 인스펙터로 빼서 보며 문제를 해결했었다. 원래는 그냥 선언되어있었음.

일단 최종적으로 Steve를 만들기 위해 Vertex, UV, Triangle(Winding Order)에 대한 배열을 만든다. 그리고 UV를 찍기 편하게 만들어주는 것들을 선언했다.

enum UV_Type { HEAD, BODY, LEGARM };
    static float p4 = 0.0625f;
    static float p8 = 0.125f;
    static float p12 = 0.1875f;
    static float p16 = 0.25f;
[SerializeField]
    private Vector3[] vertices = new Vector3[4 * 6 * 6];
    [SerializeField]
    private Vector2[] uv = new Vector2[4 * 6 * 6];
    [SerializeField]
    private int[] triangles = new int[6 * 6 * 6];

그리고 큐브 모형 단위로 관리하기 위해 큐브 클래스를 하나 만든다.

class Cube
    {
        public Vector3[] vertices;
        public Vector2[] uv;
        public int[] triangles;

...

큐브 클래스에서 큐브를 만들기에 필요한 여러 정보를 입력받아 큐브를 만들어주는 함수를 구현한다.

public Cube CreateCube(Vector3 center, float width, float height, Vector2 origin, UV_Type type, int count)
        {
            vertices = new Vector3[]{
            //RIGHT                                                                       
            new Vector3(center.x + width / 2, center.y - height / 2, center.z - width / 2),   //3   //0
            new Vector3(center.x + width / 2, center.y + height / 2, center.z - width / 2),   //2   //1
            new Vector3(center.x + width / 2, center.y + height / 2, center.z + width / 2),   //6   //2
            new Vector3(center.x + width / 2, center.y - height / 2, center.z + width / 2),   //7   //3
            //FRONT
            new Vector3(center.x - width / 2, center.y - height / 2, center.z - width / 2),   //0   //4
            new Vector3(center.x - width / 2, center.y + height / 2, center.z - width / 2),   //1   //5
            new Vector3(center.x + width / 2, center.y + height / 2, center.z - width / 2),   //2   //6
            new Vector3(center.x + width / 2, center.y - height / 2, center.z - width / 2),   //3   //7
            //TOP                                                                         
            new Vector3(center.x - width / 2, center.y + height / 2, center.z - width / 2),   //1   //8
            new Vector3(center.x - width / 2, center.y + height / 2, center.z + width / 2),   //5   //9
            new Vector3(center.x + width / 2, center.y + height / 2, center.z + width / 2),   //6   //10
            new Vector3(center.x + width / 2, center.y + height / 2, center.z - width / 2),   //2   //11
            //LEFT                                                                        
            new Vector3(center.x - width / 2, center.y - height / 2, center.z + width / 2),   //4   //12
            new Vector3(center.x - width / 2, center.y + height / 2, center.z + width / 2),   //5   //13
            new Vector3(center.x - width / 2, center.y + height / 2, center.z - width / 2),   //1   //14
            new Vector3(center.x - width / 2, center.y - height / 2, center.z - width / 2),   //0   //15
            //BOTTOM                                                                     
            new Vector3(center.x - width / 2, center.y - height / 2, center.z - width / 2),   //0   //16
            new Vector3(center.x + width / 2, center.y - height / 2, center.z - width / 2),   //3   //17
            new Vector3(center.x + width / 2, center.y - height / 2, center.z + width / 2),   //7   //18
            new Vector3(center.x - width / 2, center.y - height / 2, center.z + width / 2),   //4   //19
            //BACK                                                                        
            new Vector3(center.x + width / 2, center.y - height / 2, center.z + width / 2),   //7   //20
            new Vector3(center.x + width / 2, center.y + height / 2, center.z + width / 2),   //6   //21
            new Vector3(center.x - width / 2, center.y + height / 2, center.z + width / 2),   //5   //22
            new Vector3(center.x - width / 2, center.y - height / 2, center.z + width / 2),   //4   //23
            };
            switch (type)
            {
                case UV_Type.HEAD:
                    uv = new Vector2[]
                    {
                        //RIGHT
                        new Vector2(origin.x,origin.y),           
                        new Vector2(origin.x,origin.y+p8),        
                        new Vector2(origin.x+p8,origin.y+p8),     
                        new Vector2(origin.x+p8,origin.y),        
                        //FRONT
                        new Vector2(origin.x+p8,origin.y),        
                        new Vector2(origin.x+p8,origin.y+p8),     
                        new Vector2(origin.x+p16,origin.y+p8),    
                        new Vector2(origin.x+p16,origin.y),       
                        //TOP
                        new Vector2(origin.x+p8,origin.y+p8),     
                        new Vector2(origin.x+p8,origin.y+p16),    
                        new Vector2(origin.x+p16,origin.y+p16),   
                        new Vector2(origin.x+p16,origin.y+p8),    
                        //LEFT
                        new Vector2(origin.x+p16,origin.y),       
                        new Vector2(origin.x+p16,origin.y+p8),    
                        new Vector2(origin.x+p16+p8,origin.y+p8), 
                        new Vector2(origin.x+p16+p8,origin.y),
                        //BOTTOM
                        new Vector2(origin.x+p16,origin.y+p8),
                        new Vector2(origin.x+p16,origin.y+p16),
                        new Vector2(origin.x+p16+p8,origin.y+p16),
                        new Vector2(origin.x+p16+p8,origin.y+p8),
                        //BACK
                        new Vector2(origin.x+p16+p8,origin.y),
                        new Vector2(origin.x+p16+p8,origin.y+p8),
                        new Vector2(origin.x+p16+p16,origin.y+p8),
                        new Vector2(origin.x+p16+p16,origin.y)
                    };
                    break;
                case UV_Type.BODY:
                    uv = new Vector2[]
                    {
                        //RIGHT
                        new Vector2(origin.x,origin.y),
                        new Vector2(origin.x,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y),        
                        //FRONT
                        new Vector2(origin.x+p4,origin.y),
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y),       
                        //TOP
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y+p16),
                        new Vector2(origin.x+p12,origin.y+p16),
                        new Vector2(origin.x+p12,origin.y+p12),    
                        //LEFT
                        new Vector2(origin.x+p16+p4,origin.y),
                        new Vector2(origin.x+p16+p4,origin.y+p12),
                        new Vector2(origin.x+p16+p8,origin.y+p12),
                        new Vector2(origin.x+p16+p8,origin.y),
                        //BOTTOM
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y+p16),
                        new Vector2(origin.x+p16+p4,origin.y+p16),
                        new Vector2(origin.x+p16+p4,origin.y+p12),
                        //BACK
                        new Vector2(origin.x+p12,origin.y),
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p16+p4,origin.y+p12),
                        new Vector2(origin.x+p16+p4,origin.y)
                    };
                    break;
                case UV_Type.LEGARM:
                    uv = new Vector2[]
                    {
                        //RIGHT
                        new Vector2(origin.x,origin.y),
                        new Vector2(origin.x,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y),        
                        //FRONT
                        new Vector2(origin.x+p4,origin.y),
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p8,origin.y+p12),
                        new Vector2(origin.x+p8,origin.y),       
                        //TOP
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y+p16),
                        new Vector2(origin.x+p8,origin.y+p16),
                        new Vector2(origin.x+p8,origin.y+p12),    
                        //LEFT
                        new Vector2(origin.x+p8,origin.y),
                        new Vector2(origin.x+p8,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y),
                        //BOTTOM
                        new Vector2(origin.x+p8,origin.y+p12),
                        new Vector2(origin.x+p8,origin.y+p16),
                        new Vector2(origin.x+p12,origin.y+p16),
                        new Vector2(origin.x+p12,origin.y+p12),
                        //BACK
                        new Vector2(origin.x+p12,origin.y),
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p16,origin.y+p12),
                        new Vector2(origin.x+p16,origin.y)
                    };
                    break;
            }
            triangles = new int[]
            {
                //RIGHT
                count*24+0,count*24+1,count*24+2,
                count*24+2,count*24+3,count*24+0,
                //FRONT
                count*24+4,count*24+5,count*24+6,
                count*24+6,count*24+7,count*24+4,
                //TOP
                count*24+8,count*24+9,count*24+10,
                count*24+10,count*24+11,count*24+8,
                //LEFT
                count*24+12,count*24+13,count*24+14,
                count*24+14,count*24+15,count*24+12,
                //BOTTOM
                count*24+16,count*24+17,count*24+18,
                count*24+18,count*24+19,count*24+16,
                //BACK
                count*24+20,count*24+21,count*24+22,
                count*24+22,count*24+23,count*24+20
            };
            return this;
        }

위에서부터 보면 CreateCube 함수는 Cube를 반환하며 인자로 중심 위치를 정하는 Vector3 center, 크기를 결정하는 float형 width와 height, 가져오고자 하는 부위에서 UV 좌표의 좌 하단 부분을 넣어주는 Vector2 origin, 부위 타입에 따라 UV좌표 저장을 분기시켜줄 enum UV_Type형 type, 배열에 맞춰 카운트를 올려줄 int형 count

여기서 enum UV_Type은 이렇게 생겼다.

enum UV_Type { HEAD, BODY, LEGARM };

UV타입을 나눈 이유는 마인크래프트 캐릭터의 UV는 같은 모양을 가진 부분이 많았기 때문이다.


이게 교수님이 올려주신 마인크래프트 캐릭터의 64×64 크기의 스킨 UV맵 가이드라인인데 같은 모양끼리 나눠보자면 머리, 몸, 다리 정도가 남게되어 이렇게 정하였다.

세가지로 나눈 이유는 굳이 모양이 같은 LEG와 ARM을 분리할 필요가 없어보였기 때문이다.

사실 내 방법보다 더 멋지고 깔끔하게 하는 방법들이 많은데 (예시 :빛장형 ) 나는 멍청하기 때문이 무식하게 진행하였다.

먼저 큐브에 center, width, height를 이용해 큐브의 Vertex 위치를 잡는다.

vertices = new Vector3[]{
            //RIGHT                                                                       
            new Vector3(center.x + width / 2, center.y - height / 2, center.z - width / 2),   //3   //0
            new Vector3(center.x + width / 2, center.y + height / 2, center.z - width / 2),   //2   //1
            new Vector3(center.x + width / 2, center.y + height / 2, center.z + width / 2),   //6   //2
            new Vector3(center.x + width / 2, center.y - height / 2, center.z + width / 2),   //7   //3
            //FRONT
            new Vector3(center.x - width / 2, center.y - height / 2, center.z - width / 2),   //0   //4
            new Vector3(center.x - width / 2, center.y + height / 2, center.z - width / 2),   //1   //5
            new Vector3(center.x + width / 2, center.y + height / 2, center.z - width / 2),   //2   //6
            new Vector3(center.x + width / 2, center.y - height / 2, center.z - width / 2),   //3   //7
            //TOP                                                                         
            new Vector3(center.x - width / 2, center.y + height / 2, center.z - width / 2),   //1   //8
            new Vector3(center.x - width / 2, center.y + height / 2, center.z + width / 2),   //5   //9
            new Vector3(center.x + width / 2, center.y + height / 2, center.z + width / 2),   //6   //10
            new Vector3(center.x + width / 2, center.y + height / 2, center.z - width / 2),   //2   //11
            //LEFT                                                                        
            new Vector3(center.x - width / 2, center.y - height / 2, center.z + width / 2),   //4   //12
            new Vector3(center.x - width / 2, center.y + height / 2, center.z + width / 2),   //5   //13
            new Vector3(center.x - width / 2, center.y + height / 2, center.z - width / 2),   //1   //14
            new Vector3(center.x - width / 2, center.y - height / 2, center.z - width / 2),   //0   //15
            //BOTTOM                                                                     
            new Vector3(center.x - width / 2, center.y - height / 2, center.z - width / 2),   //0   //16
            new Vector3(center.x + width / 2, center.y - height / 2, center.z - width / 2),   //3   //17
            new Vector3(center.x + width / 2, center.y - height / 2, center.z + width / 2),   //7   //18
            new Vector3(center.x - width / 2, center.y - height / 2, center.z + width / 2),   //4   //19
            //BACK                                                                        
            new Vector3(center.x + width / 2, center.y - height / 2, center.z + width / 2),   //7   //20
            new Vector3(center.x + width / 2, center.y + height / 2, center.z + width / 2),   //6   //21
            new Vector3(center.x - width / 2, center.y + height / 2, center.z + width / 2),   //5   //22
            new Vector3(center.x - width / 2, center.y - height / 2, center.z + width / 2),   //4   //23
            };

그리고 만들고자 하는 부위에 맞춰 UV 좌표를 잡는 부분을 만든다.

switch (type)
            {
                case UV_Type.HEAD:
                    uv = new Vector2[]
                    {
                        //RIGHT
                        new Vector2(origin.x,origin.y),           
                        new Vector2(origin.x,origin.y+p8),        
                        new Vector2(origin.x+p8,origin.y+p8),     
                        new Vector2(origin.x+p8,origin.y),        
                        //FRONT
                        new Vector2(origin.x+p8,origin.y),        
                        new Vector2(origin.x+p8,origin.y+p8),     
                        new Vector2(origin.x+p16,origin.y+p8),    
                        new Vector2(origin.x+p16,origin.y),       
                        //TOP
                        new Vector2(origin.x+p8,origin.y+p8),     
                        new Vector2(origin.x+p8,origin.y+p16),    
                        new Vector2(origin.x+p16,origin.y+p16),   
                        new Vector2(origin.x+p16,origin.y+p8),    
                        //LEFT
                        new Vector2(origin.x+p16,origin.y),       
                        new Vector2(origin.x+p16,origin.y+p8),    
                        new Vector2(origin.x+p16+p8,origin.y+p8), 
                        new Vector2(origin.x+p16+p8,origin.y),
                        //BOTTOM
                        new Vector2(origin.x+p16,origin.y+p8),
                        new Vector2(origin.x+p16,origin.y+p16),
                        new Vector2(origin.x+p16+p8,origin.y+p16),
                        new Vector2(origin.x+p16+p8,origin.y+p8),
                        //BACK
                        new Vector2(origin.x+p16+p8,origin.y),
                        new Vector2(origin.x+p16+p8,origin.y+p8),
                        new Vector2(origin.x+p16+p16,origin.y+p8),
                        new Vector2(origin.x+p16+p16,origin.y)
                    };
                    break;
                case UV_Type.BODY:
                    uv = new Vector2[]
                    {
                        //RIGHT
                        new Vector2(origin.x,origin.y),
                        new Vector2(origin.x,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y),        
                        //FRONT
                        new Vector2(origin.x+p4,origin.y),
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y),       
                        //TOP
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y+p16),
                        new Vector2(origin.x+p12,origin.y+p16),
                        new Vector2(origin.x+p12,origin.y+p12),    
                        //LEFT
                        new Vector2(origin.x+p16+p4,origin.y),
                        new Vector2(origin.x+p16+p4,origin.y+p12),
                        new Vector2(origin.x+p16+p8,origin.y+p12),
                        new Vector2(origin.x+p16+p8,origin.y),
                        //BOTTOM
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y+p16),
                        new Vector2(origin.x+p16+p4,origin.y+p16),
                        new Vector2(origin.x+p16+p4,origin.y+p12),
                        //BACK
                        new Vector2(origin.x+p12,origin.y),
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p16+p4,origin.y+p12),
                        new Vector2(origin.x+p16+p4,origin.y)
                    };
                    break;
                case UV_Type.LEGARM:
                    uv = new Vector2[]
                    {
                        //RIGHT
                        new Vector2(origin.x,origin.y),
                        new Vector2(origin.x,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y),        
                        //FRONT
                        new Vector2(origin.x+p4,origin.y),
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p8,origin.y+p12),
                        new Vector2(origin.x+p8,origin.y),       
                        //TOP
                        new Vector2(origin.x+p4,origin.y+p12),
                        new Vector2(origin.x+p4,origin.y+p16),
                        new Vector2(origin.x+p8,origin.y+p16),
                        new Vector2(origin.x+p8,origin.y+p12),    
                        //LEFT
                        new Vector2(origin.x+p8,origin.y),
                        new Vector2(origin.x+p8,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p12,origin.y),
                        //BOTTOM
                        new Vector2(origin.x+p8,origin.y+p12),
                        new Vector2(origin.x+p8,origin.y+p16),
                        new Vector2(origin.x+p12,origin.y+p16),
                        new Vector2(origin.x+p12,origin.y+p12),
                        //BACK
                        new Vector2(origin.x+p12,origin.y),
                        new Vector2(origin.x+p12,origin.y+p12),
                        new Vector2(origin.x+p16,origin.y+p12),
                        new Vector2(origin.x+p16,origin.y)
                    };
                    break;
}

Winding Order 설정을 진행한 뒤 만들어진 정보를 가진 큐브를 리턴한다.

triangles = new int[]
            {
                //RIGHT
                count*24+0,count*24+1,count*24+2,
                count*24+2,count*24+3,count*24+0,
                //FRONT
                count*24+4,count*24+5,count*24+6,
                count*24+6,count*24+7,count*24+4,
                //TOP
                count*24+8,count*24+9,count*24+10,
                count*24+10,count*24+11,count*24+8,
                //LEFT
                count*24+12,count*24+13,count*24+14,
                count*24+14,count*24+15,count*24+12,
                //BOTTOM
                count*24+16,count*24+17,count*24+18,
                count*24+18,count*24+19,count*24+16,
                //BACK
                count*24+20,count*24+21,count*24+22,
                count*24+22,count*24+23,count*24+20
            };
            return this;
        }

그냥 이게 전부다 정말 간단하다. 이제 마지막으로 렌더링을 위해 for문을 돌려가며 큐브에 대한 정보들을 넣어 MineCraftCharacter에 선언된 vertices, uv, triangles에 값을 계속 추가해 완성시킨 뒤 Mesh에 정보를 넣고 완성시킨다.

void Awake()
    {
        MeshFilter mf = GetComponent<MeshFilter>();
        Mesh m = new Mesh();

        for (int i = 0; i < 6; i++)
        {
            Cube cube = new Cube();
            switch (i)
            {
                case 0: //HEAD
                    cube = cube.CreateCube(Vector3.zero, 10.0f, 10.0f, new Vector2(0, 1 - p16), UV_Type.HEAD, i);
                    break;
                case 1: //BODY
                    cube = cube.CreateCube(new Vector3(0, -12.5f), 10.0f, 15.0f, new Vector2(p16, 1 - p16 * 2), UV_Type.BODY, i);
                    break;
                case 2: //LEFT ARM
                    cube = cube.CreateCube(new Vector3(-7.0f, -12.5f), 4.0f, 15.0f, new Vector2(p16 * 2, 0), UV_Type.LEGARM, i);
                    break;
                case 3: //RIGHT ARM
                    cube = cube.CreateCube(new Vector3(7.0f, -12.5f), 4.0f, 15.0f, new Vector2(1 - p16 - p8, p16 * 2), UV_Type.LEGARM, i);
                    break;
                case 4: //LEFT LEG
                    cube = cube.CreateCube(new Vector3(-2.5f, -25.5f), 6.0f, 22.5f, new Vector2(p16, 0), UV_Type.LEGARM, i);
                    break;
                case 5: //RIGHT LEG
                    cube = cube.CreateCube(new Vector3(2.5f, -25.5f), 6.0f, 22.5f, new Vector2(0, p16 * 2), UV_Type.LEGARM, i);
                    break;
                default:
                    break;
            }
            for (int x = 0; x < 4 * 6; x++)
            {
                vertices[i * 4 * 6 + x] = cube.vertices[x];
                uv[i * 4 * 6 + x] = cube.uv[x];
            }
            for (int x = 0; x < 6 * 6; x++)
            {
                triangles[i * 6 * 6 + x] = cube.triangles[x];
            }
        }
        m.vertices = this.vertices;
        m.uv = this.uv;
        m.triangles = this.triangles;
        mf.mesh = m;
    }

그런데 문제가 있었다. 코드상에는 정말 아무 문제가 없었다고 생각하는데 디버그를 켜보니 배열의 값이 이상했다.

각각 144, 144, 216이어야 할 값들이 48,48,72로 고정되는 것이다. 이전에 다른 값을 할당한적이 한 번도 없고 그냥 바로 실행했는데 계속 이런 상태였다.

도움이 필요해 필성이가 시리얼라이즈 필드나 public을 통해 값을 봐보라고 했는데 열어보니 값이 멀쩡히 들어가있어도 48,48,72로 저장되어있었다. 응급처치로 144, 144, 216으로 수정하긴 했는데 뭔가 영 찜찜하기에 좀 더 찾아보려 했으나 별다른 성과가 없었다. 혹시 내가 인스펙터를 설정하지 않아서 그런걸까 했지만서도 그렇게 에러가 날거였다면 물어보기전부터 public이나 private으로 설정하지 않았을 상태에서도 괜찮았어야 했을텐데 도저히 이유를 모르겠다.

아무튼 과제 끝~