Опыт разработки первой игры на Unity, часть 5
Предисловие
Появляются иногда комменты, мол, если хочешь показать, что можно сделать игру с нуля, то гораздо лучше сначала сделать — и потом рассказать. Возможно. Но мне кажется, что, увидев именно трудности и то, что с ними можно справиться, эффект будет сильно выше, чем от простого “Я, который не умеет программировать и прочее, представляю вам готовую игру!”. Тут видно, что технические навыки низки, видно, что рисовать я не умею — ну и так далее, — и видно, как справляюсь с этими трудностями. Мне кажется, от такого эффект будет сильнее — наверняка многие сталкивались и будут сталкиваться с такими же проблемами, которые кажутся непреодолимыми)
Так что впереди вас ждет еще больше фейлов, еще больше кривости и еще больше подробностей — ведь теперь проект начинает хотя бы напоминать игру, а это значит, что впереди будет только сложнее и интереснее!
Итак, поехали!
Введение
Хотел сделать классическую покадровую анимацию. Но иногда нужно трезво оценивать свои силы — а рисовать хоть и было бы прикольно, но довольно сложно. У меня есть сроки, хочу в них уложиться. Что же мне делать тогда? Вот был бы способ сделать анимацию, не рисуя по 12 кадров одного и того же персонажа… Ой, что же это? Это птица? Это самолет? Нет — это spain animation! Та-даннн!
Наткнулся на такое видео — начал изучать, что да как
Поизучав еще немного, решил стать павер-металлистом сквозь огонь и пламя… Ой — использовать Dragonbones. Вроде подходит + важный аспект — она бесплатна. В ней есть некоторые проблемы — например, искажение объектов, — но с моим скиллом рисования это не сильно решающий фактор. Так что такой размен посчитал разумным.
Вообще, в Dragonbones нет некоторых вещей, которые позволяют сделать анимацию более красивой. Но тут важный момент — а оно мне надо? Для минимальной приемлемой версии имеющегося функционала достаточно. К тому же, что-то, а графика точно будет добавляться / меняться.
Еще рассматривал стандартный Unity 2D Animation, но, насколько понял, пока что он несколько недоработан.
С анимацией разобрались — но нужен исходное изображение, которое будем анимировать. Сначала хотел использовать Gimp, но внезапно узнал, что он не для рисования, а для обработки фоток. А для рисования используется некая Krita.
Вообще, есть три варианта:
-
Рисовать самому
-
Найти того, кто нарисует
-
Взять готовые ассеты
Раз определился, что рисую сам — поехали!
И получается плохо! Вообще не понимаю, что тут я делаю. После первой неудачи всерьез задумался над поиском того, кто поможет. И это правильный подход, так и нужно делать. Но потом вспомнил, зачем я вообще все это затеял — развлечься и повеселиться! А что будет интереснее? Нарисовать 5 героев — это сложная и, главное, интересная задачка! Достаточно интересная, чтобы продолжить пытаться сделать самому
Для начала попробовал продолжить решать ее с наскока — результат немного предсказуем:
И я понятия не иммею, что делать дальше! Тут началась чуть-ли не паника: “У меня не получится”, “Слишком сложно” — все вот такое. Помог, как ни странно, сон. С новыми силами вернулся к задаче. Теперь подготовился получше — нашел вот такую серию видео:
https://youtube.com/playlis? list=PL1ypg9-tKyc28he9qTdlsEjjG1KPmd1hs
И, когда смотрю, все вроде как понятно. Но стоит начать делать самому — как полная пустота в голове! В итоге цикл получился таким: Просмотр видео — возвращение к работе — просмотри видео — возвращение — ну и так далее.
Результат за 4 часа выглядит ну очень плохо — такое сильно расстраивает. Но я просто напомнил себе, что невозможно получить хотя бы немного хорошие результаты за 4 часа работы с «нулевого уровня навыка».
Поставил для себя срок — 10 дней на одного героя. Если за 10 дней не увижу прогресса — точно начну искать другие способы. Звучит как крепкая такая задача с измеримыми критериями выполнения.
Но тут случилось нечто непредвиденное.
Помните, однажды говорил, как важно видеть прогресс для сохранения рабочего настроения? Так вот, за 20 или 30 часов работы, 4+ из которых записывал на видео, прогресса я не увидел. Учитывая вкладываемые усилия… Ну, это привело к закономерному результату — я потерял интерес. Причем не только к рисованию этого персонажа, но и к проекту вообще.
Штош, арт я не вытяну — это очевидно. Значит, нужно найти либо спецов, которые помогут, либо использовать готовые решения (asset store, например). Узнал, что нарисовать одного героя (только нарисовать — не анимировать!) стоит ориентировочно 10к. Зато купить готовый набор героев можно примерно за 5к.
Проблема “нарисовать героя” трансформировалась в “найти героев” — и отодвинулась на будущее.
Чтобы закончить на позитивной ноте, перешел к следующему пункту плана — призыв героев.
Оно живое! Живое!
Что вообще нужно для призыва? Валюта, экран призыва, ну и место, где герои будут призваны. Начнем с экрана призыва. Почему? Да просто захотелось.
При нажатии валюта тратится на нужную стоимость (что я вообще сказал?) и призывает нужное количество героев. Если валюты не хватает на призыв… Не повезло, что тут сказать. Иди фарми ее.
Но как-то некрасиво выглядит — просто тыкаю по кнопке и циферки меняются. Давайте исправим. Вжух! Немного магии, и…
Тут есть два варианта отображения полученных героев:
-
Как сделал я (отображается по одному виду призванного героя, ниже указано, сколько таких призвал ну или их текущее количество после призыва)
-
Как сделано в AFK Arena (каждый призванный герой отображается в виде отдельной карточки)
Мне больше нравится второй вариант, но из-за некоторых особенностей первый больше подходит к этой игре. И нет — это не из-за того, что у меня не получилось так сделать))
Герои получены — но вот незадача! Я же не сделал место, откуда открывается интерфейс с призывом. Давайте это поправим
Помните мою секретную кнопку в выборе уровней? Нет? Не страшно — сейчас напомню. Именно там пока будет открытие окна призыва. Вжух!
Хм, я все еще старательно сохраняю интригу на счет меты, так что давайте сделаем вид, что вы ничего не заметили!
Почему-то мне кажется, что свободный доступ к любому уровню сделает игру совсем неинтересной — давайте это исправим
Вы это видели? Для доступа к следующему уровню игроку теперь нужно пройти текущий — это все больше похоже на настоящую игру! Щитаю, это шедевр, не иначе.
Вау, внезапно оказалось, что крупных фичей осталось всего две. Первая — сервер — возможно, окажется приятным сюрпризом (люблю нагнетать интригу), а вот вторая — новый ранее не запланированный функционал — оказалась сюрпризом уже неприятным.
Это птица? Это самолет? Нет — это же код!
О да, я знаю, многие ждали этого!
Начнем с экрана призыва. В новом скрипте все разделено на 2 блока:
-
Показать героев, участвующих в битве
-
Создание самих кнопок
С показом героев ничего сложного:
public void ShowHeroes()
{
_panelHeroese.SetActive(true);
_btnClose.SetActive(true);
for (int i = 0; i < _heroPanel.Count; i++)
{
_heroPanels[_heroIndexes[i]].transform.GetChild(0).GetChild(0).GetComponent().sprite =
_heroPanel[i].GetComponent().sprite;
_heroPanels[_heroIndexes[i]].transform.GetChild(0).GetChild(1).GetComponent().text =
_heroPanel[i].GetComponent()._id;
// тут нужно сделать так, чтобы ставила обновленный максимальный уровень
_heroPanels[_heroIndexes[i]].transform.GetChild(1).GetChild(0).GetComponent().text = "Count: " +
_heroPanel[i].GetComponent().Hero_Counter.ToString() + 'n' + "Max Level = " +
_heroPanel[i].GetComponent().Lvl_Max.ToString();
_heroPanels[_heroIndexes[i]].SetActive(true);
}
}
Напрягает тут то, что у меня уже есть похожая функция в скрипте, отвечающем за отображение героев, взятых в битву. Но утешает то, что данные в них ожидаются разные.
Со спавном кнопок тоже не возникло проблем
public void CreateBtns()
{
for (int i = 0; i < _maxBtns; i++)
{
GameObject go = Instantiate(_btn, transform.GetChild(1).position, transform.rotation, transform.GetChild(1));
go.transform.GetChild(0).GetComponent().SetComponents(_spawner.gameObject, gameObject);
go.transform.GetChild(0).GetComponent().SetIndex(i);
go.transform.GetChild(0).GetComponent().SetCost(i);
_btns.Add(go);
}
}
А вот в самих кнопках уже начинаются интересности. Для начала я все же почитал умные книжки о программировании — и при помощи наследования сделал валюты. Аж две. Зная количество нужной мне валюты, делаю так, чтобы вызывалось нужное количество героев при нажатии на кнопку
public void SetCost(int index)
{
if (index == 0)
{
_howManyHeroes = 1;
_costSum = 5 * _howManyHeroes;
_btnText.text = "x" + _costSum / 5;
}
else if (index == 1)
{
_howManyHeroes = 10;
_costSum = 5 * _howManyHeroes;
_btnText.text = "x" + _costSum / 5;
}
_currencyText.text = _costSum + " / " + _spawnerScript.Curencies[_indexOfCurencies].CurCount;
}
Видимо, книжек прочитал недостаточно — раз тут какая-то 5 затесалась. На самой же кнопке висит… Барабанная дробь…
public void TryToBuy()
{
if (_spawnerScript.Curencies[_indexOfCurencies].CanToChangeCurrency(_costSummoning * _negativeCost))
{
for (int i = 0; i < _spawnSummonBtns.Btns.Count; i++)
{
ChangeCurrencyText(i);
}
BuyHero(_costSummoning);
}
}
О да, я не покупаю героя — я проверяю, возможно ли это. Пусть сейчас никакой базы данных нет, но везде, где ожидается обращение к ней, стараюсь как-нибудь облегчить себе дальнейшую интеграцию.
Ну а с покупкой все не так радужно. Я понятия не имею, как избавиться от кучи GetComponent
private void BuyHero(int howMany)
{
int convertCostSummoningToHowMany = howMany / 5; // И эта 5 с базы данных будет
int newCounter;
for (int i = 0; i < convertCostSummoningToHowMany; i++)
{
int rnd = (int)Random.Range(0, _spawnerScript.MaxHeroes);
_spawnerScript.playerHeroes[rnd].GetComponent().Hero_Counter++;
newCounter = _spawnerScript.playerHeroes[rnd].GetComponent().Hero_Counter;
_spawnerScript.playerHeroes[rnd].GetComponent().Lvl_Max = _spawnerScript.HeroLevelStats[0].GetMaxLevelFromCount(newCounter, _spawnerScript.HeroLevelStats);
_spawnSummonBtns.HeroPanelShowables.Add(_spawnerScript.playerHeroes[rnd]);
_spawnSummonBtns.HeroIndexes.Add(_spawnerScript.playerHeroes[rnd].GetComponent().IndexInHeroesArray);
}
_spawnSummonBtns.ShowHeroes();
}
Итак, призыв готов. Но что с изменением уровней? Как работает «Level complete» и «Пройди предыдущую битву»?
А вот тут пришлось сильно помучиться. Беда в том, как кнопки уровней создаются: на первый взгляд будто есть порядок "одна — две — одна — так далее", прям как в игре классики. Но можно вспомнить, что я специально делал так, чтобы такой порядок можно было нарушать — значит, какой-нибудь алгоритм определения нужных кнопок в массиве кнопок сделать не получится. Или просто не сообразил, как сделать. В итоге сделал первое пришедшее в голову решение.
У меня есть переменная maxLevel, от которой зависит количество уровней (неожиданно, да?). Используя ее, могу определить, на каком "слое" находится нужная кнопка-уровень — причем не важно, где в массиве она находится. Так что добавляю в кнопку смену цвета (вот это логическая связь, просто нет слов)
public void SetAvalevle(bool avaleble)
{
_isAvaleble = avaleble;
if (avaleble)
{
_btnImage.color = Color.white;
}
else
{
_btnImage.color = Color.gray;
}
}
Меняю функцию по включению нужного уровня
private void ChooseDifficult(int typeOfLevel, int numberOfLevel)
{
/*
if (typeOfLevel == 0)
{
// тут будет запрос к базе данным. Подтягивается соответствующий уровень сложности битвы. А пока просто запуск
}
*/
if (_isAvaleble)
{
_switchActiveState.SetActiveState(typeOfLevel);
}
else
{
if (_avalebleLayerNumber != numberOfLevel + 1)
{
_mapSpawnerScript.ChangeTextInfo("Complete batlle " + (_avalebleLayerNumber - 1) + " to open it");
}
else if (_avalebleLayerNumber == numberOfLevel + 1)
{
_mapSpawnerScript.ChangeTextInfo("You already completed it");
}
}
}
И добавляю функцию для «открытия» следующего уровня
public void SetComplete(bool complete, int levelNumber)
{
_isComplete = complete;
if (complete)
{
if (levelNumber == _avalebleLayerNumber)
{
_lvlName.text = "Complete";
SetAvalevle(false);
}
else if (_avalebleLayerNumber == levelNumber + 1)
{
SetAvalevle(true);
}
}
}
В итоге все это работает. Пока под вопросом, как резво — но работает!
Маленький шажок навстречу неизвестности
Уф, это было сложно. Браться за что-то с нулевым навыком — то еще испытание, считаю, что потратил очень много времени безо всякого результата. Если бы я видел, что процесс хоть и медленно, но движется, такого удара по мотивации я бы не получил. Из-за этого на игру стало абсолютно все равно — продолжать работать не было никакого желания.
Следование ранее намеченному плану работ и декомпозиция задач на самые минимальные помогло влиться обратно (это когда задачу разбиваешь на более маленькие — у меня все свелось к “а теперь задача — добавить на кнопку текст с количеством призываемых героев”).
Благодаря этому вновь начал ощущать прогресс, да и сам факт выполненных задач влияет очень позитивно - неважно, насколько они мелкие.
А в следующей части будет то, чего всеми силами старался избежать — добавление функционала сверх того, что запланировал изначально. К сожалению, без него получается не игра, а технодемка игры. Что это? Узнаете в следующей части!
Японский композитор призвал завершить серию Dragon Quest
Глава Xbox считает, что будущее игровой индустрии будет похоже на её начало. Microsoft даст разработчикам новые инструменты
GFA Games закроет онлайн‑шутер Pioner спустя менее года после релиза в раннем доступе
Перед выходом GTA VI фанаты устроят необычные проводы GTA V
Издатель Baldur’s Gate 3 оценил раннюю версию Path of Exile 2 выше Diablo IV
Игроки обнаружили издание GTA VI за 2000 долларов
Фанаты Five Nights at Freddy’s, возможно, ошибались 12 лет: лор переосмысливают из-за возможных проблем со зрением у разработчика
Звезда GTA IV пожаловался, что не получает отчисления за игру Rockstar: «Я не миллионер»