using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using UnityEngine; #if FUNCELL_EDITOR namespace PathEditor.Proxy.Editor #else namespace PathEditor.Proxy.Plugin #endif { public enum PathGridType : byte { None = 0, //普通地面 Block = 1, //阻挡 Jump = 2, //可跳跃阻挡 Water = 3, //水面 Grass = 4, //草地 Stone = 5, //砖石地面 Sand = 6, //沙地 Marsh = 7, //沼泽 Wood = 8, //木地板 Snow = 9, //雪地 UserBlock = 10, //用户设置的阻挡 Safe = 11, //安全区 Num, } public class PathGirdData { public enum Flag : byte { None = 0, AddBlock = 1, RemoveBlock = 2, } public struct Point { public int x, y; public Point(int _x, int _y) { x = _x; y = _y; } } #region//静态变量 public const int MaxRowCount = 512; public const int MaxColCount = 512; public static PathGridType[,] _cacheMergedData = new PathGridType[MaxRowCount, MaxColCount]; //path grid的每个单元格的高度,大小为float public static short[] _cacheHeightMap = new short[MaxRowCount * MaxColCount]; //path grid的每个单元格周围不是none类型的个数,大小为byte public static byte[,] _cacheMergeWeightData = new byte[MaxRowCount, MaxColCount]; #endregion #region//共有变量 //场景名称 public string LevelName = string.Empty; //画刷的宽度 public int WeightRadius = 0; //场景的path grid的列数和行数 public int NumberOfColumns = 0; public int NumberOfRows = 0; //path grid的单元格的大小 public float CellSize = 1.0f; //[PathGrid]游戏对象的位置 public Vector3 Position = Vector3.zero; //单元格显示的高度的限制 public Vector2 HeightRangeLimit = new Vector2(float.MinValue, float.MaxValue); //path grid的每个单元格的类型,容量为numberOfColumns*numberOfRows,一个类型大小为byte public PathGridType[,] MergedData = null; //path grid的每个单元格的高度,大小为float public short[] HeightMap = null; //path grid的每个单元格周围不是none类型的个数,大小为byte public byte[,] MergeWeightData = null; #endregion #region//私有变量 private float _recipCellSize = 0f; #endregion #region//属性 public float RecipCellSize { get { return _recipCellSize; } } #endregion #region//共有函数 public void SetCellSize(float cellSize) { CellSize = cellSize; _recipCellSize = 1f / CellSize; } //获取int public static int GetInt(byte[] bytes, ref int readLen) { var result = BitConverter.ToInt32(bytes, readLen); readLen += 4; return result; } //获取int16 public static Int16 GetInt16(byte[] bytes, ref int readLen) { var result = BitConverter.ToInt16(bytes, readLen); readLen += 2; return result; } //获取uint16 public static UInt16 GetUInt16(byte[] bytes, ref int readLen) { var result = BitConverter.ToUInt16(bytes, readLen); readLen += 2; return result; } //获取int8 public static byte GetInt8(byte[] bytes, ref int readLen) { var result = bytes[readLen]; readLen += 1; return result; } //获fload public static float GetFloat(byte[] bytes, ref int readLen) { var result = BitConverter.ToSingle(bytes, readLen); readLen += 4; return result; } //获取string public static string GetString(byte[] bytes, int size, ref int readLen) { var result = System.Text.Encoding.UTF8.GetString(bytes, readLen, size).Trim((char)0); readLen += size; return result; } //从文件流中载入数据 public bool Load(byte[] bytes) { int readLen = 0; int count = GetInt(bytes, ref readLen); LevelName = GetString(bytes, count, ref readLen); WeightRadius = GetInt(bytes, ref readLen); NumberOfColumns = GetInt(bytes, ref readLen); NumberOfRows = GetInt(bytes, ref readLen); CellSize = GetFloat(bytes, ref readLen); Position = new Vector3(GetFloat(bytes, ref readLen), GetFloat(bytes, ref readLen), GetFloat(bytes, ref readLen)); HeightRangeLimit = new Vector2(GetFloat(bytes, ref readLen), GetFloat(bytes, ref readLen)); if(NumberOfColumns > MaxColCount || NumberOfRows > MaxRowCount) { UnityEngine.Debug.LogErrorFormat("Load PathGridData Failed, NumberOfColumns = {0}, NumberOfRows = {1}", NumberOfColumns, NumberOfRows); return false; } int listCount = GetInt(bytes, ref readLen); MergedData = _cacheMergedData; int counter = 0; int curRow = 0; int curCol = 0; for (int i = 0; i < listCount; ++i) { var curCount = GetInt16(bytes, ref readLen); var curValue = GetInt8(bytes, ref readLen); for (int j = counter; j < (counter + curCount); ++j) { MergedData[curRow, curCol] = (PathGridType)curValue; ++curCol; if (curCol >= NumberOfColumns) { ++curRow; curCol = 0; } } counter += curCount; } HeightMap = _cacheHeightMap; unsafe { fixed (byte* ptr = bytes) { var srcPrt = new IntPtr((void*)(ptr + readLen)); Marshal.Copy(srcPrt, HeightMap, 0, NumberOfRows * NumberOfColumns); } } readLen += NumberOfRows * NumberOfColumns * 2; listCount = GetInt(bytes, ref readLen); MergeWeightData = _cacheMergeWeightData; counter = 0; curRow = 0; curCol = 0; for (int i = 0; i < listCount; ++i) { var curCount = GetInt16(bytes, ref readLen); var curValue = GetInt8(bytes, ref readLen); for (int j = counter; j < (counter + curCount); ++j) { MergeWeightData[curRow, curCol] = curValue; ++curCol; if (curCol >= NumberOfColumns) { ++curRow; curCol = 0; } } counter += curCount; } _recipCellSize = 1.0f / CellSize; return true; } //保存数据到文件流 public void Save(BinaryWriter w, PathData pb) { w.Write(LevelName.Length); w.Write(LevelName.ToCharArray()); w.Write(WeightRadius); w.Write(NumberOfColumns); w.Write(NumberOfRows); w.Write(CellSize); w.Write(Position.x); w.Write(Position.y); w.Write(Position.z); w.Write(HeightRangeLimit.x); w.Write(HeightRangeLimit.y); pb.MergeOutput(); if (pb.m_mergedPathGrid != null) { var coutList = new List(); var valueList = new List(); ushort curCount = 0; PathGridType curValue = 0; int allCount = 0; for (int j = 0; j < NumberOfRows; ++j) { for (int i = 0; i < NumberOfColumns; ++i) { var tvalue = pb.m_mergedPathGrid[j, i]; if (j == 0 && i == 0) { curValue = tvalue; } if (curValue == tvalue && curCount < ushort.MaxValue) { ++curCount; } else { allCount += curCount; coutList.Add(curCount); valueList.Add(curValue); curValue = tvalue; curCount = 1; } } } allCount += curCount; coutList.Add(curCount); valueList.Add(curValue); w.Write(coutList.Count); for (int i = 0; i < coutList.Count; ++i) { w.Write(coutList[i]); w.Write((byte)valueList[i]); } } if (pb.m_heightMap != null) { for (int j = 0; j < NumberOfRows; ++j) { for (int i = 0; i < NumberOfColumns; ++i) { ushort height = (ushort)(pb.m_heightMap[j, i] * 100f); w.Write(height); } } } if (pb.m_mergedWeightPathGrid != null) { var coutList = new List(); var valueList = new List(); ushort curCount = 0; byte curValue = 0; int allCount = 0; for (int j = 0; j < NumberOfRows; ++j) { for (int i = 0; i < NumberOfColumns; ++i) { var tvalue = pb.m_mergedWeightPathGrid[j, i]; if (j == 0 && i == 0) { curValue = tvalue; } if (curValue == tvalue && curCount < ushort.MaxValue) { ++curCount; } else { allCount += curCount; coutList.Add(curCount); valueList.Add(curValue); curValue = tvalue; curCount = 1; } } } allCount += curCount; coutList.Add(curCount); valueList.Add(curValue); w.Write(coutList.Count); for (int i = 0; i < coutList.Count; ++i) { w.Write(coutList[i]); w.Write(valueList[i]); } } } //获取格子类型 public PathGridType GetCellType(int column, int row) { if (!IsRowAndColValid(column, row)) return PathGridType.None; return MergedData[row, column]; } //行列是否有效 public bool IsRowAndColValid(int col, int row) { if (row >= 0 && row < NumberOfRows && col >= 0 && col < NumberOfColumns) { return true; } return false; } //是否是阻挡 public bool IsBlock(int column, int row) { if (!IsRowAndColValid(column, row)) return true; var value = GetCellType(column, row); switch (value) { case PathGridType.Block: case PathGridType.UserBlock: case PathGridType.None: case PathGridType.Jump: return true; } return false; } //是否是跳跃阻挡 public bool IsJump(int column, int row) { if (!IsRowAndColValid(column, row)) return false; var value = GetCellType(column, row); if (value == PathGridType.Jump) return true; return false; } //是否是安全区 public bool IsSafe(int column, int row) { if (!IsRowAndColValid(column, row)) return false; var value = GetCellType(column, row); if (value == PathGridType.Wood) return true; return false; } //获取生成的阻挡数据 public PathGridType GetBakeBlock(int column, int row) { if (!IsRowAndColValid(column, row)) return PathGridType.Block; var value = GetCellType(column, row); if (value == PathGridType.Block) { return PathGridType.Block; } return PathGridType.None; } #region //获取高度 public float GetHeight(int column, int row) { if (!IsRowAndColValid(column, row)) return 0f; return HeightMap[row * NumberOfColumns + column] / 100f; } public float GetHeight(ref Vector3 pos) { if (Position.y < HeightRangeLimit.x || Position.y > HeightRangeLimit.y) { return HeightRangeLimit.x; } float size = 1 / CellSize; int column = (int)((pos.x - Position.x) * size); int row = (int)((pos.z - Position.z) * size); if (column < 0 || column >= NumberOfColumns) { return HeightRangeLimit.x; } if (row < 0 || row >= NumberOfRows) { return HeightRangeLimit.x; } return GetHeight(column, row); } public float GetLerpHeight(int col, int row, float defaultHeight) { if (!IsRowAndColValid(col, row)) { return defaultHeight; } if(IsBlock(col, row)) { //阻挡不纳入高度插值 return defaultHeight; } return GetHeight(col, row); } public float GetHeight(float x, float z) { float xPos = x - Position.x; float zPos = z - Position.z; int col = (int)(xPos / CellSize); int row = (int)(zPos / CellSize); if (!IsRowAndColValid(col, row)) { return 0f; } float xLerp = (xPos - col * CellSize) / CellSize; float zLerp = (zPos - row * CellSize) / CellSize; float height0 = GetHeight(col, row); float height1 = height0; float height2 = height0; float height3 = height0; if (xLerp <= 0.5f && zLerp <= 0.5f) { height1 = GetLerpHeight(col - 1, row, height0); height2 = GetLerpHeight(col, row - 1, height0); height3 = GetLerpHeight(col - 1, row - 1, height0); xLerp = 0.5f - xLerp; zLerp = 0.5f - zLerp; } else if (xLerp <= 0.5f && zLerp >= 0.5f) { height1 = GetLerpHeight(col - 1, row, height0); height2 = GetLerpHeight(col, row + 1, height0); height3 = GetLerpHeight(col - 1, row + 1, height0); xLerp = 0.5f - xLerp; zLerp = zLerp - 0.5f; } else if (xLerp >= 0.5f && zLerp >= 0.5f) { height1 = GetLerpHeight(col + 1, row, height0); height2 = GetLerpHeight(col, row + 1, height0); height3 = GetLerpHeight(col + 1, row + 1, height0); xLerp = xLerp - 0.5f; zLerp = zLerp - 0.5f; } else if (xLerp >= 0.5f && zLerp <= 0.5f) { height1 = GetLerpHeight(col + 1, row, height0); height2 = GetLerpHeight(col, row - 1, height0); height3 = GetLerpHeight(col + 1, row - 1, height0); xLerp = xLerp - 0.5f; zLerp = 0.5f - zLerp; } float minHeight = Mathf.Min(Mathf.Min(height0, height1), Mathf.Min(height2, height3)); float maxHeight = Mathf.Max(Mathf.Max(height0, height1), Mathf.Max(height2, height3)); //if (maxHeight - minHeight > CellSize) // return height0; return Mathf.Lerp(Mathf.Lerp(height0, height1, xLerp), Mathf.Lerp(height2, height3, xLerp), zLerp); } #endregion public bool IsBlock(Vector3 pos) { if (Position.y < HeightRangeLimit.x || Position.y > HeightRangeLimit.y) { return false; } int column = (int)((pos.x - Position.x) * _recipCellSize); int row = (int)((pos.z - Position.z) * _recipCellSize); if (column < 0 || column >= NumberOfColumns) { return false; } if (row < 0 || row >= NumberOfRows) { return false; } return IsBlock(column, row); } public bool IsValid(Vector2 pos) { int column = (int)((pos.x - Position.x) * _recipCellSize); int row = (int)((pos.y - Position.z) * _recipCellSize); if (column < 0 || column >= NumberOfColumns) { return false; } if (row < 0 || row >= NumberOfRows) { return false; } return true; } public bool CanJump(int column, int row, float height) { if (column < 0 || column >= NumberOfColumns) { return false; } if (row < 0 || row >= NumberOfRows) { return false; } var cellType = MergedData[row, column]; if (cellType == PathGridType.None || cellType == PathGridType.Block || cellType == PathGridType.UserBlock) return false; if (GetHeight(column, row) > height) return false; return true; } public int GetCellID(ref Vector3 pos) { int column = (int)((pos.x - Position.x) * _recipCellSize); int row = (int)((pos.z - Position.z) * _recipCellSize); if (column < 0 || column >= NumberOfColumns) { return -1; } if (row < 0 || row >= NumberOfRows) { return -1; } return (row << 16) | column; } public int GetCellID(ref Vector2 pos) { int column = (int)((pos.x - Position.x) * _recipCellSize); int row = (int)((pos.y - Position.z) * _recipCellSize); if (column < 0 || column >= NumberOfColumns) { return -1; } if (row < 0 || row >= NumberOfRows) { return -1; } return (row << 16) | column; } public int GetCellID(float x, float y) { int column = (int)((x - Position.x) * _recipCellSize); int row = (int)((y - Position.z) * _recipCellSize); if (column < 0 || column >= NumberOfColumns) { return -1; } if (row < 0 || row >= NumberOfRows) { return -1; } return (row << 16) | column; } public int GetCellID(int column, int row) { return (row << 16) | column; } public Vector2 GetCellPosition2d(int column, int row) { return new Vector2(column * CellSize, row * CellSize); } public Vector2 GetCellPosition2d(int id) { return new Vector2((id & 0xFFFF) * CellSize, (id >> 16) * CellSize); } public bool GetCellCoord(int id, out int column, out int row) { if (id == -1) { column = -1; row = -1; return false; } row = id >> 16; column = id & 0xFFFF; return true; } public bool IsBlock(Vector2 pos) { int column = (int)((pos.x - Position.x) * _recipCellSize); int row = (int)((pos.y - Position.z) * _recipCellSize); if (column < 0 || column >= NumberOfColumns) { return false; } if (row < 0 || row >= NumberOfRows) { return false; } return IsBlock(column, row); } public bool HasPathInRange(Vector3 pos, float radius = 1) { if (Position.y < HeightRangeLimit.x || Position.y > HeightRangeLimit.y) { return false; } int column = (int)((pos.x - Position.x) * _recipCellSize); int row = (int)((pos.z - Position.z) * _recipCellSize); if (column < 0 || column >= NumberOfColumns) { return false; } if (row < 0 || row >= NumberOfRows) { return false; } int cellRadius = (int)(radius * _recipCellSize); return _HasPathInRange(column, row, cellRadius); } public bool HasPathInRange(Vector2 pos, float radius = 1) { int column = (int)((pos.x - Position.x) * _recipCellSize); int row = (int)((pos.y - Position.z) * _recipCellSize); if (column < 0 || column >= NumberOfColumns) { return false; } if (row < 0 || row >= NumberOfRows) { return false; } int cellRadius = (int)(radius * _recipCellSize); return _HasPathInRange(column, row, cellRadius); } public bool[,] GetSolidity(bool transpose) { if (transpose) { bool[,] ret = new bool[NumberOfColumns, NumberOfRows]; for (int j = 0; j < NumberOfRows; ++j) { for (int i = 0; i < NumberOfColumns; ++i) { ret[i, j] = IsBlock(i, j); } } return ret; } else { bool[,] ret = new bool[NumberOfRows, NumberOfColumns]; for (int j = 0; j < NumberOfRows; ++j) { for (int i = 0; i < NumberOfColumns; ++i) { ret[j, i] = IsBlock(i, j); } } return ret; } } public byte[,] GetWeight(bool transpose) { if (transpose) { byte[,] ret = new byte[NumberOfColumns, NumberOfRows]; for (int j = 0; j < NumberOfRows; ++j) { for (int i = 0; i < NumberOfColumns; ++i) { ret[i, j] = MergeWeightData[j, i]; } } return ret; } else { byte[,] ret = new byte[NumberOfRows, NumberOfColumns]; for (int j = 0; j < NumberOfRows; ++j) { for (int i = 0; i < NumberOfColumns; ++i) { ret[j, i] = MergeWeightData[j, i]; } } return ret; } } bool _IsBlockJump(int column, int row) { if (column < 0 || column >= NumberOfColumns) { return true; } if (row < 0 || row >= NumberOfRows) { return true; } var cellType = MergedData[row, column]; if (cellType == PathGridType.None || cellType == PathGridType.Block || cellType == PathGridType.UserBlock) return true; return false; } bool _IsBlock(int column, int row) { if (!IsRowAndColValid(column, row)) return true; var cellType = MergedData[row, column]; if (cellType == PathGridType.None || cellType == PathGridType.Block || cellType == PathGridType.Jump || cellType == PathGridType.UserBlock) return true; return false; } bool _IsSafe(int column, int row) { if (!IsRowAndColValid(column, row)) return true; var cellType = MergedData[row, column]; if (cellType == PathGridType.Wood || cellType == PathGridType.None || cellType == PathGridType.Block || cellType == PathGridType.Jump || cellType == PathGridType.UserBlock) return true; return false; } public bool SetBlock(int column, int row, PathGridType type) { if (!IsRowAndColValid(column, row)) return false; MergedData[row, column] = type; return true; } bool _HasPathInRange(int column, int row, int radius) { int startRowPos = row - radius; int startColumnPos = column - radius; int endRowPos = row + radius; int endColumnPos = column + radius; for (int j = startRowPos; j <= endRowPos; j++) { for (int i = startColumnPos; i <= endColumnPos; i++) { if (!_IsBlock(i, j)) { return true; } } } return false; } bool _HasBlockInRange(int column, int row, int radius) { int startRowPos = row - radius; int startColumnPos = column - radius; int endRowPos = row + radius; int endColumnPos = column + radius; for (int j = startRowPos; j <= endRowPos; j++) { for (int i = startColumnPos; i <= endColumnPos; i++) { if (_IsBlock(i, j)) { return true; } } } return false; } //Ray casting technique described in paper: //A Fast Voxel Traversal Algorithm for Ray Tracing - John Amanatides, Andrew Woo //http://www.cse.yorku.ca/~amana/research/grid.pdf public bool Raycast2d(Vector2 start, Vector2 end, out Vector2 hit) { var p1 = new Vector2(start.x * _recipCellSize, start.y * _recipCellSize); var p2 = new Vector2(end.x * _recipCellSize, end.y * _recipCellSize); hit = Vector2.zero; if ((int)p1.x == (int)p2.x && (int)p1.y == (int)p2.y) { //since it doesn't cross any boundaries, there can't be a collision return false; } //find out which direction to step, on each axis var stepX = (p2.x > p1.x) ? 1 : -1; var stepY = (p2.y > p1.y) ? 1 : -1; var rayDirection = new Vector2(p2.x - p1.x, p2.y - p1.y); //find out how far to move on each axis for every whole integer step on the other var ratioX = rayDirection.x / rayDirection.y; var ratioY = rayDirection.y / rayDirection.x; if (rayDirection.y == 0.0f) ratioX = 0.0f; if (rayDirection.x == 0.0f) ratioY = 0.0f; var deltaY = p2.x - p1.x; var deltaX = p2.y - p1.y; //faster than Math.abs()... deltaX = deltaX < 0 ? -deltaX : deltaX; deltaY = deltaY < 0 ? -deltaY : deltaY; //initialise the integer test coordinates with the coordinates of the starting tile, in tile space ( integer ) //Note: using noralised version of p1 var testX = (int)p1.x; var testY = (int)p1.y; //initialise the non-integer step, by advancing to the next tile boundary / ( whole integer of opposing axis ) //if moving in positive direction, move to end of curent tile, otherwise the beginning var maxX = deltaX * ((stepX > 0) ? (1.0f - (p1.x % 1)) : (p1.x % 1)); var maxY = deltaY * ((stepY > 0) ? (1.0f - (p1.y % 1)) : (p1.y % 1)); var endTileX = (int)p2.x; var endTileY = (int)p2.y; var collisionPoint = new Vector2(); while (testX != endTileX || testY != endTileY) { if (maxX < maxY) { maxX += deltaX; testX += stepX; if (deltaX == 0.0f) { bool state = stepX > 0 ? (testX > endTileX) : (testX < endTileX); if (state) return false; } if (_IsBlock(testX, testY)) { collisionPoint.x = testX; if (stepX < 0) { //add one if going left collisionPoint.x += 1.0f; } collisionPoint.y = p1.y + ratioY * (collisionPoint.x - p1.x); collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; return true; } } else { maxY += deltaY; testY += stepY; if (deltaY == 0.0f) { bool state = stepY > 0 ? (testY > endTileY) : (testY < endTileY); if (state) return false; } if (_IsBlock(testX, testY)) { collisionPoint.y = testY; if (stepY < 0) { //add one if going up collisionPoint.y += 1.0f; } collisionPoint.x = p1.x + ratioX * (collisionPoint.y - p1.y); //scale up collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; return true; } } } return false; } public bool Raycast2dJump(Vector2 start, Vector2 end, out Vector2 hit) { var p1 = new Vector2(start.x * _recipCellSize, start.y * _recipCellSize); var p2 = new Vector2(end.x * _recipCellSize, end.y * _recipCellSize); hit = Vector2.zero; if ((int)p1.x == (int)p2.x && (int)p1.y == (int)p2.y) { //since it doesn't cross any boundaries, there can't be a collision return false; } //find out which direction to step, on each axis var stepX = (p2.x > p1.x) ? 1 : -1; var stepY = (p2.y > p1.y) ? 1 : -1; var rayDirection = new Vector2(p2.x - p1.x, p2.y - p1.y); //find out how far to move on each axis for every whole integer step on the other var ratioX = rayDirection.x / rayDirection.y; var ratioY = rayDirection.y / rayDirection.x; var deltaY = p2.x - p1.x; var deltaX = p2.y - p1.y; //faster than Math.abs()... deltaX = deltaX < 0 ? -deltaX : deltaX; deltaY = deltaY < 0 ? -deltaY : deltaY; //initialise the integer test coordinates with the coordinates of the starting tile, in tile space ( integer ) //Note: using noralised version of p1 var testX = (int)p1.x; var testY = (int)p1.y; //initialise the non-integer step, by advancing to the next tile boundary / ( whole integer of opposing axis ) //if moving in positive direction, move to end of curent tile, otherwise the beginning var maxX = deltaX * ((stepX > 0) ? (1.0f - (p1.x % 1)) : (p1.x % 1)); var maxY = deltaY * ((stepY > 0) ? (1.0f - (p1.y % 1)) : (p1.y % 1)); var endTileX = (int)p2.x; var endTileY = (int)p2.y; var collisionPoint = new Vector2(); while (testX != endTileX || testY != endTileY) { if (maxX < maxY) { maxX += deltaX; testX += stepX; if (_IsBlockJump(testX, testY)) { collisionPoint.x = testX; if (stepX < 0) { //add one if going left collisionPoint.x += 1.0f; } collisionPoint.y = p1.y + ratioY * (collisionPoint.x - p1.x); collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; return true; } } else { maxY += deltaY; testY += stepY; if (_IsBlockJump(testX, testY)) { collisionPoint.y = testY; if (stepY < 0) { //add one if going up collisionPoint.y += 1.0f; } collisionPoint.x = p1.x + ratioX * (collisionPoint.y - p1.y); //scale up collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; return true; } } } return false; } public bool Raycast2dSafe(Vector2 start, Vector2 end, out Vector2 hit) { var p1 = new Vector2(start.x * _recipCellSize, start.y * _recipCellSize); var p2 = new Vector2(end.x * _recipCellSize, end.y * _recipCellSize); hit = Vector2.zero; if ((int)p1.x == (int)p2.x && (int)p1.y == (int)p2.y) { //since it doesn't cross any boundaries, there can't be a collision return false; } //find out which direction to step, on each axis var stepX = (p2.x > p1.x) ? 1 : -1; var stepY = (p2.y > p1.y) ? 1 : -1; var rayDirection = new Vector2(p2.x - p1.x, p2.y - p1.y); //find out how far to move on each axis for every whole integer step on the other var ratioX = rayDirection.x / rayDirection.y; var ratioY = rayDirection.y / rayDirection.x; var deltaY = p2.x - p1.x; var deltaX = p2.y - p1.y; //faster than Math.abs()... deltaX = deltaX < 0 ? -deltaX : deltaX; deltaY = deltaY < 0 ? -deltaY : deltaY; //initialise the integer test coordinates with the coordinates of the starting tile, in tile space ( integer ) //Note: using noralised version of p1 var testX = (int)p1.x; var testY = (int)p1.y; //initialise the non-integer step, by advancing to the next tile boundary / ( whole integer of opposing axis ) //if moving in positive direction, move to end of curent tile, otherwise the beginning var maxX = deltaX * ((stepX > 0) ? (1.0f - (p1.x % 1)) : (p1.x % 1)); var maxY = deltaY * ((stepY > 0) ? (1.0f - (p1.y % 1)) : (p1.y % 1)); var endTileX = (int)p2.x; var endTileY = (int)p2.y; var collisionPoint = new Vector2(); while (testX != endTileX || testY != endTileY) { if (maxX < maxY) { maxX += deltaX; if (testX != endTileX) { testX += stepX; //Debug.Log(string.Format("====testX {0} testY {1}====", testX, testY)); if (_IsSafe(testX, testY)) { collisionPoint.x = testX; if (stepX < 0) { //add one if going left collisionPoint.x += 1.0f; } collisionPoint.y = p1.y + ratioY * (collisionPoint.x - p1.x); collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; //Debug.LogError("is safe 1"); return true; } } } else { maxY += deltaY; if (testY != endTileY) { testY += stepY; //Debug.Log(string.Format("====testX {0} testY {1}====", testX, testY)); if (_IsSafe(testX, testY)) { collisionPoint.y = testY; if (stepY < 0) { //add one if going up collisionPoint.y += 1.0f; } collisionPoint.x = p1.x + ratioX * (collisionPoint.y - p1.y); //scale up collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; //Debug.LogError("is safe 2"); return true; } } } } return false; } //是否有可跳跃的阻挡 bool _IsJumpBlock(int column, int row) { if (!IsRowAndColValid(column, row)) return true; if (MergedData[row, column] == PathGridType.Jump) return true; return false; } public bool Raycast2dJumpBlock(Vector2 start, Vector2 end, out Vector2 hit) { var p1 = new Vector2(start.x * _recipCellSize, start.y * _recipCellSize); var p2 = new Vector2(end.x * _recipCellSize, end.y * _recipCellSize); hit = Vector2.zero; if ((int)p1.x == (int)p2.x && (int)p1.y == (int)p2.y) { //since it doesn't cross any boundaries, there can't be a collision return false; } //find out which direction to step, on each axis var stepX = (p2.x > p1.x) ? 1 : -1; var stepY = (p2.y > p1.y) ? 1 : -1; var rayDirection = new Vector2(p2.x - p1.x, p2.y - p1.y); //find out how far to move on each axis for every whole integer step on the other var ratioX = rayDirection.x / rayDirection.y; var ratioY = rayDirection.y / rayDirection.x; var deltaY = p2.x - p1.x; var deltaX = p2.y - p1.y; //faster than Math.abs()... deltaX = deltaX < 0 ? -deltaX : deltaX; deltaY = deltaY < 0 ? -deltaY : deltaY; //initialise the integer test coordinates with the coordinates of the starting tile, in tile space ( integer ) //Note: using noralised version of p1 var testX = (int)p1.x; var testY = (int)p1.y; //initialise the non-integer step, by advancing to the next tile boundary / ( whole integer of opposing axis ) //if moving in positive direction, move to end of curent tile, otherwise the beginning var maxX = deltaX * ((stepX > 0) ? (1.0f - (p1.x % 1)) : (p1.x % 1)); var maxY = deltaY * ((stepY > 0) ? (1.0f - (p1.y % 1)) : (p1.y % 1)); var endTileX = (int)p2.x; var endTileY = (int)p2.y; var collisionPoint = new Vector2(); while (testX != endTileX || testY != endTileY) { if (maxX < maxY) { maxX += deltaX; testX += stepX; if (_IsJumpBlock(testX, testY)) { collisionPoint.x = testX; if (stepX < 0) { //add one if going left collisionPoint.x += 1.0f; } collisionPoint.y = p1.y + ratioY * (collisionPoint.x - p1.x); collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; return true; } } else { maxY += deltaY; testY += stepY; if (_IsJumpBlock(testX, testY)) { collisionPoint.y = testY; if (stepY < 0) { //add one if going up collisionPoint.y += 1.0f; } collisionPoint.x = p1.x + ratioX * (collisionPoint.y - p1.y); //scale up collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; return true; } } } return false; } public bool RaycastEx2d(Vector2 start, Vector2 end, out Vector2 hit, Func func) { var p1 = new Vector2(start.x * _recipCellSize, start.y * _recipCellSize); var p2 = new Vector2(end.x * _recipCellSize, end.y * _recipCellSize); hit = Vector2.zero; if ((int)p1.x == (int)p2.x && (int)p1.y == (int)p2.y) { //since it doesn't cross any boundaries, there can't be a collision return false; } //find out which direction to step, on each axis var stepX = (p2.x > p1.x) ? 1 : -1; var stepY = (p2.y > p1.y) ? 1 : -1; var rayDirection = new Vector2(p2.x - p1.x, p2.y - p1.y); //find out how far to move on each axis for every whole integer step on the other var ratioX = rayDirection.x / rayDirection.y; var ratioY = rayDirection.y / rayDirection.x; var deltaY = p2.x - p1.x; var deltaX = p2.y - p1.y; //faster than Math.abs()... deltaX = deltaX < 0 ? -deltaX : deltaX; deltaY = deltaY < 0 ? -deltaY : deltaY; //initialise the integer test coordinates with the coordinates of the starting tile, in tile space ( integer ) //Note: using noralised version of p1 var testX = (int)p1.x; var testY = (int)p1.y; //initialise the non-integer step, by advancing to the next tile boundary / ( whole integer of opposing axis ) //if moving in positive direction, move to end of curent tile, otherwise the beginning var maxX = deltaX * ((stepX > 0) ? (1.0f - (p1.x % 1)) : (p1.x % 1)); var maxY = deltaY * ((stepY > 0) ? (1.0f - (p1.y % 1)) : (p1.y % 1)); var endTileX = (int)p2.x; var endTileY = (int)p2.y; var collisionPoint = new Vector2(); while (testX != endTileX || testY != endTileY) { if (maxX < maxY) { maxX += deltaX; testX += stepX; if (func(testX, testY, _IsBlock(testX, testY))) { collisionPoint.x = testX; if (stepX < 0) { //add one if going left collisionPoint.x += 1.0f; } collisionPoint.y = p1.y + ratioY * (collisionPoint.x - p1.x); collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; return true; } } else { maxY += deltaY; testY += stepY; if (func(testX, testY, _IsBlock(testX, testY))) { collisionPoint.y = testY; if (stepY < 0) { //add one if going up collisionPoint.y += 1.0f; } collisionPoint.x = p1.x + ratioX * (collisionPoint.y - p1.y); //scale up collisionPoint.x *= CellSize; collisionPoint.y *= CellSize; hit = collisionPoint; return true; } } } return false; } #endregion } }