Часть 1. Предыстория и идея

Здравствуй, Хабр!

Я хочу поделиться опытом в создании одной гиперказуалки. Сам я, правда, ещё совсем зелен и юн на этой тернистой тропе игроделания, но может кому-то станет интересно и он прочтёт цикл этих статей. Самого кода здесь не будет, а если и будет, то в очень мизерных количествах, в связи с чем вряд-ли эти статьи будут интересны людям, которые хотят применить главную основу программирования "Ctrl+C/Ctrl+V" да и так как я сам новичок то заядлым "про" тут тоже будет скучновато, а плюсом на них может наложиться дебафф "BloodFromTheEyes" от порой наинеумнейших решений наиглупейших проблем. Но всё же опыт есть опыт, так что расскажу что есть.

Пожалуй, начну с рассказа как я до такого докатился.

Предыстория

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

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

Потом мне потихоньку надоело безвылазно сидеть в кубическом мире и я отдалился от него. Как раз в то время в школе началась информатика. Не то чтобы учительница была хорошо сведущая (перепутать блок питания и видеокарту это сильно), но основы Паскаля она заложить смогла. Это показало мне, что оказывается я могу довольно легко писать программы и игры в том числе, если выучу язык программирования, ведь это быстро и просто! (как же я ошибался...)

Так я захотел создать игру и немного почитав начал выбирать движок. Естественно мой взор пал на Unity. Могу выделить в нем такие плюсы как:

  1. Простота, ведь и само меню и основной язык C# являются довольно лёгкими в обучении

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

  3. Я есть школота. Школота есть сила. Сила есть единство. Единство есть Unity!

Так, спустя немного времени, я получал 5 несколько четвертей подряд за созданную наспех игру. Сама игра было максимально простой. В игре были небольшие квадратные комнаты и коридоры, соединявшие этим комнаты. Игровое поле представляло собой массив этих комнат, соединённых коридорами (размер поля 16*16 комнат). В некоторых комнатах были монстры. Монстры были разные. Некоторые маленькие и слабые, но буквально прижимали к стенке количеством и затыкивали до смерти. Некоторые сносили почти всю полосу здоровья 1-м ударом, но были медленными и не могли покинуть свою комнату. Цель этой игры - собрать 5 ключей для открытия нужной комнаты и загнать туда 1-го монстра, который движется быстрее игрока и наносит 99 урона (максимальное здоровье - 100). Игрок мог умирать сколько хочет. Возрождался он в 1-й из 4х комнат, куда не могут зайти монстры в разных углах локации. Сама игра не была оснащена даже кнопкой выхода или настройками, но для школьных учителей информатики и этого хватало.

Кому интересно как "Это" выглядело, то вот:

Как я придумал идею

Так, я окончил школу и поступил в вуз. За это время я начинал по меньшей мере ещё 3 проекта и строил грандиозные планы по ААА проектам, которые взорвут сеть. Однако я всё же звёзд с неба не хватаю и принял решение выпустить игру на подобии прошлых гигантов, таких как Crossy Road и Geometry Dash. У первого я взял идею простоты, а у второго идею графики со светящимися в темноте цветами. Правда, по ходу создания друг подметил что больше всего игра напоминает другую классику - Doodle Jump.

Для создания этой игры я решил освободить максимум времени и отложил другие проекты. Теперь я почувствовал, что начал нечто, что должен закончить. Всё же неправильно интересоваться созданием игр и не выпустить ни одной.

Суть игры, как писалось выше, максимально простая. Нужно допрыгать так высоко вверх, как только возможно, но по мере увеличения высоты будут появляться новые виды и комбинации платформ. Чтобы игроку было не скучно, можно будет менять образы (Стандартный - "Ниндзя воды". Его можно заметить на изображении) и темы самой карты. Новые образы и темы можно будет получить с помощью "Рандомайзера", заплатив 100 монет, которые вы собрали на карте или получили, выполняя миссии.

Сами образы делятся на:

  • Стандартные. Их больше всего и шанс их выпадения около 74%

  • Редкие. Их поменьше. Шанс получить такой будет не больше 20%

  • Эпические. Красивые образы, шанс получить которые будет 5%

  • Легендарные. Название само говорит за себя. Шанс выпадения такого равен 1%.

  • Особые. Это образы событий или тематические. Шанс получить такой в рандомайзере всего 0,01%.

Подробнее об каждом компоненте и их реализации я расскажу в следующих статьях, а сейчас искренне благодарствую каждому за то, что уделили этой статье свои пару минут. Надеюсь, я смог заложить в вас хоть чуточку интереса и впредь буду рад делиться опытом создания этой игры!

Часть 2. Игрок и сборщик уровней

В прошлой части я рассказал как придумал идею и что из себя представляет эта игрулька. В этой же речь пойдёт о системе передвижения персонажа и сборщике уровней.

Дисклеймер: данная статья не является гайдом и не рекомендуется к прочтению профи из-за опасность улететь на своём кресле в стратосферу.

Игрок

Для начала разберёмся, что мы вообще хотим от игрока:

  1. Должен прыгать и падать

  2. Должен плавно перемещаться по горизонтальным поверхностям

Для простой реализации физики в Unity представлены компоненты Rigidbody и Character Controller. Первый представляет собой объект физического движка, а второй это скрипт, управляющий объектом. Изначально я начал делать персонажа с помощью Rigidbody, но уже на начальных этапах столкнулся с проблемой, когда персонаж сам по себе входил с стены, липнул ко всему подряд и в некоторых случаях перемещался рывками, поэтому было принято решение избавится от Rigidbody в пользу Character Controller.

Реализовывал гравитацию и передвижение я в двух методах:

В GamingGravity() я присваивал переменной gravityForse значение -50f, если персонаж не касается земли и -1f, если касается. Этого хватило, чтобы обеспечить довольно быстрое падение и не дать объекту самому оторваться от земли. Для проверки соприкосновения с землёй была использована одна из функций самого Caracter Controller: [chController].isGrounded возвращает true, если объект касается земли и false, если нет. Данные значения были умножены на Time.deltatime.

В методе CharacterMove() было реализовано всё остальное передвижение персонажа. В самом начале метода переменная типа Vector3 обнулялась [moveVector] = Vector3.zero, чтобы не переносить значения из прошлой работы метода.

Далее персонажу даётся возможность прыгать, если игрок касается земли и зажата левая кнопка мыши (На android работает как нажатие). Для самого прыжка присваиваем gravityForse значение 20f.

Следующим шагом даём игроку возможность перемещаться влево и право:

Если позиция мыши по оси х меньше начальной позиции, то переменной типа float присваиваем значение: [move] = ((startMausPos - Input.mousePosition.x) / 30f) * sensitivity, где sensitivity это переменная чувствительности, а 30f было подобрано испытательным методом, чтобы при sensitivity = 1 игрок перемещался с нужной скоростью. Далее нужно сделать проверку на максимальную скорость: Если наша скорость меньше максимальной, то присваиваем её отрицательное значение переменной [moveVector].x, в обратном случае присваиваем отрицательное значение максимальной скорости.

Если же позиция мыши по оси х меньше начальной позиции, то при присвоении значения переменной [move] вычитаем из текущей позиции начальную, а переменной [moveVector].x присваиваем не отрицательные, а исходные значения [move] и максимальной скорости.

Финальным этапом присваиваем [moveVector].y значение gravityForse.

Оба метода (GamingGravity() и CharacterMove()) вызываем в Update и радуемся передвижению нашего персонажа.

Генерация уровней

Все префабы уровней я подготовил заранее, так что в итоге у меня вышел этакий сборщик префабов. Из плюсов можно отметить гибкость в сборке: есть возможность выбирать какие уровни нельзя создать после текущего, выбрать тип уровня и его сложность. Из минусов нагромождение в коде от которого больно глазам, душе. пятой точке и соответственно креслу.

На сцене я создал объект Spawner и повесил на него скрипт, который и отвечает за саму сборку.

В начале скрипта располагается структура Lvls, которая содержит:

public GameObject thisLvl; - сам префаб уровня,
public GameObject[] impossibleLvl; - все префабы, которые нельзя поместить после этого уровня,
public GameObject posseblelvl; - префаб, который точно можно поместить. Он нужен для подстраховки.

Чтобы уровни были более разнообразными, я разделил их на 2 типа и 2 сложности: Y5 (его высота равняется 5) и Y10 (его высота равняется 10) и 1 и 2 сложность. Для каждого типа каждой сложности я создал массивы структуры Lvls. Так же добавил переменную сложности difficultyFactor с изначальным значением 0.

Чтобы генерировать не бесконечное количество уровней, а лишь на несколько шагов вперёд, я дал сборщику возможность передвигаться: если игрок ниже сборщика менее чем на 20 единиц, то сборщик запускает метод генерации уровня, а сам перемещается вверх на высоту этого уровня.

Теперь о самом процессе подбора уровня.

Когда игрок подобрался к сборщику менее чем на 20 единиц, то скрипт начинает определять какой уровень создать. Так как уровней типа Y5 меньше и они добавляют сложности игре, шанс их появления 30%, а остальные 70% остаются для Y10. С помощью рандома создаётся переменная и случайным образом выбирается 1 из типов. после выбора переменной отвечающей за сложность (у меня это difficultyFactor) прибавляется значение 1. Предположим выбран тип Y10.

Переносимся в метод SpawningY10(), где тут же отфутболиваемся в другой метод передав ему параметр 10 (высота уровня в Y10) для определения какой именно уровень выбрать. Этот дополнительный метод вызывается во всех методах спавна и принимает на вход нужную высоту. Он нужен для удобства чтения скрипта и даёт возможность выбирать к примеру из различных образов карты. Внутри этого метода выбираем какой сложности сгенерировать уровень. Если рандомная переменная (от 0 до 100) больше difficultyFactor, то сложность 1, если меньше, то 2. Выбранный префаб возвращается в метод SpawningY10(), После чего идёт проверка, есть ли этот новый уровень в массиве impossibleLvl от текущего. Если есть, то повторяем вызов метода для выбора уровня. Если же скрипт совершил более 50 повторений и всё ещё выбирает невозможные уровни, то генерируем объект для подстраховки, posseblelvl.

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

Послесловие

Чтобы не затягивать статью в огромную ленту, пока остановлюсь на этом. В следующей части расскажу как я делал платформы с ловушками, на какие камни наступал и что в итоге вышло.

Всех благодарю за то, что уделили пару минут своего времени этой статье. До скорого!

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


  1. Tosha4389
    16.05.2022 08:13
    +1

    Молодец! Желаю творческих успехов! Позволю дать пару советов:

    1. По поводу обработки ввода игрока погугли, более подходящий для этого, функционал IDragHandler, IPointerClick и тп.

    2. По поводу физики Rigidbody: основная фишка физики - скорость передвижения и размер коллайдера. Если за один кадр предмет пройдет большее расстояние, чем толщина коллайдера стены, то коллизии не произойдет. А прилипания и тп настраиваются материалами на rigidbody


    1. AniMAnt_ZeZo Автор
      16.05.2022 08:32

      Спасибо большое! Буду и дальше стараться! Я уже пытался реализовать и то и это, но столкнулся с некоторыми трудностями, а именно:

      1. Я уже довольно давно делал вышеописанные этапы, поэтому конкретно проблему не помню, но с использованием IDragHander и прочих касания часто не читались. Скорее всего это я криворукий, но пока последую принципу "Работает - не трогай". (Зато они пригодились в системе воскрешения. Опишу в следующей части)

      2. По поводу физики, возможно я переведу персонажа на rb, но только когда доконца разберусь как его использовать. Если посередине игры игрок вылетит из башни, то будет немного неудобно...


  1. DrinkFromTheCup
    16.05.2022 09:34
    +2

    Ice Climber пытается получить второе дыхание... неплохо!

    Зря Вы в Unity полезли. Обдерут-с. Да и как только захотите взяться за что-то крупненькое - аукнется за обе щёки и крайне убогая работа с ресурсами, и в целом нехорошая производительность, и необходимость производить довольно нетипичные манипуляции для того, чтобы избавиться от багов самого Unity.

    Если хотите C# и хотите по-большому - попробуйте навести справки о движке https://www.monogame.net/ , на нём Barotrauma написана (и много чего приличного ещё), в обращении неплох, bad practices вроде как не содержит. Правда, и за руку особо не держит, кой-чего придётся "с нуля" писать самому.

    Иначе - старый добрый Godot тоже сгодится (и облегчит разработку). Вот прям идеальный вариант для мелких проектов, руку набить, о себе заявить.


    1. AniMAnt_ZeZo Автор
      16.05.2022 10:15
      +1

      Спасибо за комментарий!

      Насчёт юнити, я знаю что он не создан для качественных больших проектов, но мелкие вроде этого на нём могут получиться довольно хорошо. Прикипел я к нему как-то, но если начну делать что-то крупное, то думаю частично перейду на UE.


      1. DrinkFromTheCup
        16.05.2022 10:37
        +1

        Для маленьких качественных проектов Unity тоже подходит плохо, честно говоря. Им не нужно, чтобы Вы сделали и окупили хит - им нужно, чтобы Вы закупились в asset store. Из-за этого движок просто пухнет от неудачных решений, которые и учат плохому, и игроку гадят...

        Из той огромной кучи поделий, что побывали у меня на харде, более-менее адекватных (как геймплейно, так и технически) единицы, причём от самых упорных. Amazing Cultivation Simulator, серия Reigns, Sunless Sea/Skies, Valheim - больше игр-на-Unity-за-которые-не-стыдно назвать не могу сходу.

        Ну и совсем нишевые поделия типа "Тук-тук-тук!" да Cultist Simulator (и огромного вороха бесплатного барахла на Nutaku (контент для взрослых, не гуглите, если не дозрели)).

        Основной козырь Unity - возможность без особой возни (ха-ха...) один и тот же проект скомпилировать хоть для линукса, хоть для iOS, хоть для вставки в браузер. Терпеть ради этого необходимость пересобирать проект каждый раз, когда надо подкинуть/поменять пару файлов, баги движка и проблемы с производительностью... я бы не стал :)


        1. AniMAnt_ZeZo Автор
          16.05.2022 11:05

          Хм, если всё так плохо, то впредь я буду тщательней выбирать движок, хотя как и писал выше, хочу перейти на UE, ибо он универсален да и компонентов много) этакий юнити+ (ох и захейтят же)), но этот проект оставлю на старом добром юнити, он уже почти готов и нет смысла его переносить, тем более, для меня геймдев это хобби, поэтому поразвлекаюсь со всем и вся, пока есть возможность :]


        1. FeNUMe
          16.05.2022 20:43
          +1

          Для меня игра за которую не стыдно на юнити это Genshin Impact - китайцы реально постарались и сделали ААА+ проект. Еще вполне неплохо себя чувствует Subnautica, а из сложных игр Cities: Skylines, на выходе она конечно лагала весьма знатно, но в итоге довели до ума.


  1. realwar_fx
    16.05.2022 10:05
    +1

    Жаль что кода нет, а без него и сказать-то нечего :)

    Единственное, я бы добавил обводку персонажу, а то он как-то сливается с фоном на мой взгляд.


    1. AniMAnt_ZeZo Автор
      16.05.2022 10:20

      Стыдно код выставлять xP, да и в счёт его лапшичности, корявости и багнутости будет не хорошо, если кто-то совсем новый воспримет это как подробный гайд и я стану родоначальником багнутой лапшичной армии :]

      По поводу обводки, в игре есть система скинов (в следующей статье о ней расскажу), да и фон я попытаюсь сделать ярким и немного динамичным, а то скучно смотрится)