Comments 21
пропускается весь «фейерверк», но при этом в симуляции все честно отрабатывается и происходит честный подсчет всех коинов с последующим награждением. И все это выполняется моментально.
как иронично, экономить несколько секунд в игре, которая заставляет тратить на нее часы
Причина оказалась проста: отсутствие промежуточного положения фишек в пространстве и дискретное перемещение. Из-за крупных «тиков» фишка могла находиться либо в одной ячейке, либо в соседней, но никак не между ними. Пробовать это исправить красиво только средствами визуализации довольно сложно, поэтому мы поступили иначе.А в чем, собственно, проблема? Есть логика, она выдает «результат хода» в виде «фишка #32 перемещается из [1,1] в [1,2]», есть визуализатор, который знает как нужно анимировать перемещение – он его и анимирует.
хотя симуляция живет отдельно от визуала, она обязана резервировать некоторое время на различные взаимодействия на стороне представленияЕсть еще как минимум два варианта: 1) логика выполняется вся сразу и записывает результаты каждого хода в стопочку, а визуализатор их последовательно анимирует; 2) клиентская реализация логики управляется по шагам: тикает шаг, результат и управление передается визуализатору, и только после завершения анимации логика тикает снова.
Обычно, когда перед Unity-программистами возникает необходимость реализации подобных таск-менеджеров, они это реализуют при помощи Unity Coroutines.Да по-разному делают, когда что-то свое, когда готовое. Я когда-то пользовался вот такими промисами: github.com/Real-Serious-Games/C-Sharp-Promise
А можете рассказать, как вы реализуете гравитацию? Она работает для поля в общем виде, или считается/задается для каждой клетки индивидуально? Обрабатывать начинаете снизу-вверх, или сверху-вниз? Интересно, как разруливаются ситуации, когда, например, верхушка столбика, заблокирована и заполнение идет из соседних.
А в чем, собственно, проблема? Есть логика, она выдает «результат хода» в виде «фишка #32 перемещается из [1,1] в [1,2]», есть визуализатор, который знает как нужно анимировать перемещение – он его и анимирует.
Проблема была в том и, наверное, в статье следовало бы на этом сделать акцент, что симуляция у нас не блокируется после ввода от игрока. Игрок потенциально может влиять на перемещающиеся фишки, поэтому мы не знаем на 100%, что фишка гарантированно переместиться из [1,1] в [1,2]: где-то в промежутке с фишкой может случиться все, что угодно. Именно поэтому симуляция "тикает" мелкими шагами и на каждом шаге обновляет свое состояние.
Да по-разному делают, когда что-то свое, когда готовое. Я когда-то пользовался вот такими промисами: github.com/Real-Serious-Games/C-Sharp-Promise
Да, вот и мы, по сути, решили использовать привычное и понятное нам решение :)
А можете рассказать, как вы реализуете гравитацию? Она работает для поля в общем виде, или считается/задается для каждой клетки индивидуально? Обрабатывать начинаете снизу-вверх, или сверху-вниз?
Гравитация может задаваться индивидуально для каждой фишки. У нас гравитация высчитывается в несколько этапов: 1) проверка на потенциальные "падения" фишек 2) собственно "падение". Несколько этапов нивелируют проблему порядка обхода фишек.
Интересно, как разруливаются ситуации, когда, например, верхушка столбика, заблокирована и заполнение идет из соседних.
Для этого у нас есть специальная довольно замороченная эвристика "скатывания со склона". Когда мы ее реализовывали, то смотрели на уже существующие игры на Youtube с замедлением времени :)
Игрок потенциально может влиять на перемещающиеся фишки, поэтому мы не знаем на 100%, что фишка гарантированно переместиться из [1,1] в [1,2]: где-то в промежутке с фишкой может случиться все, что угодно.У вас есть какие-то особенные механики, связанные с воздействием на фишки во время полета? Просто, известные мне по топовым играм механики, не требуют ничего подобного.
Поясню свою мысль примером. Я ради забавы делал прототип match3, и у меня была совершенно независимая сущность – «заполнятор» поля, который оперировал только координатами ячеек и состояниями [блокировано|свободно|фишка] и тесты с маленькими фрагментами поля, похожими на ваши. Его тик равен перемещению на одну клетку, на выходе он выдавал набор траекторий падения в виде [{[0,0]→[0,1]→ [0,2]}, {null, [1,0]→ [1,1]}], его больше ничего не волновало.
Другая независимая штука – обработка матчей, которая может сказать допустим ли ход [2,2]→ [2,3].
А дальше еще одна совсем отдельная штука «клиент», которая почти ничего не знает про логику, но умеет рисовать анимации и обрабатывать пользователя. Когда палец шевелит фишку, то в зависимости от всяких условий клиент рисует перемещения фишки и анимации (с учетом того, какие ходы доступны) – и только после того, когда однозначно определено движение хода, которое нельзя отменить, уходит команда в логику. Логика сразу обрабатывает матч, затем обрабатывает заполнение поля и возвращает клиенту набор траекторий, которые клиент затем анимирует с любой скоростью, какая ему понравится.
Гравитация может задаваться индивидуально для каждой фишки. У нас гравитация высчитывается в несколько этапов: 1) проверка на потенциальные «падения» фишек 2) собственно «падение». Несколько этапов нивелируют проблему порядка обхода фишек.А как часта сменяются этапы: после движения на одну клетку, или после более крупных логических шагов?
Порядок обхода в любом случае играет роль, так как с ним связаны логика проверок и формат хранения промежуточных состояний. Можно «толкать» фишки с самого «верха», можно тянуть «снизу» (кавычки из-за условности верха и низа в произвольном графе гравитаций клеток), а можно обходить в произвольном порядке, но тогда, вероятно, для каждого этапа потребуется несколько проходов. У вас вот как, например?
Вообще, мне кажется, что эта тема очень интересная, на ее примере можно многое обсудить и по алгоритмам, и по структурам данных.
К сожалению, когда искал, не нашел вообще никаких публикаций на эту тему. Вы тоже все сами изобретали?
Когда мы ее реализовывали, то смотрели на уже существующие игры на Youtube с замедлением времени :)Я тоже пытался анализировать, и временами у меня создавалось впечатление, что оно везде работает черте-как, на разных уровнях может срабатывать по-разному.
У вас есть какие-то особенные механики, связанные с воздействием на фишки во время полета?
Да. Например, где-то взрывается бомба и ее взрывная волна, которая у нас распространяется не мгновенно, повреждает фишки в полете. И это лишь один из примеров.
А как часта сменяются этапы: после движения на одну клетку, или после более крупных логических шагов?
Симуляция просчитывает потенциальное воздействие гравитации каждый тик, т.е. постоянно.
Порядок обхода в любом случае играет роль, так как с ним связаны логика проверок и формат хранения промежуточных состояний.
Как я выше написал, предварительный этап потенциального просчета вариантов падения фишки делают порядок обхода произвольным. Учитывая, что гравитация может быть расставлена на поле как угодно, для нас это было крайне важным условием.
К сожалению, когда искал, не нашел вообще никаких публикаций на эту тему. Вы тоже все сами изобретали?
К сожалению или к счастью, да :)
Т.к. в симуляции потенциально все может измениться в следующий тик, мы не пытаемся просчитать "глубоко". Учитываются текущие, актуальные для этого тика препятствия/фишки.
Да, примерно так и произойдет
При всём уважении Павел вы переизобрели уровневую архитектуру игрового приложения где на нижнем слое менеджеры общего назначения-> выше жанровое ядро-> логика представления -> арт. Ну и назвали это детернимированным движком. Забыли ещё про жанровый плагинный редактор упомянуть тогда повестрование было закончено.
Очень спорный момент с обработкой состояний на каждом "тике", разве событийная модель не лучше? Это же не приложение требующее реального времени выполнения.
"Детерминированность"(нормальная архитектура) не имеет никакого отношения к воспроизводимости реплея. Вам достаточно добавить в архитектуру паттерн "команда" для управляющих сообщений и перед вами откроются бездны сериализации и прокрутки вперёд и назад с логированием перед эксепшеном или крашем.
Господи Карл гравитация в М3? рукалицо. В М3 для перемещения плиток со времён угля и пара используют динамическую анимацию(кокос) или твины(юнити, годо).
"Активация" эффектов - логика инитится из конфигурационного файла. а не реализуется в коде.
Для анимации "жука" не нужны потоки(таски) или кастомные автоматы состояний, это делается с помощью динамической анимации/твинов по завершению которого генерится событие в котором проигрывается эффект или анимация "взрыва".
Ну и завершая, весь смысл того что вы называете "детерминированностью", а другие уровневой архитектурой - это разрыв связности между уровнями и выставление между ними только универсализированных интерфейсов. Судя по последнему абзацу вы так и не поняли зачем это нужно, даю подсказку - уровни представления и конфигурируемой логики легко меняются как и арт и это решение максимально полезно именно для гиперказуалок. Вы можете клепать их десятками не тестируя, комбинировать и переиспользовать.
вы переизобрели уровневую архитектуру игрового приложения где на нижнем слое менеджеры общего назначения-> выше жанровое ядро-> логика представления -> арт. Ну и назвали это детернимированным движком. Забыли ещё про жанровый плагинный редактор упомянуть тогда повестрование было закончено.
Переизобретать мы ничего не хотели, а просто следовали устоявшимся best practices в индустрии.
"Детерминированность"(нормальная архитектура) не имеет никакого отношения к воспроизводимости реплея.
У меня стойкое ощущение, что мы говорим о разных вещах. В статье "детерминированность" является переводом термина "deterministic". Предлагаю ознакомиться со следующим материалом - https://gafferongames.com/post/deterministic_lockstep/ , послужившим источником вдохновения для упомянутых выше решений.
Вам достаточно добавить в архитектуру паттерн "команда" для управляющих сообщений и перед вами откроются бездны сериализации и прокрутки вперёд и назад с логированием перед эксепшеном или крашем.
Да, с подобным паттерном я знаком и в какой-то степени мы его используем, записывая ввод игрока, однако сам паттерн "команда" не сделает магическим образом симуляцию детерминированной. Статья говорит о более низкоуровневых проблемах.
Господи Карл гравитация в М3? рукалицо. В М3 для перемещения плиток со времён угля и пара используют динамическую анимацию(кокос) или твины(юнити, годо).
Перечисленные выше решения могут использоваться, но не являются единственно возможными. И, да, мы тоже используем твины, только не для гравитации, где требуется более гранулированный контроль.
"Активация" эффектов - логика инитится из конфигурационного файла. а не реализуется в коде.
Я предлагаю еще раз внимательнее прочитать статью. Использование скриптового языка как раз и дает нам возможность реализовать геймплейную логику, если в ваших терминах, через "конфигурационные файлы".
Спасибо, что подняли тему
Match-3
Чем не устраивает русское "три в ряд"?
Спасибо за статью!
Я правильно понял, что ускорение фишек "зашито" в симуляцию (а не является частью представления)?
Тогда возникает вопрос, в какой момент начинают падать выше-идущие фишки?
Задержка ровно в 1 тик?
Да, все верно, ускорение зашито в симуляцию конфигурационным параметром. Фишки начинают падать, как только во время тика гравитации определяется, что им можно падать. Т.к. во время симуляции может происходить множество разных событий из-за действий игрока, сказать на каком тике это произойдет точно невозможно.
Еще раз спасибо. Возвращаюсь к статье время от времени, чтобы сравнить со своей реализацией.
Такой вопрос: представленные вами тесты, если так посмотреть, скорее являются интеграционными тестами, т.к. проверяют работу всей симуляции, сколько бы новой логики в нее не было добавлено.
Бывали реальные случаи, когда добавляя новый функционал ломались старые тесты?
Понадобились ли вам на этом проекте модульные тесты?
И наверное самое интересное - если все же тест не проходил, не были ли проблемы с выявлением того, в каком модуле была ошибка, ведь Assert не указывает на конкретный блок кода.
У нас есть и модульные тесты, и интеграционные тесты, и приемочные (на ферме из девайсов). Да, случаи бывают, что тесты ломаются :) Но ведь это нормально. Самое главное, чтобы тесты никогда не были "обузой", из-за которых не поднимается рука что-то менять.
На счет ассерта.... ну да, тест не показывает напрямую ошибку, но по контексту довольно быстро понятно, где она произошла, так что это никогда не было проблемой.
Разделяй и властвуй: детерминированный и скриптованный Match-3 движок