Я работаю над игрой в Unity, которую играют на шестигранной доске с единицами, которые вы контролируете. Когда единица выбрана, гексы вокруг нее подсвечиваются, указывая (по цвету - синий для перемещения, оранжевый для атаки), который вы можете переместить к ним или атаковать все, что на них. Вот скриншот для справки:
Вот моя функция, чтобы показать гексы:
//breadth first search
//has built in range checker so you dont have to check the path length of every individual node
//works whether attackRange or moveRange is longer
public void showUnitsHexes(ArmyUnit unitScript, bool trueShowFalseHide)
{
//HIDING
if(trueShowFalseHide == false)
{
foreach(TileNode node in nodesToHide)
{
//change the mat back to the default white
comReader.changeNodeMat(node, CommandReader.hexMats.defaultt);
node.Hide();
}
nodesToHide.Clear();
return;
}
//SHOWING
//Only show if its your turn (return on enemy turn)
if ((comReader.playerNum == getWhichButtonAndPlayer.playerType.P1 && comReader.CurrPlayerTurn == 2)
|| (comReader.playerNum == getWhichButtonAndPlayer.playerType.P2 && comReader.CurrPlayerTurn == 1))
return;
#region SETUP
//these were originally parameters but it easier to just set them in the funct itself
TileNode startNode = unitScript.node;
int attackRange = unitScript.attackRange;
int moveRange = unitScript.moveRange;
//safety-net
if (startNode == null || (attackRange < 1 && moveRange < 1))
return;
//how to know when you're done
int finishedRadius;
if (moveRange > attackRange)
finishedRadius = moveRange;
else
finishedRadius = attackRange;
#endregion
//get the list of nodes not to go over twice, and the queue of nodes to go through
List<TileNode> nodesPassedAlready = new List<TileNode>();
nodesPassedAlready.Add(startNode);
Queue<TileNode> nodesToGoToInCurrentRadius = new Queue<TileNode>();
Queue<TileNode> nodesToGoToInNextRadius = new Queue<TileNode>();
int currentRadius = 1;
bool finished = false;
//add the 6 nodes surrounding the initial node to the queue to start things off
foreach (TileNode n in startNode.nodeLinks)
nodesToGoToInCurrentRadius.Enqueue(n);
//while the queue is not empty...
while (finished == false)
{
//END CHECK
//if done in current radius, need to go to next radius
if (nodesToGoToInCurrentRadius.Count < 1)
{
if (currentRadius == finishedRadius)
{
finished = true;
continue;
}
else
{
currentRadius++;
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
nodesToGoToInNextRadius = new Queue<TileNode>();
}
}
//...get the next node and...
TileNode n = nodesToGoToInCurrentRadius.Dequeue();
//...if there no issue...
#region safety check
//...if the node is null, has already been shown, is inhabited by a non-unit, don't bother with it
if (n == null || nodesPassedAlready.Contains(n) || comReader.hexIsInhabited(n, false, true))
continue;
//...if is inhabited and outside of attackRange, or inhabited by a friendly unit, don't bother with it
//NOTE: rather than combining this with the above if check, leave it separated (and AFTER) to avoid errors when n == null)
ArmyUnit currUnit = comReader.CurrentlySelectedUnit;
if (comReader.hexIsInhabited(n, true, true) && (currentRadius > currUnit.attackRange || comReader.unitIsMine(comReader.getUnitOnHex(n).GetComponent<ArmyUnit>())))
continue;
#endregion
//...1) show it
#region show nodes
//show as requested, add it to the list
//change the mat to whatever color is relevant: if n is inhabited and in attack range, color atkColor, otherwise color moveColor
if (comReader.hexIsInhabited(n, true, true) && currentRadius <= attackRange)
{
Debug.Log("attackRange: " + attackRange);
Debug.Log(n.name);
if (n.name.Equals("node345"))
Debug.Log("checked it");
comReader.changeNodeMat(n, CommandReader.hexMats.attack);
n.Show();
nodesToHide.Add(n);
}
//make sure hex is in moveRange. possible that it isnt, if attack range > moveRange
else if (moveRange >= currentRadius)
{
comReader.changeNodeMat(n, CommandReader.hexMats.move);
n.Show();
nodesToHide.Add(n);
}
//do not take n.show() out of those braces. If you do, it will sometimes show white nodes and you don't want to show them
#endregion
//...2) don't go over it a second time
nodesPassedAlready.Add(n);
//...and 3) add all surrounding nodes to the queue if they haven't been gone over yet AND ARE NOT ALREADY IN THE QUEUE
foreach (TileNode adjacentNode in n.nodeLinks)
{
#region safety check
//...if the node is null or has already been shown or is inhabited by a non-unit, don't bother with it
if (adjacentNode == null || nodesPassedAlready.Contains(adjacentNode) || comReader.hexIsInhabited(adjacentNode, false, true))
continue;
//...if is inhabited and outside of attackRange, don't bother with it
//NOTE: rather than combining this with the above if check, leave it separated (and AFTER) to avoid errors when n == null)
//currUnit already defined in the above safetycheck
if (comReader.hexIsInhabited(adjacentNode, true, true) && currentRadius > currUnit.attackRange)
continue;
#endregion
nodesToGoToInNextRadius.Enqueue(adjacentNode);
}
}
}
Моя проблема в том, что, когда я устанавливаю диапазон атаки дольше, чем размер карты (установлен на 30, карта 12x19), я получаю ошибку:
InvalidOperationException: операция недействительна из-за текущего состояния объекта System.Collections.Generic.Queue'1 [TileNode].Peek()
Аналогично, когда я устанавливаю диапазон до 17 или 18 - достаточно, чтобы иметь возможность атаковать базу противника от того места, где вы видите мое устройство на картинке, - узел для этой базы никогда не рассматривается в функции, несмотря на то, что он в зоне атаки.
Что означает это сообщение об ошибке? Где моя логическая ошибка? Извините, если это небрежно написано - я буду рад ответить на любые ваши вопросы. Спасибо!
Есть ли вероятность, что nodesToGoToInNextRadius
пуст?
Queue.Dequeue вызывает Queue.Peek под ним и выбрасывает InvalidOperationException, когда очередь пуста. Вы назначаете узлыToGoToInNextRadius для узловToGoToInCurrentRadius:
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
и не проверять снова, если в очереди есть что-то.
if (nodesToGoToInCurrentRadius.Count < 1) // that fine
{
if (currentRadius == finishedRadius)
{
finished = true;
continue;
}
else
{
currentRadius++;
//1. now you are swapping queues
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
nodesToGoToInNextRadius = new Queue<TileNode>();
}
}
//2. and calling Dequeue on swapped queue without checking if it empty.
TileNode n = nodesToGoToInCurrentRadius.Dequeue();