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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
 * 랜덤 맵 생성 & 방문자 스크립트
 * 1) 일정 비율로 벽을 생성하고
 * 2) 벽과 바닥의 경계를 뚜렷하게 다듬는다
 * 3) 방문자들을 배치하여 지도를 작성한다
 * 4) 지도를 바탕으로 큰 방 하나만 남기고 모두 제거한다
 */
public class MapGenerator : MonoBehaviour {
    int [,]map;                            
    public List<Coord> bigRoom;            //맵 프로세스 결과 완성 맵
    public int width;                    //맵 가로
    public int height;                    //맵 세로
    [Range(0100)]
    public int randomFillPercent;        //랜덤 맵 채우기 비율(슬라이드)
    //맵 생성 함수
    void GenerateMap() {
        map = new int[width, height];   //원하는 가로, 세로 만큼의 맵을 생성합니다.
        randomFillMap ();               //맵을 세포 자동자 방식으로 채웁니다.
        //맵의 바닥과 벽 경계를 뚜렷하기 위해 세포 자동자 방식의 맵 다듬기를 5회 수행합니다.
        for (int i = 0; i < 5; i++) {
            SmoothMap ();
        }
        //완성된 맵에 대한 지도를 작성하고, 해당 지도를 바탕으로 가장 큰 방을 남기고 모두 벽으로 만듭니다.
        ProcessMap ();
        //메쉬 렌더링 부분
        MeshGenerator meshGen = GetComponent<MeshGenerator> ();
        meshGen.GenerateMesh (map, 1);
    }
    //방문자들이 작성한 지도를 바탕으로 큰 방만 남기고 모두 제거한다
    void ProcessMap() {
        List<List<Coord>> wallRegions = GetRegions (0);              //각 방들의 정보를 가지고 있는 방문자들에 대한 정보를 받습니다.
        List<List<Coord>> processOneRoom = new List<List<Coord>> (); //큰 방을 제외한 방들을 관리하는 저장소
        bigRoom = new List<Coord> ();                                //가장 큰 방을 저장하는 저장소
        //각 방문자들이 가지고 있는 방들의 크기를 비교합니다.
        foreach (List<Coord> wallRegion in wallRegions) {
            //현재 큰 방보다 방이 작으면 processOnRoom에 추가합니다.
            if (wallRegion.Count < bigRoom.Count) {
                processOneRoom.Add (wallRegion);
            } else {
            //현재 크 방보다 크면, 해당 방을 bigRoom에 대입하고, 현재 큰 방은 processOnRoom에 추가합니다.
                processOneRoom.Add (bigRoom);
                bigRoom = wallRegion;
            }
        }
        //큰 방을 제외한 나머지 작은 방들은 필요가 없으므로, 각 방들을 벽으로 채웁니다.
        foreach(List<Coord> room in processOneRoom){
            foreach (Coord tile in room) {
                map [tile.tileX, tile.tileY] = 1;
            }
        }
    }
    //방문자 지도 작성 함수 (tileType = 0 (바닥) : 1 (벽) 감지)
    List<List<Coord>> GetRegions(int tileType) {
        List<List<Coord>> regions = new List<List<Coord>> ();   //맵의 모든 방의 정보를 가진 방문자들(1방 , 1방문자)
        int[,] mapFlags = new int[width, height];               //방문자용 지도 맵
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                //방문자가 이미 방문한 맵 타일이 아니고, 해당 타일이 감지용 타일 종류이면
                if (mapFlags [x, y] == 0 && map [x, y] == tileType) {
                    //해당 방을 모두 방문한 방문자를 regions에 추가한다.
                    List<Coord> newRegion = GetRegionTiles (x, y);
                    regions.Add (newRegion);
                    //해당 방에 대한 정보를 모두 방문자용 지도 맵에 작성한다.
                    foreach (Coord tile in newRegion) {
                        mapFlags [tile.tileX, tile.tileY] = 1;
                    }
                }
            }
        }
        return regions;
    }
    //방문자 탐색 함수
    //starX, starY 좌표를 시작으로 해당 방의 모든 장소를 방문합니다.
    List<Coord> GetRegionTiles(int startX, int startY) {
        List<Coord> tiles = new List<Coord> ();     //해당 방의 방문자가 다녀간 모든 정보
        int[,] mapFlags = new int[width, height];   //해당 방용 방문 지도
        int tileType = map [startX, startY];        //tileType과 같음(사용자가 탐지 원하는 타일 타입(벽, 바닥))
        Queue<Coord> queue = new Queue<Coord> ();   //방문자 행동을 위한 queue
        queue.Enqueue (new Coord (startX, startY));
        mapFlags [startX, startY] = 1;
        //방문자가 더이상 갈 곳이 없을 때까지 반복합니다.
        while (queue.Count > 0) {
            //queue에 저장된 좌표 정보가 있다 == 방문자가 한번도 가지 못한, 갈 수 있는 곳 이므로
            //tiles에 추가합니다.
            Coord tile = queue.Dequeue ();
            tiles.Add (tile);   
            //방문자 주변을 검색하여 방문자가 갈 수 있는 곳인지 확인합니다.
            for(int x = tile.tileX - 1; x <= tile.tileX + 1; x++) {
                for(int y = tile.tileY - 1; y <= tile.tileY + 1; y++) {
                    //맵 밖의 영역이 아니고, 대각선이 아닌 전후좌우 방향일 때 
                    if(IsInMapRange(x, y) && (y == tile.tileY || x == tile.tileX)) {
                        //해당 방용 방문지도에 다녀간 기록이 없고, 찾는 블록 타입일 때
                        if (mapFlags[x, y] == 0 && map[x, y] == tileType) {
                            //방문지도에 기록을 남기고, 이동한 칸을 당을 행동으로 queue에 넣습니다.
                            mapFlags [x, y] = 1;
                            queue.Enqueue (new Coord (x, y));
                        }
                    }
                }
            }
        }
        return tiles;
    }
    //맵 경계 여부 확인 함수
    bool IsInMapRange(int x, int y) {
        return x >= 0 && x < width && y >= 0 && y < height;
    }
    //맵 랜덤 벽 발생 함수
    void randomFillMap() {
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                //맵의 외각라인은 벽으로 둘러싸야 하므로 모두 벽(1) 처리 해줍니다.
                if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
                    map [x, y] = 1;
                } else {
                    //사용자가 입력한 벽 생성 확률을 바탕으로 바닥과 벽을 생성합니다.
                    map [x, y] = Random.Range (1100< randomFillPercent ? 1 : 0;
                }
            }
        }
    }
    //맵 다듬기 함수
    //대상 타일의 주변 정보 (벽, 바닥)의 가중치를 두어 벽과 바닥의 구분을 더 뚜렷하게 다듬습니다.
    void SmoothMap() {
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                //맵의 블럭의 각각 가중치를 구합니다.
                int neighbourWallTiles = GetSurroundingWallCount (x, y);
                if (neighbourWallTiles > 4) {       //가중치가 4초과면 해당 블럭을 벽으로
                    map [x, y] = 1;
                } else if(neighbourWallTiles < 4){  //가중치가 4미만이면 해당 블럭을 바닥으로
                    map [x, y] = 0;
                }
            }
        }
    }
    //벽 주변 상태 확인 함수
    //본인 타일을 제외한 주변 타일(3 X 3) 상태를 확인하여 가중치를 결정합니다.
    int GetSurroundingWallCount(int gridX, int gridY) {
        int wallCount = 0;
        for (int x = gridX - 1; x <= gridX + 1; x++) {
            for (int y = gridY - 1; y <= gridY + 1; y++) {
                if (IsInMapRange(x, y)) {               //맵의 범위 안에 있는 대상인지 확인
                    if (x != gridX || y != gridY) {     //본인 타일을 제외하는 조건문
                        wallCount += map [x, y];        //대상 타일이 벽이면 카운트를 1 증가시킨다.
                    }
                } else {
                    wallCount++;                        //맵의 범위 밖에 있는 대상이면 카운트를 1증가시킨다.
                                                        //(범위 밖 감지 == 맵의 경계 블럭) 이므로 벽이 필요
                }
            }
        }
        return wallCount;
    }
    //맵 x, y좌표 값 저장 구조체
    public struct Coord {
        public int tileX;
        public int tileY;
        public Coord(int x, int y) {
            tileX = x;
            tileY = y;
        }
    }
    // Use this for initialization
    void Awake () {
        GenerateMap ();
    }
}
cs

95%는 해당 동영상을 참조해서 만든 코드 입니다. (5%는 제가 쓰기 위해서 메소드 하나 추가했습니다ㅎ..)

동영상 없이 스크립트만 보고도 이해하기 쉽도록 주석을 달아놨습니다.

Posted by Haedo
,