using System.Collections.Generic; using System.IO; using TGS; using Unity.Mathematics; using UnityEngine; using System.Diagnostics; public class MapHex { public const float Length = 1.73205f; public const float Radius = 1.5f; public int index; public int Nx; public int Nz; public int Ax; public int Ay; public int Az; public bool CanMove; public float Wx; public float Wz; public int CityID; public void SetData( int nWidth, int nHeight, int nIndex, int type ) { //index = Nz * nWidth + Nx index = nIndex; Nz = nIndex/nWidth; Nx = nIndex - Nz *nWidth; Az = Nz; Ax = Nx-Nz/2; Ay = -Ax-Az; Wx = (Nx + Nz * 0.5f - Nz / 2 ) * (Radius*2.0f); Wz = Nz * (Length*1.5f); CanMove = type != 0; } public override string ToString() { return $"index={index}, NC=({Nx}, {Nz}), AC=({Ax}, {Ay}, {Az}), WC=({Wx}, 0, {Wz}), CanMove={CanMove}, City ID={CityID}"; } public bool IsMyNext(MapHex hex) { int xOffset = math.abs(hex.Ax- Ax); int zOffset = math.abs(hex.Az-Az); int yOffset = math.abs(hex.Ay-Ay); return xOffset <= 1 && zOffset <= 1 && yOffset <= 1; } public bool IsMe(MapHex hex) { return hex.Nx == Nx && hex.Nz == Nz; } public int Distance(MapHex hex) { int xOffset = math.abs(hex.Ax- Ax); int zOffset = math.abs(hex.Az-Az); int yOffset = math.abs(hex.Ay-Ay); return math.max(yOffset, math.max(xOffset, zOffset)); } } public class PathNode { public MapHex hex; public HexDir dir; //public List childs; public PathNode parent; public CityRoad cr; public int crIndex; public bool goRight; public PathNode() { //childs = new List(); } } public class CityRoad { public MapHex [] road; public void GetNextHex(MapHex hex, out MapHex left, out int leftIndex, out MapHex right, out int rightIndex) { int index = -1; for(int i = 0; i < road.Length; i ++) { if(road[i].IsMe(hex)) { index = i; break; } } if(index == -1) { UnityEngine.Debug.LogError($"CityRoad GetNextHex not found index, hex={hex}"); left = null; right = null; leftIndex = 0; rightIndex = 0; return; } leftIndex = index -1; if(leftIndex < 0) leftIndex = road.Length - 1; rightIndex = index +1; if(rightIndex >= road.Length) rightIndex = 0; left = road[leftIndex]; right = road[rightIndex]; UnityEngine.Debug.Log($"GetNextHex 1 rsult, index={index}, leftIndex={leftIndex}, rightIndex={rightIndex}"); UnityEngine.Debug.Log($"hex={hex}"); UnityEngine.Debug.Log($"left={left}"); UnityEngine.Debug.Log($"right={right}"); } public void GetNextHex(int index, bool goRight, out MapHex next, out int nextIndex) { if(goRight) { index ++; if(index >= road.Length) index = 0; } else { index --; if(index < 0) index = road.Length - 1; } nextIndex = index; next = road[index]; UnityEngine.Debug.Log($"GetNextHex 2 index={index}, goRight={goRight}, nextIndex={nextIndex}"); UnityEngine.Debug.Log($"next={next}"); } public List GetPath(MapHex start, MapHex end) { List p1 = new List(); List p2 = new List(); int indexStart = -1; int indexEnd = -1; for(int i = 0; i < road.Length; i ++) { if(road[i].IsMe(start)) { indexStart = i; } else if(road[i].IsMe(end)) { indexEnd = i; } } if(indexStart == -1 || indexEnd == -1) { UnityEngine.Debug.LogError($"CityRoad GetPath not found index, start={start}, end={end}"); return null; } int indexCheck = indexStart; while(indexCheck != indexEnd) { indexCheck++; if(indexCheck>= road.Length) { indexCheck = 0; } p1.Add(road[indexCheck]); } indexCheck = indexStart; while(indexCheck != indexEnd) { indexCheck--; if(indexCheck < 0) { indexCheck = road.Length-1; } p2.Add(road[indexCheck]); } return p1.Count > p2.Count ? p2 : p1; } } public enum HexDir { None = 0, RightUp, RightDown, LeftUp, LeftDown, Right, Left, UpRL, DownRL, } [ExecuteAlways] public class MapPathfind : MonoBehaviour { public TerrainGridSystem TGS; MapData m_mapData; public bool Reload; public MapHex [] m_hexes; // width x轴 // height z轴 public int startNx; public int startNz; public int endNx; public int endNz; public bool FindPath; MapHex m_Start; MapHex m_End; Dictionary m_Roads; public GameObject PathGo; public GameObject RoundGo; public GameObject CityGo; public GameObject StartGo; public GameObject EndGo; PathNode m_EndNode; public HashSet m_CloseList; public bool DestroyPath; void Start() { Reload = true; // 19,3 0,15 } void CreateMap() { m_Roads = new Dictionary(); string jsonPath = Application.dataPath + "/Editor/MapPath/MapData_MapsTest.json"; string txt = File.ReadAllText(jsonPath); m_mapData = JsonUtility.FromJson(txt); UnityEngine.Debug.Log($"width={m_mapData.Width}, height={m_mapData.Height}, data length={m_mapData.BlocksData.Count}"); m_hexes = new MapHex[m_mapData.BlocksData.Count]; for(int i = 0; i < m_mapData.BlocksData.Count; i ++) { MapHex hex = new MapHex(); hex.SetData(m_mapData.Width, m_mapData.Height, i, m_mapData.BlocksData[i].TerrainTypeIndex); m_hexes[i] = hex; UnityEngine.Debug.Log(hex.ToString()); } AddCity(1, 10, 21, 1 ); AddCity(2, 12, 21, 1 ); AddCity(3, 4, 19, 1 ); AddCity(4, 16, 19, 1 ); AddCity(5, 14, 18, 1 ); AddCity(6, 12, 17, 1 ); AddCity(7, 4, 16, 1 ); AddCity(8, 2, 15, 1 ); AddCity(9, 14, 13, 1 ); AddCity(10, 6, 12, 1 ); AddCity(11, 15, 11, 1 ); AddCity(12, 9, 10, 1 ); AddCity(13, 9, 6, 1 ); AddCity(14, 6, 21, 2 ); AddCity(15, 16, 16, 2 ); AddCity(16, 3, 11, 2 ); AddCity(17, 13, 8, 2 ); AddCity(18, 9, 15, 3 ); } public void AddCity(int nCityID, int Nx, int Nz, int range ) { MapHex center = m_hexes[GetIndex(Nx, Nz)]; // 找最外一圈路径 List RoundRoad = new List(); // 获取建筑所在的格子 int MaxX = center.Ax + range; int MinX = center.Ax - range; int MaxY = center.Ay + range; int MinY = center.Ay - range; int MaxZ = center.Az + range; int MinZ = center.Az - range; for(int x = MinX; x <= MaxX; x ++) { for(int y = MinY; y <= MaxY; y ++) { int z = -x-y; if(z >= MinZ && z <= MaxZ) { MapHex hex = GetMapHex(x, y, z); if(x == MinX || x == MaxX || y == MinY || y == MaxY || z == MinZ || z == MaxZ) { RoundRoad.Add(hex); } else { hex.CityID = nCityID; UnityEngine.Debug.Log(hex.ToString()); /* var go = Instantiate(CityGo); go.transform.SetParent(transform); go.transform.position = new Vector3(hex.Wx, 0, hex.Wz); */ } } } } int RoadNodeCount = RoundRoad.Count; List rounds = new List(); MapHex next = RoundRoad[0]; rounds.Add(next); RoundRoad.RemoveAt(0); while(RoundRoad.Count > 0) { UnityEngine.Debug.Log($"find next, city={nCityID}, count={RoundRoad.Count}, current={next}"); MapHex nextHex = null; for(int i = 0; i < RoundRoad.Count; i ++) { if(next.IsMyNext(RoundRoad[i])) { next = RoundRoad[i]; RoundRoad.RemoveAt(i); rounds.Add(next); nextHex = next; break; } } if(nextHex == null) { UnityEngine.Debug.LogError("nextHex not found ..."); break; } } if(rounds.Count == RoadNodeCount) { CityRoad cr = new CityRoad(); cr.road = rounds.ToArray(); m_Roads.Add(nCityID, cr); /* for(int i = 0; i < cr.road.Length; i ++) { var go = Instantiate(RoundGo); go.transform.SetParent(transform); go.transform.position = new Vector3(cr.road[i].Wx, 0, cr.road[i].Wz); } //*/ } else { UnityEngine.Debug.LogError($"Road create error, city={nCityID}, rounds.Count:{rounds.Count} != RoadNodeCount:{RoadNodeCount}"); } /* UnityEngine.Debug.Log("Road Start ========================= count=" +rounds.Count); for(int i = 0; i < rounds.Count; i ++) { UnityEngine.Debug.Log(rounds[i]); } UnityEngine.Debug.Log("Road End ========================="); */ } int GetIndex(int Nx, int Nz) { return Nz*m_mapData.Width+Nx; } void Update() { if(Reload) { Reload = false; CreateMap(); } if(FindPath) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); DoFind2(); stopwatch.Stop(); FindPath = false; UnityEngine.Debug.Log($"DoFind2 执行时间: {stopwatch.ElapsedMilliseconds} 毫秒"); } if(DestroyPath) { DestroyPath = false; //删除本节点下所有的GameObect foreach (Transform child in transform) { DestroyImmediate(child.gameObject); } } } HexDir GetTargetPositionDir(MapHex start, MapHex end) { var dir = GetLineDir(start, end); if(dir != HexDir.None) return dir; if(end.Az > start.Az) { if(end.Ax < start.Ax && end.Ay < start.Ay) { return HexDir.UpRL; } else if(end.Ay > start.Ay) { return HexDir.LeftUp; } else { return HexDir.RightUp; } } else { if(end.Ax > start.Ax && end.Ay > start.Ay) { return HexDir.DownRL; } else if(end.Ax < start.Ax) { return HexDir.LeftDown; } else { return HexDir.RightDown; } } } HexDir GetLineDir(MapHex start, MapHex end) { if(start.Ax == end.Ax) { if(end.Az > start.Az) { return HexDir.RightUp; } else { return HexDir.LeftDown; } } else if( start.Az == end.Az) { if(end.Ax > start.Ax) { return HexDir.Right; } else { return HexDir.Left; } } else if( start.Ay == end.Ay) { if(end.Az > start.Az) { return HexDir.LeftUp; } else { return HexDir.RightDown; } } return HexDir.None; } void AddPathNode(PathNode node, PathNode parent) { node.parent = parent; /* PathNode result = null; PathNode check = parent.parent; if(check == null) { node.parent = parent; return; } while(check.parent != null) { if(check.hex.IsMyNext(node.hex)) { result = check; } check = check.parent; } if(result != null) { node.parent = result; } else { node.parent = parent; } */ } bool GetNextNode(PathNode currentNode, List openList) { var dir = currentNode.dir; var nextHex = GetNextHex(currentNode.hex, dir); if(nextHex != null) { // 下一格是建筑,那当前这一格,肯定就在路点上 if(nextHex.CityID > 0) { // 如果当前格没有路点信息,则找左右两个路点作为下一格 if(currentNode.cr == null) { m_Roads.TryGetValue(nextHex.CityID, out CityRoad cr); if(cr == null) { UnityEngine.Debug.LogError("City road not found, city id=" + nextHex.CityID); return false; } cr.GetNextHex(currentNode.hex, out MapHex left, out int leftIndex, out MapHex right, out int rightIndex); if(!m_CloseList.TryGetValue(left.index, out int hexIndex)) { PathNode nodeLeft = new PathNode(); nodeLeft.hex = left; nodeLeft.dir = GetTargetPositionDir(left, m_End); nodeLeft.cr = cr; nodeLeft.crIndex = leftIndex; nodeLeft.goRight = false; //nodeLeft.parent = currentNode; AddPathNode(nodeLeft, currentNode); if(m_End.IsMe(left)) { m_EndNode = nodeLeft; return true; } openList.Add(nodeLeft); } if(!m_CloseList.TryGetValue(right.index, out int hexIndex2)) { PathNode nodeRight = new PathNode(); nodeRight.hex = right; nodeRight.dir = GetTargetPositionDir(right, m_End); nodeRight.cr = cr; nodeRight.crIndex = rightIndex; nodeRight.goRight = true; //nodeRight.parent = currentNode; AddPathNode(nodeRight, currentNode); if(m_End.IsMe(right)) { m_EndNode = nodeRight; return true; } openList.Add(nodeRight); } } else // 当前格本身就在路点上,则找下一格路点 { currentNode.cr.GetNextHex(currentNode.crIndex, currentNode.goRight, out MapHex next, out int nextIndex); if(!m_CloseList.TryGetValue(next.index, out int hexIndex2)) { PathNode nodeNext = new PathNode(); nodeNext.hex = next; nodeNext.cr = currentNode.cr; nodeNext.dir = GetTargetPositionDir(next, m_End); nodeNext.goRight = currentNode.goRight; nodeNext.crIndex = nextIndex; //nodeNext.parent = currentNode; AddPathNode(nodeNext, currentNode); if(m_End.IsMe(next)) { m_EndNode = nodeNext; return true; } openList.Add(nodeNext); } } } else { PathNode nextNode = new PathNode(); nextNode.hex = nextHex; nextNode.dir = dir; var dirNext = GetLineDir(nextHex, m_End); if(dirNext != HexDir.None) { nextNode.dir = dirNext; } nextNode.parent = currentNode; AddPathNode(nextNode, currentNode); if(m_End.IsMe(nextHex)) { m_EndNode = nextNode; return true; } openList.Add(nextNode); } } return false; } void DoFind2() { m_EndNode = null; m_CloseList = new HashSet(); List m_paths = new List(); m_Start = m_hexes[GetIndex(startNx, startNz)]; m_End = m_hexes[GetIndex(endNx, endNz)]; StartGo.transform.position = new Vector3(m_Start.Wx, 0, m_Start.Wz); EndGo.transform.position = new Vector3(m_End.Wx, 0, m_End.Wz); List openList = new List(); PathNode path = new PathNode(); path.hex = m_Start; path.dir = GetTargetPositionDir(m_Start, m_End); openList.Add(path); List hexList = new List(); while(openList.Count > 0) { UnityEngine.Debug.Log("START ============================================== openList.Count=" + openList.Count); List newOpenList = new List(); for(int i = 0; i < openList.Count; i ++) { hexList.Clear(); var node = openList[i]; m_CloseList.Add(node.hex.index); UnityEngine.Debug.Log($"node.dir={node.dir}, node={node.hex}"); switch(node.dir) { case HexDir.DownRL: { node.dir = HexDir.LeftDown; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("LeftDown go end"); goto End; } node.dir = HexDir.RightDown; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("RightDown go end"); goto End; } } break; case HexDir.UpRL: { node.dir = HexDir.LeftUp; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("LeftUp go end"); goto End; } node.dir = HexDir.RightUp; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("RightUp go end"); goto End; } } break; /* case HexDir.LeftUp: { var nextHex1 = GetNextHex(node.hex, HexDir.LeftUp); var nextHex2 = GetNextHex(node.hex, HexDir.Left); var dis1 = nextHex1.Distance(m_End); var dis2 = nextHex2.Distance(m_End); if(dis1 > dis2) { node.dir = HexDir.Left; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("LeftUp HexDir.Left go end"); goto End; } } else { node.dir = HexDir.LeftUp; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("HexDir.LeftUp go end"); goto End; } } } break; case HexDir.LeftDown: { var nextHex1 = GetNextHex(node.hex, HexDir.LeftDown); var nextHex2 = GetNextHex(node.hex, HexDir.Left); var dis1 = nextHex1.Distance(m_End); var dis2 = nextHex2.Distance(m_End); if(dis1 > dis2) { node.dir = HexDir.Left; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("LeftDown HexDir.Left go end"); goto End; } } else { node.dir = HexDir.LeftDown; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("HexDir.LeftDown go end"); goto End; } } } break; case HexDir.RightUp: { var nextHex1 = GetNextHex(node.hex, HexDir.RightUp); var nextHex2 = GetNextHex(node.hex, HexDir.Right); var dis1 = nextHex1.Distance(m_End); var dis2 = nextHex2.Distance(m_End); if(dis1 > dis2) { node.dir = HexDir.Right; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("RightUp HexDir.Right go end"); goto End; } } else { node.dir = HexDir.RightUp; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("HexDir.RightUp go end"); goto End; } } } break; case HexDir.RightDown: { var nextHex1 = GetNextHex(node.hex, HexDir.RightDown); var nextHex2 = GetNextHex(node.hex, HexDir.Right); var dis1 = nextHex1.Distance(m_End); var dis2 = nextHex2.Distance(m_End); if(dis1 > dis2) { node.dir = HexDir.Right; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("RightDown HexDir.Right go end"); goto End; } } else { node.dir = HexDir.RightDown; if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("HexDir.RightDown go end"); goto End; } } } break; //*/ default: { if(GetNextNode(node, newOpenList)) { UnityEngine.Debug.Log("default go end"); goto End; } } break; } } openList.Clear(); openList.AddRange(newOpenList); UnityEngine.Debug.Log("NEXT ============================================== openList.Count=" + openList.Count); } End: PathNode pathNode2 = m_EndNode; while(pathNode2.parent != null) { PathNode check = pathNode2.parent; while(check.parent != null) { var dir = GetLineDir(pathNode2.hex, check.hex); if(dir != HexDir.None) { List list = new List(); MapHex hex = pathNode2.hex; while(true) { MapHex nextHex = GetNextHex(hex, dir); if(nextHex.CityID <= 0) { if(nextHex.IsMe(check.hex)) { break; } list.Add(nextHex); hex = nextHex; } else { list.Clear(); break; } } if(list.Count > 0) { PathNode current = pathNode2; while(list.Count > 0) { PathNode newNode = new PathNode(); newNode.hex = list[0]; current.parent = newNode; current = newNode; list.RemoveAt(0); } current.parent = check; } } check = check.parent; } pathNode2 = pathNode2.parent; } int nCount = 0; PathNode pathNode = m_EndNode; while(pathNode.parent != null) { UnityEngine.Debug.Log(pathNode.hex); var go = Instantiate(PathGo); go.transform.SetParent(transform); go.transform.position = new Vector3(pathNode.hex.Wx, 0, pathNode.hex.Wz); nCount ++; pathNode = pathNode.parent; } UnityEngine.Debug.Log("all path node count="+ nCount); } MapHex GetMapHex(int Ax, int Ay, int Az) { int Nz = Az; int Nx = Ax+Az/2; //UnityEngine.Debug.Log($"GetMapHex Ax={Ax}, Ay={Ay}, Az={Az}, Nx={Nx}, Nz={Nz}"); if(Nx < 0 || Nx > m_mapData.Width-1) { UnityEngine.Debug.LogError($"GetMapHex 1 not found Nx={Nx}, Nz={Nz}"); return null; } if(Nz < 0 || Nz > m_mapData.Height-1) { UnityEngine.Debug.LogError($"GetMapHex 2 not found Nx={Nx}, Nz={Nz}"); return null; } int index = GetIndex(Nx, Nz); return m_hexes[index]; } /* */ MapHex GetNextHex(MapHex hex, HexDir dir) { switch(dir) { case HexDir.RightUp: { //往右上走,Ax不变,Az+1 int NextAx = hex.Ax; int NextAz = hex.Az+1; int NextAy = -NextAx-NextAz; return GetMapHex(NextAx, NextAy, NextAz); } case HexDir.RightDown: { // 往右下走,Ay不变,Az-1 int NextAz = hex.Az-1; int NextAy = hex.Ay; int NextAx = -NextAz-NextAy; return GetMapHex(NextAx, NextAy, NextAz); } case HexDir.LeftUp: { //往左上走,Ay不变,Az+1 int NextAy = hex.Ay; int NextAz = hex.Az+1; int NextAx = -NextAz-NextAy; return GetMapHex(NextAx, NextAy, NextAz); } case HexDir.LeftDown: { //往左下走,Ax不变,Az-1 int NextAx = hex.Ax; int NextAz = hex.Az-1; int NextAy = -NextAx-NextAz; return GetMapHex(NextAx, NextAy, NextAz); } case HexDir.Right: { //往右走,Az不变,Ay-1 int NextAz = hex.Az; int NextAy = hex.Ay-1; int NextAx = -NextAz-NextAy; return GetMapHex(NextAx, NextAy, NextAz); } case HexDir.Left: { //往左走,Az不变,Ay+1 int NextAz = hex.Az; int NextAy = hex.Ay+1; int NextAx = -NextAz-NextAy; return GetMapHex(NextAx, NextAy, NextAz); } default: UnityEngine.Debug.LogError("GetNextHex invalid dir, dir=" + dir); return null; } } }