看到好多小伙伴似乎都想做六边形地图,我刚好在之前的demo里基于Unity3d实现了一个。
六边形地图编辑器怎么弄我就不发了,可以看看TX爸爸出的这篇博客http://gad.qq.com/program/translateview/7173811。
主要是关于寻路,AStar寻路有很多的优化方法,想要优化什么的自行百度,我就发个基础的代码(哪天我得空了可以再放一下分步讲解)。
using UnityEngine;
using System.Collections.Generic;
using System;
/// <summary>
/// 六边形地图的A*寻路,在使用时先调用AstarMainFunc方法,之后会根据输入的起始点下表改变路径上需要考虑的cell的父节点;
/// 之后该方法将父节点从终点寻址储存到移动功能类中;
/// 因为该节点是从终点寻址到起点,存在先进后出的读取要求,所以推荐用栈存储;
/// 在存储完后在移动功能类中调用ClearFValueAndGValue方法;
/// 在ClearFValueAndGValue方法被调用后才可以再进行下一次寻路;
/// 因此可以有单位同时移动,但不可以有单位同时寻路=》除非所有节点的父节点元素是一个数组;
/// 如果所有节点的父节点元素是一个数组,则在修改本类源码后,可以有该数组长度的数量的单位同时寻路。
/// 2017年12月14日 14:07:03
/// 最后一次修改人:朱珂杰
/// </summary>
class AStarMain : MonoBehaviour
{
private HexCell[] cellsForAll;
private List<int> cellsForOpenContainer;
private List<int> cellsForOpen;
private List<int> cellsForClose;
private int currentCellIndex;
private int hValue;
/// <summary>
/// HexGrid中的width,需要在代码中赋值
/// </summary>
private int width = 0;
private int height = 0;
/// <summary>
/// 某一点四周所有点的集合,第一个是该点右上角的点,然后按照顺时针直至该点左上角,y坐标为-x-z
/// 以下为备用元素,在地图大改时可能会需要
/// </summary>
//private HexCoordinates[] aroundHexCoordinates = {
// new HexCoordinates (0, 1),
// new HexCoordinates(1, 0),
// new HexCoordinates(1, -1),
// new HexCoordinates(0, -1),
// new HexCoordinates(-1, 0),
// new HexCoordinates(-1, 1),
//};
//private int[] aroundHexCellForIndex;
public void Awake()
{
cellsForOpenContainer = new List<int>();
cellsForOpen = new List<int>();
cellsForClose = new List<int>();
}
public void Start()
{
GetCells();
width = GameObject.Find("HexPlaneRoot").GetComponent<HexGrid>().width;
height = GameObject.Find("HexPlaneRoot").GetComponent<HexGrid>().height;
Debug.Log("得到cells");
}
private void GetCells()
{
cellsForAll = GameObject.Find("HexPlaneRoot").transform.GetComponentsInChildren<HexCell>();
}
/// <summary>
/// 寻路主函数,先不考虑行数很低时的情况;
/// 在运行结束后,根据cellForEndIndex下标对应的cell的父节点寻踪直至起始点得到路径
/// </summary>
/// <param name="cell">起始点</param>
public void AstarMainFunc(int cellForBeginIndex, int cellForEndIndex)
{
//1,将cellForBegin添加到关闭列表
cellsForClose.Add(cellForBeginIndex);
currentCellIndex = cellForBeginIndex;
AddCellsToOpen();
while (!cellsForOpen.Contains(cellForEndIndex))
{
//遍历开放列表找出所有点中F最小的点
int fValueMinIndexInOpen = 0;
if (cellsForOpen.Count > 1)
for (int i = 1; i < cellsForOpen.Count; i++)
{
//hValue
hValue = cellsForAll[cellForEndIndex].coordinates - cellsForAll[cellsForOpen[i]].coordinates;
cellsForAll[cellsForOpen[i]].fValue = cellsForAll[cellsForOpen[i]].gValue + hValue;
if (cellsForAll[cellsForOpen[i]].fValue < cellsForAll[cellsForOpen[fValueMinIndexInOpen]].fValue)
fValueMinIndexInOpen = i;
}
//将该点添加到关闭列表中
cellsForClose.Add(cellsForOpen[fValueMinIndexInOpen]);
//将该点从开放列表移除
//没做,忘了,记得补=。=
currentCellIndex = cellsForOpen[fValueMinIndexInOpen];
cellsForOpen.RemoveAt(fValueMinIndexInOpen);
AddCellsToOpen();
}
cellsForOpenContainer.Clear();
cellsForOpen.Clear();
cellsForClose.Clear();
ClearFValueAndGValue();
}
private void ClearFValueAndGValue()
{
foreach (HexCell cell in cellsForAll)
{
cell.fValue = 0;
cell.gValue = 0;
}
}
/// <summary>
/// 清理所有节点的父节点,在储存完栈后,于调用寻路的类中被调用
/// </summary>
public void ClearCellsFather()
{
foreach (HexCell cell in cellsForAll)
{
cell.hexCellFather = null;
}
}
/// <summary>
/// 添加当前点四周可添加的点进入开放列表,这个方法结束后,会针对新添加的点和重复的点刷新它内部的父节点和gValue;
/// 在调用此方法前,需要先对当前点的下表进行标定。
/// </summary>
private void AddCellsToOpen()
{
//行数,0开始计数
int row = currentCellIndex / width;
//取出当前点四周所有的新点进入开放列表
//这些写完后抽象成方法,因为这部分可能随着地图生成频繁修改
//左列
if (currentCellIndex % width == 0)
{
//底行,加两个
if (currentCellIndex / width == 0)
{
cellsForOpenContainer.Add(currentCellIndex + width);
cellsForOpenContainer.Add(currentCellIndex + 1);
}
//顶行
if (currentCellIndex / width == height - 1)
{
//偶行,0开始计数,加两个
if (row % 2 == 0)
{
cellsForOpenContainer.Add(currentCellIndex - width);
cellsForOpenContainer.Add(currentCellIndex + 1);
}
//奇行,0开始计数,加三个
else
{
cellsForOpenContainer.Add(currentCellIndex - width);
cellsForOpenContainer.Add(currentCellIndex - width + 1);
cellsForOpenContainer.Add(currentCellIndex + 1);
}
}
//中间行
if (currentCellIndex / width > 0 && currentCellIndex / width < height - 1)
{
//偶行,0开始计数,加三个
if (row % 2 == 0)
{
cellsForOpenContainer.Add(currentCellIndex - width);
cellsForOpenContainer.Add(currentCellIndex + 1);
cellsForOpenContainer.Add(currentCellIndex + width);
}
//奇行,0开始计数,加五个
else
{
cellsForOpenContainer.Add(currentCellIndex - width);
cellsForOpenContainer.Add(currentCellIndex - width + 1);
cellsForOpenContainer.Add(currentCellIndex + 1);
cellsForOpenContainer.Add(currentCellIndex + width);
cellsForOpenContainer.Add(currentCellIndex + width + 1);
}
}
}
//右列
if (currentCellIndex % width == width - 1)
{
//底行,加三个
if (currentCellIndex / width == 0)
{
cellsForOpenContainer.Add(currentCellIndex + width);
cellsForOpenContainer.Add(currentCellIndex - 1);
cellsForOpenContainer.Add(currentCellIndex + width - 1);
}
//顶行
if (currentCellIndex / width == height - 1)
{
//偶行,0开始计数,加三个
if (row % 2 == 0)
{
cellsForOpenContainer.Add(currentCellIndex - width);
cellsForOpenContainer.Add(currentCellIndex - 1);
cellsForOpenContainer.Add(currentCellIndex - width - 1);
}
//奇行,0开始计数,加两个
else
{
cellsForOpenContainer.Add(currentCellIndex - width);
cellsForOpenContainer.Add(currentCellIndex - 1);
}
}
//中间行
if (currentCellIndex / width > 0 && currentCellIndex / width < height - 1)
{
//偶行,0开始计数,加五个
if (row % 2 == 0)
{
cellsForOpenContainer.Add(currentCellIndex - width);
cellsForOpenContainer.Add(currentCellIndex - width - 1);
cellsForOpenContainer.Add(currentCellIndex - 1);
cellsForOpenContainer.Add(currentCellIndex + width);
cellsForOpenContainer.Add(currentCellIndex + width - 1);
}
//奇行,0开始计数,加三个
else
{
cellsForOpenContainer.Add(currentCellIndex - width);
cellsForOpenContainer.Add(currentCellIndex - 1);
cellsForOpenContainer.Add(currentCellIndex + width);
}
}
}
//中间列
if (currentCellIndex % width < width - 1 && currentCellIndex % width > 0)
{
//底行,加四个
if (currentCellIndex / width == 0)
{
cellsForOpenContainer.Add(currentCellIndex + width);
cellsForOpenContainer.Add(currentCellIndex + 1);
cellsForOpenContainer.Add(currentCellIndex - 1);
cellsForOpenContainer.Add(currentCellIndex + width - 1);
}
//顶行,加四个
if (currentCellIndex / width == height - 1)
{
cellsForOpenContainer.Add(currentCellIndex + 1);
cellsForOpenContainer.Add(currentCellIndex - 1);
cellsForOpenContainer.Add(currentCellIndex - width + (int)Math.Pow(-1, row + 1));
cellsForOpenContainer.Add(currentCellIndex - width);
}
//中间行,加六个
if (currentCellIndex / width > 0 && currentCellIndex / width < height - 1)
{
cellsForOpenContainer.Add(currentCellIndex + width + (int)Math.Pow(-1, row + 1));
cellsForOpenContainer.Add(currentCellIndex + width);
cellsForOpenContainer.Add(currentCellIndex + 1);
cellsForOpenContainer.Add(currentCellIndex - 1);
cellsForOpenContainer.Add(currentCellIndex - width + (int)Math.Pow(-1, row + 1));
cellsForOpenContainer.Add(currentCellIndex - width);
}
}
//判断容器中所有的点是否是可以添加的点,是就添加到开放列表中,全部判断完清空容器
for (int i = 0; i < cellsForOpenContainer.Count; i++)
{
//关闭列表和不允许通过的地型都不考虑
if (cellsForAll[cellsForOpenContainer[i]].isEnable == false || cellsForClose.Contains(cellsForOpenContainer[i]))
continue;
int currentGValue = GetCurrentGValue(cellsForOpenContainer[i]);
if (!cellsForOpen.Contains(cellsForOpenContainer[i]))
{
cellsForOpen.Add(cellsForOpenContainer[i]);
cellsForAll[cellsForOpenContainer[i]].hexCellFather = cellsForAll[currentCellIndex];
cellsForAll[cellsForOpenContainer[i]].gValue = currentGValue;
continue;
}
if (cellsForOpen.Contains(cellsForOpenContainer[i]) && cellsForAll[cellsForOpenContainer[i]].gValue <= GetCurrentGValue(cellsForOpenContainer[i]))
continue;
if (cellsForOpen.Contains(cellsForOpenContainer[i]) && cellsForAll[cellsForOpenContainer[i]].gValue > GetCurrentGValue(cellsForOpenContainer[i]))
{
cellsForOpen.Add(cellsForOpenContainer[i]);
cellsForAll[cellsForOpenContainer[i]].hexCellFather = cellsForAll[currentCellIndex];
cellsForAll[cellsForOpenContainer[i]].gValue = GetGValue(cellsForOpenContainer[i]);
continue;
}
}
cellsForOpenContainer.Clear();
}
/// <summary>
/// 计算cellsForALL列表中某个点的gValue并且返回
/// </summary>
/// <param name="i">该点在列表中的序号</param>
private int GetGValue(int i)
{
int gValue = 0;
HexCell cellContainer = cellsForAll[i];
while (cellContainer.hexCellFather != null)
{
gValue += cellContainer.mapAssessValue;
cellContainer = cellContainer.hexCellFather;
}
return gValue;
}
/// <summary>
/// 假设列表下标为i的点的父节点为当前点,计算出此时的GValue并且不改变该点的父节点
/// </summary>
/// <param name="i"></param>
private int GetCurrentGValue(int i)
{
int gValue = 0;
HexCell cellContainer = cellsForAll[i];
gValue += cellContainer.mapAssessValue;
cellContainer = cellsForAll[currentCellIndex];
while (cellContainer.hexCellFather != null)
{
gValue += cellContainer.mapAssessValue;
cellContainer = cellContainer.hexCellFather;
}
return gValue;
}
}
我在添加一个地格四周的地格时使用的是笨办法,有经验的读者可以自行优化(比如直接获得四周所有的地格,但如果是NULL就剔除什么的),由于六边形地图每次移动和计算的量并不大,我就不做了。
然后是我地格的类HexCell类,这个类大家看看就好,有一些写得不够好的地方请轻喷,本人还是个萌新。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum CellTypeForTown
{
townCenter, //城镇中心
townCell, //城镇普通地格
normalCell, //野外地格
buildCell //已经建有建筑物的地格
}
public class HexCell : MonoBehaviour
{
public CellTypeForTown cellTypeForTown = CellTypeForTown.normalCell;
private Town town;
public HexCoordinates coordinates;
/// <summary>
/// 父方格,用于A星寻路
/// </summary>
//[System.NonSerialized]
public HexCell hexCellFather;
public Color color;
/// <summary>
/// 地型评估值,表示到达该地需要付出的地形惩罚,负数表示越走行走值越多;
/// 比如A点到B点,根据B点地型计算惩罚
/// </summary>
public int mapAssessValue = 1;
public bool isEnable = true;
/// <summary>
/// 用于ASTAR寻路
/// </summary>
public int fValue = 0;
public int gValue = 0;
public void SetTownForCell(Town town)
{
this.town = town;
}
public Town GetTownForCell()
{
return this.town;
}
public static HexCell FindCellByCoordinates(HexCell[] cells, HexCoordinates coordinates)
{
int row = coordinates.Z * Global.instance.width;
int colum = coordinates.X + coordinates.Z / 2;
if (colum < 0 || colum > Global.instance.width - 1)
return null;
if (row + colum >= 0 && row + colum < cells.Length)
return cells[row + colum];
return null;
}
public static HexCell FindCellByCoordinates(HexCoordinates coordinates)
{
HexCell[] cells = Global.instance.cells;
int row = coordinates.Z * Global.instance.width;
int colum = coordinates.X + coordinates.Z / 2;
if (colum < 0 || colum > Global.instance.width - 1)
return null;
if (row + colum >= 0 && row + colum < cells.Length)
return cells[row + colum];
return null;
}
/// <summary>
/// 返回-1表示数组越界
/// </summary>
public static int GetCellIndexByCoordinates(HexCell[] cells, HexCoordinates coordinates)
{
int row = coordinates.Z * Global.instance.width;
int colum = coordinates.X + coordinates.Z / 2;
if (colum < 0 || colum > Global.instance.width - 1)
return -1;
if (row + colum >= 0 && row + colum < cells.Length)
return row + colum;
return -1;
}
/// <summary>
/// 返回-1表示数组越界
/// </summary>
public static int GetCellIndexByCoordinates(HexCoordinates coordinates)
{
HexCell[] cells = Global.instance.cells;
int row = coordinates.Z * Global.instance.width;
int colum = coordinates.X + coordinates.Z / 2;
if (colum < 0 || colum > Global.instance.width - 1)
return -1;
if (row + colum >= 0 && row + colum < cells.Length)
return row + colum;
return -1;
}
/// <summary>
/// 根据一个基础cell和附加坐标,得到基础cell坐标+附加坐标的cell对象;如果数组越界,就返回Null
/// </summary>
public static HexCell GetCellByAddCoordinatesVector(HexCell[] cells, HexCell basicCell, HexCoordinates addCoordinates)
{
HexCoordinates mHexCoordinates = basicCell.coordinates + addCoordinates;
return FindCellByCoordinates(cells, mHexCoordinates);
}
public static List<HexCell> GetCellsAroundCentetByDistance(HexCell[] cells, HexCell centerCell, int distance)
{
List<HexCell> townCells = new List<HexCell>();
int count = distance * distance * 3 + distance * 3 + 1;
HexCell[] mCells = new HexCell[count];
int i = 0;
for (int z = -distance; z <= distance; z++)
for (int x = -distance; x <= distance; x++)
{
int y = -z - x;
if (y < -distance || y > distance)
continue;
mCells[i] = HexCell.GetCellByAddCoordinatesVector(Global.instance.cells, centerCell, new HexCoordinates(x, z));
i++;
}
for (i = 0; i < count; i++)
{
if (mCells[i] != null)
{
townCells.Add(mCells[i]);
}
}
return townCells;
}
public bool CanSetEntityObjToCell(GameObject obj)
{
return CanSetEntityObjToCell(obj.transform);
}
public bool CanSetEntityObjToCell(Transform obj)
{
return CanSetEntityObjToCell(obj.tag);
}
public bool CanSetEntityObjToCell(string objTagName)
{
if (objTagName == "Untagged")
return true;
Transform[] sons = transform.GetComponentsInChildren<Transform>();
for (int i = 0; i < sons.Length; i++)
{
if (sons[i].CompareTag(objTagName))
return false;
}
return true;
}
}