1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
 * 타일 맵 정보를 바탕으로 매쉬 렌더링 제작 스크립트
 * 1) 맵 정보를 바탕으로 블럭 사이즈 만큼의 맵을 생성한다.
 * 2) 각 블럭의 4군데 버텍스의 정보(벽/바닥)를 바탕으로 가중치를 부여한다.
 * 3) 가중치를 바탕으로 삼각 폴리곤의 렌더링 정보를 생성한다.
 * 4) 인접한 두 삼각 폴리곤에 대한 해시 테이블 정보 또한 작성한다.
 * 5) 아웃라인에 해당하는 버텍스를 검출한다. (아웃라인 : 인접한 두 삼각 폴리곤의 두 정점을 공유하는 변)
 * 6) 한 아웃라인 버텍스를 기준으로 인접한 아웃라인 버텍스를 찾아 아웃라인 드로운 정보를 저장한다. (outlines)
 * 7) outlines정보를 바탕으로 엣지 컬라이더를 그린다.
 */
public class MeshGenerator : MonoBehaviour {
 
    public MeshFilter wall;
 
    public SquareGrid squareGrid;
    List<Vector3> vertices;
    List<int> triangles;
 
    Dictionary<intList<Triangle>> triangleDictionary = new Dictionary<intList<Triangle>> ();
    List<List<int>> outlines = new List<List<int>> ();
    //HashSet은 Unity5.0부터 사용가능하고, 값이 들어오면 중복을 알아서 거르고 저장해줍니다.
    HashSet<int> checkedvertices = new HashSet<int> ();
 
    //맵을 바탕으로 메쉬를 그립니다,
    public void GenerateMesh(int[,] map, float squareSize) {
        triangleDictionary.Clear ();
        outlines.Clear ();
        checkedvertices.Clear ();
 
        //map정보를 바탕으로 각 블럭의 위치 부여 및 메쉬의 렌더링 정보 저장
        squareGrid = new SquareGrid (map, squareSize);
 
        vertices = new List<Vector3> ();
        triangles = new List<int> ();
 
        //렌더링 정보를 바탕으로 삼각형 폴리곤의 각 버텍스 렌더링 정보 생성
        for (int x = 0; x < squareGrid.squares.GetLength (0); x++) {
            for (int y = 0; y < squareGrid.squares.GetLength (1); y++) {
                TriangulateSquare (squareGrid.squares [x, y]);
            }
        }
 
        //메쉬를 그립니다.
        Mesh mesh = new Mesh ();
        wall.mesh = mesh;
 
        mesh.vertices = vertices.ToArray ();
        mesh.triangles = triangles.ToArray ();
        mesh.RecalculateNormals ();
 
        //벽의 외곽에 엣지 컬라이더를 생성합니다.
        Generate2DColliders ();
    }
 
    //엣지 컬라이더 생성을 위한 맵의 벽면 외곽 검출 메소드
    void Generate2DColliders() {
 
        //엣지 컬라이더 정보를 가져와서 초기화를 해줍니다. 
        EdgeCollider2D[] currentColliders = gameObject.GetComponents<EdgeCollider2D> ();
        for (int i = 0; i < currentColliders.Length; i++) {
            Destroy(currentColliders[i]);
        }
 
        //삼각 폴리곤 메쉬 정보를 바탕으로 외곽 라인 버텍스 정보를 구합니다.
        CalcuateMeshOutlines ();
 
        //CalcuateMeshOutlines에서 구한 outlines를 바탕으로 엣지 컬라이더를 그립니다.
        foreach (List<int> outline in outlines) {
            EdgeCollider2D edgecollider = gameObject.AddComponent<EdgeCollider2D> ();
            Vector2[] edgePoints = new Vector2[outline.Count];
 
            for (int i = 0; i < outline.Count; i++) {
                edgePoints[i] = new Vector2 (vertices[outline[i]].x, vertices[outline[i]].y);
            }
            edgecollider.points = edgePoints;
            //edgecollider.edgeRadius = 0.1f;
        }
 
    }
 
    //squareGrid 생성 시 저장했던 각 블럭의 꼭지점의 가중치를 바탕으로 삼각형 폴리곤의 메쉬를 생성합니다.
    //(인접 블럭의 상태(바닥인지, 벽인지)를 바탕으로 깍아낸다고 생각하시면 됩니다)
    void TriangulateSquare(Square square) {
        switch (square.configuration) {
        case 0:
            break;
        case 1:
            MeshFromPoints (square.centerLeft, square.centerBottom, square.bottomLeft);
            break;
        case 2:
            MeshFromPoints (square.bottomRight, square.centerBottom, square.centerRight);
            break;
        case 4:
            MeshFromPoints (square.topRight, square.centerRight, square.centerTop);
            break;
        case 8:
            MeshFromPoints (square.topLeft, square.centerTop, square.centerLeft);
            break;
        case 3:
            MeshFromPoints (square.centerRight, square.bottomRight, square.bottomLeft, square.centerLeft);
            break;
        case 6:
            MeshFromPoints (square.centerTop, square.topRight, square.bottomRight, square.centerBottom);
            break;
        case 9:
            MeshFromPoints (square.topLeft, square.centerTop, square.centerBottom, square.bottomLeft);
            break;
        case 12:
            MeshFromPoints (square.topLeft, square.topRight, square.centerRight, square.centerLeft);
            break;
        case 5:
            MeshFromPoints (square.centerTop, square.topRight, square.centerRight, square.centerBottom, square.bottomLeft, square.centerLeft);
            break;
        case 10:
            MeshFromPoints (square.topLeft, square.centerTop, square.centerRight, square.bottomRight, square.centerBottom, square.centerLeft);
            break;        
        case 7:
            MeshFromPoints (square.centerTop, square.topRight, square.bottomRight, square.bottomLeft, square.centerLeft);
            break;
        case 11:
            MeshFromPoints (square.topLeft, square.centerTop, square.centerRight, square.bottomRight, square.bottomLeft);
            break;
        case 13:
            MeshFromPoints (square.topLeft, square.topRight, square.centerRight, square.centerBottom, square.bottomLeft);
            break;
        case 14:
            MeshFromPoints (square.topLeft, square.topRight, square.bottomRight, square.centerBottom, square.centerLeft);
            break;
        case 15:
            MeshFromPoints (square.topLeft, square.topRight, square.bottomRight, square.bottomLeft);
            //가중치가 15인 경우 완전히 내부를 이루는 블럭이기 때문에, 
            //중복 연산 방지를 위한 해시 테이블(checkedvertices)에 블럭의 가장자리 버텍스 값을 저장합니다.
            checkedvertices.Add (square.topLeft.vertexIndex);
            checkedvertices.Add (square.topRight.vertexIndex);
            checkedvertices.Add (square.bottomRight.vertexIndex);
            checkedvertices.Add (square.bottomLeft.vertexIndex);
            break;
        }
    }
 
    //메쉬를 생성할 삼각형(폴리곤)의 정보를 생성합니다. 
    void MeshFromPoints(params Node[] points) {
        AssignVertices (points);
 
        if (points.Length >= 3)
            CreateTriangle (points [0], points [1], points [2]);
        if (points.Length >= 4)
            CreateTriangle (points [0], points [2], points [3]);
        if (points.Length >= 5)
            CreateTriangle (points [0], points [3], points [4]);
        if (points.Length >= 6)
            CreateTriangle (points [0], points [4], points [5]);
    }
 
    //메쉬의 각 버텍스 위치 정보를 저장합니다.
    void AssignVertices(Node[] points) {
        for (int i = 0; i < points.Length; i++) {
            if (points [i].vertexIndex == -1) {
                points [i].vertexIndex = vertices.Count;
                vertices.Add (points [i].position);
            }
        }
    }
 
    //삼각형 폴리곤의 각 버텍스 값(인덱스)를 triangles에 저장하고, 아웃라인을 그리기 위한 해시 테이블(triangleDictionary)도 작성합니다.
    void CreateTriangle(Node a, Node b, Node c) {
        triangles.Add (a.vertexIndex);
        triangles.Add (b.vertexIndex);
        triangles.Add (c.vertexIndex);
 
        Triangle triangle = new Triangle (a.vertexIndex, b.vertexIndex, c.vertexIndex);
        AddTriangleToDictionary (triangle.vertexIndexA, triangle);
        AddTriangleToDictionary (triangle.vertexIndexB, triangle);
        AddTriangleToDictionary (triangle.vertexIndexC, triangle);
    }
 
    //아웃라인을 위한 해시 테이블(triangleDictionary)를 작성합니다.
    void AddTriangleToDictionary(int vertexIndexKey, Triangle triangle) {
        //키를 검색하여 정보가 있으면, 해당 키의 List값에 폴리곤 정보를 저장(중복된 버텍스를 공유하는 삼각형 이므로)
        if (triangleDictionary.ContainsKey (vertexIndexKey)) {
            triangleDictionary [vertexIndexKey].Add (triangle);
        //없으면 해시테이블의 정보를 새로 작성
        } else {
            List<Triangle> triangleList = new List<Triangle> ();
            triangleList.Add (triangle);
            triangleDictionary.Add (vertexIndexKey, triangleList);
        }
    }
 
    //매쉬의 벽면 외곽 라인을 계산합니다.
    void CalcuateMeshOutlines() {
        for (int vertexIndex = 0; vertexIndex < vertices.Count; vertexIndex++) {
            if (!checkedvertices.Contains (vertexIndex)) {
                int newOutlineVertex = GetConnectedOutlineVertex (vertexIndex); 
                //-1이 아니면 아웃라인에 해당하는 버텍스 키를 받았다는 뜻입니다.              
                if (newOutlineVertex != -1) {
                    //해당 검사에서 반환받은 아웃라인 버텍스 키가 존재하면, 검색을 요청한 인덱스 값도 아웃라인 이기 때문에
                    //인덱스 값 또한 checkedvertices에 저장합니다.
                    checkedvertices.Add (vertexIndex);
 
                    List<int> newOutline = new List<int> ();
                    newOutline.Add (vertexIndex);
                    outlines.Add (newOutline);
                    FollowOutline (newOutlineVertex, outlines.Count - 1);
 
                    //FollowOutline 재귀 메소드를 탈출하면 발견된 아웃라인 버텍스의 아웃라인 정보를 모두 outlines에 저장하고
                    //한바퀴 돌아 제자리(vertexIndex좌표)-1 상태로 오기 때문에, outlines 끝에 현재 vertexIndex를 입력해서 닫힌계를 만들어줍니다.     
                    outlines [outlines.Count - 1].Add (vertexIndex);
                }
            }
        }
    }
 
    //재귀 메소드 입니다. 다음 외각 버텍스가 존재하면 계속 호출되어 outlines에 추가해줍니다. 
    void FollowOutline(int vertexIndex, int outlineIndex) {
        outlines [outlineIndex].Add (vertexIndex);
        checkedvertices.Add (vertexIndex);
        int nextVertexIndex = GetConnectedOutlineVertex (vertexIndex);
 
        if(nextVertexIndex != -1) {
            FollowOutline (nextVertexIndex, outlineIndex);
        }
    }
 
    //버텍스 키(인덱스)를 받아 아웃라인 검출 여부를 확인합니다.
    //아웃라인에 해당하는 버텍스인 경우 해당 버텍스의 키 값을, 아니면 -1을 반환합니다.
    int GetConnectedOutlineVertex(int vertexIndex) {
        //아웃라인을 위한 해시 테이블(triangleDictionary)을 검색하여 해당 꼭지점을 공유하는 삼각 폴리곤 정보를 받습니다.
        List<Triangle> trianglesContainingVertex = triangleDictionary [vertexIndex];
 
        //받은 삼각 폴리곤을 하나씩 비교합니다..
        for (int i = 0; i < trianglesContainingVertex.Count; i++) {
            Triangle triangle = trianglesContainingVertex [i];
 
            for (int j = 0; j < 3; j++) {
                int vertexB = triangle [j];
                //아웃라인 대상은 [하나의 삼각형을 공유하는 두 정점]일 경우 입니다.
                //때문에 검사를 위한 버텍스 값과 해당 삼각형의 한 버텍스 값이 다르고(검사를 위한 버텍스 값은 해당 삼각형 모두 공유하고 있으므로), 
                //중복 연산 방지를 위한 해쉬테이블(checkedvertices)에 정보가 없는 경우, 아웃라인 인지 검사합니다.
                if (vertexB != vertexIndex && !checkedvertices.Contains(vertexB)) {
                    if(IsOutlineEdge(vertexIndex, vertexB)) {
                        return vertexB;
                    }
                }
            }
        }
 
        return -1;
    }
 
    //검사 대상의 버텍스 키를 공유하는 삼각형 정보를 triangleDictionary해쉬테이블을 검색하여 받습니다.
    //각 삼각형의 버텍스 키와 검사 대상의 버텍스 키가 하나라도 일치하면 아웃라인 버텍스에 해당되므로, true(1)을 반환합니다.
    bool IsOutlineEdge(int vertexA, int vertexB) {
        List<Triangle> trianglesContainingVertexA = triangleDictionary [vertexA];
        int sharedTriangleCount = 0;
 
        for (int i = 0; i < trianglesContainingVertexA.Count; i++) {
            if (trianglesContainingVertexA [i].Contains (vertexB)) {
                sharedTriangleCount++;
                if (sharedTriangleCount > 1) {
                    break;
                }
            }
        }
        return sharedTriangleCount == 1;
    }
 
    //삼각 폴리곤 정보를 저장하는 구조체, 삼각형의 각 버텍스 키 값 반환과, 키 중복 검사 메소드 포함
    struct Triangle {
        public int vertexIndexA;
        public int vertexIndexB;
        public int vertexIndexC;
        int [] vertices;
 
        public Triangle(int a, int b, int c) {
            vertexIndexA = a;
            vertexIndexB = b;
            vertexIndexC = c;
 
            vertices = new int[3];
            vertices[0= a;
            vertices[1= b;
            vertices[2= c;
        }
 
        public int this[int i] {
            get {
                return vertices [i];
            }
        }
 
        public bool Contains(int vertexIndex) {
            return vertexIndex == vertexIndexA || vertexIndex == vertexIndexB || vertexIndex == vertexIndexC;
        }
    }
 
    //인스턴스화 할때 맵의 각각 블럭의 닿는 면적을 계산합니다,
    public class SquareGrid {
        public Square[,] squares;
 
        public SquareGrid(int[,] map, float squareSize) {
            int nodeCountX = map.GetLength(0);
            int nodeCountY = map.GetLength(1);
            float mapWidth = nodeCountX * squareSize;
            float mapHeight = nodeCountY * squareSize;
 
            ControlNode[,] controlNodes = new ControlNode[nodeCountX, nodeCountY];
 
            //사각형 블럭 사이즈를 바탕으로, 각각의 블럭이 맵의 어디 위치에 있는지 저장합니다. 벽인지 바닥인지 여부도 저장합니다. (0바닥 , 1벽) 
            for(int x = 0; x < nodeCountX; x++) {
                for(int y = 0; y < nodeCountY; y++) {
                    Vector3 pos = new Vector3(-mapWidth / 2 + x * squareSize + squareSize / 2-mapHeight / 2 + y * squareSize + squareSize / 20);
                    controlNodes[x, y] = new ControlNode(pos, map[x, y] == 1, squareSize);
                }
            }
 
            //주변 4블럭이 벽인지 판단하여 가중치 부여 및, 블럭의 8개 포인트 위치 정보를 저장합니다. 
            squares = new Square[nodeCountX - 1, nodeCountY - 1];
            for(int x = 0; x < nodeCountX - 1; x++) {
                for(int y = 0; y < nodeCountY - 1; y++) {
                    squares[x, y] = new Square(controlNodes[x, y + 1], controlNodes[x + 1, y + 1], controlNodes[x + 1, y], controlNodes[x, y]);  
                }
            }
        }
    }
 
    //그려야할 사각형의 8군데 정보(각 꼭지점 4포인트 + 각 변 중앙 4포인트) 에 대한 정보를 가집니다.
    //각 변 중앙 4포인트는 위치 정보만 필요하기 때문에 Node형,
    //각 꼭지점 4포인트는 인접한 블럭의 여부 구분이 필요하므로, 각 변마다 다른 가중치를 두어 총 16가지의 렌더링 상태 종류를 구분합니다. 
    public class Square {
        public ControlNode topLeft, topRight, bottomRight, bottomLeft;
        public Node centerTop, centerRight, centerBottom, centerLeft;
        public int configuration;
 
        public Square(ControlNode _topLeft, ControlNode _topRight, ControlNode _bottomRight, ControlNode _bottomLeft) {
            topLeft = _topLeft;
            topRight = _topRight;
            bottomRight = _bottomRight;
            bottomLeft = _bottomLeft;
 
            centerTop = topLeft.right;
            centerRight = bottomRight.above;
            centerBottom = bottomLeft.right;
            centerLeft = bottomLeft.above;
        
            if(topLeft.active) configuration += 8;
            if(topRight.active) configuration += 4;
            if(bottomRight.active) configuration += 2;
            if(bottomLeft.active) configuration += 1;
        }
    }
 
    //자신의 위치를 저장하는 클래스
    public class Node {
        public Vector3 position;
        public int vertexIndex = -1;
 
        public Node(Vector3 _pos) {
            position = _pos;
        }
    }
 
    //(사각형 꼭지점)현재의 자신의 위치 및 벽인지 바닥인지 여부, 자신의 주변 외각 변 중심 점 위치 정보 또한 저장합니다.
    public class ControlNode : Node {
        public bool active;
        public Node above, right;
 
        public ControlNode(Vector3 _pos, bool _active, float squarSize) : base(_pos) {
            active = _active;
            above = new Node(position + Vector3.up * squarSize / 2f);
            right = new Node(position + Vector3.right * squarSize / 2f);
        }
    }
}
cs

100% 해당 동영상 코드를 사용했습니다.

빠르게 볼 수 있도록, 제가 이해한 코드를 주석으로 나름대로 세세하게 달아놓았습니다.

Posted by Haedo
,