Зона, с которой игрок чаще всего взаимодействует в карточной игре — это его рука (не путать с рукой в играх с 18+ контентом). Рука — это набор карт, которые игрок может разыграть в текущий ход. И раз это одно из самых важных мест, я решил уже на этапе прототипа сделать руку приятной и красивой.
Дисклеймер: я тут описываю свой говнокод. Программировать я совсем не умею, так что не советую использовать как инструкцию.
Вот как раз во время работы с рукой (не путать с «во время работы рукой») я понял отличие качественной игры от поделки на уровне прототипа для тестирования геймплея. Все дело в мелочах и деталях. Посмотрите, как ведут себя карты в руке в Slay the Spire:
![](https://habrastorage.org/getpro/habr/upload_files/e24/a32/d60/e24a32d6065de5213fb7f6c8bbb9054e.gif)
Теперь посмотрите на те же карты на видосе у чела, который пишет в названии ролика, что он сделал Slay the Spire за неделю (9:11):
Теперь у меня это название вызывает горький смех, потому что как раз из-за таких небольших фишек с анимациями запрограммировать всё из Slay the Spire не удастся за неделю, даже при условии, что у тебя будет готов абсолютно весь арт, геймдизайн и другой контент.
Ключевая разница в этих двух примерах: то, как карты реагируют на наведение на них курсора мыши. В оригинале карта выходит на передний фон и красиво скейлится, а остальные карты разлетаются в сторону от нее. В копии карта просто поднимается и даже не вылезает на передний фон. Именно об это я споткнулся, когда захотел сделать для руки красивые анимации.
Как я делал руку с картами
Первую руку я сделал по туториалу, с которого делал первый прототип. В нём за формирование руки отвечал компонент Grid, который расставляет объекты по сетке. Grid лишает вас необходимости писать код, который отвечает за расстановку карт: вы просто задаете настройки и карты сами встают ровно при любом количестве, когда попадают в нужный родительский объект.
![Интерфейс компонента: как видите, все отступы, расположение и расстояния можно настроить тут, не нужно писать код Интерфейс компонента: как видите, все отступы, расположение и расстояния можно настроить тут, не нужно писать код](https://habrastorage.org/getpro/habr/upload_files/286/c45/a1a/286c45a1a46650744e8ed8fdd076d85b.png)
Проблема с Grid была в том, что делалось это некрасиво: карты телепортировались в сетку, а мне хотелось, чтобы они плавно переходили.
Недолго погуглив, я нашёл в Ассет Сторе бесплатный ассет Smooth Grid. Там были желанные плавные переходы. Если я вытаскивал карту, она потом вставала в конец — ну да и ладно, решил я.
![В настройках все то же самое, только еще есть плавность: насколько быстро карты переходят в свое место в сетке В настройках все то же самое, только еще есть плавность: насколько быстро карты переходят в свое место в сетке](https://habrastorage.org/getpro/habr/upload_files/2f2/74b/555/2f274b55598eb1110ef12167c2f13767.png)
Проблемы со Smooth Grid появились, когда я решил сделать руку похожей на руку в реальной жизни и в других карточных играх — чтобы карты слегка наезжали друг на друга — и после этого стал думать, как мне сделать выделение карты по наведению курсора. Сначала карты просто не хотели выезжать. Когда я разобрался с выездом, понял, что карта не хочет выходить на передний план. Всплыла проблема с тем, что эта плавная сетка перемещала карты влево или вправо в зависимости от положения объекта с картой в инспекторе. А значит, если я буду выводить карту на передний план с помощью перемещения в инспекторе, карта будет перескакивать со своей позиции в самую крайнюю в руке. Такое мне точно не подходило.
![](https://habrastorage.org/getpro/habr/upload_files/033/4cb/716/0334cb71624af2aca574facf0b019c78.gif)
Я понял, что в 2D в Unity не было способов перетащить объект на задний и передний план кроме свойства Sorting Layer. Вот только применить его можно только к объектам с компонентом Sprite Renderer. Он отвечает, например, за то, чтобы у карточки выводилась нужная картинка. А у меня все объекты имели компонент Image — я выводил изображение карточки с помощью него.
Так вот, чтобы выводить карточку на передний план, мне нужно было менять компонент Image на компонент Sprite. И это привело к своим сюрпризам. Оказывается, все объекты внутри Canvas с компонентом Image будут перекрывать любой другой объект. И когда я создаю объект Background, который задумывал как фон, он перекрывает спрайты карточек, и поверх фона ничего не видно. И это довольно логично: любой объект внутри Canvas Unity распознает как часть интерфейса: а интерфейс как бы должен быть поверх всего остального в игре. Но…на осознание этих правил у меня ушло много времени. А еще много времени на то, чтобы разобраться, как выставить настройки у спрайтов, поправить поехавшую камеру и сделать кучу мелких действий.
Следующее открытие: Event Trigger, компонент, с помощью которого я делал выведение карточек на передний план при наведении мыши и разыгрывании карточек, не хотел работать со спрайтами. Один и тот же скрипт у меня работал на объекте с Image и переставал работать на объекте со Sprite Renderer. Оказалось, есть еще один способ регистрировать наведение мышки: с помощью методов MouseEnter, MouseExit и MouseOver.
Дальше меня ждали новые проблемы. Я использовал метод Lerp, чтобы карточки красиво залетали в руку и красиво выдвигались, когда наводишь на них курсор. Все отлично работало ровно до тех пор, пока я не совмещал анимации карточек с их добавлением в Smooth Grid. Тогда начиналась котовасия.
![](https://habrastorage.org/getpro/habr/upload_files/173/8bc/e6e/1738bce6e93990db0a8d95b0bd0aab01.gif)
Дело в том, что Smooth Grid тоже построен на Lerp. А Lerp — это такая функция, которая как резиночка тянет объект на родительское место. И вот проблема была в том, что Lerp в сетке и мой Lerp спорили между собой, а карточки в итоге трясло и кидало в разные стороны. Я пытался это решить попеременным отключением Lerp’ов — не сработало. Все начинало ехать еще больше.
В итоге пришлось отказаться от компонента Grid совсем: и от Smooth, и от обычного. Я сам сделал простенький вывод на нужную позицию с помощью цикла for, максимально костыльно.
![Смотрю и даже стыдно смотреть... Смотрю и даже стыдно смотреть...](https://habrastorage.org/getpro/habr/upload_files/dbf/7aa/2aa/dbf7aa2aa334cfde5f2b352c0c057515.png)
Вот что у меня получилось в итоге
![](https://habrastorage.org/getpro/habr/upload_files/928/63e/313/92863e313cf6cf24acfd7a29ea1fe8e6.gif)
Скорее всего, прокатит для трейлера. Но для реальной игры нужно будет править, чтобы, например, карточки выравнивались по центру и расстояние между ними менялось, когда карточек в ряду становится больше или меньше. А еще наведение курсора не всегда регистрируется, и при желании порядок выдачи и улетания карт в сброс можно сломать.
Что думаю про сишарп
Несмотря на кучу проблем, мне нравится работать с сишарпом. Но по сравнению со всякими JS и Python синтаксис у него выглядит более избыточным:
![](https://habrastorage.org/getpro/habr/upload_files/bff/53c/f12/bff53cf12f32066dbebced38c83e4961.png)
Не напоминает ли это вам этот момент из «Кролика ДжоДжо» (0:18):
Хотелось бы конечно сделать так же сочно, как в Slay the Spire, но это капец сколько ещё ковыряться. Вообще при разработке стараюсь максимально отсекать всё лишнее. Например, когда я понял сейчас, что надо много всего перепиливать, я отсек вообще всю логику, которая не нужна была для той задачи, которую я поставил себе здесь, в телеге. Этим путём я следую и при разработке части игры для трейлера: максимальный говнокод (даже по моим меркам, а я тот ещё программист), максимум бутафорских штук. Например, у меня карточки тупо вылазят из края экрана, а там они изначально лежат перед запуском экрана.
Если поделитесь в комментариях гайдами и туториалами, как можно лучше сделать руку с картами, буду очень благодарен.
Этот пост входит в цикл постов про игру, которую я потихоньку делаю уже несколько месяцев. Я делюсь всем производственным процессом: какие решения я принимаю в разработке, геймдизайне, интерфейсе, арте и других сферах. В моем телеграм-канале вы можете посмотреть другие посты и узнать, когда ждать следующие: @nigylamchan.