Cellular Automata 细胞自动机生成2D洞穴

Cellular Automata

规则

  • 随机生成2D黑白图
  • 计算每个格子周围8个邻居黑白值之和
  • 平滑计算
    • 邻居个数>4,生成wall
    • 邻居个数=4,维持不变
    • 邻居个数<4,生成cave
    • 反复迭代

代码

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
public class CellularAutomataCave : MonoBehaviour
{
public enum NeighbourType
{
Live,
Dead,
Keep
}

public int width = 128;
public int height = 128;

[Range(0, 100)] public int fillPercent = 50;
[Range(0, 100)] public int seed = 0;
[Range(0, 10)] public int smoothIteration = 1;
public bool solidBoard = true;
public bool smooth = false;
[Label("邻居为4时")] public NeighbourType neighbourType = NeighbourType.Keep;

private int[,] m_map;

void OnEnable()
{
GenerateMap();
}

public void GenerateMap()
{
FillRandomMap();
if (smooth)
{
for (int i = 0; i < smoothIteration; i++)
{
SmoothMap();
}
}
}

void FillRandomMap()
{
m_map = new int[width, height];
var rnd = new System.Random(seed);
for (int x = 0; x < width; ++x)
{
for (int y = 0; y < height; ++y)
{
bool board = (x == 0 || x == width - 1 || y == 0 || y == height - 1);
if (solidBoard && board)
{
// 边缘实心墙
m_map[x, y] = 1;
}
else
{
m_map[x, y] = (rnd.Next(0, 100) < fillPercent) ? 1 : 0;
}
}
}
}

int GetNeighbourCount(int idx, int idy)
{
int count = 0;
for (int x = idx - 1; x <= idx + 1; x++)
{
for (int y = idy - 1; y <= idy + 1; y++)
{
if (x >= 0 && y >= 0 && x < width && y < height)
{
if (x != idx || y != idy)
{
count += m_map[x, y];
}
}
else
{
// 边缘处理
count++;
}
}
}

return count;
}

void SmoothMap()
{
// 不能直接赋值m_map,否则邻居计算不对
int[,] nextMap = new int[width, height];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int count = GetNeighbourCount(x, y);
if (count > 4)
nextMap[x, y] = 1;
else if (count == 4)
{
switch (neighbourType)
{
case NeighbourType.Live:
nextMap[x, y] = 1;
break;
case NeighbourType.Dead:
nextMap[x, y] = 0;
break;
case NeighbourType.Keep:
nextMap[x, y] = m_map[x, y];
break;
}
}
else
nextMap[x, y] = 0;
}
}

m_map = nextMap;
}

void OnDrawGizmos()
{
for (int x = 0; x < width; ++x)
{
for (int y = 0; y < height; ++y)
{
Gizmos.color = (m_map[x, y] == 0) ? Color.black : Color.white;
Vector3 pos = new Vector3(x + 0.5f - width / 2.0f, 0, y + 0.5f - height / 2.0f);
Gizmos.DrawCube(pos, Vector3.one);
}
}
}

#if UNITY_EDITOR

[CustomEditor(typeof(CellularAutomataCave))]
public class CellularAutomataCaveEditor : Editor
{
public override void OnInspectorGUI()
{
var cellularAutomataCave = target as CellularAutomataCave;
if (!cellularAutomataCave) return;

if (DrawDefaultInspector())
{
cellularAutomataCave.GenerateMap();
}

if (GUILayout.Button("Update", GUILayout.MaxWidth(100)))
{
cellularAutomataCave.GenerateMap();
}
}
}
#endif
}

参考

Procedural Dungeon | Wave Function Collapse | Cellular Automata


Cellular Automata 细胞自动机生成2D洞穴
https://automask.github.io/wild/2022/06/28/lab/S_Unity_Cellular_Automata/
作者
Kyle Zhou
发布于
2022年6月28日
许可协议