Как сделать остров в юнити

Добавил пользователь Alex
Обновлено: 04.10.2024

Мы - Wow Games,
команда, которая разрабатывает игры, а также приложения, помогающие Вам делать свои игры. Мы поможем, если у Вас возникли проблемы, и мы всегда рады новым участникам в нашей команде. Не бойтесь задавать вопросы, мы всегда на них ответим.

Всем привет!
Мы - Wow Games, команда, которая разрабатывает игры, а также приложения, помогающие Вам делать свои игры. Мы поможем, если у Вас возникли проблемы, и мы всегда рады новым участникам в нашей команде. Не бойтесь задавать вопросы, мы всегда на них ответим.

🏓 Создаем 2D-игру на Unity: инструкция для новичка

Недавно мы рассказали о том, как научиться разработке игр на Unity . Продолжим тему на практике и покажем, как новичку создать на этой платформе первую 2D-игру.

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

Двумерные игры сравнительно просты: для них не требуется сложных 3D-моделей, программный код по сравнению с 3D-проектами выглядит понятнее. Такие игры популярны как на десктопах, так и на мобильных устройствах. Unity также позволяет разрабатывать игры и для браузеров.

За последние годы вышло много популярных двумерных игр:

Программная реализация 2D-игр проще не только из-за отсутствия третьего измерения: на самой сцене меньше объектов, вместо трехмерных моделей плоские спрайты, вместо скелетной анимации – покадровая. А еще 2D-игры проще портировать на другие платформы – легче найти новую аудиторию.

Давайте создадим простую игру в жанре пинг-понг 🏓 . Перед тем как приступить к созданию игры, продумайте, какой именно результат хотите получить. На первых этапах рекомендуется использовать схематические шаблоны, чтобы быстрее получить работающий результат. В этой инструкции мы так и поступим. Графических ресурсов использовать не будем: и ракетки, и отбиваемый мяч будем пока отображать простыми белыми спрайтами.

Предварительно рассмотрим основные понятия Unity, без понимания которых будет проблематично создать игру:

Предполагаем, что вы уже установили редактор и создали аккаунт на портале Unity.

В первую очередь создадим новый проект и откроем его настройки (Edit → Project Settings). Во вкладке Editor установим параметр Default Behaviour Mode в значение 2D

Настройка проекта Детальная настройка проекта

Следующим шагом сохраним текущую активную сцену, назвав ее, например, Scene1. Теперь создадим основные игровые объекты: ракетку, мяч и менеджер игры, в котором будет храниться основная логика игры.

1. Создаем пустой объект, переименовываем в GameManager.

Создаем пустой объект

Создаем пустой объект

3. Создаем квадратный спрайт, называем его Pad (Assets → Create → Sprites → Square). Аналогично создаем круглый спрайт Ball (Assets → Create → Sprites → Circle). Масштабируем спрайт Pad со следующими параметрами – x:0.5, y:2.5, z:1.

Создаем спрайты

Создаем спрайты

4. Создаем префабы для Pad и Ball, после чего добавляем к ним компонент Box Collider 2D (включаем параметр Is Trigger) и компонент Rigidbody 2D (выставляем параметр Body Type в значение Kinematic).

Добавляем .компонент Box Collider 2D Настраиваем.компонент Box Collider 2D Добавляем компонент Rigidbody 2D Масштабируем спрайты

6. Заполняем скрипты следующим кодом.

GameManager.cs Ball.cs Pad.cs

6. Добавляем к префабу Ball и Pad теги с аналогичными именами. Выделив префабы, в инспекторе мы можем видеть выпадающий список тегов. Там же расположены и кнопки для добавления и редактирования тегов.

7. В настройках камеры выставляем параметр Projection в значение Orthographic, а параметр Clear Flag – в значение Solid Color.

Настройка камеры

Настройка камеры

8. Настраиваем кнопки, как показано на следующих скриншотах (Edit → Project Settings → Input Manager).

Настройка ввода, основное Настройка ввода, первый игрок Настройка ввода, второй игрок

Вот и всё, игра готова!

Пинг-понг, итоговый результат

Пинг-понг, итоговый результат

Билд для платформы Windows

1. Официальный туториал от Unity, где детально рассмотрен процесс создания roguelike RPG.

2. Youtube-канал Brackeys , где можно найти серию видеоуроков по созданию 2D-платформера.

3. Youtube-канал N3K EN содержит множество уроков как по отдельным компонентам Unity, так и полноценные серии уроков по созданию игр с нуля.

Если у вас мало опыта в разработке игр на Unity, мы рекомендуем обратить внимание на факультет разработки игр GeekBrains . Материал хорошо структурирован и содержит все необходимое для того, чтобы стать профессиональным Unity-разработчиком.

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

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

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

С тех пор как разработка игр стала легко доступной для инди разработчиков, появилось не мало игр, где дизайну уделялось не такое большое внимание, а все силы уходили именно на геймплей. Позже разработчики пришли к тому, что стали доверять машинам и алгоритмам генерации. Самый яркий пример таких игр с генерируемыми мирами, это конечно же – Minecraft , где весь ландшафт состоит из блоков, хоть это и является иллюзией – ведь хранить даже обозримое кол-во блоков в оперативной памяти невероятно затратно, и все что мы видим в игре на самом деле это один большой “ супермеш ”.

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

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

Геймплей

Сначала разберем то, что будет из себя представлять будущая игра, точнее, нужно придумать примерно абстрактную модель генерируемого мира в игре. Пусть это будет простая 2D игра, где персонажу необходимо попасть из точки А в точку Б в случайно сгенерируемом уровне.

Сцена состоит из 2D блоков, которые будут случайным образом сгенерированы перед прохождением.


Процесс игры разобьем на три этапа:

  • Генерация уровня. Сперва необходимо будет сгенерировать уровень, состоящий из блоков.
  • Прохождение уровня. Помещаем персонажа на сцену и проходим уровень.
  • Завершение игры. Уничтожаем старый уровень и переходим к созданию нового.

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

Уровень будет состоять из блоков, разного типа:

  • Стартовый блок. Это именно то место, откуда игрок будет начинать свое путешествие.
  • Конечный блок. При попадании игрока на последний блок уровня, игра считается пройденной.


  • Промежуточный блок. Из этих блоков будет состоять большая часть уровня.


Именно кол-во промежуточных блоков будет влиять на дизайн и длину уровня.

Программная часть генерации блоков

Создадим скрипт Control , где укажем три переменных спрайтовых ( Sprite ) переменных для каждого типа блока.

  1. publicclass Control : MonoBehaviour
  2. public Sprite startBlock ;
  3. public Sprite midBlock ;
  4. public Sprite endBloc ;
  5. >

В эти три переменные занесем каждый спрайт блока по отдельности: стартовый, промежуточный и конечный. Дополним скрипт Control стартовым методом Start .

  1. publicclass Control : MonoBehaviour
  2. public Sprite startBlock ;
  3. public Sprite midBlock ;
  4. public Sprite endBloc ;
  5. publicvoid Start () <>
  6. >

В методе Start мы будем запускать генерацию уровня во время старта игры.

  1. publicclass Control : MonoBehaviour
  2. public Sprite startBlock ;
  3. public Sprite midBlock ;
  4. public Sprite endBloc ;
  5. publicvoid Start () <>
  6. private IEnumerator OnGeneratingRoutine () <>
  7. >

В методе OnGeneratingRoutine , будем выполнять сам процесс генерации уровня. Так как уровни у нас могут быть как большими, так и маленькими и генерироваться разное количество времени, процесс генерации мы поместим в корутину, чтобы игра не “ зависала ” во время работы “ генератора ”. Далее добавим одну числовую переменную completeLevels, с помощью которой будем указывать количество пройденных уровней.

  1. publicclass Control : MonoBehaviour
  2. public Sprite startBlock ;
  3. public Sprite midBlock ;
  4. public Sprite endBloc ;
  5. privateint completeLevels = 0 ;
  6. publicvoid Start () <>
  7. private IEnumerator OnGeneratingRoutine () <>
  8. publicvoid CompleteLevel ()
  9. this . completeLevels += 1 ;
  10. >
  11. >

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

Теперь можно переходить к методу OnGeneratingRoutine , где мы начнем описывать сам алгоритм генерации уровня.

  1. publicclass Control : MonoBehaviour
  2. public Sprite startBlock ;
  3. public Sprite midBlock ;
  4. public Sprite endBloc ;
  5. privateint completeLevels = 0 ;
  6. /*…остальной код…*/
  7. private IEnumerator OnGeneratingRoutine ()
  8. Vector2 size = new Vector2 ( 1 , 1 );
  9. Vector2 position = new Vector2 ( 0 , 0 );
  10. yieldreturn new WaitForEndOfFrame ();
  11. >
  12. >

Для начала в методе OnGeneratingRoutine объявим две векторные переменные: size , где укажем размер блоков по длине и высоте и position, где укажем точку, откуда будет начинать строится уровень. Теперь можно построить стартовый блок.

  1. publicclass Control : MonoBehaviour
  2. public Sprite startBlock ;
  3. public Sprite midBlock ;
  4. public Sprite endBloc ;
  5. privateint completeLevels = 0 ;
  6. /*…остальной код…*/
  7. private IEnumerator OnGeneratingRoutine ()
  8. Vector2 size = new Vector2 ( 1 , 1 );
  9. Vector2 position = new Vector2 ( 0 , 0 );
  10. GameObject newBlock = new GameObject ( “ Start block” );
  11. yieldreturn new WaitForEndOfFrame ();
  12. >
  13. >

Создаем новый GameObject newBlock на сцене.

  1. private IEnumerator OnGeneratingRoutine ()
  2. Vector2 size = new Vector2 ( 1 , 1 );
  3. Vector2 position = new Vector2 ( 0 , 0 );
  4. GameObject newBlock = new GameObject ( “ Start block” );
  5. newBlock . transform . position = position ;
  6. newBlock . transform . localScale = size ;
  7. SpriteRendere renderer = newBlock . AddComponent SpriteRenderer >();
  8. renderer . sprite = this . startBlock ;
  9. yieldreturn new WaitForEndOfFrame ();
  10. >

После создания нового блока, устанавливаем ему позицию и размер через его transform , добавляем блоку компонент SpriteRenderer, чтобы отобразить на сцене и указываем, какой именно спрайт ему отобразить, в нашем случае это будет стартовый спрайт первого блока startBlock .

Теперь запустим корутину OnGeneratingRoutine в методе Start и проверим ее выполнение.

Переходим к созданию промежуточных блоков. Для этого в корутине OnGeneratingRoutine добавим еще одну переменную count .

  1. private IEnumerator OnGeneratingRoutine ()
  2. Vector2 size = new Vector2 ( 1 , 1 );
  3. Vector2 position = new Vector2 ( 0 , 0 );
  4. GameObject newBlock = new GameObject ( “ Start block” );
  5. newBlock . transform . position = position ;
  6. newBlock . transform . localScale = size ;
  7. SpriteRendere renderer = newBlock . AddComponent SpriteRenderer >();
  8. renderer . sprite = this . startBlock ;
  9. int count = this . completeLevels + 5 ;
  10. yieldreturn new WaitForEndOfFrame ();
  11. >

Числовая переменная count будет указывать какое кол-во промежуточных блоков необходимо построить, это число будет зависеть от количества пройденных уровней и, чтобы их изначально не было слишком мало на первых уровнях, еще пяти (5) дополнительных блоков. Строить промежуточные блоки будем через цикл for .

  1. private IEnumerator OnGeneratingRoutine ()
  2. Vector2 size = new Vector2 ( 1 , 1 );
  3. Vector2 position = new Vector2 ( 0 , 0 );
  4. GameObject newBlock = new GameObject ( “ Start block” );
  5. newBlock . transform . position = position ;
  6. newBlock . transform . localScale = size ;
  7. SpriteRendere renderer = newBlock . AddComponent SpriteRenderer >();
  8. renderer . sprite = this . startBlock ;
  9. int count = this . completeLevels + 5 ;
  10. for ( int i = 0 ; i count ; i ++)
  11. newBlock = new GameObject ( “ Middle block” );
  12. renderer = newBlock . AddComponent SpriteRenderer >();
  13. renderer . sprite = this . midBlock ;
  14. >
  15. yieldreturn new WaitForEndOfFrame ();
  16. >

Также как мы строили стартовый блок, также строим и промежуточные: создаем новый GameObject , добавляем ему компонент SpriteRenderer , указываем спрайт для отображения на сцене и задаем размер и позицию.

Так как промежуточные блоки строятся по горизонтали, значит и позицию необходимо с каждым новым блоком сдвигать немного вправо. Для того чтобы узнать на сколько ее необходимо сдвинуть, воспользуемся переменной size , где указаны размеры блоков.

  1. private IEnumerator OnGeneratingRoutine ()
  2. Vector2 size = new Vector2 ( 1 , 1 );
  3. Vector2 position = new Vector2 ( 0 , 0 );
  4. /*…остальной код…*/
  5. int count = this . completeLevels + 5 ;
  6. for ( int i = 0 ; i count ; i ++)
  7. newBlock = new GameObject ( “ Middle block” );
  8. renderer = newBlock . AddComponent SpriteRenderer >();
  9. renderer . sprite = this . midBlock ;
  10. newBlock . transform . localScale = size ;
  11. position . x += size . x ;
  12. newBlock . transform . position = position ;
  13. >
  14. yieldreturn new WaitForEndOfFrame ();
  15. >

Чтобы сдвинуть позицию блока вверх или вниз, воспользуемся случайной генерацией чисел через Random .

  1. private IEnumerator OnGeneratingRoutine ()
  2. Vector2 size = new Vector2 ( 1 , 1 );
  3. Vector2 position = new Vector2 ( 0 , 0 );
  4. /*…остальной код…*/
  5. int count = this . completeLevels + 5 ;
  6. for ( int i = 0 ; i count ; i ++)
  7. newBlock = new GameObject ( “ Middle block” );
  8. renderer = newBlock . AddComponent SpriteRenderer >();
  9. renderer . sprite = this . midBlock ;
  10. newBlock . transform . localScale = size ;
  11. position . x += size . x ;
  12. position . y += size . y * Random . Range (- 1 , 2 );
  13. newBlock . transform . position = position ;
  14. >
  15. yieldreturn new WaitForEndOfFrame ();
  16. >

Высота блока по Y в переменной position также смещается вверх, либо вниз, в зависимости от размера блока, умноженного на случайное число от -1 до 1. Метод Random.Range генерирует ЦЕЛЫЕ числа от минимального до максимально (ИСКЛЮЧИТЕЛЬНО), это значит, что максимальное указанное число никогда достигнуто не будет. Завершаем цикл постройки промежуточных блоков новым WaitForEndOfFrame .

  1. private IEnumerator OnGeneratingRoutine ()
  2. Vector2 size = new Vector2 ( 1 , 1 );
  3. Vector2 position = new Vector2 ( 0 , 0 );
  4. /*…остальной код…*/
  5. int count = this . completeLevels + 5 ;
  6. for ( int i = 0 ; i count ; i ++)
  7. newBlock = new GameObject ( “ Middle block” );
  8. renderer = newBlock . AddComponent SpriteRenderer >();
  9. renderer . sprite = this . midBlock ;
  10. newBlock . transform . localScale = size ;
  11. position . x += size . x ;
  12. position . y += size . y * Random . Range (- 1 , 2 );
  13. newBlock . transform . position = position ;
  14. yieldreturn new WaitForEndOfFrame ();
  15. >
  16. yieldreturn new WaitForEndOfFrame ();
  17. >

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

  1. private IEnumerator OnGeneratingRoutine ()
  2. Vector2 size = new Vector2 ( 1 , 1 );
  3. Vector2 position = new Vector2 ( 0 , 0 );
  4. /*…остальной код…*/
  5. newBlock = new GameObject ( “ End block” );
  6. renderer = newBlock . AddComponent SpriteRenderer >();
  7. renderer . sprite = this . endBlock ;
  8. position . x += size . x ;
  9. position . y += size . y * Random . Range (- 1 , 2 );
  10. newBlock . transform . position = position ;
  11. newBlock . transform . localScale = size ;
  12. yieldreturn new WaitForEndOfFrame ();
  13. >

Готово, алгоритм генерации завершен, запускаем игру для последней проверки.

Заключение

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

  1. publicclass Control : MonoBehaviour
  2. public Sprite startBlock ;
  3. public Sprite midBlock ;
  4. public Sprite endBloc ;
  5. privateint completeLevels = 0 ;
  6. publicvoid Start () <>
  7. private IEnumerator OnGeneratingRoutine () <>
  8. publicvoid CompleteLevel ()
  9. this . completeLevels += 1 ;
  10. StartCoroutine ( OnGeneratingRoutine ());
  11. >
  12. >

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

Самое главное что вы должны знать о графике — это то, из чего она состоит.

Графика строится из набора изображений, который смешивается друг с другом по определенному закону, который приближает визуализацию финального изображения к реальному (либо стилизованному, если так задумано). Так устроен любой рендерер(и корона и вирей и редшифт и октан и все все все) и движок Unity работает точно по такому же принципу. Если кратко, то берем базовые цвета поверхностей, перемножаем их с освещенностью, добавляем отражения и спекуляр. Это общеизвестные факты, но без них никуда, любой 3дшник должен это знать и использовать!

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

В 2019 году мы имеем стандартные тени такого вида

Справа из ассетстора за деньги, называется NGSS. Речь идет о Реалтаймовых тенях, а не о запеченных.

Объемный свет

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

Есть бесплатный вариант AURA, есть платные решения. AURA мне по нескольким причинам не понравилась, в первую очередь из-за своей стабильности, поэтому я пользуюсь платным ассетом: HxVolumetricLighting.

Постобработка

У юнити есть есть PostProcessStackV2 и он в целом справляется со своей задачей, но кое чего всё равно не хватает, это хорошего AO, точнее HBAO+. Это ScreenSpace Ambient Occlusion, который не создает теней на объектах разнесенных по глубине.

Кроме самозатенения тут есть и очень интересная возможность Color Bleeding — эмуляция вторичного отражения света, которое добавляет картинке жизни, в простонародье - вторичка(от Древнегреческого Secondary GI).

Шейдеры

Ну и в конце концов не стандартными материалами едиными живём, шейдеры тоже нужно делать уникальные и для этого совсем не обязательно писать код, потому что есть Amplify Shader Editor, нодовый редактор, который может освоить любой 3дмаксер, майер, блендераст и прочие синемешники. :) без обид, блендер клёвый!

В частности можно сделать вот такой вот шейдер стекла из картинки выше, а на картинке ниже то, как он выглядит в редакторе:

Как вы могли заметить, это мало чем отличается от стандартных материал эдиторов популярных 3д пакетов, и на самом деле переход с них на редактор шейдеров в юнити очень прост, достаточно просто изучить список доступных нод, а принцип всего остально тот же самый. Но вы не представляете насколько классно после часов страданий ожиданий в 3дмаксике результатов настройки материала - видеть всё в режиме реального времени да ещё и вращать всё на одном месте камерой.

Для кого-то это покажется несущественным, но для меня наличие данных возможностей — единственное, что позволяет вообще делать графику на юнити, а не там где вы возможно подумали, потому что главное в Юнити это всё таки удобство и стоимость разработки с малым числом людей и бюджетом, а для нас — инди — это самое главное. Как известно: дьявол кроется в деталях и я перечислил те из них, которые считаю самыми важными. Всё остальное это уже стандартный подход: замоделить модельки, затекстурить модельки, сделать шейдер и т. п. По данной теме итак предостаточно информации.

PS: Покатились астедава! :)))

Интересно! Спасибо! Подскажи, пожалуйста, а почему не UE? Предполагаю, что из-за расходной составляющей. Или нет? Тогда интересно узнать Твоё мнение. Заранее благодарен за ответ!

Геймплей явно страдает, это мне стало ясно из видео. Да и цена для пазла дороговато. Но графика вполне.

Руслан Латыпов Интересно! Спасибо! Подскажи, пожалуйста, а почему не UE? Предполагаю, что из-за расходной составляющей. Или нет? Тогда интересно узнать Твоё мнение. Заранее благодарен за ответ!

Сергей Патюк Геймплей явно страдает, это мне стало ясно из видео. Да и цена для пазла дороговато. Но графика вполне.


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

Читайте также: