Часть 1. Предыстория и идея
Здравствуй, Хабр!
Я хочу поделиться опытом в создании одной гиперказуалки. Сам я, правда, ещё совсем зелен и юн на этой тернистой тропе игроделания, но может кому-то станет интересно и он прочтёт цикл этих статей. Самого кода здесь не будет, а если и будет, то в очень мизерных количествах, в связи с чем вряд-ли эти статьи будут интересны людям, которые хотят применить главную основу программирования "Ctrl+C/Ctrl+V" да и так как я сам новичок то заядлым "про" тут тоже будет скучновато, а плюсом на них может наложиться дебафф "BloodFromTheEyes" от порой наинеумнейших решений наиглупейших проблем. Но всё же опыт есть опыт, так что расскажу что есть.
Пожалуй, начну с рассказа как я до такого докатился.
Предыстория
В детстве у меня не было собственного компьютера, так что приходилось довольствоваться часом игры в вк-игры. Смартфон у меня тоже появился относительно поздно, но мне хватало и тех игр что были в кнопочном телефоне. Как таковым геймером я не был, а скоротать время на перемене вполне можно.
Позже, когда мне всё же купили ноутбук, меня всецело поглотил Minecraft года на 4. Наверно это была единственная игра которая мне так понравилась (и я всё ещё считаю её 1-й из лучших), так как там можно буквально всё.
Потом мне потихоньку надоело безвылазно сидеть в кубическом мире и я отдалился от него. Как раз в то время в школе началась информатика. Не то чтобы учительница была хорошо сведущая (перепутать блок питания и видеокарту это сильно), но основы Паскаля она заложить смогла. Это показало мне, что оказывается я могу довольно легко писать программы и игры в том числе, если выучу язык программирования, ведь это быстро и просто! (как же я ошибался...)
Так я захотел создать игру и немного почитав начал выбирать движок. Естественно мой взор пал на Unity. Могу выделить в нем такие плюсы как:
Простота, ведь и само меню и основной язык C# являются довольно лёгкими в обучении
Огромное количество статей, тем на форумах и видеоуроков по практически любой проблеме. (К примеру у UE видеоуроков на русском заметно меньше)
Я есть школота. Школота есть сила. Сила есть единство. Единство есть 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. Игрок и сборщик уровней
В прошлой части я рассказал как придумал идею и что из себя представляет эта игрулька. В этой же речь пойдёт о системе передвижения персонажа и сборщике уровней.
Дисклеймер: данная статья не является гайдом и не рекомендуется к прочтению профи из-за опасность улететь на своём кресле в стратосферу.
Игрок
Для начала разберёмся, что мы вообще хотим от игрока:
Должен прыгать и падать
Должен плавно перемещаться по горизонтальным поверхностям
Для простой реализации физики в 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)
DrinkFromTheCup
16.05.2022 09:34+2Ice Climber пытается получить второе дыхание... неплохо!
Зря Вы в Unity полезли. Обдерут-с. Да и как только захотите взяться за что-то крупненькое - аукнется за обе щёки и крайне убогая работа с ресурсами, и в целом нехорошая производительность, и необходимость производить довольно нетипичные манипуляции для того, чтобы избавиться от багов самого Unity.
Если хотите C# и хотите по-большому - попробуйте навести справки о движке https://www.monogame.net/ , на нём Barotrauma написана (и много чего приличного ещё), в обращении неплох, bad practices вроде как не содержит. Правда, и за руку особо не держит, кой-чего придётся "с нуля" писать самому.
Иначе - старый добрый Godot тоже сгодится (и облегчит разработку). Вот прям идеальный вариант для мелких проектов, руку набить, о себе заявить.
AniMAnt_ZeZo Автор
16.05.2022 10:15+1Спасибо за комментарий!
Насчёт юнити, я знаю что он не создан для качественных больших проектов, но мелкие вроде этого на нём могут получиться довольно хорошо. Прикипел я к нему как-то, но если начну делать что-то крупное, то думаю частично перейду на UE.
DrinkFromTheCup
16.05.2022 10:37+1Для маленьких качественных проектов Unity тоже подходит плохо, честно говоря. Им не нужно, чтобы Вы сделали и окупили хит - им нужно, чтобы Вы закупились в asset store. Из-за этого движок просто пухнет от неудачных решений, которые и учат плохому, и игроку гадят...
Из той огромной кучи поделий, что побывали у меня на харде, более-менее адекватных (как геймплейно, так и технически) единицы, причём от самых упорных. Amazing Cultivation Simulator, серия Reigns, Sunless Sea/Skies, Valheim - больше игр-на-Unity-за-которые-не-стыдно назвать не могу сходу.
Ну и совсем нишевые поделия типа "Тук-тук-тук!" да Cultist Simulator (и огромного вороха бесплатного барахла на Nutaku (контент для взрослых, не гуглите, если не дозрели)).
Основной козырь Unity - возможность без особой возни (ха-ха...) один и тот же проект скомпилировать хоть для линукса, хоть для iOS, хоть для вставки в браузер. Терпеть ради этого необходимость пересобирать проект каждый раз, когда надо подкинуть/поменять пару файлов, баги движка и проблемы с производительностью... я бы не стал :)
AniMAnt_ZeZo Автор
16.05.2022 11:05Хм, если всё так плохо, то впредь я буду тщательней выбирать движок, хотя как и писал выше, хочу перейти на UE, ибо он универсален да и компонентов много) этакий юнити+ (ох и захейтят же)), но этот проект оставлю на старом добром юнити, он уже почти готов и нет смысла его переносить, тем более, для меня геймдев это хобби, поэтому поразвлекаюсь со всем и вся, пока есть возможность :]
FeNUMe
16.05.2022 20:43+1Для меня игра за которую не стыдно на юнити это Genshin Impact - китайцы реально постарались и сделали ААА+ проект. Еще вполне неплохо себя чувствует Subnautica, а из сложных игр Cities: Skylines, на выходе она конечно лагала весьма знатно, но в итоге довели до ума.
realwar_fx
16.05.2022 10:05+1Жаль что кода нет, а без него и сказать-то нечего :)
Единственное, я бы добавил обводку персонажу, а то он как-то сливается с фоном на мой взгляд.
AniMAnt_ZeZo Автор
16.05.2022 10:20Стыдно код выставлять xP, да и в счёт его лапшичности, корявости и багнутости будет не хорошо, если кто-то совсем новый воспримет это как подробный гайд и я стану родоначальником багнутой лапшичной армии :]
По поводу обводки, в игре есть система скинов (в следующей статье о ней расскажу), да и фон я попытаюсь сделать ярким и немного динамичным, а то скучно смотрится)
Tosha4389
Молодец! Желаю творческих успехов! Позволю дать пару советов:
По поводу обработки ввода игрока погугли, более подходящий для этого, функционал IDragHandler, IPointerClick и тп.
По поводу физики Rigidbody: основная фишка физики - скорость передвижения и размер коллайдера. Если за один кадр предмет пройдет большее расстояние, чем толщина коллайдера стены, то коллизии не произойдет. А прилипания и тп настраиваются материалами на rigidbody
AniMAnt_ZeZo Автор
Спасибо большое! Буду и дальше стараться! Я уже пытался реализовать и то и это, но столкнулся с некоторыми трудностями, а именно:
Я уже довольно давно делал вышеописанные этапы, поэтому конкретно проблему не помню, но с использованием IDragHander и прочих касания часто не читались. Скорее всего это я криворукий, но пока последую принципу "Работает - не трогай". (Зато они пригодились в системе воскрешения. Опишу в следующей части)
По поводу физики, возможно я переведу персонажа на rb, но только когда доконца разберусь как его использовать. Если посередине игры игрок вылетит из башни, то будет немного неудобно...