Создать игру в браузере
Разработка игр не требует совершенно нового набора знаний и навыков. По сути, ваши текущие навыки в веб-разработке с применением HTML, JavaScript, CSS и других средств замечательно подходят к широкому спектру игр. Когда вы создаете игру на основе веб-технологий, она будет работать почти на любом устройстве в браузере.
Чтобы доказать это, я продемонстрирую создание игры с нуля, используя веб-технологии и всего две внешние библиотеки, причем сделаю все это менее чем за час. Я буду рассказывать о самой разнообразной тематике, связанной с разработкой игр, — от базового дизайна и разметки, элементов управления и спрайтов до искусственного интеллекта (ИИ) (artificial intelligence, AI), пригодного для простого оппонента. Я даже собираюсь создать игру такой, чтобы она работала на ПК, планшетах и смартфонах. Если у вас есть некоторый опыт в программировании в качестве веб-разработчика или в другой области разработки, но нет никакого опыта в написании игр, эта статья послужит вам отправной точкой. Если вы дадите мне один час, обещаю ввести вас в курс дела.
Приступаем
Я веду всю разработку в Visual Studio, которая позволяет быстро запускать веб-приложения по мере внесения изменений. Удостоверьтесь, что у вас наиболее новая версия Visual Studio (скачайте с bit.ly/1xEjEnX), чтобы вы могли следовать за мной. Я использовал Visual Studio 2013 Pro, но обновил код с помощью Visual Studio 2013 Community.
В HTML-файле index нужно лишь три ресурса: jQuery, основная таблица стилей и основной JavaScript-файл. Я добавляю в проект пустой CSS-файл style.css и пустой JavaScript-файл ping.js, чтобы избежать ошибок при загрузке страницы:
Базовый дизайн
Рис. 1. Общий дизайн Ping
Player | Игрок |
Ball | Мяч |
Left Controls | Управление слева |
Scoreboard | Табло |
Opponent | Оппонент |
Right Controls | Управление справа |
Разработав разметку игры, остается лишь добавить каждый элемент в HTML, чтобы создать игру. Однако стоит отметить, что я буду группировать табло со счетом и элементы управления, чтобы они были расположены вместе. На рис. 2 видно, как я по одному добавил нужные элементы.
Рис. 2. Начальная HTML-разметка
Играем стильно
Если бы вы загрузили эту страницу, то ничего не увидели бы, потому что никакого стиля не применено. Я уже подготовил ссылку на файл main.css в своем HTML, поэтому помещу все CSS в новый файл с тем же именем. Первым делом я позиционирую все элементы на экране. Тело страницы должно занимать весь экран, поэтому сначала я уделяю внимание этому:
Во-вторых, мне нужно, чтобы арена была заполнена фоновым изображением (рис. 3):
Рис. 3. Фоновое изображение для арены
Далее я размещаю табло. Я хочу, чтобы оно находилось вверху по центру — поверх других элементов. Команда «position: absolute» позволяет разместить его где угодно и оставить там: 50% — позиционирует табло посередине верхней части окна, но начиная с самой левой части элемента табло (scoreboard element). Чтобы гарантировать точную центровку, я использую свойство transform, а свойство z-index обеспечивает, что табло будет всегда находиться поверх других элементов:
Теперь результаты (scores) находятся в теге h1, поэтому я могу задать этот шрифт для всех тегов h1. На случай отсутствия этого шрифта я предусмотрю несколько запасных вариантов:
Для других элементов я буду использовать таблицу изображений-спрайтов. Таблица спрайтов (sprite sheet) содержит все необходимые игре изображения в одном файле (рис. 4).
Рис. 4. Таблица спрайтов для Ping
Зачастую, прежде чем создавать игру, лучше всего нарисовать то, как она должна выглядеть.
Любому элементу, имеющему изображение в этой таблице, будет назначен класс sprite. Тогда для каждого элемента я буду использовать background-position, чтобы определить, какую часть таблицы спрайтов мне нужно показывать:
Затем я добавлю класс sprite ко всем элементам, которые будут использовать таблицу спрайтов. Для этого мне придется ненадолго переключиться на HTML:
Теперь мне нужно указать позиции каждого спрайта в таблице для каждого элемента. И вновь я буду делать это с помощью background-position, как показано на рис. 5.
Рис. 5. Добавление смещений для таблицы спрайтов
Свойство position: absolute в player, opponent и ball позволит мне перемещать их по полю, используя JavaScript. Если вы посмотрите на страницу теперь, то увидите, что к элементам управления и мячу прикреплены ненужные куски. Дело в том, что размеры спрайтов меньше 128 пикселей по умолчанию, поэтому я подстраиваю их под правильный размер. Мяч всего один, и я задаю его размер напрямую:
Элементов управления четыре (кнопки, которые пользователь может нажимать для перемещения своего игрока), поэтому мне следует создать для них специальный класс. Я также буду добавлять некий зазор, чтобы вокруг них было небольшое свободное пространство:
После добавления этого класса элементы управления в игре выглядят гораздо лучше:
Последнее, что нужно сделать, — позиционировать элементы управления так, чтобы они находились под большими пальцами пользователя, когда страница выполняется на мобильном устройстве. Я закреплю их в углах внизу:
Одна из приятных особенностей этого дизайна заключается в том, что все расположено в относительных позициях. То есть экран может быть самых разных размеров, а игра все равно будет выглядеть так, как задумывалась.
Скачущий мяч
Теперь я заставлю мяч перемещаться по полю. В случае JavaScript-кода я ссылался в HTML на файл ping.js точно так же, как делал это с CSS. Я добавлю этот код в новый файл с тем же именем. Я создам объекты для мяча и каждого из игроков, но буду использовать для объектов шаблон фабрики.
Это простая концепция. Функция Ball создает новый мяч, когда вы вызываете ее. Использовать ключевое слово new не требуется. Этот шаблон исключает некоторую путаницу вокруг переменной Ball, проясняя доступные свойства объекта. А поскольку у меня всего час на создание этой игры, нужно свести к минимуму любые запутанные концепции.
Структура этого шаблона, когда я создаю простой класс Ball, показана на рис. 6.
Рис. 6. Класс Ball
Чтобы создать новый мяч, я просто вызываю только что определенную функцию:
Теперь я хочу заставить мяч перемещаться и скакать по экрану. Сначала мне нужно через какой-то интервал вызывать функцию update, чтобы создать анимацию мяча. Современные браузеры предоставляют функцию, предназначенную для этой цели, — requestAnimationFrame. Она принимает функцию как аргумент и вызовет эту переданную функцию, когда она в следующий раз запустит свой цикл анимации. Это позволяет плавно перемещать мяч, когда браузер готов к обновлению. Вызывая переданную функцию, он дает ей время, измеряемое в секундах, так как страница должна загрузиться. Это крайне важно, чтобы анимация не потеряла целостность со временем. В игре requestAnimationFrame используется так:
Заметьте, что requestAnimationFrame снова вызывается в функции, когда обновление мяча закончено. Это гарантирует непрерывность анимации.
Хотя этот код работает, может возникнуть проблема, если скрипт начинает выполняться до полной загрузки страницы. Чтобы избежать этого, я запускаю код, когда страница загружена, и использую для этого jQuery:
Поскольку я знаю скорость мяча (velocity) и время с момента ее последнего обновления, я могу выполнить некоторые простые физические расчеты, чтобы перемещать мяч вперед:
Попробуйте запустить этот код и вы увидите, что мяч движется под углом, а потом вылетает за пределы экрана. Это забавно для первого раза, но, как только мяч пересекает край экрана, уже не до смеха. Поэтому следующий шаг — сделать так, чтобы мяч отскакивал от краев экрана, как реализовано на рис. 7. Добавьте этот код и запустите приложение: вы увидите постоянно скачущий мяч.
Рис. 7. Простая физика отскакивания мяча
Перемещаемый игрок
Теперь пришла пора создать объекты Player. Первый шаг в реализации этого класса — заставить функцию move изменять позицию игрока. Переменная side будет указывать, на какой стороне поля находится игрок, что будет определять горизонтальную позицию игрока. Значение y, переданное функции move, сообщает, насколько следует переместить игрока вверх или вниз:
На рис. 8 определяются перемещения игрока, причем его движение прекращается, если его спрайт достигает верхней или нижней границы окна.
Теперь я могу создать двух игроков и перемещать их по соответствующим сторонам экрана:
Рис. 8. Элементы управления перемещением спрайта игрока
Ввод с клавиатуры
Итак, вы можете передвигать игрока, но он не будет перемещаться без команд. Добавьте некоторые элементы управления к игроку слева. Вам нужны два способа управления этим игроком: с клавиатуры (на ПК) и сенсорным вводом (на планшетах и смартфонах).
Рис. 9 демонстрирует применение Hand.js и jQuery для управления игроком при нажатии клавиш на клавиатуре A и Z или при касании сенсорных элементов управления.
Рис. 9. Добавление элементов управления с поддержкой сенсорного ввода и ввода с клавиатуры
Ловим мяч
Я хочу, чтобы игроки ловили скачущий мяч. Когда он пойман, у него появляется владелец, и он следует движениям владельца. На рис. 10 в метод move мяча добавлена функциональность, позволяющая мячу перемещаться вслед за владельцем.
Рис. 10. Заставляем мяч следовать за своим владельцем
На данный момент способа получить позицию объекта Player пока нет, поэтому я добавлю аксессоры getPosition и getSide в объект Player:
Теперь, если у мяча есть владелец, он будет следовать за этим владельцем. Но как определить владельца? Кто-то должен поймать мяч. На рис. 11 показано, как узнать, когда один из спрайтов игроков касается мяча. Когда это происходит, я устанавливаю владельцем мяча этого игрока.
Первый шаг в реализации класса player — заставить функцию move изменять позицию игрока.
Рис. 11. Обнаружение коллизии для мяча и игроков
Если вы сейчас попытаетесь поиграть в эту игру, то обнаружите, что мяч отскакивает от верхнего края экрана и что можно двигать игрока, чтобы поймать мяч. А как бросить мяч? Для этого и предназначены элементы управления по правую руку. На рис. 12 игроку добавлена функция fire, а также свойство aim.
Рис. 12. Свойство aim и функция fire для мяча
Код на рис. 13 дополняет функцию, связанную с клавиатурой. Он задает aim и fire объекта игрока. Прицеливание работает слегка иначе. Когда клавиша прицеливания освобождается, задается прямое направление (straightforward).
Рис. 13. Подготавливаем функцию прицеливания для игрока
И завершающее добавление относится к сенсорной поддержке для всех элементов управления. Элементы управления справа изменяют направление прицеливания игрока. Кроме того, я сделаю так, чтобы касание любого участка экрана приводило к броску мяча:
Ведем счет
Когда мяч летит на игрока, я хочу изменять счет и отдавать мяч этому игроку. Я буду использоваться собственные события, чтобы можно было отделить подсчет от любого из существующих объектов. Функция update становится слишком длинной, поэтому я добавлю новую закрытую функцию — checkScored:
На рис. 14 показан код, который реагирует на эти события, обновляя счет и передавая мяч в руки игрока. Добавьте этот код вниз JavaScript-документа.
Рис. 14. Обновление табло
Теперь, когда мяч перемещается мимо вашего оппонента (что не трудно, так как оппонент не движется), ваш счет будет расти, а мяч передаваться оппоненту. Однако оппонент будет просто держать мяч.
Делаем игру немного умнее
Игра почти готова. Жаль, что играть вам не с кем. В качестве последнего этапа я покажу, как управлять оппонентом с помощью простой ИИ. Оппонент будет пытаться держаться параллельно мячу, когда тот летит в его сторону. Если оппонент ловит мяч, он перемещается случайным образом и бросает мяч в случайном направлении. Чтобы сделать ИИ чуточку человечнее, я добавлю задержки во все, что делается на поле. Это не особо умный ИИ, но все же против него будет можно играть.
При разработке системы такого рода хорошо мыслить состояниями. ИИ оппонента имеет три возможных состояния: following (следует), aiming/shooting (прицеливание/бросок) и waiting (ожидание). Начнем только с объекта AI:
В зависимости от состояния AI (ИИ) я хочу, чтобы он выполнял разные действия. По аналогии с мячом я создам функцию update, которую смогу вызывать в requestAnimationFrame, чтобы AI действовал в соответствии со своим состоянием:
Состояние FOLLOWING достаточно простое. Оппонент перемещается в вертикальном направлении мяча, и AI переходит в состояние WAITING, чтобы добавить некоторое замедление реакции. Эти два состояния показаны на рис. 15.
При разработке системы такого рода хорошо мыслить состояниями.
Рис. 15. Простое состояние FOLLOWING
Используя код с рис. 15, AI выбирает между следованием за мячом и ожиданием доли секунды. Теперь добавляем код в функцию update уровня всей игры:
Запустив игру, вы увидите, что оппонент следует за перемещениями мяча — не такой уж и плохой ИИ, состоящий менее чем из 30 строк кода. Конечно, если оппонент поймает мяч, он ничего делать не будет. Поэтому на излете отведенного мне часа пора заняться обработкой действий для состояния AIMING. Я хочу, чтобы ИИ заставлял вашего оппонента случайным образом перемещаться несколько раз, а затем кидать мяч в случайном направлении. На рис. 16 добавлена закрытая функция, которая именно это и делает. Добавление функции aimAndFire к выражению case с AIMING создает полнофункциональный ИИ, против которого интересно играть.
Рис. 16. ИИ прицеливается и бросает мяч
Заключение
Теперь у вас есть полноценная веб-игра, которая работает на ПК, смартфонах и планшетах. Эту игру можно усовершенствовать по многим направлениям. Например, сейчас она будет плохо выглядеть в портретном (книжном) режиме на смартфоне, поэтому вам нужно держать смартфон только в альбомном режиме, чтобы игра работала корректно. Это лишь небольшая демонстрация возможностей разработки игр для Web и не только.
Michael Oneppo - креативный технолог и бывший менеджер программ в группе Microsoft Direct3D. В последнее время работает в качестве главного технического директора в технологической некоммерческой компании Library For All и ведет исследования по программе NYU Interactive Telecommunications Program для получения степени магистра.
Выражаю благодарность за рецензирование статьи эксперту Магомету Амину Ибрагиму (Mohamed Ameen Ibrahim).
Современная вычислительная техника позволяет создавать классные компьютерные игры! И сейчас, достаточно популярны игры с 3d-графикой, так как, играя в них, ты окунаешься в вымышленный мир и теряешь всякую связь с реальностью. Развитие интернета и браузерных технологий сделало возможным запускать головоломки и стрелялки в любимом Хроме, Мозилле или еще в чем-то там (про Эксплорер помолчим) в онлайн-режиме, без загрузки. Так вот, здесь я расскажу о том, как создать простую трехмерную браузерную игру.
Выбор жанра, сюжета и стилистики игры является достаточно интересной задачей, и от решения этих вопросов может зависеть успех игры. Кроме этого, свои нюансы вносит и выбор технологии, на основе которой будет создаваться продукт. Моя цель – показать элементарные основы этого увлекательного процесса, поэтому я буду делать 3-мерный лабиринт с незамысловатым оформлением. Более того, я это сделаю на чистом коде без использования библиотек и движков, типа three.js (хотя большие проекты лучше делать все-таки на нем), чтобы показать, как можно создать движок для своих нужд. Полностью самописная игра может быть оригинальной, а потому интересной. В общем, оба подхода имеют свои плюсы и минусы.
Я полагаю, если вы читаете эту статью, то вам интересна тема создания игр для гугл Хром, а, значит, понимаете, как работает связка html-css-javaScript, поэтому не буду останавливаться на основах, а сразу приступлю к разработке. В html5 и css3, которые поддерживают все современные браузеры (Эксплорер не в счет), есть возможность расположения блоков в 3-мерном пространстве. Также есть элемент , в котором можно рисовать линии и графические примитивы. Большинство браузерных движков используют <сanvas>, так как на нем можно сделать больше вещей, да и производительность на нем выше. Но для простых вещей вполне можно использовать методы transform-3d, которые будут занимать меньше кода.
1. Инструменты для разработки
Я использую для проверки сайтов и игр только 2 браузера: Chrome и Mozilla. Все остальные браузеры (кроме того самого Эксплорера) построены на движке первого, поэтому использовать их я не вижу смысла, ибо результаты точно такие же, как и в Chrome. Для написания кода достаточно Notepad++.
2. Как реализуется трехмерное пространство в html?
Посмотрим на систему координат блока:
По умолчанию, дочерний блок имеет координаты (left и top) 0 пикселей по x и 0 пикселей по y. Смещение (translate), также 0 пикселей по всем трем осям. Покажем это на примере, для чего создадим новую папку. В нем создадим файлы index.html, style.css и script.js. Откроем index.html и запишем туда следующее:
В файле style.css зададим стили для элементов “container” и “world”.
Сохраним. Откроем index.html c помощью Chrome, получим:
Попробуем применить translate3d к элементу “world”:
Как вы поняли, я перешел в полноэкранный режим. Теперь зададим смещение по оси Z:
transform:translate3d(200px,100px,-1000px);
Если вы снова откроете html-файл в браузере, то никаких изменений вы не увидите. Чтобы увидеть изменения, нужно задать перспективу для объекта “container”:
Квадрат отдалился от нас. Как работает перспектива в html? Взглянем на картинку:
d – расстояние от пользователя до объекта, а z – его координата. Отрицательный z (в html это translateZ) означает, что мы отдалили объект, а положительный – наоборот. Значение perspective определяет величину d. Если же свойство perspective не задано, то значение d принимается за бесконечность, а в этом случае объект визуально не изменяется для пользователя с изменением z. В нашем случае мы задали d = 600px. По умолчанию, точка взгляда перспективы находится в центре элемента, однако ее можно изменить путем задания свойства perspective-origin: .
Теперь повернем “world” вокруг какой-нибудь оси. В сss можно использовать 2 способа вращения. Первый – вращение вокруг осей x,y и z. Для этого используются transform-свойства rotateX(), rotateY() и rotateZ(). Второй – вращение вокруг заданной оси с помощью свойства rotate3d(). Мы будем использовать первый способ, так как он больше подходит для наших задач. Обратите внимание, что оси вращения выходят из центра прямоугольника!
Точка, относительно которой происходят трансформации, может быть изменена путем задания свойства translate-origin: . Итак, зададим вращение “world” по оси x:
Заметно смещение против часовой стрелки. Если же мы добавим rotateY(), то получим смещение уже по оси Y. Важно заметить, что при вращении блока оси вращения также поворачиваются. Вы также можете поэкспериментировать с различными значениями вращения.
Теперь внутри блока “world” создадим еще один блок, для этого добавим тег в html-файл:
В style.css добавим стили к этому блоку:
То есть, элементы внутри блока “world” будут трансформироваться в составе этого блока. Попробуем повернуть “square1” по оси y, добавив к нему стиль вращения:
transform: rotateY(30deg);
«Где вращение?» — спросите вы? На самом деле именно так выглядит проекция блока “square1” на плоскость, образуемую элементом “world”. Но нам нужна не проекция, а настоящее вращение. Чтобы все элементы внутри “world” стали объемными, необходимо применить к нему свойство transform-style:preserve-3d. После подстановки свойства внутрь списка стилей “world” проверим изменения:
3. Создаем движение в трехмерном мире
Для того, чтобы пользователь мог по этому миру передвигаться, нужно задать обработчики нажатия клавиш и перемещения мыши. Управление будет стандартным, какое присутствует в большинстве 3д-шутеров. Клавишами W, S, A, D мы будем перемещаться вперед, назад, влево, вправо, пробелом мы будем прыгать (проще говоря – перемещаться вверх), а мышью мы будем менять направление взгляда. Для этого откроем пока еще пустой файл script.js. Сначала впишем туда такие переменные:
Изначально клавиши не нажаты. Если мы нажмем клавишу, то значение определенной переменной изменится на 1. Если отпустим ее, то она снова станет 0. Реализуем это посредством добавления обработчиков нажатия и отжатия клавиш:
Номер 32 – код пробела. Как видите, тут появилась переменная onGround, указывающая на то, находимся ли мы на земле. Пока разрешим движение вверх, добавив после переменных press… переменную onGround:
Итак, мы добавили алгоритм нажатия и отжатия. Теперь необходимо добавить само передвижение. Что, собственно, мы передвигаем. Представим, что у нас есть объект, который мы двинаем. Назовем его “pawn”. Как и принято у нормальных разработчиков, для него мы создадим отдельный класс “Player”. Классы в javaScript создаются, как ни странно, с помощью функций:
Вставим этот код в script.js в самом начале файла. В конце же файла создадим объект данного типа:
Распишем, что означают эти переменные. x, y, z – это начальные координаты игрока, rx, ry – углы его поворота относительно осей x и y в градусах. Последняя записанная строка означает, что мы создаем объект “pawn” типа “player” (специально пишу тип, а не класс, так как классы в javascript означают несколько другие вещи) с нулевыми начальными координатами. Когда мы двигаем объект, координата мира изменяться не должна, а должна изменяться координата «pawn». Это с точки зрения переменных. А с точки зрения пользователя, игрок находится на одном месте, а вот мир двигается. Таким образом, нужно заставить программу изменять координаты игрока, обрабатывать эти изменения и двигать, в конце концов, мир. На деле это проще, чем кажется.
Итак, после загрузки документа в браузер мы запустим функцию, которая перерисовывает мир. Напишем функцию перерисовки:
В новых браузерах world будет соответствовать элементу с однако надежнее ее присвоить перед функцией update() с помощью следующей конструкции:
Мы будем изменять положение мира каждые 10 мс (100 обновлений в секунду), для чего запустим бесконечный цикл:
Запустим игру. Ура, теперь мы можем двигаться! Однако мир вылазит за пределы рамок элемента «container». Чтобы этого не происходило, зададим css-свойство для него в style.css. Добавим строку overflow:hidden; и посмотрим на изменения. Теперь мир остается в пределах контейнера.
Вполне возможно, что вы не всегда понимаете, куда нужно записывать те или иные строчки кода, поэтому сейчас я вам представлю файлы, которые, как я полагаю, у вас должны получиться:
Если у вас что-то по-другому, обязательно поправьте!
Мы научились двигать персонажа, однако мы еще не умеем поворачивать его! Поворот персонажа, конечно же, будет осуществляться с помощью мыши. Для мыши к переменным состояния клавиш press… мы добавим переменные состояния движения мыши:
А после обработчиков нажатия-отжатия вставим обработчик движения:
В функцию update добавим поворот:
Обратите внимание на то, что движение мыши по оси y вращает pawn по оси x и наоборот. Если мы посмотрим на результат, то ужаснемся от увиденного. Дело в том, что если смещения нет, то MouseX и MouseY остаются прежними, а не приравниваются к нулю. Значит, после каждой итерации update смещения миши должно обнуляться:
Уже лучше, мы избавились от инерции вращения, однако вращение происходит все равно странно! Чтобы понять, что все-таки происходит, добавим div-элемент «pawn» внутрь «container»:
Зададим ему стили в style.css:
Проверим результат. Теперь все ровно! Единственное — синий квадрат остается впереди, но пока оставим это. Чтобы сделать игру от первого лица, а не от третьего, нужно приблизить мир к нам на значение perspective. Сделаем это в script.js в функции update():
Теперь можно делать игру от первого лица. Скроем pawn добавив строку в style.css:
Отлично. Сразу скажу, что ориентироваться в мире с одним квадратом крайне тяжело, поэтому создадим площадку. Добавим в «world» блок «square2»:
А в style.css добавим стили для него:
Теперь все четко. Ну… не совсем. Когда мы нажимаем по клавишам, мы движемся строго по осям X и Z. А мы хотим сделать движение по направлению взгляда. Сделаем следующее: в самом начале файла script.js добавим 2 переменные:
Градус — это pi/180 от радиана. Нам придется применить синусы и косинусы, которые считаются от радиан. Что нужно сделать? Взгляните на рисунок:
Когда наш взгляд направлен под углом и мы хотим пойти вперед, то изменятся обе координаты: X и Z. В случае перемещения в сторону тригонометрические функции просто поменяются местами, а перед образовавшимся синусом изменится знак. Изменим уравнения смещений в update():
Внимательно просмотрите все файлы полностью! Если у вас что-то оказалось не так, то потом обязательно буду ошибки, из-за которых вы сломаете голову!
С движением мы почти разобрались. Но осталось неудобство: курсор мыши может двигаться только в пределах экрана. В трехмерных шутерах можно вращать мышью сколь угодно долго и сколь угодно далеко. Сделаем также: при нажатии на экран игры (на “container”) курсор будет пропадать, и мы сможем вращать мышью без ограничений на размер экрана. Активируем захват мыши при нажатии на экран, для чего перед обработчиками нажатия клавиш поставим обработчик нажатия мыши на “container”:
Теперь совсем другое дело. Однако лучше вообще сделать так, чтобы вращение производилось только тогда, когда курсор захвачен. Введем новую переменную после переменных нажатия клавиш press…
Добавим обработчик изменения состояния захвата курсора (захвачен или нет) перед обработчиком захвата курсора (извините за тавтологию):
А в update() добавим условие вращения “pawn”:
А сам захват мыши при клике по контейнеру разрешим только тогда, когда курсор еще не захвачен:
С движением мы полностью разобрались. Перейдем к генерации мира
4. Загрузка карты
Мир в нашем случае удобнее всего представить в виде множества прямоугольников, имеющих разное местоположение, поворот, размеры и цвет. Вместо цвета также можно использовать текстуры. На самом деле, все современные трехмерные миры в играх – это набор треугольников и прямоугольников, которые называют полигонами. В крутых играх их количество может достигать десятков тысяч в одном только кадре. У нас же их будет около сотни, так как браузер сам по себе имеет невысокую графическую производительность. В предыдущих пунктах мы вставляли блоки “div” внутрь “world”. Но если таких блоков много (сотни), то вставлять каждый из них в контейнер очень утомительно. Да и уровней может быть много. Поэтому пусть эти прямоугольники вставляет javaScript, а не мы. Для него же мы будем создавать специальный массив.
Откроем index.html и удалим из блока “world” все внутренние блоки:
Теперь создадим массив прямоугольников (запихнем его, примеру, между конструктором player и переменными press… в script.js):
Можно было это сделать в виде конструктора, но пока обойдемся чисто массивом, так как запуск цикла расстановки прямоугольников проще реализовать именно через массивы, а не через конструкторы. Я же поясню, что означают цифры в нем. Массив map содержит одномерные массивы из 9 переменных: [. ]. Я думаю, вы понимаете, что первые три числа – это координаты центра прямоугольника, вторые три числа – углы поворота в градусах (относительно того же центра), затем два числа – его размеры и последнее число – фон. Причем фон может быть сплошным цветом, градиентом или фотографией. Последнее очень удобно использовать в качестве текстур.
Массив мы записали, теперь запишем функцию, которая переделает этот массив в собственно прямоугольники:
Поясню, что происходит: мы создаем новую переменную, которая указывает на только что созданный элемент. Ему мы присваиваем id и css-класс (именно это и имеется ввиду под словом класс в языке javaScript), задаем ширину с высотой, фон и трансформацию. Примечательно, что в трансформации помимо координат центра прямоугольника мы указываем смещение на 600 и 400 и половины размеров для того, чтобы центр прямоугольника точно оказался в точке с нужными координатами. Запустим генератор мира перед таймером:
Теперь мы видим площадку с розовыми стенами и серым полом. Как видите, создание карты технически несложно реализовать. А в результате ваш код в трех файлах должен получиться примерно таким:
Если все хорошо, переходим к следующему пункту.
5. Столкновения игрока с объектами мира
Мы создали технику движения, генератор мира из массива. Мы можем передвигаться по миру, который может быть красивым. Однако наш игрок еще никак не взаимодействует с ним. Чтобы это взаимодействие происходило, нам необходимо проверять, сталкивается ли игрок с каким-нибудь прямоугольником или нет? То есть, мы будем проверять наличие коллизий. Для начала вставим пустую функцию:
А вызывать ее будем в update():
Как это происходит? Представим себе, что игрок – это шар с радиусом r. И он движется в сторону прямоугольника:
Очевидно, что если расстояние от шара до плоскости прямоугольника больше r, то коллизии точно не происходит. Чтобы узнать это расстояние, можно перевести координаты игрока в систему координат прямоугольника. Напишем функцию перевода из мировой системы в систему прямоугольника:
И обратную функцию:
Вставим эти функции после функции update(). Я не буду объяснять, как это работает, потому что мне не хочется рассказывать курс аналитической геометрии. Скажу, что есть такие формулы перевода координат при вращении и мы просто ими воспользовались. С точки зрения прямоугольника наш игрок расположен вот так:
В этом случае условие коллизии становится таким: если после смещения шара на величину v (v – это вектор) координата z между –r и r, а координаты x и y лежат в пределах прямоугольника или отстоят от него на величину, не большую r, то объявляется коллизия. В этом случае координата игрока по z после смещения будет составлять r или – r (в зависимости от того, с какой стороны придет игрок). В соответствии с этим, смещение игрока изменяется. Мы специально вызываем коллизию перед тем, как в update() координаты игрока будут обновлены, чтобы вовремя изменить смещение. Таким образом, шар никогда не пересечется с прямоугольником, как бывает в других алгоритмах коллизии. Хотя физически игрок будет представлять собой, скорее, случае куб, мы не будем обращать на это внимание. Итак, реализуем это в javaScript:
x0,y0 и z0 – начальные координаты игрока в системе координат прямоугольника (без поворотов. x1,y1 и z1 – координаты игрока после смещения без учета коллизии. point0, point0, point1 и point2 – начальный радиус-вектор, радиус-вектор после смещения без коллизии и радиус-вектор с коллизией соответственно. map[i][3] и другие, если вы помните, это углы поворота прямоугольника. Заметим, что в условии мы к размерам прямоугольника прибавляем не 100, а 98. Это костыль, зачем, подумайте сами. Запустите игру и вы увидите довольно качественные столкновения.
Как видим, все эти действия происходят в цикле for для всех прямоугольников. При их большом количестве такая операция становится очень дорогой, так как тут и так есть 3 вызова функций преобразований координат, которые тоже производят достаточно много математических операций. Очевидно, что если прямоугольники находятся очень далеко от игрока, то коллизию считать не имеет смысла. Добавим это условие:
Итак, с коллизиями мы разобрались. Мы спокойно можем взбираться и по наклонным поверхностям, а возникновение багов возможно только на медленных системах, если, конечно, возможно. По сути, вся основная техническая часть на этом закончилась. Нам осталось лишь добавить частные вещи, такие как гравитация, вещи, меню, звуки, красивую графику. Но это достаточно легко сделать, а к самому движку, который мы только что сделали, это отношения не имеет. Поэтому об этом я расскажу в следующей части. А сейчас проверьте то, что у вас получилось с моим кодом:
Я привык относить себя к тем везучим людям, у которых хобби совпадает с работой — я люблю разработку ПО. Поэтому для меня абсолютно нормально, вернувшись домой, вновь сесть за компьютер, открыть Visual Studio и продолжить что-то разрабатывать — отдых от этой деятельности мне не нужен. Проблема лишь одна — нужен проект, который мне интересен и который я смог бы осилить один в свободное время — по вечерам и в выходные дни.
Примерно год назад мне показали довольно популярную браузерную онлайн-игру — слитерио. После ознакомления у меня появилась навязчивая идея — мне захотелось сделать что-то похожее по подходу, но с чуть более продвинутым геймплеем. Спустя пару месяцев идея сформировалась в тему этой публикации — игру World of Frogs.
Суть игры — вы управляете лягушкой, можете нападать на других игроков, а также на управляемые компьютером объекты — мух, тараканов, болотных лягушек. Мухи не умеют нападать и умирают с одного удара, тараканы нападают лишь обороняясь, болотные же лягушки нападают как на мух, так и на игроков.
Побеждая врагов вы получаете опыт, растёте по уровням, изучаете новые способности и становитесь сильнее.
Основные пункты, от которых я отталкивался:
- С клиентской стороны никакого Flash, только html + js;
- Одна машина должна тянуть как можно больший онлайн игроков;
- Возможность горизонтального масштабирования;
- Низкий порог вхождения в игру и быстрый старт;
- Чуть более разнообразный геймплей, чем в слитерио;
- Красивый и запоминающийся домен;
1) Клиентский код
Мне не хотелось погрязнуть в межбраузерных различиях, а также в реализации примитивов, поэтому я сразу задвинул подальше идею работать напрямую с canvas — я начал с поиска графической библиотеки (разумеется, бесплатной).
Изначально взгляд упал на pixi.js — это движок, по которому немало документации, о котором положительно отзываются в плане производительности и вообще всячески хвалят.
Однако углубившись в поиски, я остановился на phaser.js (о нём уже были статьи на хабре) — это более высокоуровневая библиотека, которая позволила мне забыть о многих нюансах и сосредоточиться непосредственно на игровой логике.
Движок позволил без особых проблем прикрутить анимации, бэкграунд текстуру, камеру, границы мира и многое другое. И всё бы хорошо, но когда настало время проверять работу на других компьютерах, с другими операционными системами, выявились следующие проблемы:
1.1 Главная из проблем — фоновая текстура (tilesprite) жутко тормозит на windows 7
Выяснил я это с рабочего компьютера после первого деплоя на хостинг — ФПС был очень и очень низким — в районе 5. И так было во всех браузерах кроме, на удивление, IE — в нём всё работало вполне прилично, пускай и не идеально.
До того, что тормозит бэкграунд я додумался далеко не сразу — первым делом, я, методом тыка выяснил, что игра резко перестаёт тормозить при уменьшении размера окна браузера. Нагуглить по что-то по таким симптомам мне не удалось, поэтому я, профилактики ради, решил внедрить часть практик, которые советуют ребята из Mozilla — в частности, использование Object Pool (переиспользование игровых объектов). Особых успехов такого рода оптимизациями я не достиг, а профилировщик по-прежнему показывал что больше всего ресурсов съедает рендеринг.
Тогда, прибегнув к постепенному отключению отображения различных элементов игры я и выявил виновника — tilesprite.
Погуглив по tilesprite я выяснил, что такая проблема не у меня одного, и причина кроется в том, что canvas перерисовывается полностью при любом изменении — т.е. маленький объект сдвинулся — перерисоываем весь канвас, включая фон, что даёт нам высокий расход на отрисовку.
В попытке решить эту проблему я вынес фон на отдельный канвас с меньшим z-index, чтобы он перерисовывался отдельно, независимо от движущихся объектов — особых результатов это не дало.
В конечном итоге я решил отказаться от phaser.js и работать напрямую с canvas, созданным для отрисовки фона — в результате ФПС вырос примерно до 20.
1.2 Разные версии phaser — разная производительность в разных операционках
После изменения принципа отрисовки фона с производительностью всё стало намного лучше, но 20 ФПС — это всё ещё не желаемые 60 — было над чем поработать. Путём тыканья пальцем в небо было выяснено, что phaser версии 2.4.6 работает быстрее на windows 7, а версии 2.6.2 быстрее на windows 10. На линухе и маке обе версии показали себя одинаково хорошо.
Пришлось добавить условие, которое подключало ту или иную версию библиотеки в зависимости от браузера пользователя — это повысило ФПС на моей рабочей машине до 25-30. Выше поднять ФПС у меня так и не получилось — на этом я решил остановиться, т.к. после опроса друзей/знакомых, у которых стоит семёрка, сложилось впечатление, что проблема редкая, да и уже не такая серьёзная как изначально.
Описанное в этих двух пунктах — это не единственные, но основные и наиболее запомнившиеся проблемы, связанные с phaser.js — всё остальное прошло в общем-то гладко.
Также стоит отметить, что на разных машинах с windows 7 производительность была разной — кое-где и без всех моих телодвижений всё было хорошо, где-то же наблюдались проблемы аналогичные тем, что я наблюдал — какой-либо корреляции я установить не смог
2) Производительность одного инстанса игрового сервера
Здесь начать стоит с того, какая в целом архитектура у приложения, которое служит как игровой сервер. Было решено использовать следующую схему:
Если отобразить на схеме, то верхнеуровнево серверная архитектура выглядит так:
Т.к. с бэкендом у меня опыта прилично больше, здесь каких-то особо запомнившихся трудностей не было — неоптимальные места я отлавливал профилировщиком — где-то применял микрооптимизации вычислений, где-то кэширование, где-то оптимизации иного рода.
Самым серьёзным бустом к производительности был отказ от использования SignalR, т.к. он не поддерживает бинарный протокол, а на сериализацию в json уходило вычислительных ресурсов больше, чем на всю остальную логику игрового сервера вместе взятую. Остановился в итоге на использовании Fleck, т.к. он поддерживает бинарный формат, а также позволяет отключить алгоритм Нэйгла.
3) Возможность горизонтального масштабирования
Будучи оптимистом я решил заранее заложиться на то, что игра всем понравится и в неё захочет играть множество людей. В рамках одной машины можно долго заниматься оптимизациями, можно бесконечно апгрейдить железо, можно переписать приложение на чистом си с ассемблерными вставками для микрооптимизаций, но всё равно рано или поздно упрёшься в потолок. Было решено иметь архитектуру, позволяющую иметь множество серверов малой мощности, на каждом из которых потолок по онлайну в районе 200-300 человек.
Чтобы не было бутылочного горлышка в виде сети до какого-то одного глобального прокси, а также чтобы обеспечить минимальный пинг, было решено на стороне сайта выбирать из пула серверов один конкретный, с минимальным онлайном, закреплять его за сессией пользователя и в дальнейшем обеспечивать взаимодействие браузера пользователя напрямую с игровым сервером.
На текущий момент в выборе сервера из пула используются простая логика — берётся сервер с минимальным онлайном. В дальнейшем планируется также добавить логику учёта местоположения клиента и сервера.
4) Низкий порог вхождения в игру и быстрый старт
Мне не раз приходилось видеть игры, которые просто обязаны иметь наиужаснейшую конверсию из-за перегруженности интерфейса или же из-за необходимости вводить миллион полей для того, чтобы зайти в игру.
Я хотел обеспечить вход по одному клику мыши с главной страницы, т.е. никакой обязательной регистрации. В том же слитерио используется похожий подход, за одной небольшой разницей — от игрока всё же хотят, чтобы он ввёл ник.
Т.к. онлайн игра как правило предполагает возможность отличать одного игрока от другого, я решил использовать подход никогенерации — при входе в игру берётся случайное прилагательное из заранее заданного списка и комбинируется с случайным существительным, что выдаёт ники вида Неспящий Бугай, Жадный Бурундук, Могучий Валенок и т.п…
В дальнейшем, если игроку понравилась игра, ему предоставляется возможность зарегистрироваться, сохранив за собой прогресс, а также сменив ник на что-то более вменяемое.
Для более быстрого погружения на главную страницу было добавлено обучающее видео, а в саму игру были добавлены тултипы, выплывающие при изучении новых способностей.
5) Чуть более разнообразный геймплей, чем в слитерио
Как бывший поклонник игры WoW, я хотел разнообразить игру, внеся в неё такие элементы как набор опыта, рост по уровням, получение новых способностей по мере роста, PvE, PvP.
Игроку доступно к использованию 6 способностей (1-я доступна сразу, 2-4 становятся доступны по мере роста по уровням, а 5-6 оформлены как одноразовые поверапы — их можно поднять на игровом поле):
- Удар языком — лягушка выстреливает языком и наносит малый урон первой цели на пути;
- Прыжок — лягушка прыгает в указанном направлении и в месте приземления наносит высокий урон. Высокий урон способности компенсируется сложностью попадания, а также длительной задержкой между использованиями;
- Щит — в течение 3-х секунд поглощает 2 следующие вражеские атаки;
- Плевок — лягушка выплёвывает снаряд, который наносит средние повреждения всем врагам на траектории движения);
- Лечение — восстанавливает половину жизни;
- Ускорение — увеличивает скорость передвижения на 100% на 4 секунды;
Для возможности немного выделиться была добавлена возможность выбрать другую модель игрового персонажа.
К браузерным играм не привыкли относиться всерьез, но на самом деле – это неплохой бизнес для небольшой команды друзей, у которых нет денег, но есть светлые мысли. А если еще и деньги есть, то тогда все значительно упрощается.
На самом деле создать браузерную онлайн игру может любой, у кого есть желание учиться, но не думайте, что вас ждет легкая прогулка. Даже если вы мастер на все руки (программист и дизайнер в одном флаконе), то придется потратить уйму времени. Если же вы не владеете ни кодом, ни карандашом, то затраты будут финансовыми.
Как создать браузерную игру ничего не изучая?
Ответ – никак. Для начала нужно будет выучить HTML – язык разметки, который работает в связке с каскадной таблицей стилей – CSS. Эти языки располагают текст и изображения на странице, собирая картинку из множества фрагментов. Поскольку вы намерены сделать именно браузерную игру, то ей обязательно понадобится сайт, желательно красивый и функциональный. Поэтому не обойтись без JavaScript – самого простого способа визуализировать вашу игру на стороне геймера. Этот язык позволяет менять местами элементы страницы без ее перезагрузки. По сути, JavaScript управляет элементами CSS и HTML в реальном времени.
Для создания браузерной игры придется выучить HTML, CSS, PHP и JavaScript, так что бессонные ночи вам гарантированы
Язык программирования PHP нужен для создания движка игры. Он используется на сервере для обработки данных, поступающих от разных геймеров. Это – ядро, которое приводит в действие все внутренние механизмы.
Я не хочу ничего учить, но у меня есть деньги
Поздравляем, вы избежали многих проблем. И тут же получили новые. Чтобы сделать браузерную игру хорошего качества (в плохую никто играть не будет) нужно иметь хорошую команду разработчиков, а хорошие спецы стоят денег. Если брать по минимуму, то вам необходимы:
- Программист – 2 штуки
- Дизайнер и художник – 2 штуки
- Гейм-дизайнер – 1 штука
- Комьюнити-менеджер – 1 штука
- Гейм-мастер – 1 штука.
Всех необходимых работников можно найти на биржах фрилансеров
Правда, в этом случае нужно быть очень осторожным, поскольку фрилансеры привыкли регулярно срывать сроки. Как говорят опытные руководители, можно бесконечно смотреть на 3 вещи: как течет память, как горит дедлайн и как не работает фрилансер.
Во сколько обойдется разработка?
Во столько, сколько у вас есть денег. Вот здесь приведены примерные выкладки и структура расходов на создание среднестатистической браузерки. Итоговая сумма – 25 миллионов рублей. Однако не стоит пугаться этой цифры. В статье автор посчитал расходы на разработку по максимуму, так что при желании эту сумму модно уменьшить в разы и даже десятки раз. Или увеличить, если ваш папа – прокурор. Безусловно, такие проекты, как Drakensang Online или City of Steam требуют сумм как минимум с шестью нулями, но простую табличную браузерку можно сделать за пару десятков тысяч рублей.
В конце концов, вы всегда можете обратиться к краудфандингу. Пути Кикстартера неисповедимы, взлететь там может любой проект. Главное правильно подать блюдо.
Этапы разработки
Создание браузерной игры с нуля можно разделить на несколько этапов:
1. Поиск идеи
Поскольку у вас вряд ли есть 25 миллионов рублей и вы вряд ли являетесь гением в программировании и дизайне, единственное, чем вы можете зацепить пользователя, это необычный сеттинг. Даже пресловутая кампания из эльфов, орков, людей и гномов способна увлечь клиента, если погрузить их в необычную среду или заставить делать странные вещи. Например, игра Ботва Онлайн была написана «по приколу» четырьмя людьми в свободное от работы время, но благодаря своему необычному сеттингу быстро стала успешным бизнес проектом.
2. Разработка сюжета
Сюжет очень важен (если конечно, вы не делаете стратегию). Если у самого воображения не хватает, наймите кого-нибудь, у кого оно есть. В крайнем случае, обратитесь к аутсорсингу и найдите толкового копирайтера, у которого уже есть опыт создания квестов.
3. Создание игры
Даже если вы выучили вдоль и поперек JavaScript и CSS, вы все еще ничего не знаете о том, как создать браузерную игру. Это примерно то же самое, как если бы вы выучили теорию плавания, ни разу не побывав в бассейне. Лучший выход в этом случае – воспользоваться самоучителем. Вот здесь можно многое узнать о разработке игр, причем не только браузерных.
Скорее всего, ваша первая браузерка будет выглядеть так. Это – легендарный Бойцовский Клуб
4. Продвижение проекта
Последний этап, на котором дело в свои руки берут комьюнити-менеджер и специалист по маркетингу (если таковой имеется). В крупных компаниях на долю рекламы уходит примерно 60% бюджета, поэтому на эту статью денег лучше не жалеть. Если же ваши финансы поют романсы, то тогда не остается ничего другого, как самому перепахивать игровые форумы и завлекать геймеров всеми возможными способами.
Как создать браузерную игру с помощью конструктора
Оказывается, ушлые программисты уже давно создали кучу конструкторов MMO, которые могут помочь новичку создать браузерную онлайн игру бесплатно. Это не значит, что вы можете успокоиться и удалить все закладки по программированию, но с помощью конструктора можно здорово ускорить процесс.
Популярный игровой конструктор Construct 2
Большой популярностью пользуется MMO Constructor – отечественный продукт, в котором можно создать все элементы полноценной браузерной RPG. Взамен авторы требуют совсем ничего – 50% от прибыли проекта. Разобраться в конструкторе непросто, но добрые люди уже написали гайды. Также чтобы создать браузерную игру, можно воспользоваться такими программами, как Construct Classic, Eclipse, FPS Creator.
Больше информации о конструкторах можно узнать на форуме Gcup, где тусуются все: и продвинутые геймдевы, и зеленые новички.
Так что там насчет миллиона?
Любая, даже мобильная игра должна приносить прибыль. Иначе нет никакого смысла тратить свое и чужое время. Дабы сделать свой проект финансово успешным, нужно слушать, что говорят опытные разработчики и делать то же самое:
Напоследок хочется сказать – не так страшен черт, как его малюют. Даже лучшие геймдевы когда-то начинали с нуля, и кто знает, может быть именно вы станете следующим Джоном Кармаком или Ричардом Гэрриотом?
Читайте также: