[Перевод] Создаем игру для WebGL с помощью Unity 5 и JavaScript

06f3116ab3f14162bcd1ec9b5f7849b1.jpg

Unity — это кроссплатформенный игровой движок, позволяющий создавать игры для ПК, консолей, мобильный устройств и веб-сайтов. Последняя версия движка (Unity 5) имеет возможность экспорта в WebGL, благодаря чему разработчики могут без труда публиковать свои игры в вебе. Как следует из названия, данный экспортер использует WebGL — JavaScript API для рендеринга интерактивной компьютерной 3D графики, а также asm.js — подмножество JavaScript, которое было разработано компанией Mozilla и преподносилось как «язык ассемблера для веба». Больше информации о Asm.js и WebGL для Unity и Unreal Engine доступно здесь.

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

По окончании урока у вас получится вот такая игра (для просмотра потребуется браузер с поддержкой WebGL). Проект также доступен для скачивания из репозитория на GitHub.

Итак, начнем.

Несколько слов о JavaScript в Unity


Говоря о JavaScript в Unity, мы подразумеваем нечто вроде диалекта JS под названием UnityScript. И хотя сами специалисты из Unity называют этот язык JavaScript, более скептично настроенные пользователи интернета полагают, что поддержка JavaScript в Unity — это маркетинговая уловка. Так или иначе, нужно сразу сделать оговорку, что UnityScript не соответствует спецификации ECMAScript, и никто даже не делает попытки устранить эти несоответствия.

Устанавливаем Unity


Для начала нам нужна рабочая версия Unity, скачать ее можно здесь. Файлы установки доступны для Windows и для Mac OS X. Пользователи Linux могут запустить Unity с помощью Wine или использовать другой удобный способ.

c0fd8f3ae4fe42c1be7fb1b37aa3e898.png

После установки можем приступать. Откроем Unity и создадим новый 3D проект.

1efa52e4712445fd922598e3f8ac2955.png

Настраиваем проект


Теперь, когда программа открылась, давайте кратко пройдемся по основному интерфейсу:

6993f8b8c57346e0b26e7e792704f5db.png

  1. Слева находится панель Hierarchy. Она показывает все элементы текущей сцены. Сцена — это визуальное отображение игры (уровня или меню). На данный момент в панели находятся два элемента: Main Camera и Directional Light.
  2. Панель Scene посредине отображает камеру и свет в 3D пространстве.
  3. Рядом с Scene есть вкладка Game. Там показано, как будет видеть игру игрок. Это необходимо для тестирования игры в редакторе.
  4. Справа находится панель Inspector, где можно менять настройки элементов. Давайте посмотрим, как она работает. Сначала выберем Directional Light в панели Hierarchy. Мы увидим много информации об этом типе света и сможем отключить в нем тени, выбрав Shadow Type: No Shadows.
  5. Внизу экрана находится окно Project, показывающее файлы, которые мы должны создать для игры.

Осталось сделать еще одну вещь перед началом работы: сохранить текущую сцену. Опция File > Save Scene открывает диалоговое окно Save Scene, ведущее к папке Assets. Самый распространенный способ организовать файлы в Unity — использовать подпапки. Поэтому добавим новую подпапку Scenes в папку Assets и сохраним там текущую сцену, назвав ее Level.unity.

Создаем героя


Герой в нашей игре будет прыгать вверх с одной платформы на другую. Если ему не удастся вовремя заскочить на одну из них, он упадет в пропасть и проиграет. Мы начнем с создания героя. Поскольку игра будет от первого лица, внешность героя не имеет никакого значения, и мы можем использовать вместо него стандартную сферу. Плюс в том, что сфера быстро создается, и ее физические характеристики отлично подходят для прыжков по платформам. Добавим ее, выбрав Create в панели Hierarchy и отредактировав следующие параметры во вкладке Inspector:
Position { X: 0, Y: 2.5, Z: 0 }
Scale { X: 0.3, Y: 0.3, Z: 0.3 }

20cdaa5c0add4c7aadb42fcb357b2659.png
Нажмем кнопку Play, чтобы проверить результат. На экране должно появиться 3D пространство со сферой на фоне горизонта.

Чтобы сфера падала, у нее должен быть вес. Выходит, нам нужно добавить к ней новый компонент, нажав в панели Inspector кнопку Add Component и выбрав Rigidbody. Так как мы не хотим, чтобы сфера вращалась, нужно зафиксировать ее положение с помощью компонента Rigidbody. Для этого откройте Constraints и выберите все оси в строке Rotation. Проиграйте сцену еще раз и вы увидите, что теперь сфера падает.

9bfef684325e4d858c057b9b998c1f74.png

Чтобы предотвратить бесконечное падение сферы, создадим нечто вроде платформы. Для этого добавим плоский куб со значением Scale.Y, равным 0.1. Проиграйте сцену еще раз и убедитесь, что сфера успешно приземляется на платформу. Но, стоит отметить, всё это выглядит не очень естественно. Как же заставить сферу подпрыгивать? Для этого нам пригодятся физические материалы.

Наделяем сферу физическими свойствами


Прежде всего, создадим для нашей сферы физический материал, который позволит ей отпрыгивать от поверхности соприкосновения. Для этого нужно создать в папке Assets новую подпапку Materials. Внутри этой подпапки создадим новый физический материал и назовем его Bouncy_Sphere. Вот значения, которые мы должны указать в панели Inspector:
Dynamic Friction: 10
Static Friction: 10
Bounciness: 1
Friction Combine: Maximum
Bounce Combine: Maximum

Если мы добавим этот материал в Sphere Collider, сфера будет подпрыгивать, но всегда на одинаковую высоту. Чтобы она подпрыгивала с каждым разом выше, нужно добавить физический материал и для платформы. Создадим еще один материал под названием Bouncy_Platform и применим к нему следующие значения:
Dynamic Friction: 0.9
Static Friction: 0.9
Bounciness: 1
Friction Combine: Average
Bounce Combine: Multiply

Чтобы не запутаться, переименуем наш плоский куб-платформу на Platform, кликнув по нему дважды в панели Hierarchy. Теперь, начав игру, вы увидите, что сфера с каждым разом подпрыгивает выше.

Также создадим новый стандартный материал под названием Platform, чтобы появилась возможность дать платформе какой-нибудь цвет. Создав его, вставьте цвет #C8FF00 напротив значения Albedo, а затем перетащите этот материал на элемент платформы. Теперь платформа должна стать желтой.

Добавляем вид от первого лица


Для этого нужно в панели Hierarchy перетащить камеру на сферу. В результате камера станет дочерним элементом сферы и будет двигаться вместе со сферой. Камере также необходимо задать некоторые дополнительные настройки:
Position { X: 0, Y: 1, Z: 0 }
Rotation { X: 90, Y: 0, Z: 0 }
Scale { X: 2.5, Y: 2.5, Z: 2.5 }
Clear Flags: Solid Color
Background: #000
Field of View: 80.3

Помимо этого добавим фонарь в качестве второго дочернего элемента сферы. Он поможет игроку иметь представление о высоте прыжка сферы в любой момент игры. Параметры фонаря выглядят следующим образом:
Rotation { X:90, Y:0, Z:0 }

Настраиваем управление в игре


Наша цель — использовать мышь или трекпад, чтобы позволить игроку двигать сферу в определенном направлении. С этой целью мы напишем первый скрипт. Как и в случае с Rigidbody, скрипт добавляется к элементу игры в качестве компонента. В нашем примере мы добавим JS-скрипт под названием InputController к камере. Так же, как мы это делали со сценой и материалами, создадим в панели Project новую папку под названием Scripts, в которой и будет находиться наш скрипт. Двойной клик по новому скрипту откроет стандартный для Unity редактор MonoDevelop. Его можно заменить на любой другой редактор (Unity > Preferences > External Tools), но сейчас это не имеет никакого значения.

Как видно, в скрипте уже имеется какой-то код. Прежде всего, создадим несколько переменных под первой строкой с текстом #pragma strict (включает принужденное определение типов в Unity):

#pragma strict

public var Hero : GameObject;
private var halfScreenWidth : float;
private var  halfScreenHeight : float;

function Start () {}

function Update () {}

Первой идет общедоступная переменная, принадлежащая к типу GameObject. Она обозначает сферу. Вернемся к Unity, по прежнему оставив камеру выбранной. Мы увидим, что эта общедоступная переменная находится рядом с пустым полем для ввода. Перетащим сферу на данный участок, таким образом присвоив значение этой переменной.

Две остальные переменные — закрытые, им будут присвоены значения в функции Start. Эта функция вызывается всего один раз, после запуска сцены. Обоим закрытым переменным будет присвоена половина ширины и высоты экрана соответственно. Для этого мы используем встроенный класс Screen:

function Start () {
  halfScreenWidth = Screen.width / 2;
  halfScreenHeight = Screen.height / 2;
}

Единственное, что осталось реализовать в скрипте InputController — получение данных о положении и передвижении мышки. Для этого мы воспользуемся функцией Update, которая вызывается для каждого кадра:
function Update () {
  var x : float = 0.0;
  var z : float = 0.0;

  x = ( Input.mousePosition.x - halfScreenWidth ) / halfScreenWidth;
  z = ( Input.mousePosition.y - halfScreenHeight ) / halfScreenHeight;

  Hero.GetComponent( HeroController ).SetPosition( x, z );
}

Каждая из двух новых переменных x и z обозначает соответствующую ось. Когда мы смотрим вдоль оси y, мы видим горизонтальную ось x и вертикальную ось z. Мы будем менять положение сферы на этих осях в зависимости от данных, полученных с мышки. Нам понадобится статическая переменная Input.mousePosition, возвращающая двухмерный вектор. Вектор, нулевое значение которого приходится на нижний левый угол, должен переместиться в нашей системе координат на середину экрана. Следующий фрагмент кода демонстрирует такую трансформацию координат. Наконец, вызовем функцию setHeroPosition с обоими рассчитанными значениями в качестве аргументов. Мы запишем эту функцию в новый скрипт HeroController, привязанный к сфере:
#pragma strict

public function SetPosition ( x : float, z : float ) {
  transform.position.x = x;
  transform.position.z = z;
}

Проверим, как работает наш код: подвигаем мышку или трекпад, чтобы сфера упала с платформы.

Реализуем процедурное создание платформ


Для автоматического создания платформ нам нужно нечто вроде шаблона платформы. В Unity такие шаблоны называются префабами. Чтобы создать префаб, необходимо перетащить платформу из панели Hierarchy в новую подпапку Prefabs папки assets. Префабы легко распознать в панели Hierarchy по голубому цвету. Все платформы, за исключением первой, будут создаваться с помощью нового скрипта GameManager, привязанного к камере. Сначала в скрипте обратимся к необходимым переменным:
#pragma strict

public var Platform : GameObject;
public var Hero : GameObject;

private var boundary : float;
private var rotation: Quaternion;
private var lastPlatformPosition : Vector3;

function Start () {
  boundary = 1.0;
  rotation = Quaternion.identity;
  lastPlatformPosition = new Vector3( 0, 0, 0 );
}

function Update () {}

Нужно обратиться к панели префаба и сфере, потому нужно перетащить их на соответствующе участки редактора. Мы также создаем три закрытых переменных, которые будут использоваться для создания экземпляра панели префаба.
  1. Переменная boundary определяет границу оси y. Каждый раз, когда герой прыгает выше этой отметки, должна создаваться новая панель.
  2. Вторая переменная отвечает за поворот, необходимый для создания нового экземпляра префаба. Значение Quaternion.identity отменяет поворот, как нам и нужно.
  3. Переменная lastPlatformPosition сохраняет положение последней платформы как трехмерный вектор.

Теперь сделаем так, чтобы в каждом кадре проверялось, находится ли сфера выше заданной границы. Если да, граница повысится и создастся новый экземпляр панели:
function Update () {
  if ( Hero.transform.position.y > boundary ) {
    var position : Vector3;

    boundary += 1.0;
    position = getNextPlatformPosition();
    Instantiate( Platform, position, rotation );
  }
}

Затем добавим код для получения положения следующей панели. Поместим этот код в дополнительную функцию, чтобы сохранить общую читабельность:
private function getNextPlatformPosition () {
  var position : Vector3;

  do {
    position = new Vector3( Random.Range( -1, 2 ), boundary, Random.Range( -1, 2 ) );
  } while ( position.x == lastPlatformPosition && position.z == lastPlatformPosition );

  lastPlatformPosition = position;

  return position;
}

Чтобы избежать дублирования значений координат x и z в новой панели по отношению к предыдущей, воспользуемся циклом do while. Функция Unity Random.Range поможет нам получить произвольные значения осей x и z. В любом случае, нам нужно, чтобы их диапазон был между -1 и 2. Наконец, сохраняем новое положение панели в качестве последнего и возвращаем его.

Добавляем меню игры


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

Прежде всего нужно проверить, упала ли сфера ниже определенной отметки. Для этого отредактируем оператор if функции update в скрипте GameManager. Оператор else if будет проверять, является ли положение сферы ниже -2.0 единиц. Если да, он вызовет закрытую функцию gameOver:

function Update () {
  if ( Hero.transform.position.y > boundary ) {
    var position : Vector3;

    boundary += 1.0;
    position = getNextPlatformPosition();
    Instantiate( Platform, position, rotation );
  } else if (Hero.transform.position.y < -2.0) {
    gameOver();
  }
}

Для отслеживания состояния игры мы воспользуемся новой функцией:
private function gameOver () {
  Application.LoadLevel( 'Menu' );
}

В данном случае мы используем класс Application, позволяющий с помощью метода LoadLevel загрузить новую сцену Menu. Для этого сначала создадим сцену, выбрав File > New Scene, и сохраним ее под названием Menu. Затем нам нужно добавить обе сцены в процесс сборки. Настройки сборки доступны во вкладке File > Build Settings. Не закрывая сцену с меню, нажмите кнопку Add Current и добавьте сцену в настройки сборки. Повторите то же действие с открытой сценой уровня. Теперь по завершении игры у вас на экране будет появляться только что созданная сцена с меню игры.

Добавляем кнопку для начала игры


Чтобы в игру можно было поиграть, нужно добавить в меню кнопку для ее запуска. Потому вернемся к настройкам сцены меню игры и изменим прежде всего параметры камеры в панели Inspector:
Clear Flags: Solid Color
Background: #000
Width: 200
Height: 60

Чтобы добавить кнопку, мы воспользуемся элементами пользовательского интерфейса Unity, которые могут быть добавлены как 3D элементы через панель Hierarchy. После добавления кнопки интерфейса в Hierarchy должны появиться следующие элементы: EventSystem и Canvas вместе с дочерним элементом Button и его дочерним элементом Text.

Canvas — это контейнер для всех элементов интерфейса, его можно сделать в некотором смысле адаптивным. Для этого нужно в панели Inspector переключить настройку Canvas Scaler: UI Scale Mode с Constant Pixel Size на Scale With Screen Size. Теперь можно менять положение кнопки:

Rect Transform { Pos X: 0, Pos Y: 0, Pos Z: 0 }
Rect Transform { Width: 200, Height: 60 }

Убрав исходное изображение кнопки и выставив ее цвет на #C8FF00, мы придадим меню чуть более приличный вид. Теперь поменяем текст в элементе Text на PLAY PREJUMP и выставим шрифт 16-го кегля. Чтобы кнопка заработала, воспользуемся новой функцией, которую мы добавим в новый скрипт UIController для элемента Button. Скрипт состоит всего из одной функции, загружающей сцену уровня:
public function StartGame () {
  Application.LoadLevel( 'Level' );
}

Эту функцию можно применить в опциях кнопки в панели Inspector. В настройках компонента Button (Script) можно сделать так, чтобы функция выполнялась, когда пользователь кликает данный компонент. С этой целью мы добавим новую функцию к событию On Click (), нажав иконку +. Теперь можно перетащить саму кнопку на поле ввода. Затем выберем только что написанную функцию из скрипта UIController (UIController.StartGame).

43349eea4ffe408ea4d0242c0ac7713c.gif

Публикуем проект как браузерную игру для WebGL


С Unity вы можете экспортировать ваш проект как приложение для WebGL. Откройте настройки сборки и выберите WebGL в качестве платформы. Затем подтвердите выбор, нажав кнопку Switch Platform. После этого остается только нажать кнопку Build и выбрать название для игры. По завершении сборки откройте html-файл с помощью любого браузера, поддерживающего WebGL.

a1f328b461f942acb5cf26f995eb6cf5.png

Дальнейшие шаги


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

А вы используете Unity в ваших проектах? Есть ли у вас примеры интересных игр, написанных с помощью этого движка? Жду ответов в комментариях под записью.

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

© Habrahabr.ru