Как в Unity запустить обход в глубину параллельно?
Всем привет. Я бы хотел сделать настольную крестико-нолико-подобную игру, в которой человек может играть с человеком (на одном устройстве), и человек может играть с ботом, который выбирает ходы обходом дерева позиций в глубину (например, альфа-бета отсечение).
Далее в коде GameState, Move, PlayerColor не наследуют MonoBehavior и вообще изолированы от логики Unity.
Класс Player имеет наследников Human, Bot. Метод Player.GetTopMoves(GameState gameState) возвращает список некоторых выбранных ходов. Стратегия выбора ходов у каждого наследника своя:
Human - это, по сути, пользователь, и выбор хода эквивалентен тому, что пользователь сам выбирает ход нажатием мыши по некоторым объектам.
Bot - выбирает ходы обходом в глубину дерева возможных позиций
В следующем коде, очевидно, метод StartGame абсолютно непригоден для использования в Unity, так как
- сам метод работает пока не закончится вся настольная игра (скорее всего его нужно будет переписать как корутину?)
- метод выбора хода тоже занимает большое время (человек долго думает над ходом и бот долго считает)
public class Game : MonoBehaviour
{
[SerializeField] private BoardViewer _boardViewer;
private GameState _gameState;
private Dictionary<PlayerColor, Player> _players;
private Player _playerToMove;
public void StartGame(Player whitePlayer, Player blackPlayer)
{
Dictionary<PlayerColor, Player> players = new Dictionary<PlayerColor, Player>
{
{ PlayerColor.White, whitePlayer },
{ PlayerColor.Black, blackPlayer },
};
_gameState = GameState.NewGame;
while (_gameState.IsOver() == false)
{
_playerToMove = _players[_gameState.PlayerColorToMove];
// логика выбора хода зависит от типа Игрока
Move bestMove = _playerToMove.GetTopMoves(_gameState)[0];
_boardViewer.SpawnStone(...);
_gameState = _gameState.ApplyMove(bestMove);
}
}
}
Таким образом, я бы хотел адоптировать код выше для использования в проекте Unity, но абсолютно не имею понятия как это можно сделать. Не хотел бы запускать выбор хода корутиной (так как где-то вычитал, что тяжелые вычисления для этого менее пригодны), но тогда и Thread не подходит, так как Player (точнее его наследник Human) наследует MonoBehavior (насколько я знаю, это потоко небезопасно).
Возможно стоит забить на общий класс Player и вместо Game сделать две версии PvP и PvE?
Спасибо
Ответы (1 шт):
Есть же методы Update и FixedUpdate, здесь по сути ничего изобретать не надо. Это вполне себе заменяет цикл while. Если нужна теория для разобраться - почитайте про шаблон проектирования "конечный автомат" или еще его называют "машина состояний".
К примеру, возмем цикл, умножающий число на 2 десять раз, для наглядности буду использовать тот же while
int i = 0;
int number = 1;
while (i < 10)
{
number *= 2;
i++;
}
Console.WriteLine(number);
Выведем саму операцию в отдельный метод.
public void Multiply(int number, int count)
{
int i = 0;
while (i < count)
{
number *= 2;
i++;
}
return number;
}
Тогда это вызвать можно будет так
int number = Multiply(1, 10);
Console.WriteLine(number);
Вот по сути получился метод, содержащий всю логику внутри.
Этот цикл не завершится, пока не закончит считать. Когда надо еще что-то делать или делать несколько дел одновременно, вызывая итерации цикла по одной, используют конечный автомат.
Вынесем логику в отдельный класс. Переименую i в state
class CounterMachine
{
private int _state;
private int _number;
private readonly int _count;
public bool Finished { get; private set; }
public CounterMachine(int number, int count)
{
_number = number;
_count = count;
}
public void Update()
{
if (_state < _count)
{
_number *= 2;
_state++;
}
else if (_state == _count)
{
Console.WriteLine(_number);
_state++;
Finished = true;
}
}
}
Вот вся логика упакована в некий черный ящик.
Вызов этого монстра будет выглядеть так, что надо его создать и вызывать обновление, пока он не закончит работать.
var counterMachine = new CounterMachine(1, 10);
while (!counterMachine.Finished)
counterMachine.Update();
А теперь представьте, что вот это создание класса и вызовы Update будет делать Unity за вас, останется только написать саму машину. А сама машина - это и есть скрипт Unity. Единственное отличие в том, что эта машина бесконечная, будет выполняться, пока существует объект, к которому прицеплен скрипт.
public class Counter : MonoBehavior
{
private int _state;
private int _number = 1;
private int _count = 10;
private bool _finished;
void Update()
{
if (_finished)
return;
if (_state < _count)
{
_number *= 2;
_state++;
}
else if (_state == _count)
{
_finished = true;
OnFinished();
}
}
private void OnFinished()
{
// вывести _number
}
}
Эта логика как бы ложится на игру. Внутри метода вы делаете одну итерацию обсчета, меняете состояние игры и поехали дальше по кругу.