Опыт разработки первой игры на Unity, часть 4
Или о том, как я обманываю читателей
Дело в том, что я снова ошибся в планах — причем опять на том же самом месте! Вновь для того, чтобы сделать прокачку героев, мне перед этим нужно реализовать другой функционал.
Беда, что только участвующие в битве герои должны получать опыт (хотя тут есть важный геймплейный нюанс, о котором в другой раз), а в текущей архитектуре это невозможно. Могу придумать какой-нибудь костыль, но гораздо лучше будет, если сделаю все правильно (ну, в моем представлении)
Поэтому перед повышением уровня нужно сначала сделать выбор участвующих в битве героев
Подготовка
Для начала нужно добавить интерфейс, в котором и будет происходить выбор героев для битвы.
- Снизу экрана должны отображаться все имеющиеся у игрока герои
- Сверху — просто расположение выбранных героев на поле битвы
- Хочу кликнуть по герою — и чтоб он появился сверху
- Еще хочу уметь перетаскивать героев сверху на разные позиции
Реализация
Пыщь-пыщь — немного магии — и готово.
Упс — что-то пошло не так. Лезем обратно в код — и получаем вот такое чудо:
Дело нехитрое, но как же ужасно выглядит! Давайте договоримся: вы сделаете вид, что не замечаете интерфейс. Под UI / UX выделен отдельный пункт плана работ — и какой же кошмар меня ожидает! Тут можно заметить, что весь экран по центру разделен на две части: с квадратами и — вот неожиданность — другими квадратами (причем снизу кнопки). Итак, что тут:
Герои снизу — просто кнопки. Нажимаю, и он появляется в первой доступной ячейке. Нажимаю вновь — пропадает. Запускаю бой — в битве участвуют только выбранные герои — причем на нужных позициях! Не верите? А вот:
Трудности — куча их!
И знаете, что тут оказалось самым сложным? Ни за что не угадаете! Появление героя в нужной ячейке при нажатии на кнопку. Как я с этим намучался. Оказалось, что нельзя просто взять и сделать так, чтобы объект просто заменялся на нужный. Ну или я просто не сообразил, как так делается.
В Unity можно сделать "выключенные" объекты — они как бы есть, но движок их не обрабатывает (соответственно, игрок их не увидит). Был вариант “сделать сюда кучу героев и деактивировать их. А потом в нужный момент просто активировать нужного". Спасибо за генерацию идей — сказал я себе, — и принялся думать дальше. В итоге сейчас просто нужному объекту присваивается спрайт нужного мне героя. Уии, магия!
Осталось немного — нужно уметь менять героев местами перетаскиванием в разные точки. Сделал 1 в 1 как в этом видео:
Вот тут возникла неприятная особенность — на этапе продумывания я понятия не имел, как сделать так, чтобы герои именно менялись местами. Это казалось абсолютно непонятным. То есть, в общих чертах я представлял, как нужно делать, но детали казались абсолютно непонятными. Это стало в том числе причиной следующего.
Выгорание, ты ли это?
Примерно тут мне все начало слегка так надоедать. Код разрастается, понимаю я в нем все меньше и меньше. Для того, чтобы делать новые фишки, приходится перелопачивать старые. Усугубляется тем, что сейчас я не слишком следую плану. Делая что-то сейчас, я стараюсь учитывать, какие еще фичи должны быть поверх текущих или параллельно им. И из-за этого приходится делать много чего, что не связано с текущей задачей — а это по ощущениям сильно замедляет скорость работы.
Справится с этим можно довольно просто: более качественно декомпозировать задачи — тогда сама разработка будет более последовательна, и не придется скакать туда-сюда. Но, если честно, не уверен, что это вообще возможно. И это при том, что у меня есть конечный список того, что мне нужно в минимальной рабочей версии — ничего сверх него я не собираюсь добавлять.
Как итог — я так и не придумал, как сделать так, чтобы герои менялись местами при перетаскивании одного на другого. Подозреваю, это не просто. Скорее всего, это очень просто. Беда в том, что это важный UX — элемент, без которого игрок будет чувствовать много боли. И оставлять это недоделанным — такое себе…
С другой стороны, я очень долго с этим вожусь, мне нужно передохнуть. К тому же, я сделал так, что работу над этой фичей можно продолжить в любой момент — ее отсутствие / реализация не потребует изменений уже сделанного.
Ох и нагнал я негатива. Да, было не очень комфортно — но гляньте на результат! Настоящая магия!
А как работает, семпай?
А теперь ваша любимая часть! Сердце сего шедевра, его мозг. Путь, по которому движется сей самурай. Движок, бьющийся… Ладно, ладно, прекращаю. Встречайте: то, от чего у программистов появляется непреодолимое желание взять учебник по языку и дать его почитать — код!
Правда, никаких неординарных задач тут нет
При нажатии по герою снизу он заполняет первую свободную ячейку сверху. При этом отправляет выбранных героев в архив "активных героев" — именно они будут участвовать в битве:
И… Это все xD
Заключе… Ох, стоп. Это что, продолжение?
Воу, статья еще не кончилась?
Да-да, в этом выпуске будет больше одной фичи! Помните повышение уровня? Теперь сделаю… Нет, еще не его.
Для повышения уровня рассматривал несколько вариантов:
- Герой получает опыт при каждом убийстве противника. Максимально приближенный к «большим» РПГ игровой опыт
- Герои получают опыт только после победы над каждой волной противников
- Герои получают опыт только после победы над всеми противниками
Изначально хотел сделать первый вариант, но остановило то, что герои будет увеличивать уровень чуть ли не после каждого убийства. А при повышении восстанавливается здоровье. Они же не убиваемыми получатся! Это можно решить, назначив требованием к level up «получить 9000 опыта», но я хочу игрока награждать почаще. Остальные варианты в своей сути одинаковы.
К чему это я? Остался последний штрих перед повышением уровня — игра должна знать о нашей победе или поражении. Иии… Тут без сюрпризов: добавить UI панели — разместить нужные картинки и текст — вжух-вжух — и готово!
Решил не делать красивую анимацию “перетекания” полученного опыта в героя (чтоб красиво так повышался уровень). Пока просто отображает, сколько опыта герой получил за битву. Чуть не забыл! Выбранные в битву герои сохраняют свои позиции даже после битвы — красота.
Добро пожаловать в школу программирования
И вновь — попытка поехать на велосипеде с помощью костылей и какого-то чуда
Вот. Вот оно — то, с чем я возился больше 10 часов. И я не шучу. В поисках этого решения я перерыл весь интернет. Вы готовы?
Проверка того, что все объекты в массиве мертвы. Я сам не знаю, как так получилось — это же невероятно просто. Это буквально стандартное решение, для которого даже думать не нужно!
А больше ничего интересного и не было. Хотя нет — я понял, что с моим "переключателем сцен" (который пока что просто включает/выключает объекты) нужно что-то делать. Сейчас это что-то жуткое, в котором наделать баги проще простого. Ну вы видели в предыдущей части, что у меня там. А сейчас туда добавился экран выбора героев и экраны победы с поражением.
Штош, на этом все. Хах, ладно. Обещал сделать прокачку герев — будет прокачка героев.
Это же значит, что теперь я не обманываю читателей!
И вот тут столкнулся с неожиданной трудностью. По изначальному плану к этому моменту у меня все герои уже должны знать о том, какие характеристики на каких уровнях они будут иметь. Но этап с подтягиванием данных я ведь отложил. Значит, мне нужно сделать что-то, в чем будут храниться нужные мне данные.
И я решил не париться от слова “совсем”. Это решение наверняка плохое, но в дальнейшем я, скорее всего, от него избавлюсь. А пока… Решил использовать struct. Первый struct хранит характеристики на первом уровне. Второй — на сотом. Все значения между ними по задумке будут высчитываться интерполяцией.
Почему не сделать грамотно (например, сделав на устройстве файлик с этими значениями и подтягивать из него)? А все просто — еще не время разбираться в этом (ну и мне лень, чего уж там). Опять же — в дальнейшем struct наверное пропадет, а эти же данные будут подтягиваться из таблиц.
План определен — поехали
И тут же останавливаемся. Оказалось, что текущая система данных в таблице неудачная — часть показывает характеристики на первом уровне, а часть — какими должны быть характеристики для достижения последнего уровня. Если проще — показывают характеристики на предпоследнем уровне.
Делаю колдунство с таблицей — и все вроде как нормально.
Нужно разобраться, что мне вообще делать:
- Получить список характеристик (как раз struct подготовил)
- Сделать так, чтобы за битву давали опыт в зависимости от противников
- Повышать в зависимости от полученного опыта уровень героя
- И находить соответствующие уровню характеристики
Для первого пункта делаю вот так
О том, что сделать это можно через for, подумал почему-то только что. А, и магические числа, да. Но у меня есть половинка оправдания! В дальнейшем вместо них будут поступать данные с таблицы. Хотя и сейчас можно к этому все подготовить))
Пункт 2
Внезапно стало легко определить, сколько опыта выдавать героям игрока — просто перемножаем количество опыта за противника на число противников:
По поводу for... Честно — понятия не имею, почему при стандартном i++ у меня остается один активный объект.
Операция «Повышение»
Ой, а я же уже показал. Вычисляется, сколько уровней герой может получить в зависимости от полученного опыта. Затем уровень присваивается, а оставшийся остаток от деления становится «текущим опытом». Интересна тут функция hero.GetComponent().SetNewLvl(newLevel);, которая приводит нас к...
Свободная касса!
Итак — проблема. Мне известен набор характеристик на первом уровне героя. Известен набор характеристик на 150 уровне героя. А тут, внезапно, понадобилось узнать параметры героя на условном 38 уровне. Как это сделать?
Можно попробовать через for. Это будет чуть проще, чем через if или switch. Но, хоть я тот еще извращенец, к таким подвигам не готов. Зная пограничные значения, можно высчитать то, какие значения будут в любом месте между границами. Не буду томить — мне подсказали вот такую замечательную формулу:
Функцию придумал уже я — и она 100% поменяется. Мне крайне не нравится, что приходится вручную указывать пограничные для значения структуры.
Ах да, думаю, вы уже успели отдохнуть от надругательства над беднягой c#. Не переживайте, подергивающийся глаз от встреченного Stats stat вас не обманул — это именно то, о чем вы подумали:
И так на каждый параметр. Я так и не придумал, как избавится от сравнения (хотя на 100% уверен, что можно).
Зато гляньте, какое чудо получается!
Магия! Это все больше и больше становится похоже на игру! Ну разве не чудо?
Заключение
Это часть получилась довольно тяжелой, зато сделал целых три пункта из запланированного. Возникало невероятное количество проблем — порой на ровном месте. Зато было довольно весело. Но теперь мне нужно отдохнуть от кода. Изначально минимально рабочую версию собирался сделать до февраля, но сейчас начинаю сильно сомневаться, что успею. С продолжением вернусь уже в следующем году, так что с наступающим — и не скучайте!
И напишите, как вам эта часть! В предыдущей было много кода, но из-за того, что он был разбросан повсюду, читать было сильно скучно. Тут попытался сделать иначе. Как больше нравится — когда текст пишется по ходу событий или больше по итогу всего?
И я тут подумал... Вам не кажется, что битва квадратов с кружочками — это совсем не серьезно?
Ты молодец! Главное не отступай перед трудностями и не бросай разработку.
Спасибо!)
Старайся, тут был чел который I am king создал, тоже шаги выкладывал и смог игру выложить в стим
Это здорово))
Я бы рекомендовал с таким кодом как можно быстрее доделывать проект, не добавляя новые механики, если можно обойтись без них. Со временем расширять функционал будет все сложнее и сложнее, причем требуемое для этого время будет расти нелинейно. К тому же могут начаться проблемы с производительностью и багами, которые тоже требуют много времени
К счастью, есть конечный список задач))
Если бы все шло по плану, то к февралю все было бы готово, но с этой частью я задержался на 10 дней. Думаю, такими темпами будет готово к апрелю
Декомпозировать задачи это как? Можно по подробнее?