Сага опций
Чтоб я вновь устроил Сампо,
Сделал короб многострунный,
Вновь пустил на небо месяц,
Солнцу снова дал свободу…
«Калевала»
Настольные игры выглядит очень привлекательно для начинающих разработчиков. Здесь нет необходимости в построении фотореалистичных изображений и применении сложных шейдеров (хотя никто не запрещает этим заниматься). Даже использование 3D, в подавляющем большинстве игр, вовсе не обязательно. Сложность настольных игр сосредоточена на не визуальных составляющих. Пользовательский интерфейс выглядит простым и до предела примитивным.
Вот только… он таковым не является! Сегодня я хочу рассказать о своём опыте в разработке дизайна пользовательского интерфейса настольных игр, накопленном более чем за год работы.
Моё серьёзное увлечение настольными играми началось с момента знакомства с Zillions of Games. Я уже много писал об этом уникальном продукте, позволяющем мастерить компьютерные настольные игры, буквально «на коленке». Напомню лишь, что именно эта программа послужила прототипом Dagaz — свободного и бесплатного продукта, разработкой которого я занимаюсь в настоящее время.
Я многому научился у ZoG и этот опыт послужил надёжным фундаментом для моей собственной разработки. Очень многое в ZoG мне не нравилось и именно это побудило меня к работе. Кое что было переделано, но в основе продолжают лежать концепции Zillions of Games. Например, такие как:
- «pass turn» — Это можно изменить, но в большинстве игр участвуют по двое игроков, ходы которых чередуются. Выполнение хода является обязательным и его невозможность приводит к немедленному завершению игры (не обязательно к поражению). Не все игры таковы. Например, в Го игрок имеет право «пасовать», пропуская ход, что является важным аспектом правил игры. Во многих играх, таких как Reversi, игрок обязан пропустить ход, если не имеет возможности сходить по правилам. Опция «pass turn» управляет этой возможностью.
- «pass partial» — Более сложная вариация предыдущей опции предназначенная для игр с составными (частичными, в терминологии ZoG) ходами. По умолчанию, начав цепочку взятий (в таких играх как шашки), игрок обязан завершить её до конца (то есть, продолжать брать фигуры, пока есть такая возможность). Обычно, это правило сочетается с приоритетом взятия (игрок обязан брать фигуру противника, если есть такая возможность). Мне известна единственная традиционная игра, взятие в которой обязательно при том, что цепочку взятий можно прерывать. Без опции «pass partial», реализовать её в Zillions корректно было бы невозможно.
- «maximal captures» — Это хитрая опция! Само по себе то, что мы обязаны до конца продолжать цепочку взятий, вовсе не заставляет нас брать максимальное количество фигур! Выполняя очередное взятие, мы можем «свернуть не туда» и завершить цепочку по более короткой ветви. Опция «maximal captures» не даст нам этого сделать. Без неё, возникли бы проблемы с реализацией «Международных шашек»!
- «recycle captures» — В Zillions of Games довольно сложно реализованы drop-ходы (добавление новых фигур на доску). В частности, для всех фигур, участвующих в таких ходах, должны быть указаны специальные счётчики, учитывающие количество фигур этого типа «за пределами» доски (это делается при помощи ключевого слова «off»). Опция «recycle captures» возвращает взятые фигуры в соответствующие им off-счётчики, в результате чего их можно помещать на доску повторно. Это редко используемая опция, но когда она действительно нужна — без неё не обойтись.
- «include off-pieces» — Ещё одна опция, связанная off-счётчиками. Результат некоторых игр определяется подсчётом количества фигур, находящихся у игроков, но в играх с drop-ходами, такими как Seega, необходимо подсчитывать и те фигуры, которые ещё не выставлены на доску! Опция служит именно для этого.
Я думаю, вы уловили мысль. Большинство игр можно реализовать без всего перечисленного, но если требуется что-то особенное — приходится подключать опции. Для Zillions of Games это хардкод! Причём, даже в большей степени чем вы думаете. Например, опция «pass turn», помимо всем понятных false и true включает в себя »forced», а «maximal captures», для «Итальянских шашек», так и вовсе, включается значением »2» (в этой игре, дамки считаются «более равными», чем все остальные).
В Dagaz, я расширил механизм опций. Конечно, в тех случаях, когда можно обойтись глобальными флагами, я так и делаю, но «maximal-captures», например, кодирует очень сложное поведение, связанное с подсчётом фигур. Эта опция реализована расширением игры. Более того, тот же подход позволил мне реализовать новые опции, спрятав, например, в «deferred-captures» сложную логику «Турецкого удара», в Zillions традиционно кодируемую крайне непростым образом.
Как я уже писал выше, пользовательский интерфейс ZoG выглядит очень простым. Указываем мышью, какую фигуру собираемся перетащить (при этом, подсвечиваются те поля, на которые можно переместиться), после чего перетаскиваем фигуру на новое поле. Здесь есть два момента, которые мне не нравятся. Первый связан с мобильными устройствами, на которых Dagaz, худо-бедно, работает, а Zillions of Games нет.
На сенсорных экранах, указатель мыши успешно заменяется пальцем. Но это означает, что пока мы на что-то нажимаем, этот самый палец загораживает добрую половину экрана. В результате, разглядеть подсветку полей становится затруднительно. Когда палец отрывается от экрана, подсветка исчезает. Это кажется очевидным, но поскольку я, в основном, отлаживал Dagaz на десктопе, то далеко не сразу догадался, до чего же дьявольски неудобно приходится пользователям мобильных устройств.
Второй момент связан с «перетаскиванием» фигур по экрану. Дело в том, что далеко не во всех играх фигура состоит из одного «куска». В такой ситуации, «Drag & Drop» всё портит. На Zillions of Games подобные игры выглядят просто ужасно. Это главная причина, по которой я отказался от перетаскивания. В Dagaz, нажатие на фигуру включает «подсветку» целевых полей, а последующее нажатие на целевое поле запускает перемещение. Для мобильных устройств, это вдвойне хорошо, поскольку второе нажатие является своего рода «подтверждением» — пока оно не выполнено, можно отказаться от хода, выбрав другую фигуру.
В Dagaz я разделил «smart-moves» на две настройки: «from», используемую во всех манкалах и головоломках с движущимися кусками и «to», для которой нашлись применения в «Сокобане» и ещё одной игре. Разумеется, значение «true» включит обе настройки сразу, но вряд ли имеет смысл это делать в подавляющем большинстве игр. Специально для «Сокобана» я сделал ещё и альтернативный модуль, позволяющий управлять игрой при помощи стрелок компьютерной клавиатуры.
Завершая разговор о наследии Zillions of Games, хочется рассказать о ещё одном важном механизме. Обратите внимание на жёлтые прямоугольники на картинке. Именно они определяют, какая фигура будет выбрана, при нажатии мышью. В этом и заключается проблема. Если фигуры расположены в два (и более) слоя, регион, расположенный выше, всегда будет перехватывать нажатие мышью, независимо от того, есть в нём фигура или нет. По пальцам невозможно пересчитать, сколько раз мне это мешало! Разумеется, в Dagaz это исправлено.
В некоторых играх («Фермерских шахматах», например) очень важно, чтобы анимация перемещения фигур выполнялась по определённой траектории. Перенос фигур «по прямой» может существенно затруднить понимание правил игры. Здесь возможны различные подходы, но я исходил из того, что модель должна «знать» только начальную и конечную позиции перемещения. Всё остальное находится в ведении представления игры. В конфигурации представления определяются «вектора», связывающие отдельные позиции, а его задача, получив пару позиций от модели — автоматически построить последовательность связывающих их «векторов», для анимации перемещения.
view.addVector(Dagaz.Model.stringToPos("a1"), Dagaz.Model.stringToPos("a6"));
view.addVector(Dagaz.Model.stringToPos("a2"), Dagaz.Model.stringToPos("a6"));
view.addVector(Dagaz.Model.stringToPos("a3"), Dagaz.Model.stringToPos("a6"));
view.addVector(Dagaz.Model.stringToPos("a4"), Dagaz.Model.stringToPos("a6"));
view.addVector(Dagaz.Model.stringToPos("a5"), Dagaz.Model.stringToPos("a6"));
...
Часто бывает полезно иметь возможность дать игроку визуальную подсказку о целях игры. Иногда (как на рисунке выше), доска сама по себе является подсказкой, но если этого недостаточно (как в головоломках с движущимися кусками, например), поможет ещё одна, унаследованная от Zillions, «декоративная» опция. Если »highlight-goals» активирована, при наведении на фигуру указателя мыши, будут подсвечены те позиции, на которые её следует провести.
Продолжая тему визуальных «подсказок» игроку, стоит рассказать о ещё одной новой опции Dagaz. В играх со сложными составными ходами очень легко «потеряться», забыв (или не поняв), что фигура ещё не завершила свой ход полностью. Реализуя настройку «show-blink», я решил не перегружать дизайн новыми цветовыми пометками, а использовать анимацию, для выделения фигуры, выполняющей ход.
Это решение может показаться избыточным, но здесь важно понимать, что реализуя эту опцию, я ориентировался, в основном, вовсе не на шашки. Существует множество гораздо более сложных игр с составными ходами, например таких как «Ko Shogi» или «Tenjiku Shogi». Даже хорошо зная правила, на таких досках очень легко «заблудиться». А есть и такие игры, дизайн составного хода которых непривычен совершенно:
Тесно связан с этой темой и вопрос индикации взятия фигур. Если взятие в игре шахматное или игра всем знакома (как шашки, например) — особых проблем нет, но даже в Шахматах забираемая фигура не всегда располагается на том поле, где завершается ход. А если игра менее знакома, индикация потенциальных взятий становится жизненно важной. Более того, в некоторых играх должна предоставляться возможность выбора группы захватываемых фигур:
В мадагаскарской Фанороне, существует два типа хода со взятием: в первом случае, фигура (или группа фигур) забирается приближением к ней, во втором — отступлением. В игре возможны ситуации, в которых игрок должен выбрать, каким образом осуществляется захват. Всё ещё более осложняется тем, что этот захват может быть встроен в цепочку составного хода. Также, возможность выбора захватываемых фигур востребована в играх с «бонусным» взятием, таких как «Мельница», «Йотай» или «Болотуду».
Как и в случае с «show-blink», я решил не перегружать интерфейс цветовыми пометками, а отображать потенциальные взятия полупрозрачными фигурами. Аналогичным образом показываются drop-ходы. Фигура, добавляемая на доску drop-ходом, отображается в полупрозрачном виде при прохождении указателя мыши над соответствующей позицией (к сожалению, такая индикация не работает на мобильных устройствах).
В некоторых случаях, правилами игры drop-ходы разрешены не во все позиции. Если разрешённых для хода позиций немного, для того, чтобы не вводить игрока в заблуждение, имеет смысл отобразить на доске все допустимые ходы. Это хорошо работает в таких играх как Reversi и Renju, с её дебютным регламентом:
Очень часто, запрещённых ходов гораздо меньше чем разрешённых (это относится к фолам в Рендзю, например). Запрещённые позиции необходимо помечать, чтобы не вводить игрока в заблуждение. Я не стал придумывать ничего нового и использовал известное многим игрокам обозначение «Ко».
В Renju, Fangqi и подобным им играх таких квадратиков на доске может быть больше одного. Также имеется некоторая сложность, связанная с перемещением фигур. В играх семейства «Мельница», может действовать правило, запрещающее построение одного и того же ряда два раза подряд. При этом, не вполне правильно помечать только пустое поле, достраивающее ряд, поскольку ход в эту позицию может быть допустим, если он ведёт к построению другого ряда. В этом случае, имеет смысл помечать все фигуры, составляющие форму:
Вот такая у меня получилась «Мельница». В ассортименте. А ещё пара похожих игр от китайских товарищей и, конечно же, «Болотуду». Разумеется, это не конец истории. Например, я очень хочу сделать вот эту игру. Правда, одними только опциями здесь дело уже не ограничится.