Предисловие

Появляются иногда комменты, мол, если хочешь показать, что можно сделать игру с нуля, то гораздо лучше сначала сделать — и потом рассказать. Возможно. Но мне кажется, что, увидев именно трудности и то, что с ними можно справиться, эффект будет сильно выше, чем от простого “Я, который не умеет программировать и прочее, представляю вам готовую игру!”. Тут видно, что технические навыки низки, видно, что рисовать я не умею — ну и так далее, — и видно, как справляюсь с этими трудностями. Мне кажется, от такого эффект будет сильнее — наверняка многие сталкивались и будут сталкиваться с такими же проблемами, которые кажутся непреодолимыми)

Так что впереди вас ждет еще больше фейлов, еще больше кривости и еще больше подробностей — ведь теперь проект начинает хотя бы напоминать игру, а это значит, что впереди будет только сложнее и интереснее!

Итак, поехали!

Введение

Хотел сделать классическую покадровую анимацию. Но иногда нужно трезво оценивать свои силы — а рисовать хоть и было бы прикольно, но довольно сложно. У меня есть сроки, хочу в них уложиться. Что же мне делать тогда? Вот был бы способ сделать анимацию, не рисуя по 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<Image>().sprite =
                _heroPanel[i].GetComponent<SpriteRenderer>().sprite;

            _heroPanels[_heroIndexes[i]].transform.GetChild(0).GetChild(1).GetComponent<TextMeshProUGUI>().text =
                _heroPanel[i].GetComponent<Characteristics>()._id;

            // тут нужно сделать так, чтобы ставила обновленный максимальный уровень
            _heroPanels[_heroIndexes[i]].transform.GetChild(1).GetChild(0).GetComponent<TextMeshProUGUI>().text = "Count: " +
                _heroPanel[i].GetComponent<Characteristics>().Hero_Counter.ToString() + '\n' + "Max Level = " +
                _heroPanel[i].GetComponent<Characteristics>().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<Btn>().SetComponents(_spawner.gameObject, gameObject);
            go.transform.GetChild(0).GetComponent<Btn>().SetIndex(i);
            go.transform.GetChild(0).GetComponent<BtnSummon>().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<Characteristics>().Hero_Counter++;
            newCounter = _spawnerScript.playerHeroes[rnd].GetComponent<Characteristics>().Hero_Counter;

            _spawnerScript.playerHeroes[rnd].GetComponent<Characteristics>().Lvl_Max = _spawnerScript.HeroLevelStats[0].GetMaxLevelFromCount(newCounter, _spawnerScript.HeroLevelStats);

            _spawnSummonBtns.HeroPanelShowables.Add(_spawnerScript.playerHeroes[rnd]);
            _spawnSummonBtns.HeroIndexes.Add(_spawnerScript.playerHeroes[rnd].GetComponent<Characteristics>().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);
            }
        }
    }

В итоге все это работает. Пока под вопросом, как резво — но работает!

Маленький шажок навстречу неизвестности

Уф, это было сложно. Браться за что-то с нулевым навыком — то еще испытание, считаю, что потратил очень много времени безо всякого результата. Если бы я видел, что процесс хоть и медленно, но движется, такого удара по мотивации я бы не получил. Из-за этого на игру стало абсолютно все равно — продолжать работать не было никакого желания.

Следование ранее намеченному плану работ и декомпозиция задач на самые минимальные помогло влиться обратно (это когда задачу разбиваешь на более маленькие — у меня все свелось к “а теперь задача — добавить на кнопку текст с количеством призываемых героев”).

Благодаря этому вновь начал ощущать прогресс, да и сам факт выполненных задач влияет очень позитивно - неважно, насколько они мелкие.

А в следующей части будет то, чего всеми силами старался избежать — добавление функционала сверх того, что запланировал изначально. К сожалению, без него получается не игра, а технодемка игры. Что это? Узнаете в следующей части!

Комментарии (2)


  1. Farshmac-Inc
    15.01.2022 02:49

    Напишу кратенько со стороны кодера, который не считает себя даже джуном:
    1) Отсутствует всякое представление ООП, что приводит просто к кошмарному коду. И даже сейчас не о паттернах речь. На протяжении всех предыдущих статей вижу как ты ломаешь себе голову в попытках придумать реализацию кода, которую может было бы реализовать применив простейшее наследование.
    2) Далее несмотря на то, что многие думают, что всякие кликеры и им подобный шлак мобильного утраказуального геймдева, делается профессионалами на колленках, это всё игры, подразумевающее постоянное расширение контентом причём абсолютно разным. Если в игре будет крайне плохо устроена архитектура, то не о какой расширяемости и речь идти не может. Уже на данном этапе я наблюдаю, что ты сломаешь себе ноги при попытке добавить что-то в свою архитектуру.
    Теперь думаю вклинюсь как начинающий геймдиз.
    Сразу скажу, что я терпеть не могу мобильные гриндилки, в том числе батлеры как ты их называешь. Но могу сразу же выявить одну глобальную проблему об которую ты уже не раз споткнулся и не раз споткнешься. У тебя нет диздока твоей игры, хотя бы в формате концепта или списка игровых механик. Это влечёт за собой отсутствие чёткого плана разработки, что уже трижды насколько я понял отодвинуло разработку механики прокачки героев. Кроме того вообще не понятно что ты собственно говоря хочешь сделать, ибо в данный момент это выглядит (из твоих же примеров и референсов) как клон АФК Арены. Ибо механики копируешь, игровые состовляющие копируешь, даже пытался персонажей срисовать. Я думаю что следующую статью тебе следует посвятить именно процедуре разработки концепта своей игры. И тема эта достаточно обширна. Так как включает в себя анализ игр аналогично жанра, рынка и т.д. Формирование списка механик с объснением выбора и т.д. А лишь потом возвращаться к разработке.


    1. Vivicpony Автор
      15.01.2022 03:15

      которую может было бы реализовать применив простейшее наследование.

      О да, Но я вроде как и понимаю, что нужно делать, а вот как сделать - не выходит. Сильно не хватает теоретической базы, видео и книги помогают, но прогресс идет очень медленно

      делается профессионалами на колленках

      Согласен с тем, что это тоже большой труд. Более того - все эти мини-игры с мисслидовскими рекламами тоже сложно делать. Многие считают, что мобилки ими переполены, но только представьте, как в такой конкуренции сложно сделать игру, которую а) заметят игроки, б) игра им понравится и в) которая будет приносить доход, покрывающий все расходы на нее.

      Если посмотреть с этой стороны, то выбор жанра имеет смысл - конкуренции в нем все так же много, но обычно разработка таких игр - очень дорого. Прям совсем очень. Не многим профессиональным командам удается завершить игру к техланчу - не говоря уже о том, чтобы выпустить ее хоть как-то. А у меня из расходов - дошик да квартплата))

      не о какой расширяемости и речь идти не может

      К счастью для минимальной версии расширяемость мне особо не нужна)) Ее задача - проверить r1 (сколько поигравших игроков вернутся в игру на следующий день). Если с ней все будет хорошо - то все проблемы с кодом сами собой отпадут (будет проверенная гипотеза, что игроки готовы в такое играть = можно кидать деньги в разработку). Если с ней все будет плохо - то беспокоиться тем более смысла нет.

      Хотя я согласен с тем, что подходить к разработке нужно грамотно, а не с наскока как я.

      нет диздока твоей игры, хотя бы в формате концепта или списка игровых механик

      На самом деле, все не так плохо) Есть заранее продуманный список фичей, которые нужны для минимальной версии. Пока что серьезные проблемы только с фичей, которую делаю сейчас - ее в первоначальном списке не было. Тут, действительно, не уследил, когда анализировал референсные игры и похожие механики.

      Еще, действительно, есть проблема в планировании - довольно неграмотно декомпозировал фичи на задачи, отчего порядок их внедрения пришлось менять

      не понятно что ты собственно говоря хочешь сделать

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

      И спасибо за критику!