Progress28.ru

IT Новости
2 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Написание игры на javascript

Пишем игру на JavaScript, наконец-то до конца! (ну или типа того. )

Перед тем, как перейти к делу, небольшое вступление. Этот пост я решил написать по трём причинам:

  • Я хочу поделиться с читателями и сам структурировать данные по полученному результату в своей голове
  • Я так и не нашёл ни одной статьи про геймдев на JavaScript, которая была бы дописана до конца
  • Я таки корыстно хочу получить инвайт 🙂

Я надеюсь, что люди, которые решат ознакомиться с этим материалом, имеют базовое представление о работе c html, javascript и элементом canvas… Эй… Эй! Ну куда же вы!? Стойте, стойте! Не пугайтесь, я всё равно всё разжую, просто уберу это под спойлеры.

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

«… — Это пока прототип. — Прототип? — Да, прототип… »

Нет, правда, чем больше ссылок и комментариев по коду, тем радостнее я буду. Серьёзно изучать JS я начал с этого проекта, по этому он сырой и не оптимизированный. Конечно, пока это не совсем игра, а скорее технчиеское демо, но это уже больше информации, чем я получил из статей по разработке игр на Хабре (да и, честно говоря, вообще в интернете). Всё гуглилось по ходу дела, все алгоритмы движения я сочинял сам, попутно вспоминая школьный курс тригонометрии. После такого долгого оправдания, для самых нетерпеливых даю ссылку на тех. демо (да, пожалуй остановлюсь на этом термине)

И ещё раз перед самым хабракатом: я не профи и не гуру, я только учусь, если Вам кажется, что я где-то неправ, пожалуйста, не сливайте меня без объяснения причин, мне будет очень интересно узнать что именно не так. А так же Ваш гневный комментарий сбережёт других читателей от ошибки пользования моим мануалом не вникая в суть 🙂 Ну, поехали!

Для начала самое простое. Конец.

Итак, мы будем делать не самый изощрённый симулятор железной дороги. В 2D, вид сверху, графику будем собирать из блоков 64 х 64px.

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

Или нет? На собственном опыте я очень хорошо понял, что сразу бросаться в бой не стоит, хорошенько продумайте структуру будущей игры и размещение файлов. Думайте над тем, как называете функции и переменные. Думайте, какие именно аттрибуты в ваши функции можно и нужно передавать. Не начинайте писать ваши замечательные функции как придётся, с мыслью «поправлю потом». У меня таких «поправлю» до сих пор в достатке, о каждой я обязательно расскажу.

… сначала мы, конечно же, создадим html-документ примерно следующего вида:

Тут всё очень просто. Тэг html без доп. пояснений в новых стандартах сообщает браузеру, что нужно двигаться в светлое будущее с HTML5. Тэг canvas будет нашим будущим полотном для работы, а параметр id поможет обратиться к нему с помощью javascript. Так же тут есть много src тегов, какие-то странно обозванные div-ы, и даже один input. На что кто и где ссылается и почему именно в таком порядке, я объясню чуть позже

Ну так вот, давайте-ка для начала подумаем, какая перед нами сейчас стоит задача:

  • Загрузить наши картинки-блоки
  • Написать скрипт для отрисовки основных элементов (на данном этапе только игрового поля)
  • Отрисовать само поле

Итак, уже видно, что можно с чистой совестью создавать три отдельных .js файла. В моём случае это были image_load.js, trains_main.js, trains_playgr.js

Перед подробным разбором кода, расскажу, как имено будем рисовать игровое поле. Для 2д-вид-сверху-игр оптимальным мне кажется следующий вариант: представляем игровое поле в виде квадратной матрицы A[m,n], где каждая клетка A[i,j] это поле определённого типа со своими свойствами. Далее пишем (или генерируем, есть у меня желание такое реализовать, но не добрался пока) нужную нам матрицу и проходимся по ней циклом, рисуя в нужных местах нужные картинки. Вот так, легко и просто. Более подробно для симулятора поезда значения для матрицы у меня следующие:

ЗначениеЧто это?
пустая клетка (трава)
1прямой участок Запад-Восток
2прямой участок Север-Юг
3поворот Юг-Восток
4поворот Юг-Запад
5поворот Север-Запад
6поворот Север-Восток

image_load.js

Сам код тут прост до безобразия, я даже не уверен, что он требует каких-то комментариев:

Создаём объект изображение, присваиваем его аттрибуту src (источник) ссылку на нужную картинку. У меня они лежали в папке src/img, как видно из кода.

Как же так? В таблице аж семь значений, а тут всего три картинки! Но если подумать, больше нам и не нужно, все остальные мы можем получить, повернув исходные railsTurnUL (изображение поворота) и railsStraight изображение прямого участка рельс. Об этом чуть дальше.

Код ерунда, что важно, так это размещение тэга script со ссылкой на этот код в html документе. Вместо использования onload-ов для этих объектов вместе с функциями для подсчёта все ли картинки загрузились, я просто решил разместить тэг script в шапке html документа, а весь остальной код, как обычно, под тегом canvas.

(Вот тут я, кстати, не уверен, что действую правильно, но подробного разбора подобных ситуаций найти не удалось, да и данный способ ни разу не давал сбоев, возможно в следующих версиях я таки добавлю счётчик объектов)
trains_main.js

Вот тут уже становится чуть интереснее:

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

Читать еще:  Что делать если не запускается восстановление системы

На самом деле эта функция делает буквально следующее:

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

trains_playgr.js

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

С полем покончено

Вот и всё! Вызов функции drawField(playgr_1); нарисует нам поле с назначенными элементами.

А теперь немного об этих «потом поменяю» элементах, о которых я говорил чуть выше. Так вот, проблемы есть прямо в функции drawField, менял я её прямо по ходу написания этой статьи, а дело в том, что в неё нельзя было передать массив с полем, он в тупую задавался прямо в функции. Думаю не стоит объяснять, почему это плохо, ну или как минимум неудобно.

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

Прибытие поезда на вокзал Ла-Сьота́

Иии… нет. Прости, Хабр, я честно хотел написать одну большую статью, но это слишком сложно. Писать обучающие статьи оказалось много труднее, чем я думал сначала (на эту я потратил где-то пять часов). Поэтому я предлагаю договор: если эта статья «пойдет», если вам понравится, я обещаю, учитывая всю критику, написать вторую часть как можно быстрее, а в ней будет самое интересное — тонкости просчёта движения и вывода картинки на экран, разжёвывание моего нелепого алгоритма движения поезда, ну и конечно прохладные истории про муки моего вечно спящего мозга при сочинении механики движения поезда по рельсам.

Спасибо Никите Москвину за графику для игры 🙂

Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.

Пишем игру на HTML5/JS

На выходных нашлось немного свободного времени и я решил попрактиковаться в gamedev разработке. Давно собирался написать какую-нибудь игрушку, но все руки не доходили. Бегло пробежался по сети в поисках как это делают настоящие гуру. Мне понравилась вот эта статья. За основу своей будущей игры я взял фреймворк автора статьи.

Начало

  • sprite.js — библиотечка работы со спрайтами
  • resources.js — подгрузка ресурсов
  • input.js — библиотека ввода с клавиатуры
  • app.js — основной файл игры

Далее буду рассказывать только о файле app.js . Разберем его содержимое.

Для плавности анимации будем использовать requestAnimationFrame . Подробно о нем ознакомиться можно здесь

Разделим разработку нашей игры на несколько этапов:

  1. Создание и инициализация холста (canvas) на странице
  2. Добавление основной функции-цикла игры
  3. Инициализация и рендер объектов и ресурсов игры
  4. Обработка событий ввода пользователя
  5. Математика и расчет столкновений объектов в игре
  6. Окончание и перезагрузка игры

Этап 1. Создание и инициализация холста

Первым делом что мы должны сделать — это создать canvas элемент и добавить его к тегу body основной страницы игры.

  • Создаем объект canvas
  • Указываем, что мы создаем 2D игру (далее будем использовать везде в коде объект ctx )
  • Задаем размеры холста
  • Добавляем холст к тегу body на странице

Этап 2. Добавление основной функции-цикла

Основной цикл необходим для обновления и рендера игры.

Здесь вызываем функцию requestAnimFrame (к сожалению, поддерживается не во всех браузерах), которая генерирует 60 фреймов/секунду (как это было описано выше).

Этап 3. Инициализация и рендер объектов и ресурсов игры

Используем resource.js для загрузки ресурсов в игру. Хорошим правилом является добавить все изображения в 1 спрайт, но т.к я рисовал не сам, а брал готовые картинки, поэтому я решил с этим на заморачиваться, тем более, что в данном случае это не столь критично. Так это выглядит в коде

В функции init загружаем мир и добавлеем хэндлер кнопки reset , после game over.

Начальное состояние

Обновление состояния игрового процесса

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

Здесь же используем sprite.js . Всю функцию можно посмотреть в исходниках.

Этап 4. Обработка событий ввода пользователя

Наш герой должен уметь двигаться вверх, вниз, влево, вправо. Соответственно привожу ниже реализацию данного решения

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

Этап 5. Математика и расчет столкновений объектов в игре

Анимация персонажей, математика движения пуль, и логика движения NPC в игре описаны в функции updateEntities . Вот тут как раз нам и потребуются базовые знания линейной алгебры.

Логика обновления анимации спрайтов башни. И создаем патроны для каждой башни в своем массиве.

Динамика пуль башни:

Напомню, что нашей целью было чтобы башни стреляли случайным образом во всех направлениях.

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

Полный код функции updateEntities можно посмотреть в исходникак на GitHub.

Математика расчета столкновений хорошо описана в статье автора (раздел Collision Detection) используемого мной 2d бутстрапа.

Этап 6. Game Over и рестарт

Когда пауки доползают до нашего героя наступает конец света игры.

Читать еще:  Видеокарта перестала отвечать и была успешно восстановлена

Показываем окно GAME OVER и кнопку «Начать заного». Кликаем ее и все начинается сначала 🙂

Заключение

В итоге, я для себя понял, что в gamedev много плюсов:

  • Весело и интересно проводишь время
  • Повторяешь курс школьной геометрии. Если игра серьезней, то и универ вспоминаешь 🙂
  • Практика программирования игр
  • Удовлетворение от проделанной работы

Посмотреть исходники можно тут, поиграть здесь.

Создаём простую игру на Vanilla JS

  • Переводы, 4 октября 2018 в 11:52
  • Никита Прияцелюк

В этой статье мы создадим простую игру с помощью HTML5, CSS3 и чистого JavaScript. Вам не понадобятся глубокие знания программирования. Если вы знаете, для чего нужны HTML, CSS и JS, то этого более чем достаточно. На работу игры вы можете посмотреть здесь.

Структура файлов

Начнём с создания нужных папок и файлов:

Начальный шаблон, соединяющий CSS- и JS-файлы:

В игре будет 12 карточек. Каждая карта состоит из контейнера div с классом .memory-card , внутри которого находится два элемента img . Первая отвечает за лицо ( front-face ) карточки, а вторая — за рубашку ( back-face ).

Необходимые изображения можно скачать из репозитория проекта.

Обернём набор карточек в контейнер section . В итоге получаем:

Мы используем простой, но очень полезный сброс стилей, который будет применён ко всем элементам:

Свойство box-sizing: border-box учитывает значения внутренних отступов и границ элемента при подсчёте общей высоты и ширины, поэтому нам не нужно заниматься математикой.

Если применить к body свойство display: flex и margin: auto к контейнеру .memory-game , то он будет выровнен вертикально и горизонтально.

.memory-game также будет flex-контейнером. По умолчанию ширина элементов уменьшается, чтобы они помещались в контейнер. Если присвоить свойству flex-wrap значение wrap , элементы будут располагаться на нескольких строках в соответствии с их размерами:

Ширина и высота каждой карточки подсчитывается с помощью CSS-функции calc() . Создадим три ряда по четыре карточки, установив значения ширины и высоты равными 25% и 33.333% соответственно минус 10px от внешнего отступа.

LATOKEN, Москва, от 4000 до 5000 $

Чтобы разместить наследников .memory-card , добавим position: relative . Так мы сможем абсолютно расположить наследников относительно родительского элемента.

Свойство position: absolute , установленное для .front-face и .back-face , уберёт элементы с их исходных позиций и разместит поверх друг друга:

Поле из карточек должно выглядеть примерно так:

Добавим ещё эффект при клике. Псевдокласс :active будет срабатывать при каждом нажатии на элемент. Он устанавливает длительность анимации равной 0.2 с:

Переворачиваем карточки

Чтобы перевернуть карточку после нажатия, добавим класс flip . Для этого давайте выберем все элементы memory-card с помощью document.querySelectorAll() . Затем пройдёмся по ним в forEach -цикле и добавим обработчики событий. При каждом нажатии на карточку будет вызываться функция flipCard() . this отвечает за нажатую карточку. Функция получает доступ к списку классов элемента и активирует класс flip :

CSS класс flip переворачивает карточку на 180 градусов:

Чтобы создать 3D-эффект переворота, добавим свойство perspective в .memory-game . Это свойство отвечает за расстояние между объектом и пользователем в z-плоскости. Чем ниже значение, тем сильнее эффект. Установим значение 1000px для едва уловимого эффекта:

Добавим к элементам .memory-card свойство transform-style: preserve-3d , чтобы поместить их в 3D-пространство, созданное в родителе, вместо того, чтобы ограничивать их плоскостью z = 0 (transform-style):

Теперь мы можем применить transition к свойству transform , чтобы создать эффект движения:

Отлично, теперь карточки переворачиваются в 3D! Но почему мы не видим лицо карточки? На данный момент .front-face и .back-face наложены друг на друга из-за абсолютного позиционирования. Рубашкой каждого элемента является зеркальное отражение его лица. По умолчанию значение свойства backface-visibility равно visible , поэтому вот что мы видим при перевороте карточки:

Чтобы исправить это, применим свойство backface-visibility: hidden для .front-face и .back-face :

Если перезагрузить страницу и снова перевернуть карточку, она пропадёт!

Так как мы скрыли заднюю сторону обеих картинок, на обратной стороне ничего нет. Поэтому сейчас нам нужно перевернуть .front-face на 180 градусов:

Наконец, мы получили желаемый эффект переворота!

Ищем пару

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

После нажатия на первую карточку она ожидает переворота другой. Переменные hasFlippedCard и flippedCard будут отвечать за состояние переворота. Если ни одна карточка не перевёрнута, значение hasFlippedCard устанавливается равным true , а нажатой карточке присваивается flippedCard . Ещё давайте сменим метод toggle() на add() :

Теперь при нажатии на вторую карточку мы попадаем в else-блок нашего условия. Чтобы проверить, совпадают ли карточки, нужно их всех идентифицировать.

Всякий раз, когда нам нужно добавить дополнительную информацию к HTML-элементам, мы можем использовать data-* атрибуты, где вместо «*» может быть любое слово. Добавим каждой карточке атрибут data-framework :

Теперь мы можем проверить, совпадают ли карточки, с помощью свойства dataset . Поместим логику сравнения в метод checkForMatch() и снова присвоим переменной hasFlippedCard значение false . В случае совпадения будет вызван метод disableCards() и обработчики событий будут откреплены от обеих карточек, чтобы предотвратить их переворот. В противном случае метод unflipCards() перевернёт обе карточки с помощью 1500 мс тайм-аута, который удалит класс .flip :

Складываем всё воедино:

Более элегантный способ написать условие совпадения — тернарный оператор. Он состоит из трёх частей. Первая часть — это условие, вторая часть выполняется, если условие возвращает true , в противном случае выполняется третья часть:

Блокируем поле

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

Объявим переменную lockBoard . Когда игрок нажмёт на вторую карточку, lockBoard будет присвоено значение true , а условие if (lockBoard) return; предотвратит переворот других карточек до того, как эти две будут спрятаны или совпадут:

Нажатие на ту же карточку

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

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

Переменные firstCard и secondCard нужно обнулять после каждого раунда. Реализуем эту логику в новом методе resetBoard() . Поместим в него hasFlippedCard = false и lockBoard = false . Деструктурирующее присваивание [var1, var2] = [‘value1’, ‘value2’] из ES6 позволяет писать код меньших размеров:

Новый метод будет вызываться из disableCards() и unflipCards() :

Перемешивание

Наша игра выглядит довольно неплохо, но играть в неё не очень весело, если карточки всегда на одном месте. Пора это исправить.

Когда у контейнера есть свойство display: flex , его элементы упорядочиваются сначала по номеру группы, а потом по порядку в исходном коде. Каждая группа определяется свойством order , которое содержит положительное или отрицательное целое число. По умолчанию свойство order каждого flex-элемента имеет значение 0 . Если групп больше одной, элементы сначала упорядочиваются по возрастанию порядка группы.

В игре есть 12 карточек, поэтому мы пройдёмся по ним в цикле, сгенерируем случайное число от 0 до 12 и присвоим его свойству order :

Чтобы вызвать функцию shuffle() , сделаем её IIFE (Immediately Invoked Function Expression). Это значит, что она будет выполнена сразу после объявления. Скрипт должен иметь примерно такой вид:

Создаем многопользовательскую веб-игру Javascript

Создаем многопользовательскую веб-игру Javascript

Вышедшая в 2015 году Agar.io стала прародителем нового жанра игр .io, популярность которого с тех пор сильно возросла. Рост популярности игр .io я испытал на себе: за последние три года я создал и продал две игры этого жанра..

На случай, если вы никогда раньше не слышали о таких играх: это бесплатные многопользовательские веб-игры, в которых легко участвовать (не требуется учётная запись). Обычно они сталкивают на одной арене множество противоборствующих игроков. Другие знаменитые игры жанра .io: Slither.io и Diep.io.

В этом посте мы будем разбираться, как с нуля создать игру .io. Для этого достаточно будет только знания Javascript: вам нужно понимать такие вещи, как синтаксис ES6, ключевое слово this и Promises. Даже если вы знаете Javascript не в совершенстве, то всё равно сможете разобраться в большей части поста.

Пример игры .io

Для помощи в обучении мы будем ссылаться на пример игры .io. Попробуйте в сыграть в неё!

Игра довольно проста: вы управляете кораблём на арене, где есть другие игроки. Ваш корабль автоматически стреляет снарядами и вы пытаетесь попасть в других игроков, в то же время избегая их снарядов.

1. Краткий обзор/структура проекта

В примере используется следующее:

  • Express — самый популярный веб-фреймворк для Node.js, управляющий веб-сервером игры.
  • socket.io — библиотека websocket для обмена данными между браузером и сервером.
  • Webpack — менеджер модулей. О том, зачем использовать Webpack, можно прочитать здесь.

Вот как выглядит структура каталога проекта:

public/

Всё в папке public/ будет статически передаваться сервером. В public/assets/ содержатся используемые нашим проектом изображения.

Весь исходный код находится в папке src/ . Названия client/ и server/ говорят сами за себя, а shared/ содержит файл констант, импортируемый и клиентом, и сервером.

2. Сборки/параметры проекта

Как сказано выше, для сборки проекта мы используем менеджер модулей Webpack. Давайте взглянем на нашу конфигурацию Webpack:

webpack.common.js:

Самыми важными здесь являются следующие строки:

  • src/client/index.js — это входная точка клиента Javascript (JS). Webpack будет начинать отсюда и станет рекурсивно искать другие импортированные файлы.
  • Выходной JS нашей сборки Webpack будет располагаться в каталоге dist/ . Я буду называть этот файл нашим пакетом JS.
  • Мы используем Babel, и в частности конфигурацию @babel/preset-env для транспиляции (transpiling) нашего кода JS для старых браузеров.
  • Мы используем плагин для извлечения всех CSS, на которые ссылаются файлы JS, и для объединения их в одном месте. Я буду называть его нашим пакетом CSS.

Вы могли заметить странные имена файлов пакетов ‘[name].[contenthash].ext’ . В них содержатся подстановки имён файлов Webpack: [name] будет заменён на имя входной точки (в нашем случае это game ), а [contenthash] будет заменён на хеш содержимого файла. Мы делаем это, чтобы оптимизировать проект для хеширования — можно приказать браузерам бесконечно кешировать наши пакеты JS, потому что если пакет изменяется, то меняется и его имя файла (изменяется contenthash ). Готовым результатом будет имя файла вида game.dbeee76e91a97d0c7207.js .

Файл webpack.common.js — это базовый файл конфигурации, который мы импортируем в конфигурации разработки и готового проекта. Вот, например, конфигурация разработки:

webpack.dev.js

Для эффективности мы используем в процессе разработки webpack.dev.js , и переключается на webpack.prod.js , чтобы оптимизировать размеры пакетов при развёртывании в продакшен.

Локальная настройка

Рекомендую устанавливать проект на локальной машине, чтобы вы могли следовать за этапами, перечисленными в этом посте. Настройка проста: во-первых, в системе должны быть установлены Node и NPM. Далее нужно выполнить

и вы готовы к работе! Для запуска сервера разработки достаточно выполнить

и зайти в веб-браузере на localhost:3000. Сервер разработки будет автоматически пересобирать заново пакеты JS и CSS в процессе изменения кода — просто обновите страницу, чтобы увидеть все изменения!

3. Входные точки клиента

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

index.html

Этот пример кода слегка упрощён для понятности, то же самое я сделаю и со многими другими примерами поста. Полный код всегда можно посмотреть на Github.

  • Элемент HTML5 Canvas ( ), который мы будем использовать для рендеринга игры.
  • для добавления нашего пакета CSS.
Ссылка на основную публикацию
Adblock
detector
×
×