Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / generate / GenerateDungeon.cs
  1.  
  2. using System.Collections.Generic;
  3. using Godot;
  4.  
  5. /// <summary>
  6. /// 地牢生成器
  7. /// </summary>
  8. public class GenerateDungeon
  9. {
  10. /// <summary>
  11. /// 过道宽度
  12. /// </summary>
  13. public const int CorridorWidth = 4;
  14. /// <summary>
  15. /// 所有生成的房间, 调用过 Generate() 函数才能获取到值
  16. /// </summary>
  17. public List<RoomInfo> RoomInfos { get; } = new List<RoomInfo>();
  18.  
  19. /// <summary>
  20. /// 起始房间
  21. /// </summary>
  22. public RoomInfo StartRoom { get; private set; }
  23. /// <summary>
  24. /// 生成的房间数量
  25. /// </summary>
  26. private int _maxCount = 15;
  27.  
  28. //用于标记地图上的坐标是否被占用
  29. private Grid<bool> _roomGrid { get; } = new Grid<bool>();
  30. //当前房间数量
  31. private int _count = 0;
  32. //宽高
  33. private int _roomMinWidth = 15;
  34. private int _roomMaxWidth = 35;
  35. private int _roomMinHeight = 15;
  36. private int _roomMaxHeight = 30;
  37.  
  38. //间隔
  39. private int _roomMinInterval = 6;
  40. private int _roomMaxInterval = 10;
  41.  
  42. //房间横轴分散程度
  43. private float _roomHorizontalMinDispersion = 0.7f;
  44. private float _roomHorizontalMaxDispersion = 1.1f;
  45.  
  46. //房间纵轴分散程度
  47. private float _roomVerticalMinDispersion = 0.7f;
  48. private float _roomVerticalMaxDispersion = 1.1f;
  49.  
  50. //区域限制
  51. private bool _enableLimitRange = true;
  52. private int _rangeX = 110;
  53. private int _rangeY = 110;
  54. //找房间失败次数, 过大则会关闭区域限制
  55. private int _maxFailCount = 10;
  56. private int _failCount = 0;
  57.  
  58. private enum GenerateRoomErrorCode
  59. {
  60. NoError,
  61. //房间已满
  62. RoomFull,
  63. //超出区域
  64. OutArea,
  65. //碰到其他房间或过道
  66. HasCollision,
  67. //没有合适的门
  68. NoProperDoor,
  69. }
  70. /// <summary>
  71. /// 生成房间
  72. /// </summary>
  73. public void Generate()
  74. {
  75. if (StartRoom != null) return;
  76.  
  77. //第一个房间
  78. GenerateRoom(null, 0, out var startRoom);
  79. StartRoom = startRoom;
  80. //如果房间数量不够, 就一直生成
  81. while (_count < _maxCount)
  82. {
  83. var room = Utils.RandChoose(RoomInfos);
  84. var errorCode = GenerateRoom(room, Utils.RandRangeInt(0, 3), out var nextRoom);
  85. if (errorCode == GenerateRoomErrorCode.NoError)
  86. {
  87. _failCount = 0;
  88. room.Next.Add(nextRoom);
  89. }
  90. else
  91. {
  92. GD.Print("生成第" + (_count + 1) + "个房间失败! 失败原因: " + errorCode);
  93. if (errorCode == GenerateRoomErrorCode.OutArea)
  94. {
  95. _failCount++;
  96. GD.Print("超出区域失败次数: " + _failCount);
  97. if (_failCount >= _maxFailCount)
  98. {
  99. _enableLimitRange = false;
  100. GD.Print("生成房间失败次数过多, 关闭区域限制!");
  101. }
  102. }
  103. }
  104. }
  105. _roomGrid.Clear();
  106. }
  107.  
  108. //生成房间
  109. private GenerateRoomErrorCode GenerateRoom(RoomInfo prevRoomInfo, int direction, out RoomInfo resultRoom)
  110. {
  111. if (_count >= _maxCount)
  112. {
  113. resultRoom = null;
  114. return GenerateRoomErrorCode.RoomFull;
  115. }
  116.  
  117. var room = new RoomInfo(_count);
  118. //房间大小
  119. room.Size = new Vector2(Utils.RandRangeInt(_roomMinWidth, _roomMaxWidth),
  120. Utils.RandRangeInt(_roomMinHeight, _roomMaxHeight));
  121.  
  122. if (prevRoomInfo != null) //表示这不是第一个房间, 就得判断当前位置下的房间是否被遮挡
  123. {
  124. //房间间隔
  125. var space = Utils.RandRangeInt(_roomMinInterval, _roomMaxInterval);
  126. //中心偏移
  127. int offset;
  128. if (direction == 0 || direction == 2)
  129. {
  130. offset = Utils.RandRangeInt(-(int)(prevRoomInfo.Size.X * _roomVerticalMinDispersion),
  131. (int)(prevRoomInfo.Size.X * _roomVerticalMaxDispersion));
  132. }
  133. else
  134. {
  135. offset = Utils.RandRangeInt(-(int)(prevRoomInfo.Size.Y * _roomHorizontalMinDispersion),
  136. (int)(prevRoomInfo.Size.Y * _roomHorizontalMaxDispersion));
  137. }
  138.  
  139. //计算房间位置
  140. if (direction == 0) //上
  141. {
  142. room.Position = new Vector2(prevRoomInfo.Position.X + offset,
  143. prevRoomInfo.Position.Y - room.Size.Y - space);
  144. }
  145. else if (direction == 1) //右
  146. {
  147. room.Position = new Vector2(prevRoomInfo.Position.X + prevRoomInfo.Size.Y + space,
  148. prevRoomInfo.Position.Y + offset);
  149. }
  150. else if (direction == 2) //下
  151. {
  152. room.Position = new Vector2(prevRoomInfo.Position.X + offset,
  153. prevRoomInfo.Position.Y + prevRoomInfo.Size.Y + space);
  154. }
  155. else if (direction == 3) //左
  156. {
  157. room.Position = new Vector2(prevRoomInfo.Position.X - room.Size.X - space,
  158. prevRoomInfo.Position.Y + offset);
  159. }
  160. //是否在限制区域内
  161. if (_enableLimitRange)
  162. {
  163. if (room.Position.X < -_rangeX || room.Position.X + room.Size.X > _rangeX || room.Position.Y < -_rangeY || room.Position.Y + room.Size.Y > _rangeY)
  164. {
  165. resultRoom = null;
  166. return GenerateRoomErrorCode.OutArea;
  167. }
  168. }
  169.  
  170. //是否碰到其他房间或者过道
  171. if (_roomGrid.RectCollision(room.Position - new Vector2(3, 3), room.Size + new Vector2(6, 6)))
  172. {
  173. resultRoom = null;
  174. return GenerateRoomErrorCode.HasCollision;
  175. }
  176.  
  177. _roomGrid.AddRect(room.Position, room.Size, true);
  178.  
  179. //找门, 与上一个房间是否能连通
  180. if (!ConnectDoor(prevRoomInfo, room))
  181. {
  182. _roomGrid.RemoveRect(room.Position, room.Size);
  183. resultRoom = null;
  184. return GenerateRoomErrorCode.NoProperDoor;
  185. }
  186. }
  187.  
  188. _count++;
  189. RoomInfos.Add(room);
  190. if (prevRoomInfo == null)
  191. {
  192. _roomGrid.AddRect(room.Position, room.Size, true);
  193. }
  194.  
  195. //下一个房间
  196. //0上, 1右, 2下, 3左
  197. var dirList = new List<int>(new[] { 0, 1, 2, 3 });
  198. if (prevRoomInfo != null)
  199. {
  200. dirList.Remove(GetReverseDirection(direction));
  201. }
  202.  
  203. //这条线有一定概率不生成下一个房间
  204. if (Utils.RandRangeInt(0, 2) != 0)
  205. {
  206. while (dirList.Count > 0)
  207. {
  208. var randDir = Utils.RandChoose(dirList);
  209. GenerateRoom(room, randDir, out var nextRoom);
  210. if (nextRoom == null)
  211. {
  212. break;
  213. }
  214.  
  215. nextRoom.Prev = room;
  216. room.Next.Add(nextRoom);
  217.  
  218. dirList.Remove(randDir);
  219. }
  220. }
  221.  
  222. resultRoom = room;
  223. return GenerateRoomErrorCode.NoError;
  224. }
  225.  
  226. /// <summary>
  227. /// 找两个房间的门
  228. /// </summary>
  229. private bool ConnectDoor(RoomInfo room, RoomInfo nextRoom)
  230. {
  231. //门描述
  232. var roomDoor = new RoomDoorInfo();
  233. var nextRoomDoor = new RoomDoorInfo();
  234. roomDoor.RoomInfo = room;
  235. nextRoomDoor.RoomInfo = nextRoom;
  236. roomDoor.ConnectRoom = nextRoom;
  237. roomDoor.ConnectDoor = nextRoomDoor;
  238. nextRoomDoor.ConnectRoom = room;
  239. nextRoomDoor.ConnectDoor = roomDoor;
  240.  
  241. var overlapX = Mathf.Min(room.Position.X + room.Size.X, nextRoom.Position.X + nextRoom.Size.X) -
  242. Mathf.Max(room.Position.X, nextRoom.Position.X);
  243. //这种情况下x轴有重叠
  244. if (overlapX >= 6)
  245. {
  246. //找到重叠区域
  247. var range = CalcOverlapRange(room.Position.X, room.Position.X + room.Size.X,
  248. nextRoom.Position.X, nextRoom.Position.X + nextRoom.Size.X);
  249. var x = Utils.RandRangeInt((int)range.X + 1, (int)range.Y - CorridorWidth - 1);
  250.  
  251. if (room.Position.Y < nextRoom.Position.Y) //room在上, nextRoom在下
  252. {
  253. roomDoor.Direction = DoorDirection.S;
  254. nextRoomDoor.Direction = DoorDirection.N;
  255. roomDoor.OriginPosition = new Vector2(x, room.Position.Y + room.Size.Y);
  256. nextRoomDoor.OriginPosition = new Vector2(x, nextRoom.Position.Y);
  257. }
  258. else //room在下, nextRoom在上
  259. {
  260. roomDoor.Direction = DoorDirection.N;
  261. nextRoomDoor.Direction = DoorDirection.S;
  262. roomDoor.OriginPosition = new Vector2(x, room.Position.Y);
  263. nextRoomDoor.OriginPosition = new Vector2(x, nextRoom.Position.Y + nextRoom.Size.Y);
  264. }
  265.  
  266. //判断门之间的通道是否有物体碰到
  267. if (!AddCorridorToGridRange(roomDoor, nextRoomDoor))
  268. {
  269. //此门不能连通
  270. return false;
  271. }
  272.  
  273. //没有撞到物体
  274. room.Doors.Add(roomDoor);
  275. nextRoom.Doors.Add(nextRoomDoor);
  276. return true;
  277. }
  278.  
  279. var overlapY = Mathf.Min(room.Position.Y + room.Size.Y, nextRoom.Position.Y + nextRoom.Size.Y) -
  280. Mathf.Max(room.Position.Y, nextRoom.Position.Y);
  281. //这种情况下y轴有重叠
  282. if (overlapY >= 6)
  283. {
  284. //找到重叠区域
  285. var range = CalcOverlapRange(room.Position.Y, room.Position.Y + room.Size.Y,
  286. nextRoom.Position.Y, nextRoom.Position.Y + nextRoom.Size.Y);
  287. var y = Utils.RandRangeInt((int)range.X + 1, (int)range.Y - CorridorWidth - 1);
  288.  
  289. if (room.Position.X < nextRoom.Position.X) //room在左, nextRoom在右
  290. {
  291. roomDoor.Direction = DoorDirection.E;
  292. nextRoomDoor.Direction = DoorDirection.W;
  293. roomDoor.OriginPosition = new Vector2(room.Position.X + room.Size.X, y);
  294. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X, y);
  295. }
  296. else //room在右, nextRoom在左
  297. {
  298. roomDoor.Direction = DoorDirection.W;
  299. nextRoomDoor.Direction = DoorDirection.E;
  300. roomDoor.OriginPosition = new Vector2(room.Position.X, y);
  301. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X + nextRoom.Size.X, y);
  302. }
  303.  
  304. //判断门之间的通道是否有物体碰到
  305. if (!AddCorridorToGridRange(roomDoor, nextRoomDoor))
  306. {
  307. //此门不能连通
  308. return false;
  309. }
  310.  
  311. //没有撞到物体
  312. room.Doors.Add(roomDoor);
  313. nextRoom.Doors.Add(nextRoomDoor);
  314. return true;
  315. }
  316.  
  317. var offset1 = Mathf.Clamp((int)overlapX + 2, 2, 6);
  318. var offset2 = Mathf.Clamp((int)overlapY + 2, 2, 6);
  319.  
  320. //焦点
  321. Vector2 cross;
  322.  
  323. //这种情况下x和y轴都没有重叠, 那么就只能生成拐角通道了
  324. if (room.Position.X > nextRoom.Position.X)
  325. {
  326. if (room.Position.Y > nextRoom.Position.Y)
  327. {
  328. if (Utils.RandBoolean())
  329. {
  330. roomDoor.Direction = DoorDirection.N; //↑
  331. nextRoomDoor.Direction = DoorDirection.E; //→
  332.  
  333. roomDoor.OriginPosition = new Vector2(room.Position.X + offset1, room.Position.Y);
  334. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X + nextRoom.Size.X,
  335. nextRoom.Position.Y + nextRoom.Size.Y - offset2 - 6);
  336. cross = new Vector2(roomDoor.OriginPosition.X, nextRoomDoor.OriginPosition.Y);
  337. }
  338. else
  339. {
  340. roomDoor.Direction = DoorDirection.W; //←
  341. nextRoomDoor.Direction = DoorDirection.S; //↓
  342. roomDoor.OriginPosition = new Vector2(room.Position.X, room.Position.Y + offset2);
  343. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X + nextRoom.Size.X - offset1 - 6,
  344. nextRoom.Position.Y + nextRoom.Size.Y);
  345. cross = new Vector2(nextRoomDoor.OriginPosition.X, roomDoor.OriginPosition.Y);
  346. }
  347. }
  348. else
  349. {
  350. if (Utils.RandBoolean())
  351. {
  352. roomDoor.Direction = DoorDirection.S; //↓
  353. nextRoomDoor.Direction = DoorDirection.E; //→
  354.  
  355. roomDoor.OriginPosition = new Vector2(room.Position.X + offset1, room.Position.Y + room.Size.Y);
  356. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X + nextRoom.Size.X,
  357. nextRoom.Position.Y + offset2);
  358. cross = new Vector2(roomDoor.OriginPosition.X, nextRoomDoor.OriginPosition.Y);
  359. }
  360. else
  361. {
  362. roomDoor.Direction = DoorDirection.W; //←
  363. nextRoomDoor.Direction = DoorDirection.N; //↑
  364.  
  365. roomDoor.OriginPosition =
  366. new Vector2(room.Position.X, room.Position.Y + room.Size.Y - offset2 - 6); //
  367. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X + nextRoom.Size.X - offset2 - 6,
  368. nextRoom.Position.Y);
  369. cross = new Vector2(nextRoomDoor.OriginPosition.X, roomDoor.OriginPosition.Y);
  370. }
  371. }
  372. }
  373. else
  374. {
  375. if (room.Position.Y > nextRoom.Position.Y)
  376. {
  377. if (Utils.RandBoolean())
  378. {
  379. roomDoor.Direction = DoorDirection.E; //→
  380. nextRoomDoor.Direction = DoorDirection.S; //↓
  381.  
  382. roomDoor.OriginPosition = new Vector2(room.Position.X + room.Size.X, room.Position.Y + offset2);
  383. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X + offset1,
  384. nextRoom.Position.Y + nextRoom.Size.Y);
  385. cross = new Vector2(nextRoomDoor.OriginPosition.X, roomDoor.OriginPosition.Y);
  386. }
  387. else
  388. {
  389. roomDoor.Direction = DoorDirection.N; //↑
  390. nextRoomDoor.Direction = DoorDirection.W; //←
  391.  
  392. roomDoor.OriginPosition = new Vector2(room.Position.X + room.Size.X - offset1 - 6, room.Position.Y);
  393. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X,
  394. nextRoom.Position.Y + nextRoom.Size.Y - offset2 - 6);
  395. cross = new Vector2(roomDoor.OriginPosition.X, nextRoomDoor.OriginPosition.Y);
  396. }
  397. }
  398. else
  399. {
  400. if (Utils.RandBoolean())
  401. {
  402. roomDoor.Direction = DoorDirection.E; //→
  403. nextRoomDoor.Direction = DoorDirection.N; //↑
  404.  
  405. roomDoor.OriginPosition = new Vector2(room.Position.X + room.Size.X,
  406. room.Position.Y + room.Size.Y - offset2 - 6);
  407. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X + offset1, nextRoom.Position.Y);
  408. cross = new Vector2(nextRoomDoor.OriginPosition.X, roomDoor.OriginPosition.Y);
  409. }
  410. else
  411. {
  412. roomDoor.Direction = DoorDirection.S; //↓
  413. nextRoomDoor.Direction = DoorDirection.W; //←
  414.  
  415. roomDoor.OriginPosition = new Vector2(room.Position.X + room.Size.X - offset1 - 6,
  416. room.Position.Y + room.Size.Y);
  417. nextRoomDoor.OriginPosition = new Vector2(nextRoom.Position.X, nextRoom.Position.Y + offset2);
  418. cross = new Vector2(roomDoor.OriginPosition.X, nextRoomDoor.OriginPosition.Y);
  419. }
  420. }
  421. }
  422.  
  423. //判断门之间的通道是否有物体碰到
  424. if (!AddCorridorToGridRange(roomDoor, nextRoomDoor, cross))
  425. {
  426. //此门不能连通
  427. return false;
  428. }
  429.  
  430. roomDoor.HasCross = true;
  431. roomDoor.Cross = cross;
  432. nextRoomDoor.HasCross = true;
  433. nextRoomDoor.Cross = cross;
  434.  
  435. room.Doors.Add(roomDoor);
  436. nextRoom.Doors.Add(nextRoomDoor);
  437. return true;
  438. }
  439.  
  440. //用于计算重叠区域坐标, 可以理解为一维轴上4个点的中间两个点
  441. private Vector2 CalcOverlapRange(float start1, float end1, float start2, float end2)
  442. {
  443. return new Vector2(Mathf.Max(start1, start2), Mathf.Min(end1, end2));
  444. }
  445.  
  446. //返回参数方向的反方向
  447. private int GetReverseDirection(int direction)
  448. {
  449. switch (direction)
  450. {
  451. case 0: return 2;
  452. case 1: return 3;
  453. case 2: return 0;
  454. case 3: return 1;
  455. }
  456.  
  457. return 2;
  458. }
  459.  
  460. //将两个门间的过道占用数据存入RoomGrid
  461. private bool AddCorridorToGridRange(RoomDoorInfo door1, RoomDoorInfo door2)
  462. {
  463. var point1 = door1.OriginPosition;
  464. var point2 = door2.OriginPosition;
  465. var pos = new Vector2(Mathf.Min(point1.X, point2.X), Mathf.Min(point1.Y, point2.Y));
  466. var size = new Vector2(
  467. point1.X == point2.X ? CorridorWidth : Mathf.Abs(point1.X - point2.X),
  468. point1.Y == point2.Y ? CorridorWidth : Mathf.Abs(point1.Y - point2.Y)
  469. );
  470.  
  471. Vector2 collPos;
  472. Vector2 collSize;
  473. if (point1.X == point2.X) //纵向加宽, 防止贴到其它墙
  474. {
  475. collPos = new Vector2(pos.X - 3, pos.Y);
  476. collSize = new Vector2(size.X + 6, size.Y);
  477. }
  478. else //横向加宽, 防止贴到其它墙
  479. {
  480. collPos = new Vector2(pos.X, pos.Y - 3);
  481. collSize = new Vector2(size.X, size.Y + 6);
  482. }
  483.  
  484. if (_roomGrid.RectCollision(collPos, collSize))
  485. {
  486. return false;
  487. }
  488.  
  489. _roomGrid.AddRect(pos, size, true);
  490. return true;
  491. }
  492.  
  493. //将两个门间的过道占用数据存入RoomGrid, 该重载
  494. private bool AddCorridorToGridRange(RoomDoorInfo door1, RoomDoorInfo door2, Vector2 cross)
  495. {
  496. var point1 = door1.OriginPosition;
  497. var point2 = door2.OriginPosition;
  498. var pos1 = new Vector2(Mathf.Min(point1.X, cross.X), Mathf.Min(point1.Y, cross.Y));
  499. var size1 = new Vector2(
  500. point1.X == cross.X ? CorridorWidth : Mathf.Abs(point1.X - cross.X),
  501. point1.Y == cross.Y ? CorridorWidth : Mathf.Abs(point1.Y - cross.Y)
  502. );
  503. var pos2 = new Vector2(Mathf.Min(point2.X, cross.X), Mathf.Min(point2.Y, cross.Y));
  504. var size2 = new Vector2(
  505. point2.X == cross.X ? CorridorWidth : Mathf.Abs(point2.X - cross.X),
  506. point2.Y == cross.Y ? CorridorWidth : Mathf.Abs(point2.Y - cross.Y)
  507. );
  508.  
  509. Vector2 collPos1;
  510. Vector2 collSize1;
  511. if (point1.X == cross.X) //纵向加宽, 防止贴到其它墙
  512. {
  513. collPos1 = new Vector2(pos1.X - 3, pos1.Y);
  514. collSize1 = new Vector2(size1.X + 6, size1.Y);
  515. }
  516. else //横向加宽, 防止贴到其它墙
  517. {
  518. collPos1 = new Vector2(pos1.X, pos1.Y - 3);
  519. collSize1 = new Vector2(size1.X, size1.Y + 6);
  520. }
  521.  
  522. if (_roomGrid.RectCollision(collPos1, collSize1))
  523. {
  524. return false;
  525. }
  526.  
  527. Vector2 collPos2;
  528. Vector2 collSize2;
  529. if (point2.X == cross.X) //纵向加宽, 防止贴到其它墙
  530. {
  531. collPos2 = new Vector2(pos2.X - 3, pos2.Y);
  532. collSize2 = new Vector2(size2.X + 6, size2.Y);
  533. }
  534. else //横向加宽, 防止贴到其它墙
  535. {
  536. collPos2 = new Vector2(pos2.X, pos2.Y - 3);
  537. collSize2 = new Vector2(size2.X, size2.Y + 6);
  538. }
  539.  
  540. if (_roomGrid.RectCollision(collPos2, collSize2))
  541. {
  542. return false;
  543. }
  544.  
  545. _roomGrid.AddRect(pos1, size1, true);
  546. _roomGrid.AddRect(pos2, size2, true);
  547. return true;
  548. }
  549. }