using System; using System.Collections.Generic; using System.Threading; using Preparation.Interface; using Preparation.Utility; namespace GameEngine { internal class CollisionChecker { /// /// 碰撞检测,如果这样行走是否会与之碰撞,返回与之碰撞的物体 /// /// 移动的物体 /// 移动的位移向量 /// 和它碰撞的物体 public IGameObj? CheckCollision(IMoveable obj, XY moveVec) { XY nextPos = obj.Position + moveVec; if (!obj.IsRigid) { if (gameMap.IsOutOfBound(obj)) return gameMap.GetOutOfBound(nextPos); return null; } // 在列表中检查碰撞 Func, ReaderWriterLockSlim, IGameObj?> CheckCollisionInList = (IEnumerable lst, ReaderWriterLockSlim listLock) => { IGameObj? collisionObj = null; listLock.EnterReadLock(); try { foreach (var listObj in lst) { if (obj.WillCollideWith(listObj, nextPos)) { collisionObj = listObj; break; } } } finally { listLock.ExitReadLock(); } return collisionObj; }; IGameObj? collisionObj = null; foreach (var list in lists) { if ((collisionObj = CheckCollisionInList(list.Item1, list.Item2)) != null) { return collisionObj; } } return null; } /// /// /// 可移动物体(圆)向矩形物体移动时,可移动且不会碰撞的最大距离。直接用double计算,防止误差 /// /// /// 矩形的中心坐标 /// // private double MaxMoveToSquare(IMoveable obj, IGameObj square) //{ // double tmpMax; // double angle = Math.Atan2(square.Position.y - obj.Position.y, square.Position.x - obj.Position.x); // if (obj.WillCollideWith(square, obj.Position)) // tmpMax = 0; // else tmpMax = // Math.Abs(XYPosition.Distance(obj.Position, square.Position) - obj.Radius - // (square.Radius / Math.Min(Math.Abs(Math.Cos(angle)), Math.Abs(Math.Sin(angle))))); // return tmpMax; // } // private double FindMaxOnlyConsiderWall(IMoveable obj, Vector moveVec) //{ // var desination = moveVec; // double maxOnlyConsiderWall = moveVec.length; // if (desination.length > 0) //如果length足够长,还是有可能穿墙的 // { // XYPosition nextXY = Vector.Vector2XY(desination) + obj.Position + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle)), (int)(obj.Radius * Math.Sin(moveVec.angle))); // if (gameMap.IsWall(nextXY)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置 // { // maxOnlyConsiderWall = MaxMoveToSquare(obj, gameMap.GetCell(nextXY)); // } // else //考虑物体宽度 // { // double dist = 0; // XYPosition nextXYConsiderWidth; // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle + Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle + Math.PI / 4))); // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置 // { // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth)); // if (dist < maxOnlyConsiderWall) // maxOnlyConsiderWall = dist; // } // nextXYConsiderWidth = nextXY + new XYPosition((int)(obj.Radius * Math.Cos(moveVec.angle - Math.PI / 4)), (int)(obj.Radius * Math.Sin(moveVec.angle - Math.PI / 4))); // if (gameMap.IsWall(nextXYConsiderWidth)) //对下一步的位置进行检查,但这里只是考虑移动物体的宽度,只是考虑下一步能达到的最远位置 // { // dist = MaxMoveToSquare(obj, gameMap.GetCell(nextXYConsiderWidth)); // if (dist < maxOnlyConsiderWall) // maxOnlyConsiderWall = dist; // } // } // } // return maxOnlyConsiderWall; // } /// /// 寻找最大可能移动距离 /// /// 移动物体,默认obj.Rigid为true /// 下一步要到达的位置 /// 移动的位移向量,默认与nextPos协调 /// 最大可能的移动距离 public double FindMax(IMoveable obj, XY nextPos, XY moveVec) { double maxLen = (double)uint.MaxValue; double tmpMax = maxLen; // 暂存最大值 // 先找只考虑墙的最大距离 // double maxOnlyConsiderWall = FindMaxOnlyConsiderWall(obj, moveVec); double maxDistance = maxLen; foreach (var listWithLock in lists) { var lst = listWithLock.Item1; var listLock = listWithLock.Item2; listLock.EnterReadLock(); try { foreach (IGameObj listObj in lst) { // 如果再走一步发生碰撞 if (obj.WillCollideWith(listObj, nextPos)) { { switch (listObj.Shape) // 默认obj为圆形 { case ShapeType.Circle: { // 计算两者之间的距离 double mod = XY.Distance(listObj.Position, obj.Position); int orgDeltaX = listObj.Position.x - obj.Position.x; int orgDeltaY = listObj.Position.y - obj.Position.y; if (mod < listObj.Radius + obj.Radius) // 如果两者已经重叠 { tmpMax = 0; } else { double tmp = mod - obj.Radius - listObj.Radius; // 计算能走的最长距离,好像这么算有一点误差? tmp = tmp / Math.Cos(Math.Atan2(orgDeltaY, orgDeltaX) - moveVec.Angle()); if (tmp < 0 || tmp > uint.MaxValue || tmp == double.NaN) { tmpMax = uint.MaxValue; } else tmpMax = tmp; } break; } case ShapeType.Square: { // if (obj.WillCollideWith(listObj, obj.Position)) // tmpMax = 0; // else tmpMax = MaxMoveToSquare(obj, listObj); // break; if (obj.WillCollideWith(listObj, obj.Position)) tmpMax = 0; else { // 二分查找最大可能移动距离 int left = 0, right = (int)moveVec.Length(); while (left < right - 1) { int mid = (right - left) / 2 + left; if (obj.WillCollideWith(listObj, obj.Position + new XY((int)(mid * Math.Cos(moveVec.Angle())), (int)(mid * Math.Sin(moveVec.Angle()))))) { right = mid; } else left = mid; } tmpMax = (uint)left; } break; } default: tmpMax = uint.MaxValue; break; } if (tmpMax < maxDistance) maxDistance = tmpMax; } } } } finally { // maxLen = Math.Min(maxOnlyConsiderWall, maxDistance); //最大可能距离的最小值 listLock.ExitReadLock(); } } // return maxLen; return maxDistance; } readonly IMap gameMap; private readonly Tuple, ReaderWriterLockSlim>[] lists; public CollisionChecker(IMap gameMap) { this.gameMap = gameMap; lists = new Tuple, ReaderWriterLockSlim>[gameMap.GameObjDict.Count]; int i = 0; foreach (var keyValuePair in gameMap.GameObjDict) { lists[i++] = new Tuple, ReaderWriterLockSlim>(keyValuePair.Value as IList, gameMap.GameObjLockDict[keyValuePair.Key]); } } } }