Компьютерная графика → Введение в Unity3D: Создание 2D Игры НЛО

Наконец-то я начну писать про Unity3D. Эта статья будет переводом видео-туториала с официального сайта Unity3D. Вот ссылка на оригинальный туториал: https://unity3d.com/learn/tutorials/s/2d-ufo-tutorial

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

С помощью Unity довольно просто создать замечательные игры! Надеюсь вы будете удивлены и вам он понравится. 

НЛО игра

Мы сделаем простую 2D игру, где игрок будет двигать НЛО и собирать кристаллы. Также научимся пользоваться средой разработки Unity.

Регистрация

Для начала вам нужно зарегистрироваться на сайте Unity. И там же скачать сам Unity. Как же прекрасно что он бесплатен! После скачивания надо его установить, при выборе компонентов, установите все, что предложено по умолчанию. После установки нужно будет на несколько вопросов ответить и зайти под своим аккаунтом. 

Проект и ассеты

Давайте начнем с создания нового проекта. Для этого на главном экране Unity нажмите на кнопку New Project. Назовите проект как хотите, выберите папку проекта и укажите что это 2D проект. 

И нажмите кнопку "Create project" чтобы создать проект и откроется пустая сцена для работы. Далее нам нужно скачать подготовленные для этой игры дополнительные ассеты. Ассеты - это готовые файлы(музыка, картинки, эффекты и т.д.) которые можно использовать в проектах. Для этого откроем магазин ассетов через меню Window -> Asset Store. Это магазин где разработчики продают и покупают разные ассеты.  Окно магазина откроется в середине экрана. Вытащите окно из среды Unity и увеличьте его, чтобы было лучше видно. Введите сверху в поле поиска "2D UFO Tutorial" и выберите его. Это бесплатный комплект ассетов для нашей игры. Нажмите кнопку Download чтобы их скачать себе. 

После скачивания ассеты нужно подключить в наш проект. Для этого нажмите красную кнопку Import, потом еще раз Import в появивщемся окне, потом еще раз Import. Когда ассеты будут подключены, нам нужно сохраить новую сцену. Чтобы сохранить нажмите Ctrl+S  или в меню File->Save Scenes. Назовите сцену Main и сохраните в папке Assets/Scenes/ которая находится в папке проекта.

После этого внизу мы увидим сцену Main в папке Scenes.

Также там есть другие папки, которые мы загрузили из магазина ассетов. В папке Completed-Assets есть готовые сцены и скрипты для нашей игры. Но мы туда пока не будем смотреть. 

Игровое поле

Перед тем как начнем создание игрового поля, давайте посмотрим какие окна есть в среде Unity. 

В самом верху где много кнопок, у Unity панель управления. В середине окно с маленькой камерой - это текущая сцена игры. В сцене мы будем видеть как будет выглядеть игра. Слева находится окно Иерархии игровых объектов сцены. Справа Инспектор, который показывает свойства объектов. И наконеу внизу Файлы проекта.

В файлах проекта внизу у нас есть папка Sprites, где хранятся спрайты, т.е. файлы картинок. Для начала подключим в нашу сцену фоновый рисунок. Для этого возьмите файл Background и перетащите его в окно Иерархии.

Теперь он является Игровым объектом. Этот игровой объект отображается на экране с помощью Рендерера спрайтов(Sprite Renderer). 

Игровые объекты это сущности которые составляют игровую сцену. Также каждый игровой объект имеет компонент трасформации (Transform), который определеяет текущую позицию, размер и угол поворота объекта.

Чтобы добавить еще какую-либо функциональность к игровому объекту, мы можем добавить к нему еще другие компоненты  в окне Инспектора (Add component). Каждый добавленный компонент заставляет игровой объект что-то делать. Например Аудио компонент заставляет объект играть музыку. А компонент Рендерер спрайтов отображает изображение, которое указано в его свойстве Sprite. Если у игрового объектв нет Рендерера, то он не будет виден на сцене. 

Когда мы перетаскиваем какое-то изображение с файлов проекта в Иерархию, то Unity автоматически создает для нового игрового объекта компонент Рендерера спрайтов, чтобы этот объект был сразу отображен на сцене. 

Сейчас на сцене мы видим только кусок фонового объекта. Нажмите на сцену мышкой и нажмите на клавишу F, чтобы сцена была видна полностью. На сцене видна сетка, которая помогает ориентироваться. Давайте уберем его нажав на меню Gizmоs на сцене и убрав галочку на пункте Show Grid. 

Теперь перетащим картинку UFO из папки Sprites в Иерерхию. У нас появится игровой объект и он отобразится на сцене. Переименуйте этот объект на "Player". 

У нас на сцене теперь два спрайта. Так как наша игра 2D(двухмерная или плоская) нам нужно самим определить какой спрайт будет отображен выше, а какой ниже. Для этого мы будем использовать свойство Сортировочный слой(Sorting Layer). Это свойство Рендерера Спрайтов игрового объекта. Чтобы изменить сортировочный слой, нужно выбрать игровой объект и в Инспекторе найти свойство Sorting Layer. 

Вы увидите там 4 готовых слоя, которые мы получили когда скачали готовые файлы для нашего проекта. Нажав на кнопку Add Sorting Layer... вы можете создать другие слои и поменять их порядок. Игровые объекты на нижних слоях будут накладываться на объекты верхних слоев. 

Экран Игры

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

Для этого выберите Main Camera из Иерархии. Камера является таким же игровым объектом, как и другие. Но у него есть компонент Camera, который вы можете увидеть в окне Inspector. 

В компоненте Camera есть свойство Projection(проекция). У этого свойства есть 2 возможных значения: Orthographic и Perspective. Perspective означает что если игровой объект будет приближаться к камере, то он будет визуально увеличиваться. А в Orthographic объект будет всегда одного размера. Убедитесь что у вас в свойства Projection выбран пункт Orthographic, потому что у нас 2D игра. 

Теперь нам нужно сделать так, чтобы на экране игры помещалось все игровое поле. Для этого в компонента Camera игрового объекта Main Camera найдите свойство Size. Это свойсто устанавливает размер обзора камеры. Чем больше Size, тем больше помещается на камеру. Переключитесь на вкладку Game и измените свойство Size так, чтобы на экран помещалось все игровое поле. Это можно сделать нажав мышкой на свойство Size и удерживая двигая мышкой налево и направо. У меня size получился 16.5.

Теперь у вас появился синий фон за игровым полем. Чтобы поменять цвет фона нажмите на свойство Background компонента Camera и выберите какой нибудь цвет. 

Управление игроком

Нам нужно сделать так, чтобы мы могли двигать игровым объектом, чтобы у него была инерция и чтобы мы могли определять столкновения. Для этого мы будем использовать встроенные компоненты физики. Для начала к игровому объекту Player нам нужно добавить компонент Rigidbody2D. Этот компонент дает игровому объекту свойства твердотельного объекта, который имеет свой вес и реагирует на гравитацию. Чтобы добавить компонент выберити в окне Hierarchy объект Player, затем в окне Inspector, внизу нажмите на кнопку "Add component" и из списка выберите Phisics 2D-> Rigidbody 2D. Вы увидите как добавился новый компонент. 

Проверьте физику запустив игру. Нажмите на кнопку Play сверху.

Вы увидете что объект НЛО падает вниз. Значит на него действует гравитация. )

Кроме гравитации на игровой объект еще должны действовать нажатые клавиши чтобы пользователь мог управлять им. Чтобы сделать это нам нужно добавить скрипт. Для этого выберите игровой объект Player и в окне инспекторе нажмите кнопку "Add component" и из списка выберите New Script (внизу). Введите в поле имени "PlayerController", выберите язык C Sharp и нажмите кнопку "Create and Add". После этого появится новый компонент Script. 

Скрипт созданный таким образом будет лежать в корне папки Assets в окне Project. Нам нужно создать там папку Scripts, если ее нет и переместить наш скрипт PlayerController в эту папку. 

 

Просто мышкой перетащите его в новую папку Scripts. Теперь откроем этот скрипт чтобы написать там нужный нам код. Для  этого кликните по файлу скрипта два раза и он откроется в установленном редакторе: MonoDevelop, VisualStudio или что-то другое. 

Это стандартный класс для описания поведения объекта. Удалите весь код внутри класса: методы Start() и Update(). В играх экран постоянно перерисовывается. Количество перерисовок за секунду называется fps (frames per second = кадры за секунду). Перед отрисовкой каждого кадра, мы должны проверить не нажал ли пользователь клавишу, которая двигает игровым объектом. И если нажал, то изменить координаты игрового объекта. 

Для этого мы можем использовать два метода: Update() и FixedUpdate(). Первый вызывается каждый раз перед отрисовкой нового кадра. Второй вызывается перед тем, как нужно сделать физические расчеты. Мы будем писать код в методы FixedUpdate(). Создайте тело такого метода:

public class PlayerController : MonoBehaviour {

	void FixedUpdate () {
		
	}
}

В Unity3D есть класс Input, который помогает пользователю вводить какие-либо данные в игру и управлять ей. Мы будем использовать метод GetAxis() класса Input, чтобы получать какие клавиши нажимает пользователь. Есть две оси, по которым мы будем определять это. Вертикальная(Vertical) и горизонтальная (Horizontal). Название оси будем передавать в метод GetAxis() и он будет возвращать значения управления по этим осям. Когда пользователь нажимает клавиши-стрелки или двигает джойстиком Unity сам определяет насколько долго пользоватеть нажимает кнопку и меняет значение осей от -1 до 1.  

Например По вертикальной оси -1 означает что пользователь уже долго нажимает вниз. А 1 означает что пользователь долго нажимает вверх. 0 - значит он уже перестал нажимать. И так, чтобы получить направления движения мы должны написать:

float moveVertical = Input.GetAxis ("Vertical");
float moveHorizontal = Input.GetAxis ("Horizontal");

Мы знаем куда хочет пользователь сдвинуть игровой объект. Нам нужно применить силу на объект, чтобы он сдвинулся с места по нужному направлению. Для этого нам поможет компонент Rigidbody2D, который мы подключили к игровому объекту Player. 

Сначала получим этот компоменет и сохраним его в переменную. А чтобы переменная была доступна во всех методах, мы объявим его как свойство класса:

public class PlayerController : MonoBehaviour {

	private Rigidbody2D rb2d;

После этого нам нужно создать метод Start(), который вызывается при первой инициализации скрипта. В этом методу мы должны проинициализировать переменную rb2d и присвоить ему объект Rigidbody2D, который привязан к нашему игровому объекту. 

void Start(){
	rb2d = GetComponent<Rigidbody2D> ();
}

Для этого мы использовали метод GetComponent() который возвращает подключенный компонент по указанному типу. 

Теперь использую объект компонента Rigidbody2D мы можем применить силу на наш игровой объект в нужных направлениях. Направления, которые пользователь задал нажатием клавиш мы сохранили в переменных moveVertical и moveHorizontal. 

Для применения силы используется метод AddForce() класса Rigidbody2D, который принимает один параметр типа Vector2. Vector2 - это класс, который имеет в себе два значения. Создадим объект Vector2 с нашими значениями и вызовем метод AddForce().

void FixedUpdate () {
	float moveVertical = Input.GetAxis ("Vertical");
	float moveHorizontal = Input.GetAxis ("Horizontal");

	Vector2 movement = new Vector2 (moveHorizontal, moveVertical);
	rb2d.AddForce (movement);
}

Теперь, сохранив скрипт, мы можем проверить как это будет работать. Но перед тем как запустить, нам нужно отключить гравитацию для объекта Player. Для этого перейдите в Unity3d, выберите игровой объект Player и в окне Inspector, в компоненте Rigidbody2D измените свойство Gravity Scale на 0. 

Можно запускать игру. 

Скорость

Попробовав подвигать объектом, вы заметите что он очень медленный. Чтобы контролировать скорость объекта, мы добавим свойство speed в скрипт PlayerController. Если мы сделаем свойство public, то можем его значение менять через окно Inspector. 

public float speed;

Силу которой мы действуем на игровой объект будем умножать на скорость. 

rb2d.AddForce (movement * speed);

В итоге получится такой скрипт. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour {
	public float speed;

	private Rigidbody2D rb2d;

	void Start(){
		rb2d = GetComponent<Rigidbody2D> ();
	}

	void FixedUpdate () {
		float moveVertical = Input.GetAxis ("Vertical");
		float moveHorizontal = Input.GetAxis ("Horizontal");

		Vector2 movement = new Vector2 (moveHorizontal, moveVertical);
		rb2d.AddForce (movement * speed);
	}
}

Сохраните его и перейите в Unity. Обновив экран (ctrl+R) вы увидете что в компоненте PlayerController(Script) появилось поле speed

Поменяйте его на 5 и попробуйте снова поиграть в игру. Вы заметите что игровой объект теперь двигается быстрее.

Обнаружение столкновений

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

Для обнаружения столкновений используется компоненты называемые Collider. Давайте добавим такой компонент в наш игровой объект. Так, как игрок НЛО круглый, мы будем использовать Circle Collider 2D. Выберите игровой объект Player и нажмите кнопку "Add Component" и введите "Circle" в поиск и найдите компонент Circle Collider 2D, чтобы добавить его. 

Теперь игровой объект Player умеет сталкаиваться. Нам нужно для стен тоже добавить коллайдеры. 

Выберите игровой объект Background и добавьте к нему компонент Box Collider 2D. Все коллайдеры сталкиваются наружными частями. Поэтому нам нужно создать 4 коллайдеры для каждой стороны стен. У компонента BoxCollider2D есть свойств Offset и Size, которые задают отступ и размеры коллайдера. Измените эти свойства так, чтобы первый коллайдер был на месте левой стены. Вы будете видеть зеленый прямоугольный коллайдера. У меня получились такие значения:

Компоненты можно скопировать. Нам нужно этот коллайдер скопировать и создать еще 3 коллайдера для других стен. Чтобы скопировать в правом-верхнем углу компонента нажмите маленькую шестеренку и выберите "Copy Component" и потом еще раз "Paste Component As New". Появится новый компонент BoxCollider2D. Измените его свойства так, чтобы он был на месте правой стены. И также сделайте для двух остальных: верхней и нижней стены.

После этого запустите игру и убедитесь что НЛО сталкивается со стенами.

Следование камеры за игроком

Иногда, когда игровой мир большой, камера не должна показывать только одно место, а следовать за игроком. Мы сделаем так, чтобы наша камера следовала за НЛО. 

Чтобы камера следовала за игровым объектом, мы должны разположение камеры менять в соответсвие с расположением игрового объекта. Чтобы это сделать нам нужно создать скрипт. Выберите игровой объект Main Camera. Нажмите на кнопку "Add Component", выберите New Script и назовите скрипт "CameraController". После создания скрипта, как раньше нужно его из папки Assets переместить в папку Scripts. 

Этот скрипт для камеры. В нем мы напишем код, который будет менять позицию камеры в соответсвие с позиицией НЛО. 

Откройте скрипт CameraController. В нем мы должны обращаться к игровому объекту Player(НЛО), поэтому нам нужно создать public свойство player типа GameObject. Все свойства скрипта, которые мы делаем public, становятся видными в окне Inspector и мы можем менять прямо в Инспекторе их значения. 

public class CameraController : MonoBehaviour {
	public GameObject player; 

Потом через Инспектор мы присвоим этому свойству ссылку на реальный игровой объект Player. А пока напишем алгоритм движения камеры. 

Мы должны следовать за игроком. Т.е. если игрок сдвинулся на 2 см вверх, то камеру тоже нужно сдвинуть на 2 см вверх. Мы можем сделать так, чтобы перед отрисовкой кадра, камере присваивались такие же координаты как и игрока, т.е. присваивать координаты игрока камере. Но, это будет неправильно если вначале камера находится не на том же месте где и игрок. Если камера у нас вначале специально отдалена от игрока на 2 см выше и на 3 см правее, то если присвоим камере координаты игрока, камера не будет больше отдалена. 

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

Координаты игровых объектов это три значения X, Y, Z. В Unity есть класс Vector3 чтобы хранить три значения.

В скрипте CameraController создайте private свойство offset типа Vector3 в котором мы будет хранить начальное расстояние между камерой и игроком. В offset будет храниться расстояние сразу по трем координатам.

public class CameraController : MonoBehaviour {
	public GameObject player; 
	private Vector3 offset;

Затем в метода Start() вычислим разницу координат игрока и камеры, т.е. расстояние между ними.

void Start () {
	offset = transform.position - player.transform.position;
}

объект transfrom - это компонент Transfrom текущего игрового объекта, т.е. камеры. Компонент Transfrom определеяет позицию, размеры и повороты игрового объекта. Чтобы получить позицию, обращаемся к свойству position объекта transform. У player  тоже есть transform.

Теперь мы знаем расстояние между игроком и камерой. Нам нужно менять позицию камеру перед отрисовкой, но после того как все другие объекты будут обработаны. Для этого мы будем писать код внутри метода LateUpdate(). Получим позицию игрока, добавляем начальное расстояние и получаем новую позицию камеры:

void LateUpdate () {
	transform.position = player.transform.position + offset;
}

Сохраните скрипт, откроейте Unity, выберите игровой объект MainCamera и перейдите в окно Inspector. Нажмите Ctrl+R чтобы обновить свойства. И в компоненте CameraController вы увидите свойтсво Player. Перетащите игровой объект Player из кона Иерархии в свойство player, что он ссылалася на игровой объект player). 

Теперь запустите игру и убедитесь что камера следует за игроком.

Добавление блинчиков

Теперь займемся блинчиками - объектами, которые будет собирать игрок. Отпройте папку Sprites внизу в окне Project. Там должны быть загруженные ранее картинки. Возьмите картинку Pickup и перетащите его в окно Hierarchy. Он теперь на сцене, но его прикрыли фоновый рисунок и НЛО.

Чтобы переместить его выше, мы должны изменить его сортировочный слой. Для этого выберите игровой объекь Pickup и в окне Инспектор поменяйте свойство SortingLayer на Pickups. Но его все равно будет закрывать объект НЛО. Потому, что сортировочный слой объекта НЛО находится вышу. Чтобы увидеть блинчик, выберите игровой объект Player в окне Hierarchy и в самом верху окне Inspector уберите галочку. Таким образом мы убираем HЛО со сцены. Теперь вы должны увидеть блинчик. 

Выберите объект Pickup в окне Hierarchy и мышкой указывая на сцену нажмите клавишу F, чтобы увеличить и увидеть блинчик. 

Так, как блинчик должен сталкиваться с игроком, мы должны добавить Collider блинчику. Для этого в окне Inspector нажмите кнопку Add Component и выберите CircleCollider2D. После добавления коллайдера, вы увидите границы столкновения указанные зеленой линией

В свойствах компонента CircleCollider2D измените свойство Size так, чтобы эта зеленая линия была примерно равна размеру блинчика. 

Теперь мы сделаем блинчик немного привлекательным. Например добавим движение вокруг своей оси. Для этого нам нужно будет создать скрипт. Выберите игровой объект Pickup и нажмите Add Component, выберите пункт New Script и создайте скрипт c названием Rotator. После создания переместите новый скрипт с папки Assets в папку Scripts. 

Откройте скрипт Rotator в редакторе кода. Уберите метод Start(), он нам здесь не нужен. Будем писать код внутри метода Update(), который выполняется перед отрисовкой следующего кадра. Нам нужно крутить объект вокруг своей оси. Помните как мы двигали игроком с помощью компонента transform. Так же будет крутить. Компонент Transform отвечает за позицию, размеры и угол объекта. У него есть метод Rotate() который принимает Vector3 значение, в котором указаны углы поворота по каждой оси. Мы будем крутить только вокруг оси Z на 45 градусов за секунду. поэтому создайте объект rotation:

void Update () {
	Vector3 rotation = new Vector3 (0, 0, 45);

}

Затем вызовем метод rotate с параметром rotation умноженными на Time.deltaTime:

void Update () {
	Vector3 rotation = new Vector3 (0, 0, 45);
	transform.Rotate (rotation * Time.deltaTime);
}

Time.deltaTime это время которое прошло с момента отрисовки предыдущего кадра в секундах. Это число не целое, т.е. может быть 0.12 сек и т.п. Почему мы умножаем угол поворота на это время? Потому что, если мы хотим чтобы за секунду блинчик повернулся на 45 градусов, то за n секунд нам нужно повернуть блинчик на 45 * n градусов. И n может быть меньше 1, потому что кадр обновляется 30-60 раз за секунду и поэтому deltaTime может быть 1/60.

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

Полуфабрикаты

У нас есть один бликчик, но нам нужно много. В Unity есть понятие полуфабрикатов(Prefab). Полуфабрикат это готовый и частично или полностью настроенный игровой объект. Мы сделаем из нашего блинчика полуфабрикат. Для этого создайте новую папку Prefabs внутри папки Assets. Затем мышкой перетащите игровой объект Pickup из окна Иерархии в папку Prefabs. 

Давайте теперь создадим побольше блинчиков. Сначала создадим новый игровой объект, который будет содержать в себе все блинчики. Для этого В верхнем меню Unity выберите Game Object -> Create Empty. Потом переименуйту новый игровой объект в Pickups. Затем переместите игровой объект Pickup из Иерархии в новый игровой объект Pickups, чтобы стало так:

Выберите объект Pickup и переместите его на сцене чуть выше с помощью мышки. Создайте копию этого игрового объекта с помощью горячей клавиши Ctrl+D и переместите левее и таким же образом создайте множество блинчиков. 

Съедаем блинчики

Давайте теперь двигать НЛО и есть блинчики. Для начала снова активируйте игровой объект Player, выбран его и поставив галочку с окне Инспетора сверху. Чтобы собирать блинчики, нам нужно знать момент столкновения игрока с блинчиком. Для этого есть метод OnTriggerEnter2D, который вызывается когда игровой объект сталкивается с другим объектом. Этот метода принимает параметр типа Collider2D - компонент коллайдер другого игрового объекта, с который столкнулся игрок. У коллайдера мы можем получить игровой объект, к которому он привязан. Мы проверим является этот объект блинчиком, если да, то деактивируем его. А проверяеть будем с помощью свойства Tag игрового объект. Tag можно задать любому объекту. 

Используя полуфабрикат который мы создали для блинчика, мы можем поменять свойство Tag у всех блинчиков одновременно. Для этого выберите Pickup из папки Prefabs в окне Project. И сверху окна инспектора поменяйте свойство Tag на PickUp.

Теперь у всех блинчиков на сцене Tag стал равным PickUp. Теперь нам нужно изменить скрипт игрока. Выберите игровой объект Player в окне Hierarchy и в окне Inspector кликните два раза на название скрипта PlayerController, чтобы открыть его. После метода FixedUpdate, добавьте :

void OnTriggerEnter2D(Collider2D other) {
	if (other.gameObject.CompareTag ("PickUp")) {
		other.gameObject.SetActive (false);
	}
}

этот метод будет вызываться когда игровой объект Player будет сталкиваться с другим объектом. Мы проверям другой объект с помощью его тега, если у него тег PickUp, значит нам нужно его деактивировать: SetActive(false).

Если вы сейчас запустите, то увидите то при столкновении блинчики не исчезают. А потому, что для компонента BoxCollider2D по умолчанию не вызывается метод OnTriggerEnter2D. Чтобы вызывался этот метод, нужно чтобы в компоненте Collider было выбрано свойство Is Trigger. 

Выберите полуфабрикат Pickup из папки Prefabs, и поставьте галочку на его свойстве Is Trigger. 

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

1659 0
Alisher Alikulov