Как сделать тетрис на юнити
Как сделать тетрис на юнити
Этот урок возник в связи с тем, что мы делали обзор на другие источники и выяснили, что большинство из них напрямую завязаны на Unity. Мы же хотели бы сделать универсальное и максимально понятное, модульное решение, котороe не зависело от движка и могло быть использовано в любом языке программирования будь то Java, С++
Необходимые знания: двумерные массивы, циклы, создание объектов из кода в Unity
1. Создадим скрипт Tetris и повесим его на Main Camera.
2. Игровое поле зададим 2мерным массивом размером 16×8. Все действия будут происходить именно в массиве, а мы лишь будем визуализировать результат средствами Unity. Необходимо инициализировать его заполнив конкретными значениями, для того, чтобы было проще тестировать наш алгоритм.
3. Объявим двумерный массив из int’ов как глобальную переменную.
4. Следующим шагом будет визуализация нашего массива. Для этого понадобится картинка блока тетриса размером 100×100 пикселей, чтобы в мире размер одного блока составлял 1×1 метр и нам не нужно было масштабировать наши блоки.
5. Из этого sprite создадим prefab. Можно пометить его static для частичной оптимизации.
6. В нашем тетрисе получим к нему доступ объявив публичную переменную
public GameObject pfbBlock;
7. Идея визуализации такова: мы изначально создадим все поле из квадратов, а потом будем включать и выключать их в зависимости от содержимого массива pole.
8. Нам очевидно потребуется получать доступ к этим блока и где-то их хранить. Будем их хранить в двумерном массиве из GameObject
9. Создадим ф-ю Fill, котороя и создаст все игровые блоки и поместит их в двумерный массив allBlocks.
11. Вызовем ф-ю Fill, после инициализации нашего поля.
Если мы сейчас запустим игру все поле должно быть в квадратах. Настройте камеру так, чтобы вы видели все игровое поле.
12. Создадим ф-ю Draw без параметров ничего не возвращающую
13. Вызываем ф-ю Draw в Update;
14. Внутри Draw будем анилизировать наш массив pole и в зависимости от того какое в нем число включать или выключать нужный кубик на экране. Для того чтобы пройтить по двумерному массиву нам понадобится for внутри for сначала по строкам, потом по x.
for ( int y = 0 ; y 16 ; y++) <
for ( int x = 0 ; x 8 ; x++) <
if ( pole[y,x] > 0 ) <
blocks[y,x].SetActive( true );
>
else <
blocks[y,x].SetActive( false );
>
>
>
15. После запуска игры вы должны увидеть ваше игровое поле на экране.
Русские Блоги
[Небольшой проект Unity] Тетрис
Unity реализует тетрис
Инструкции:
Среда разработки:
Введение в частичную реализацию
Архитектура MVC, используемая интерфейсом пользовательского интерфейса
Слой представления отвечает за реагирование на пользовательские события и отображение страницы, а уровень контроллера отвечает за реакцию на игровую логику и в качестве носителя для уровня представления и уровня модели. Уровень представления получает информацию о состоянии уровня модели путем отправки сообщений.
Механизм сообщений
EventManager Настроен словарь для хранения содержимого событий
Два интерфейса обеспечивают мониторинг и возбуждение событий соответственно
Перечисление для хранения типов событий
Управление классификацией переключения панелей UI
Есть два требования для переключения панели ui, ситуация следующая
Использовать собственный стек в проекте MyStack Чтобы сохранить панель управления, UICompositor Обеспечить отображение и скрытие интерфейса, поля BasePanel.stack Чтобы отметить необходимость сохранения панели в стеке при ее перезаписи, чтобы панель отображалась при закрытии новой панели.
Цифровая анимация прокрутки
Ключевые методы реализации:
Анимация исключения линии
Установивглубинас участиемЦветовая прозрачностьДля достижения эффекта «мерцания» (см.Фронт гифка)
Основные операции показаны на 3D-снимке экрана ниже.
Блокировать ускорение
Код ключа следующий:
Нормальное ускоренное падение блока используется для уменьшения временного интервала каждого шага, в Update Обновление внутри функции, ключевой код выглядит следующим образом
Дрожание камеры
Реализация карты
Карта использует отдельную камеру, и интервал между каждым квадратом карты равен 1, что удобно для вычислений, вращения и падения квадрата. Исходный размер карты фиксирован, а размер карты и квадрата, который мы видим, определяется камерой.
Стиль пользовательского интерфейса
Больше
О тетрисе
Тетрис заставляет людей остановиться. Помимо того, что «невозможно выиграть», он также даетИнтенсивность обратной связи。
(1) Визуально, ряд за рядом квадратов «пупу» исчезают;
(2) Количество очков на экране постоянно растет;
(3)В природе вы чувствуете, что продолжать расти(Скорость становится все быстрее и быстрее).
Как я делал Brick Game на Unity3D для Android и получил блокировку от Google
Википедия: Brick Game («Игра с кирпичами»; также его называют «Тетрис») — игровое устройство, работающее на гальванических элементах, снабжённое несколькими предустановленными играми и чёрно-белым (монохромным) экраном. На таких устройствах практически всегда присутствует игра, аналогичная «Тетрису», хотя вместе с ней часто имеются и другие игры. Существуют разные виды устройств с разным количеством игр.
Я расскажу о том, как (и почему) создавал симулятор Brick Game на Unity3D для Android с тетрисом, змейкой, гонками, танчиками и прочими играми, об эксперименте с монетизацией, а также о том за что Google Play может заблокировать подобное приложение и как его потом разблокировать.
Предыстория. «Тетрисы» без тетриса
Однажды захотел поиграть в Тетрис (сперва именно в игру Тетрис, а не в то устройство, что у нас в детстве называли «тетрисом»). Порылся в Google Play, нашел только две официальные версии от EA games. Поразился количеству бесполезных (по моему мнению) модных фишечек и наличию внутриигровых платежей. В Тетрисе! В целом ощущения были «не те».
И тут я вспомнил о такой штуковине как Brick Game и как на нём здорово было играть в Тетрис. Подумал, что в Google Play наверняка должен быть симулятор, и не ошибся. Таких симуляторов нашлось множество. Я поставил самый популярный и… не нашел тетриса среди игр. Кроме того посреди экранчика из квадратиков красовалось меню из кнопок с современными сглаженными шрифтами, с помощью которого нужно было стартовать игру, переключать звук и так далее. Не реалистично. Странно, миллион скачиваний и такие недоработки. Проверил остальные симуляторы — ещё плачевней да и тетриса нет почти нигде, а там где он есть — реализован кривовато, играть неудобно. Не говоря уже о реалистичности графического воплощения.
В общем решил я заполнить пробел и создать свой симулятор, с хорошим тетрисом и реалистичным экраном, для себя и для всех.
Из Википедии (а позже и на своём опыте, об этом читайте ниже) узнал, почему нет игры Тетрис во многих популярных симуляторах.
В 1996 году предполагаемый автор Тетриса (кстати, выходец из СССР), вместе с неким Хенком Роджерсом (англ. Henk Rogers) создал за рубежом компании The Tetris Company LLC и Blue Planet Software, пытаясь получить прибыль от бренда Tetris. The Tetris Company LLC (TTC) зарегистрировала слово Tetris как торговую марку. С тех пор несколько компаний купили у TTC лицензию на торговую марку. По американским законам, игру нельзя защитить авторским правом (только запатентовать), поэтому основным имуществом компании является торговая марка Tetris. Несмотря на это, TTC преследует клоны игры под именами, непохожими на Tetris. В мае 2010 года юрист TTC послал письмо в Google с требованием убрать с рынка Android Market все 35 клонов данной игры, хотя их имена не схожи с именем «Tetris».
Разработка. Выбор инструмента
Вначале предстояло выбрать между 1) Unity3D и 2) Android + какая-нибудь библиотека либо на голом Android API.
У меня ранее были эксперименты и с Unity3D и C# и с Java + Android, поэтому и рассматривал эти варианты.
1) Плюсы:
+ много готовых функций которые пригодятся для такого симулятора
+ возможность моментально проверить работу приложения в редакторе прямо на ПК
Минусы:
– большой вес *.apk файла — от 20 Мб
– дольше запускается
– прожорливость (батарея садится быстрее, больше греется)
2) Плюсы:
+ маленький вес («платишь» в основном за вес графических файлов)
+ высокая скорость запуска и экономия батареи
Минусы:
– придется писать больше кода (точнее, абстракций, либо утонуть в процедурном коде)
– медленный процесс тестирования на эмуляторе/устройстве
Наверняка не все согласятся с моими оценками, это дело личных умений и вкуса. Я пожертвовал размером и прожорливостью в пользу быстроты разработки и избрал Unity3D.
Разработка. Графическое представление
Начал с рисования скина с кнопками и экраном. Рисовал в векторном графическом редакторе, прорисовал все детали вплоть до каждого кубика на экране и палочки в циферных LCD блоках. Полного фотореализма достигнуть не удалось, особенно с кнопками, но в целом вышло неплохо. Экспортировал в PNG и закинул в Unity, на Quad, в качестве текстуры.

Позже добавил еще два скина — с большим экраном и повёрнутый.
Скин с большим экраном (может быть удобен на устройствах с небольшой диагональю дисплея):

Не долго думая, экран решил оживлять с помощью маленьких квадратиков с неактивными пикселями. То есть подложка у меня вся состоит из активных пикселей, а сверху объекты с неактивными пикселями, которым я переключаю видимость, а именно флаг enable. Класс дисплея содержит самый обычный модифицируемый массив из 200 элементов (10х20) (по ячейке на каждый квадратик), работать с ним удобно.

Помучался немного с расстановкой, возможно где-то и остались небольшие смещения, но их не заметно, ведь самое главное, что активные пиксели, которые ярче, расставлены строго по сетке и это вшито в картинку, поэтому масштабирование (при использовании качественного сглаживания) не должно испортить расстановку квадратиков. Проверил — так и вышло, всё ровно и аккуратно, радует глаз, решение «в лоб» сработало с первого раза.
Вокруг кнопок в ненажатом состоянии есть небольшая тень, для анимации кнопок я добавил маски с дыркой под кнопку и затенением которые появляются при нажатии и делают кнопку визуально вжатой в корпус.


Коллайдеры кнопок сферические и радиусом чуть выступают за пределы кнопок, чтобы легче было попасть (особенно это актуально для верхнего ряда маленьких кнопок).

Разработка. Звук
Пока работал над приложением нашел на городском рынке самый настоящий Brick Game, принес домой, и записал звуки нажатия и отпускания реальных кнопок. Многих игроков, кстати, эти звуки раздражали, я выпустил обновление, которое отключает эти звуки в режиме Mute, но даже сквозь пиликанье самого устройства пробивался звук нажатия кнопок и люди просили сделать эти звуки тише. А мне так нравились эти звуки, что не хотелось их приглушать.
Пиликанье сгенерировал заранее простейшим синтезатором (смешал несколько типов волн).
Разработка. Логика
Первой игрой которую я добавил в свой симулятор был, конечно же, Тетрис. Здесь я постарался: симметричная система поворотов, отскоки от стен и пола при повороте, проворачивание на 180 градусов в узком колодце, справедливый генератор выбора фигурки, скольжение после мягкого падения, управление (хорошая отзывчивость в целом, резкое падение двойным нажатием вниз, поворот в обратную сторону кнопкой вверх) и даже знаменитый T-spin вроде бы удалось сделать. В итоге мне было удобнее и приятнее играть в эту игру на своём симуляторе в смартфоне, чем на реальном Brick Game. Не говоря уже об официальном Тетрисе для Android который больше похож на цирковое представление, чем на тетрис (ИМХО).

В Unity3D можно использовать C# и все прелести ООП, что я и сделал. Написал базовый класс для всех игр (с банальным названием Game) и добавлял их по одной, наследуясь от этого класса.
Добавил змейку, гонки, еще несколько видов тетриса (например с фигурками из 5 блоков), понг, позже танчики, арканоид… Впихнул туда также парочку несвойственных этому устройству игр: Digger и Bomber. Digger получился сверхсложным и непонятным, но решил его оставить в качестве изюминки.
Сделал стандартную анимацию заливки экрана при проигрыше (с представлением дисплея в коде в виде массива это было элементарно), анимацию взрыва для гонок и пары других игр. К классам прибавились Sprite, AnimatedSprite. Например, каждая фигурка тетриса у меня это Sprite, и после сдвига этого спрайта можно проверять столкновения с игровым полем и реагировать соответственно.
В общем, разработка логики шла как по маслу и спустя месяц с начала разработки я выложил свой симулятор в Google Play. Первая иконка была скриншотом из игры, со временем нарисовал стилизованную иконку покрасивей (пока рисовал, помечтал о приложении с точно таким вот дизайном, уменьшенным экраном и тремя кнопками которых в принципе должно быть достаточно для тетриса и змейки):

Раскрутка
Из 39 тысяч, лишь пара сотен человек пришли по ссылкам из интернета, которые я оставлял на разных сайтах, посвященных Android-играм. Все остальные — самостоятельно нашли приложение в поиске Google Play.
Монетизация
Вас, должно быть, смутило, что я у себя считаю только скачивания. Как показала моя статистика от постоянных пользователей притока мало, в основном играют новоскачавшие, половина скачавших вообще удаляет приложение сразу или на следующий день, а те, кто оставил — играют настолько редко, что слабо влияет на статистику.
Через месяц я решил выключить рекламу, чтобы не портить приложение. Отмечу, что после отключения рекламы средняя оценка выросла и популярность приложения тоже пошла вверх, пользователи стали отмечать отсутствие рекламы как одно из исключительных преимуществ моего симулятора.
Блокировка и разблокировка


4 Октября игру заблокировали и Google Play Support прислали мне письмо о том, что моё приложение “has been removed from Google Play because it violates our metadata policy. This app won’t be available to users until you submit a compliant update.”, что значит — что-то не так с описанием или названием. Никаких подробностей, что именно их не устроило они не сообщили. Мол, читай правила, догадывайся сам, исправляй либо подавай аппеляцию.
Я сразу предположил, что виной всему наличие в симуляторе игры в тетрис и упоминание об этом в описании. Но это же симулятор устройства, в котором всегда был тетрис (в первых Brick Game вообще кроме тетриса ничего не было)! И так уж сложилось, что устройство Brick Game у нас называли «тетрисом» хоть это и неверно, но прижилось, стало нарицательным.
Но всё же достоверно причину я не знал, поэтому ничего не меняя подал апелляцию.
Через 2 дня пришел ответ:

Перевод: Мы не допускаем приложений с метаданными, которые обманчивы, не имеют отношения к приложению, избыточны или неприемлемы. Метаданные включают описание приложения, иконки, скриншоты и промо-изображения.
К примеру, описание вашего приложения имеет отсылки к Тетрису.
Пожалуйста, уберите посторонние ключевые слова…
Вот такой вот маразм, нельзя писать, что у тебя в игре есть тетрис, даже если он там есть, а лучше вообще его убрать, наверное, чтобы в дальнейшем не было проблем. Можно было бы сэкономить время если бы они сразу написали, что дело в слове tetris в описании.
Подверг описание цензуре:
Приложение после этого разблокировали. Не исключаю, что в будущем могут влепить блокировку за то, что там есть тетрис на скриншоте или что он есть в самом симуляторе.
2D Tetris Clone Game Tutorial – Unity3D (C#)
2D Tetris Game Tutorial – Unity3D (C#)
Do you remember the most famous single player game of early 90s? Yes, it’s Tetris. Developed by a Russian engineer, Aleksey Pajitnov, in 1985. Tetris is the 6th most played game ever. We will make our Tetris game in this tutorial.
The goal is to collect moving downward blocks at bottom with no gap in 2D. Game ends when there is no available area for new block.
Before start coding, please create or download your block texture (square) and wall texture. You can find the on google or download images below.
Open a 2D project in Unity3D and import these images. Cube images size is 32×32, so please edit Pixel Per Unit as 32 which means 32 pixels will be placed in 1 game unit. We need 7 different game object for blocks. Every block is created by 4 independent cubes because we will delete cubes separately when a row is full, not whole the object. By the way, we need J, T, S, Z, L, I, O-shaped blocks as you know.
To create blocks, create an empty game object named Block_J and create 4 cubes from cube image. Assume that there is a 4×4 square and Block_J is at the center (0, 0). Place cube objects in this 4×4 area. Be careful, you must placed cubes on squares perfectly to get best fit on game. You can see an example below.
Place all cubes in block objects as seen as picture below. Create prefabs (create a Prefabs folder under Assets in Project window and drag blocks) and delete blocks in scene.
We will check available area and restricted grid for cube movement, so you don’t need to add 2D collider to boxes and walls.
Create a C# script name SpawnBox and write code below.
Создание игры Match-3 в Unity
Несколько лет назад на SeishunCon я заново открыл для себя игры match-3. Я играл в Dr. Mario детстве, но такие более соревновательные игры, как Magical Drop, Bust-A-Move и Tokimeki Memorial Taisen Puzzle-Dama, сильно отличаются от неё.
Dr. Mario
В результате я осознал, как много нейтральных решений связано с созданием игры match-3.
На следующем джеме Ludum Dare я решил поэкспериментировать, но сначала за неделю до этого для разогрева попробовал разработать алгоритм «Тетриса», обнаруживающий и удаляющий линии. Мне очень помог этот туториал Unity Plus. [Прим. пер.: у меня ссылка не открывается. Если вы знаете, как решить проблему, напишите мне, я дополню статью.] Разумеется, алгоритм «Тетриса» для поиска заполненных рядов гораздо проще, чем алгоритм, выискивающий разнообразные сочетания совпадающих тайлов.
Если вы хотите изучить эти примеры кода в контексте, то зайдите в мой репозиторий Ludum Dare 30. (Для бесстыдной саморекламы я снова использовал эту логику для игры Shifty Shapes.)
Два мира
Magical Drop 3 (источник: Kazuya_UK)
Самая мудрёная часть создания игры-головоломки в Unity заключается в том, что игра не живёт в пространстве мира. Во всяком случае, живёт не полностью.
В этом её отличие от других жанров. Платформеры, например, почти полностью живут в игровом мире Unity. Transform игрока сообщает о его положении. Коллайдеры (или, в некоторых случаях, raycast) говорят, находится ли игрок на земле, ударяется ли об потолок или столкнулся с врагом. Даже если вы не используете внутриигровую физику, то всё равно, скорее всего, добавляете силу или указываете скорость Rigidbody, чтобы обеспечить распознавание столкновений без затрат.
Игры-головоломки совсем другие. Если в вашей игре нужно щёлкать мышью, то она наверно получает какие-то координаты в пространстве мира, но их обычно преобразовывают в ячейку сетки, которая полностью живёт в коде. Для этого есть понятная причина: гораздо проще создать логику для такой игры как Tetris или Dr. Mario, когда работаешь не с отдельными пикселями, а с блоками или тайлами.
Блоки «Тетриса» определённо не должны прилипать к стенкам стакана
На самом деле, в своих экспериментах я стремился как можно больше придерживаться пространства мира. Я использовал физику для определения «приземления» тайлов и передавал данные в двухмерный массив только для определения заполнения строки. Это казалось более безопасным: в конце концов, то, что происходит в игровом мире реально. Именно это видит игрок, поэтому если хранить данные тут, то нет риска рассинхронизации, правда?
Я ошибался. Как бы я ни пытался настроить систему, она просто не работала правильно.
Туториал Unity Plus, ссылку на который я дал выше, оказал мне огромную помощь. Как минимум, он показал, что правильным подходом был полный перенос логики из игрового мира в абстрактную структуру данных. Если вы ещё этого не сделали, то хотя бы вкратце просмотрите его, потому что в этой статье я расширю логику «Тетриса» до логики match-3.
Преобразование из поля в пространство мира
Как только я понял, что этот переход удобен, остальное было простым. Я создал класс GameTile, отслеживающий цвет, строку и столбец тайла, и на его основании обновлял положение тайла. Вот его сокращённая версия:
Тайлы в сетке
Заметьте, что в этом случае TileSize является константой, определяющей размер тайла в единицах Unity. Я использую тайлы размером 64×64 пикселя, а спрайт в Unity имеет разрешение 100 пикселей на единицу, поэтому TileSize оказывается равным 0.64. Также я использую постоянное смещение, чтобы середина поля 7×7 находилась в координатах 0,0 пространства мира, а левый нижний угол являлся тайлом 0, 0 в игровом пространстве.
Также я создал массив, определяющий игровое поле как статичное поле (static field) в классе Board. (Board сначала был статичным классом, а потом превратился в синглтон (singleton), потому что мне нужно было изменять значения в редакторе, поэтому он неуклюже сочетает в себе черты игрового объекта и статичного класса.)
В туториале Unity Plus двухмерный массив использовался для хранения целых чисел, я же решил хранить в этом массиве ссылки на мои объекты GameTile. Это позволило мне передавать данные от тайлов и к ним напрямую (как вы увидите позже), что упростило удаление тайлов и создание анимации.
При внесении изменений в состояние игрового поля мне нужно было просто пройти циклом по всему массиву поля и сообщить каждому тайлу, где он должен находиться:
Заметьте, что в каждом случае мы выполняем преобразование из абстрактного игрового пространства в пространство мира. Игровые объекты Unity не хранят сами важную информацию о состоянии игры, они всегда являются только отображением этого состояния.
… и обратно
В моей игре был единственный случай, когда нужно было выполнять преобразование из мира в игровое пространство: когда игрок щёлкал мышью на пустое пространство, чтобы бросить на поле тайл. Для этой задачи я создал большой коллайдер под всем игровым полем и прикрепил к нем следующий скрипт:
Вот, собственно, и всё. Заметьте, что в сущности в нём выполняется действие, обратное UpdatePosition(), где игровое пространство преобразуется в пространство мира.
Распознавание и удаление совпавших тайлов
Удаление совпавших тайлов
Это самая хитрая часть. Наверно, именно ради этого вы читаете статью.
Горизонтальное совпадение (как в «Тетрисе») реализовать довольно просто: нужно всего лишь искать смежные тайлы в одной строке. Даже добавление горизонтальных или вертикальных совпадений (как в Dr. Mario) является просто вариацией этой темы. Однако отслеживание набора смежных тайлов в одновременно горизонтальном и вертикальном направлении потребует рекурсии.
При каждом действии, изменяющем игровое поле, мы запускаем проверку. Первое, что мы делаем — копируем весь массив поля в другой массив:
Зачем? Мы увидим позже, что так будет гораздо проще определить, какие тайлы мы проверяли.
Мы начинаем процесс с «грубого» перебора. Пройдём от ячейки к ячейке (сначала строки, потом столбцы), проверяя каждую ячейку. Для каждой проверки мы сбрасываем некоторые переменные, используемые для отслеживания проверки, а затем вызываем отдельную функцию (которую позже применим для рекурсии):
Давайте рассмотрим эту функцию TestTile:
Если функция обнаруживает, что ячейка имеет значение null, то пропускает её. Ячейка с null означает, что она или пуста, или мы уже тестировали её. (Именно поэтому мы скопировали её в отдельный массив — так проще произвольно манипулировать новым массивом.)
Если ячейка имеет значение, то мы делаем следующие действия. Во-первых, мы запоминаем её как «текущую» ячейку, ту, которая находится на вершине рекурсивной цепочки. Затем мы удаляем её из копии игрового поля, чтобы не проверять дважды. Также добавляем её в список (List), чтобы запомнить, сколько смежных тайлов одного цвета мы нашли.
Есть два состояния, которые могут возникнуть позже в рекурсии, но мы поговорим о них потом. Проверив ячейку, мы берём четыре ячейки вокруг неё и выполняем для них ту же проверку.
«Текущая» ячейка уже установлена, и это значит, что мы не на первом уровне рекурсии. В этих вызовах функций у нас есть три варианта для каждой ячейки.
Во-первых, ячейка может быть null, и это снова значит, что мы уже проверяли её, или она пуста. И в этом случае мы снова ничего не делаем.
Во-вторых, ячейка может не совпадать с «текущей» ячейкой. В этом случае мы не считаем её «проверенной». Наша рекурсия проверяет наличие одного набора смежных тайлов одного цвета. Только потому, что этот тайл не является частью текущего набора, не значит, что он не является частью какого-нибудь другого.
В-третьих, ячейка может быть того же цвета, что и «текущая» ячейка. В таком случае, она «проверена», поэтому мы устанавливаем ей значение null в копии игрового поля. Также мы добавляем её в List, который используем как накопитель. Это одно из состояний, которое мы пропустили в примере выше:
Функция продолжит выполнять рекурсию, пока не закончатся все варианты, добравшись или до пустой ячейки, или до конца поля. В этот момент мы возвращаемся в основной цикл грубого перебора для обработки результатов.
Если в накопителе есть больше трёх тайлов, то мы нашли совпадение. Если нет, то мы проверили один или два тайла, но нам не нужно выполнять никаких действий:
Здесь, как мы рассмотрим позже, я просто включаю анимацию. Простейший подход, однако, заключается в том, чтобы пройти циклом по нашему накопителю и вызвать DestroyObject для игрового объекта каждого совпадающего тайла. Так мы убьём двух зайцев одним выстрелом: избавимся от внутриигровых объектов и присвоим ячейкам в состоянии игрового поля значение null.
Падение тайлов
Падающий тайл
Определённые изменения — например, падение тайла или удаление тайлов, в этом случае — оставляют тайлы без опоры, и этот случай нужно разрешить (конечно, если это требуется по правилам вашей игры). И на самом деле это довольно простой алгоритм.
Теперь мы проходим столбец за столбцом, а затем строку за строкой. Порядок здесь важен.
В каждом столбце мы проходим снизу вверх, пока не находим пустую ячейку. Затем мы помечаем её. Следующий найденный тайл мы просто смещаем вниз, в это положение, и добавляем единицу к индексу «пустой ячейки»:
После завершения нужно не забыть снова вызвать функцию проверки совпадений. Очень вероятно, что падающие тайлы создали пустые строки.
На самом деле, если в игре ведётся счёт, то это упростит отслеживание комбо или множителей очков. Все эти повторения падений и удаления блоков являются рекурсиями того первого вызова, запущенного действием игрока. Мы можем понять, сколько всего совпадений возникло после действия игрока и какое количество уровней «цепочек» потребовалось для каждого действия.
Анимации
Игра уже работает, но пока она не понятна интуитивно, в основном из-за отсутствия анимаций. Тайлы исчезают, а затем появляются на нижних строках. Сложно понять, что происходит, если не следить внимательно.
Это тоже сложный момент. Игровые объекты всегда являются отражением состояния игры, поэтому тайлы постоянно расположены в сетке. Тайлы всегда занимают то или иное место: тайл может быть в строке 1 или 2, но никогда — в строке 1,5.
В чём заключается сложность? Нам нельзя одновременно манипулировать игровым полем и анимацией. Вспомните, как работает «Тетрис» или Dr. Mario — следующий тайл не падает, пока все тайлы на поле не «улягутся». Это даёт игроку короткую передышку, а также гарантирует отсутствие непредвиденных состояний и взаимодействий.
Кстати, при начале нового проекта я рекомендую создавать перечисление (enumeration) «игровых состояний». Мне никогда не приходилось писать игру, в которой не нужно было знать состояние игры: сам процесс игры, пауза, отображение меню, диалоговое окно, и так далее. Лучше всего спланировать состояния на ранних этапах разработки, таким образом можно сделать так, чтобы каждая написанная вами строка кода проверяла, должна ли она выполняться в текущем состоянии.
Признаюсь, что моя реализация неуклюжа, но в целом идея такова: при удалении или падении тайла мы задействуем изменение состояния. Каждый объект GameTile знает, как обрабатывать это изменение состояния, и, что более важно, знает, когда нужно сообщать игровому полю, что он завершил свою анимацию:
После завершения анимации удаления игра должна проверить наличие падающих тайлов:
После завершения анимации падения нужно проверить наличие совпадений:
Этот цикл повторяется, пока у нас не останется больше совпадений, после чего игра может возвращаться к своей работе.