Я провел в играх сотни часов по статистике Стима, и тысячи, если считать на всех платформах. Но что меня поразило, так это соотношение времени в некоторых случаях. На прохождение великолепного Bioshock Infinite у меня ушло 8.5 часов, но на Sacura Clicker — на 12 минут больше. На Clicker Heroes — больше сорока часов, почти столько же, как на Torchlight 2. Фокус в том, что я помнил и хорошо представлял затраты времени на большие игры. Но маленькие игрушки совершенно не отложились в памяти, они украли мое время незаметно, растаскивая по пять-десять минут в течение недель и месяцев.
Когда я осознал это, то подумал, что было бы неплохо прикрутить к этой балерине турбину. То есть сделать полезный таймкиллер — простенькую игру, которая будет не воровать, а инвестировать мое время по пять-десять минут. К примеру, в словарный запас по английскому.
Когда-то я купил кучу карточек с английскими словами и читал их в маршрутке по пути в офис. Потом уволился из офиса и привычка пропала, я забыл про карточки. Полез перетряхивать рюкзак, и когда из верхнего бокового кармана веером выпали разные слова, понял — это же match 2! Игра, в которой надо составлять пары (в отличие от match 3, где берут по три и больше).
Таймкиллеры с принципом «составь пару» известны давно — к примеру, компьютерная игра маджонг-пасьянс (не путать с классической азартной китайской игрой, в которую играл еще Сэммо Хунг в гонконгских боевиках).
Маджонг из KDE Games
У маджонга-пасьянса есть упрощенная разновидность Mahjong Connect, где фишки выкладываются в простую раскладку, без фигурных пирамид. В википедии можно прочитать, что его называют сисэн-сё (англ. Shisen-Sho), но если вы хотите прогуглить образцы — первое название будет более подходящим.
Подозреваю, что первую версию сделал программист вроде меня, у которого не было опыта в программировании полноценного маджонга со сложной выкладкой — но игра внезапно стала популярной и породила собственную ветвь клонов.
Сисэн-сё из KDE Games или Mahjong Connect в народе
Посмотрите на карточки со словами и на Маджонг Коннект — они отлично подходят друг к другу — и там, и там есть пары. Остается только превратить каждую карточку в пару фишек.
Дизайн фишек и геймплея
Пять новых слов в каждом сеансе и еще несколько повторяющихся слов из предыдущих — вот идея, от которой я отталкивался. Это означало, что в игре должно участвовать как минимум около 20 фишек.
На каждой фишке необходимо было разместить хотя бы одно слово с хорошей читаемостью. Бумажные карточки позволяют разместить довольно много текста, но фишки с такими пропорциями получились бы слишком большими. Кроме того, я очень хотел сохранить ощущение «как будто маджонг», а это означало, что одна игровая фишка должна быть очень компактной и по форме, и по тексту. Если повернуть костяшку маджонга на 90 градусов, то получится достаточная для текста площадка.
Смоделировав игровую раскладку с такими костяшками в графическом редакторе (Fireworks 2003 года, если любопытно), я увидел, что игра выглядит довольно скучно. Посмотрев снова на скриншоты с маджонгами, попробовал добавить разные графические элементы для оживления картинки. И тут меня осенило — можно же просто добавить на каждую пару по самоцвету. Тогда это будет работать не только как оживляющий элемент, придающий хотя бы внешнее сходство с казуальными игрушками, но и как подсказка.
Черновой вариант игрового поля с разными вариантами фишек
Финальный вариант получился таким — три самоцвета на 10-11 пар слов. Это давало подсказку с шансом случайного совпадения около 25%. Если забыл слово — можно просмотреть фишки с теми же словами.
Дополнительный элемент оформления, ускоряющий ориентацию — серый флажок, отмечающий все слова на одном (русском) языке. Изначальная идея была в том, чтобы помечать один элемент из пары, а не национальный признак, поэтому я не стал делать триколор или еще что-то в этом духе.
Выглядит как почти читерство, но я не хотел делать экзамен или полноценный тест знаний. Экзамен — это стресс, а мне нужен был максимально легкий и приятный геймплей, чтобы во время игры формировалось чувство победы и уверенности в своих силах. Мне кажется, это одно из важнейших качеств культовых таймкиллеров вроде Bejeweled или Zuma. Игрок идет по оптимальной границе между своим скиллом и неумением, получая удовольствие и возвращаясь к игре снова и снова.
Что насчет правил — я сделал еще один шаг в упрощении от Mahjong Connect и позволил составлять пары из любых подходящих друг к другу фишек, а не только из тех, что разблокированы. Есть две причины, почему это произошло. Первая заключается в том, что у меня не было под рукой готового кода для маджонга и я тестировал просто составление пар. Вторая — что я обнаружил, что фишки со словами на разных языках сами по себе усложняют геймплей и составление пар из них уже создает ощущение игры.
Забегая вперед — если сделать полноценный коннект и маджонг с такими же фишками, играть на самом деле станет гораздо легче. Дело в том, что дополнительные ограничения на фишки сузят выбор и игроку можно будет только проверять все подходящие по самоцветам свободные для хода фишки — а это даст резкое увеличение вероятности совпадения с 25% до 50 или даже ста, в зависимости от раскладки.
Но как бы ни было легко играть или читерить, главная цель в такой игре все равно будет выполняться —
Программирование
Для этого проекта я использовал обычный JavaScript и игровой движок Phaser.io 2.10.2. Чем хороши HTML5-игры — они позволяют быстро предоставить доступ для тестирования и обкатки прототипов. Я уже успел убедиться на других проектах, что как средство для создания кроссплатформенной игры HTML5 не очень хорош (ощутимая просадка по FPS на слабых телефонах, проблемы кроссбраузерной совместимости, необходимость продумывать мосты к нативному API в ряде случаев). Но как средство для создания веб-прототипов и веб-игрушек — это хороший выбор.
Как и в случае с постъядерным караваном, я начал разработку с составления модели состояния игры — то есть data-класса с полями, которые просто отражают текущий прогресс игрока.
function GameState() {
this.pairKey = "ru-en"; // Key для пар слов, универсальный идентификатор перевода
this.pairJsonUrl = "somePath/ru-en.json"; // путь к текущему файлу для пар слов
this.matched = 0; // сколько пар убрано за все сеансы
this.gold = 0; // валюта. Начисляется за пары, но может тратиться на подсказки
this.errors = 0; // ошибочные пары
// прогресс по порциям
// индекс = номер порции
// для Phaser.ArrayUtils.numberArray нужен аргумент - индекс, а не число
this.portions = Phaser.ArrayUtils.numberArray(Balance.PORTION_AMOUNT-1)
.map(function (portion, index) {
return new PortionSaveData();
});
this.portion = 0; // текущая порция
// настройки
this.options = {
tutorial: true, // включить туториал по умолчанию
}
}
Для сохранения и загрузки состояния игры в браузерном JS достаточно просто сериализовать и десериализовать эту модель при записи и чтении в локальное хранилище — и так мы получаем сохранение прогресса на компьютере пользователя буквально парой строчек.
Выкладка фишек на поле, обработка нажатий мышки и тачскрина в Phaser реализуются достаточно просто. В нем есть класс Group, который позволяет автоматически выравнивать все добавленные в него визуальные объекты по заданной сетке.
Впрочем, так можно делать только правильные прямоугольники (или сетки, если задать большой шаг), что выглядит довольно примитивно. Поэтому я написал свою функцию раскладки, в которой каждый второй ряд смещается на половину фишки — получается выкладка «кирпичами».
// выравнять как кирпичи - каждый второй ряд смещен на определенное расстояние
function layAsBricks(group, offsetBase) {
offsetBase = offsetBase || Math.floor(AppConfig.CHIP_WIDTH / 2);
var N = group.length;
var i = 0, row, col, COLS = getColAmount(N), ROWS = N / COLS;
var offsetX, min = Math.floor(offsetBase / 2), max = offsetBase + min;
var lastWidth = 0, lastX = 0;
var nextChip;
for (row = 0; row < ROWS; row++) {
if (row % 2 > 0) offsetX = offsetBase;
else offsetX = 0;
lastWidth = 0;
lastX = 0;
for (col = 0; col < COLS; col++) {
nextChip = group.children[i];
if (nextChip === undefined) break;
group.children[i].y = row * AppConfig.CHIP_HEIGHT;
group.children[i].x = lastX + lastWidth;
lastX = group.children[i].x;
lastWidth = group.children[i].width;
group.children[i].x += offsetX;
i++;
}
}
}
Но главные вопросы мне пришлось решать со словарем.
Игры со словарем и шрифтами
Первое, что бросится в глаза каждому, если уже не бросилось — длина фишек не является достаточной для адекватного отображения всех слов в словаре. Можно решить это, произвольно меняя размер шрифта в зависимости от длины слова.
Разный размер шрифта для разной длины — выглядит немного безумно, на мой взгляд
Но этот вариант показался мне слишком плохим, поэтому я решил просто отфильтровать словарь и оставить только слова не длиннее 9 знаков в обоих случаях (русский и английский). В моем случае это было позволительно, потому что я взял за основу частотный словарь — то есть слова в нем были отсортированы по частоте употребления. В первой тысяче слов длинее 9 знаков было очень мало, а в первых десятках, то есть самых частых, их не было вовсе.
Лимит в 9 знаков годится на этапе проверки концепции и для самых употребительных слов, но если развивать прототип — рано или поздно с этим придется что-то делать. Скорее всего, менять пропорции фишек или увеличивать их размер. Для немецкого языка это придется сделать даже в рамках первых самых популярных сотен слов — а немецкий я тоже хочу потестировать, вспомнить школьные годы.
Второй момент — визуальное совпадение букв в английском и русском языке. Русское «не» и английский he в верхнем регистре часто совпадают просто до пикселя. Частотный словарь усиливает этот эффект, потому что эти короткие слова оказываются в одном уроке почти в самом начале.
Русское НЕ и английский HE в верхнем регистре выглядят одинаково
Серый флажок помогал слабо, слова путались. После первых жалоб тестеров я поменял регистр у надписей на обычный lowercase. И тут увидел следующий баг — буквы стали плясать, в некоторых словах уплывая по вертикали на целый пиксель.
Причина была в том, что я использовал для вывода один растровый шрифт (сформированный как набор картинок-букв в одном размере), масштабируя его по мере надобности. В большинстве случаев это проходит гладко, но на мелких буквах возникают ошибки округления — иногда съедается один пиксель, что и дает эффект пляски.
Мораль: растровый шрифт для мелких текстов надо сразу готовить в нужном размере
После подготовки отдельного шрифта все перестали плясать и встали строго по базовой линии.
Если вы будете использовать Phaser или другой HTML5-движок в своих проектах, то вот еще один совет: выводите растровый шрифт сразу в том цвете, который будет использоваться в игре. Иначе это дает дополнительную нагрузку на процессор, что в играх с кучей действия и эффектов может привести к сильному проседанию FPS. Кроме того, даже в последнем мобильном Chrome на некоторых Android-планшетах «неродной» цвет, заданный растровому шрифту программно, может не отображаться совсем — будет вывод в дефолтном.
И, разумеется, словарь для такой игры лучше всего подключать как отдельно загружаемый файл — это позволит обновлять его, если будут замечены опечатки или сомнительные варианты.
К примеру, мой вариант словаря, похоже, составлялся по массивам переведенных текстов с учетом контекста, поэтому «что» в первых уроках там переводилось как «that». Понятно, что в таком значении эта частица употребляется значительно чаще, чем вопрос, но для обучения без контекста это не годится.
Мой главный вывод по словарю — частицы и другие слова, сильно зависящие от контекста, не годятся для обучения по таким чистым парам. Варианты решения, которые я вижу — делать какую-то отдельную игрушку по частицам или создавать фишки с более длинными текстами. Второй вариант выглядит более логичным, так как одними словами грамотен не будешь и полноценно учить язык необходимо все же с контекстом, хотя бы на примере небольших выражений. А это значит — впереди еще много экспериментов!
Туториал
Сначала я тестировал игру, в которой просто выкладывались фишки — и это было занятно, поскольку геймплей сводился к радостному припоминанию уже известных выражений и это давало чувство легкой победы, что и требовалось. Но потом я понял, что это не будет работать с абсолютно незнакомым языком. Поэтому пришлось добавить опцию обучения — перед началом урока игра показывает фишки в правильных парах, а затем перемешивает их.
Технически это реализовано так — уровень изначально выкладывается как обычно, то есть в случайном порядке. Затем, если включена опция туториала, запускается функция, которая запоминает исходные координаты фишек, добавляет к ним анимацию полета и раскладки в рабочие пары, а потом все возвращает на свои места. Поскольку выкладка и подготовка туториала происходит до начала первого цикла update (перерисовки экрана), пользователю кажется, что игра сначала выводит фишки в правильном порядке, а затем случайно перемешивает их.
В движке Phaser у Group есть метод shuffle, который работает как перемешивание обычного массива, но попутно обновляет Z-index. Это очень удобная функция, которая безусловно пригодится для реализации полноценного маджонга и других игр, где есть хотя бы условное третье измерение и элементы накладываются друг на друга.
Алгоритм начала игры выглядит просто — берем N пар из словаря пар для заданного урока, формируем из них визуальные элементы-фишки, затем делаем shuffle и выстраиваем в сетку — получается псевдослучайная раскладка.
В туториале происходит обратный процесс — на массиве фишек вызывается функция sort, где значимым параметром является id пары из словаря — так фишки выстраиваются друг за другом попарно, после чего остается разместить их в две колонки.
Прогресс
Для наблюдения за прогрессом я добавил небольшие индикаторы в стартовое меню и звездочку, которая должна появиться при полном прохождении всех уровней в сотне слов. На скриншоте изображена искусственная ситуация с тестовой звездочкой.
Султана с лицом Картмана я взял на Craftpix.net — он шел в том же бесплатном наборе, что и алмазы. Просто, чтобы оживить заставку. Хотя если вы не любите султанов или Картмана — признаю, что ход не особо удачен.
Выводы и перспективы
Я уверен, что обучающий таймкиллер — настоящий таймкиллер, а не этот прототип, что я сделал — возможен. И когда они появятся в массовом количестве, мир может измениться к лучшему. Как минимум, миллионы людей будут тратить сотни часов не впустую.
Если у вас есть под рукой код для обычной игры на комбинации и несколько свободных дней — попробуйте поэкспериментировать, хотя бы на парных словах. Можете взять готовый JSON-файл здесь. В нем больше, чем тысяча пар, я формировал с запасом.
Все match3 и match2 игры — с шариками, алмазами, фишками для маджонга, рыбками и медвежатами, с фрактальной динамической выкладкой как Zuma или статическим полем, как в большинстве игр — это по сути задания на составление комбинаций, на подбор групп по какому-то признаку.
В текущих играх этот признак не несет никакой полезной нагрузки, люди просто комбинируют красные и зеленые шарики, полупрозрачные полосатые желейные конфеты и липких мармеладных медвежат. А что, если эти медвежата будут насыщены полезными витаминами? Я помню, что ряд английских слов я выучил по Fallout и Heroes Might and Magic, значит, такие витамины существуют.
Мой прототип игры можно потестить здесь. Там есть выбор разных сотен и уровней, сохранение, прогресс и тысяча самых употребительных английских слов. Верстка рассчитана на десктопные мониторы или планшеты. Каждый уровень рассчитан на несколько минут, содержит 5 новых слов и 6 повторных с предыдущих (по формуле случайной выборки 3, 2, 1).
Автор: Zoolander