You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

CollisionChecker.cs 11 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using Preparation.Interface;
  5. using Preparation.Utility;
  6. using Preparation.GameData;
  7. namespace GameEngine
  8. {
  9. internal class CollisionChecker
  10. {
  11. /// <summary>
  12. /// 碰撞检测,如果这样行走是否会与之碰撞,返回与之碰撞的物体
  13. /// </summary>
  14. /// <param name="obj">移动的物体</param>
  15. /// <param name="moveVec">移动的位移向量</param>
  16. /// <returns>和它碰撞的物体</returns>
  17. public IGameObj? CheckCollision(IMoveable obj, XY moveVec)
  18. {
  19. XY nextPos = obj.Position + XY.VectorToXY(moveVec);
  20. if (!obj.IsRigid)
  21. {
  22. if (gameMap.IsOutOfBound(obj))
  23. return gameMap.GetOutOfBound(nextPos);
  24. return null;
  25. }
  26. //在列表中检查碰撞
  27. Func<IEnumerable<IGameObj>, ReaderWriterLockSlim, IGameObj?> CheckCollisionInList =
  28. (IEnumerable<IGameObj> lst, ReaderWriterLockSlim listLock) =>
  29. {
  30. IGameObj? collisionObj = null;
  31. listLock.EnterReadLock();
  32. try
  33. {
  34. foreach (var listObj in lst)
  35. {
  36. if (obj.WillCollideWith(listObj, nextPos))
  37. {
  38. collisionObj = listObj;
  39. break;
  40. }
  41. }
  42. }
  43. finally { listLock.ExitReadLock(); }
  44. return collisionObj;
  45. };
  46. IGameObj? collisionObj = null;
  47. foreach (var list in lists)
  48. {
  49. if ((collisionObj = CheckCollisionInList(list.Item1, list.Item2)) != null)
  50. {
  51. return collisionObj;
  52. }
  53. }
  54. return null;
  55. }
  56. /// <summary>
  57. /// /// 可移动物体(圆)向矩形物体移动时,可移动且不会碰撞的最大距离。直接用double计算,防止误差
  58. /// </summary>
  59. /// <param name="obj"></param>
  60. /// <param name="square">矩形的中心坐标</param>
  61. /// <returns></returns>
  62. //private double MaxMoveToSquare(IMoveable obj, IGameObj square)
  63. //{
  64. // double tmpMax;
  65. // double angle = Math.Atan2(square.Position.y - obj.Position.y, square.Position.x - obj.Position.x);
  66. // if (obj.WillCollideWith(square, obj.Position))
  67. // tmpMax = 0;
  68. // else tmpMax =
  69. // Math.Abs(XYPosition.Distance(obj.Position, square.Position) - obj.Radius -
  70. // (square.Radius / Math.Min(Math.Abs(Math.Cos(angle)), Math.Abs(Math.Sin(angle)))));
  71. // return tmpMax;
  72. //}
  73. //private double FindMaxOnlyConsiderWall(IMoveable obj, Vector moveVec)
  74. //{
  75. // var desination = moveVec;
  76. // double maxOnlyConsiderWall = moveVec.length;
  77. // if (desination.length > 0) //如果length足够长,还是有可能穿墙的
  78. // {
  79. // XYPosition nextXY = Vector.Vector2XY(desination) + obj.Position + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle)), (int)(obj.Radius * Math.Sin(moveVec.angle)));
  80. // if (gameMap.IsWall(nextXY)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
  81. // {
  82. // maxOnlyConsiderWall = MaxMoveToSquare(obj, gameMap.GetCell(nextXY));
  83. // }
  84. // else //考虑物体宽度
  85. // {
  86. // double dist = 0;
  87. // XYPosition nextXYConsiderWidth;
  88. // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle + Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle + Math.PI / 4)));
  89. // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
  90. // {
  91. // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth));
  92. // if (dist < maxOnlyConsiderWall)
  93. // maxOnlyConsiderWall = dist;
  94. // }
  95. // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle - Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle - Math.PI / 4)));
  96. // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置
  97. // {
  98. // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth));
  99. // if (dist < maxOnlyConsiderWall)
  100. // maxOnlyConsiderWall = dist;
  101. // }
  102. // }
  103. // }
  104. // return maxOnlyConsiderWall;
  105. //}
  106. /// <summary>
  107. /// 寻找最大可能移动距离
  108. /// </summary>
  109. /// <param name="obj">移动物体,默认obj.Rigid为true</param>
  110. /// <param name="nextPos">下一步要到达的位置</param>
  111. /// <param name="moveVec">移动的位移向量,默认与nextPos协调</param>
  112. /// <returns>最大可能的移动距离</returns>
  113. public double FindMax(IMoveable obj, XY nextPos, XY moveVec)
  114. {
  115. double maxLen = (double)uint.MaxValue;
  116. double tmpMax = maxLen; //暂存最大值
  117. // 先找只考虑墙的最大距离
  118. //double maxOnlyConsiderWall = FindMaxOnlyConsiderWall(obj, moveVec);
  119. double maxDistance = maxLen;
  120. foreach (var listWithLock in lists)
  121. {
  122. var lst = listWithLock.Item1;
  123. var listLock = listWithLock.Item2;
  124. listLock.EnterReadLock();
  125. try
  126. {
  127. foreach (IGameObj listObj in lst)
  128. {
  129. //如果再走一步发生碰撞
  130. if (obj.WillCollideWith(listObj, nextPos))
  131. {
  132. {
  133. switch (listObj.Shape) //默认obj为圆形
  134. {
  135. case ShapeType.Circle:
  136. {
  137. //计算两者之间的距离
  138. double mod = XY.Distance(listObj.Position, obj.Position);
  139. int orgDeltaX = listObj.Position.x - obj.Position.x;
  140. int orgDeltaY = listObj.Position.y - obj.Position.y;
  141. if (mod < listObj.Radius + obj.Radius) //如果两者已经重叠
  142. {
  143. tmpMax = 0;
  144. }
  145. else
  146. {
  147. double tmp = mod - obj.Radius - listObj.Radius;
  148. //计算能走的最长距离,好像这么算有一点误差?
  149. tmp = tmp / Math.Cos(Math.Atan2(orgDeltaY, orgDeltaX) - moveVec.angle);
  150. if (tmp < 0 || tmp > uint.MaxValue || tmp == double.NaN)
  151. {
  152. tmpMax = uint.MaxValue;
  153. }
  154. else tmpMax = tmp;
  155. }
  156. break;
  157. }
  158. case ShapeType.Square:
  159. {
  160. //if (obj.WillCollideWith(listObj, obj.Position))
  161. // tmpMax = 0;
  162. //else tmpMax = MaxMoveToSquare(obj, listObj);
  163. //break;
  164. if (obj.WillCollideWith(listObj, obj.Position))
  165. tmpMax = 0;
  166. else
  167. {
  168. //二分查找最大可能移动距离
  169. int left = 0, right = (int)moveVec.length;
  170. while (left < right - 1)
  171. {
  172. int mid = (right - left) / 2 + left;
  173. if (obj.WillCollideWith(listObj, obj.Position + new XY((int)(mid * Math.Cos(moveVec.angle)), (int)(mid * Math.Sin(moveVec.angle)))))
  174. {
  175. right = mid;
  176. }
  177. else left = mid;
  178. }
  179. tmpMax = (uint)left;
  180. }
  181. break;
  182. }
  183. default:
  184. tmpMax = uint.MaxValue;
  185. break;
  186. }
  187. if (tmpMax < maxDistance)
  188. maxDistance = tmpMax;
  189. }
  190. }
  191. }
  192. }
  193. finally
  194. {
  195. //maxLen = Math.Min(maxOnlyConsiderWall, maxDistance); //最大可能距离的最小值
  196. listLock.ExitReadLock();
  197. }
  198. }
  199. //return maxLen;
  200. return maxDistance;
  201. }
  202. readonly IMap gameMap;
  203. private readonly Tuple<IEnumerable<IGameObj>, ReaderWriterLockSlim>[] lists;
  204. public CollisionChecker(IMap gameMap)
  205. {
  206. this.gameMap = gameMap;
  207. lists = new Tuple<IEnumerable<IGameObj>, ReaderWriterLockSlim>[gameMap.GameObjDict.Count];
  208. int i = 0;
  209. foreach(var keyValuePair in gameMap.GameObjDict)
  210. {
  211. lists[i++] = new Tuple<IEnumerable<IGameObj>, ReaderWriterLockSlim>(keyValuePair.Value as IList<IGameObj>, gameMap.GameObjLockDict[keyValuePair.Key]);
  212. }
  213. }
  214. }
  215. }