上篇教程結束,可以得到一個處理過的地下城map
數組,擁有多個房間以及房間之間的兩個最短連接點,如下圖所示:
要連通整個地下城,需要在房間之間創建路徑,既然我們已經有了路徑的首尾兩點,那問題就變成了如何在一個二維像素網格中,給定兩個點,點出連接兩點的線段:
可能在上個世紀人們造像素顯示器的時候就面臨過這個問題,也留下了一個很經典的算法:布雷森漢姆直線演算法。
從最簡單的情況開始,假設我們有兩點p1(x1, y1),p2(x2, y2),p2在p1的右上方。可以算出斜率delta為(y2 - y1) / (x2 - x1)。設定一個誤差值error,初始設為0。
版本1:
- 從x1開始,順序遍歷從x1到x2的點x,y從y1開始先保持不變。
- 填充點(x, y)。
- x每增加1,error就增加斜率delta。
- 當error值大于0.5的時候,說明這時候偏差更偏向于y,y增加1,error值減去1。
- 重復步驟2,直到x遍歷結束。
連線后的結果如圖所示:
上面的基礎算法只適用于p2在p1右上角,且斜率delta小于1的情況,可以稍微修改下以適用于一般情況:
版本2:
- 如果斜率delta大于1,則交換x1,y1和交換x2,y2,相當于旋轉坐標系90度。
- 如果p2在p1左邊(x2 < x1),則交換起始點。
- 如果p2在p1下邊(y2 < y1),則當error值大于0.5時,y減去1。
考慮到浮點數的運算效率比較慢,還可以把error和delta變為整數進行運算:
版本3:
- error值初始設置為(int)((x2 - x1)/2)
- deltaY值設置為abs(y2 - y1),deltaX值設置為(x2 - x1)
- x每增加1,error值就減去deltaY,如果error小于0,y發生變化,error加上deltaX。
最終版本的兩點畫線代碼如下:
List<Coord> GetLine(Coord from, Coord to) {
List<Coord> line = new List<Coord>();
int x = from.tileX;
int y = from.tileY;
int dx = to.tileX - from.tileX;
int dy = to.tileY - from.tileY;
bool inverted = false;
int step = Math.Sign(dx);
int gradientStep = Math.Sign(dy);
int longest = Math.Abs(dx);
int shortest = Math.Abs(dy);
if (longest < shortest) {
inverted = true;
step = Math.Sign(dy);
gradientStep = Math.Sign(dx);
longest = Math.Abs(dy);
shortest = Math.Abs(dx);
}
int gradientAccumulation = longest / 2;
for (int i = 0; i < longest; i++) {
line.Add(new Coord(x, y));
if (inverted) {
y += step;
} else {
x += step;
}
gradientAccumulation += shortest;
if (gradientAccumulation >= longest) {
if (inverted) {
x += gradientStep;
} else {
y += gradientStep;
}
gradientAccumulation -= longest;
}
}
return line;
}
在CreatePassage
函數中:
void CreatePassage(Room roomA, Room roomB, Coord tileA, Coord tileB) {
Room.ConnectRooms(roomA, roomB);
//Debug.DrawLine (CoordToWorldPoint (tileA), CoordToWorldPoint (tileB), Color.green, 100);
List<Coord> line = GetLine(tileA, tileB);
foreach (Coord c in line) {
DrawCircle(c, 2);
}
}
獲取路徑點集合后,通過DrawCircle
方法在路徑點周圍清除map,確保路徑暢通。
最終效果如圖所示:
源代碼可參考SebLague小哥的Github,戳我直達