Стартуем с Crafty.js

На дворе XXI век. 95% компьютерных пользователей активно пользуются интернетом и играют в браузерные игры. А это значит, что стоит всерьёз начать делать HTML5 игры.

История Crafty

Сегодняшний интернет держится на HTML5, а Crafty.js - это HTML5 открытый игровой движок, написанный на чистом JavaScript, предназначенный для создания 2D игр. Используя этот движок вы получите игру, работающую во всех основных браузеров, в том числе, на мобильных устойствах. Движок легко интегрируется с другими библиотеками: jQuery, Greensock. Одним из плюсов Crafty является то, что он может использовать Canvas и DOM для рендеринга графики, в том числе использовать их вместе. Это действительно очень круто; комбинируя такие механизмы отображения можно использовать движок для создания насыщенных веб-страниц даже для относительно старых браузеров.

Устанавливаем движок

Установить движок можно несколькими способами.

1. Скачать скрипты движка из сайта http://craftyjs.com/.

2. Установить пакет через командную строку через NPM:
npm install craftyjs

3. Установить через Bower:
bower install crafty

Стартуем

Создаём страницу index.html и подключаем в тег head файл скрипта, в тег body добавляем контейнер для игры:
<div id="canvasRacer"></div>

Перед закрытием тега body создаём новый скрипт:
// инициализируем движок, передавая ему в аргументах ширину, высоту и id элемента игрового контейнера
Crafty.init(360, 640, 'canvasRacer')
// инициализируем canvas
Crafty.canvas.init()

Коллизии

Crafty поддерживает продвинутые системы распознавания ударов одного объекта с другим. За это взаимодействие отвечает компонент Collision. Для регистрации ударов необходимо явно определить полигоны. Если полигонов много я рекомендую использовать сервис:
http://os-class.ru/mapcreator/index.htm

Чтобы включить возможность дебага текущих коллизий добавьте компонент WiredHitBox и подсветите его, например, белым цветом:

Crafty
  .e('PlayerCar')
  .addComponent('WiredHitBox')
  .debugStroke('white')
Обратите внимание на появившуюся вокруг авто белую коллизийную рамку

Собственные события

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

Например, для объекта obj это делается так:
obj.bind('myClick', function() {})

Вызываем
obj.trigger('myClick')

Компонентная модель

Crafty.js в отличие от многих других JavaScript-движков использует объектно-компонентную модель, что делает его необычайно гибким и более быстрым в использовании и проектировании. Компонент в Crafty представляет собой объект, который может очень просто наследовать другие компоненты. Компоненты могут отвечать за что-угодно: устройство ввода, персонажа, игровую сетку или даже отдельную анимацию.

Новый компонент в Crafty создается таким способом:
Crafty.c('ComponentName', {})

Crafty поддерживает карты спрайтов, а значит можно просто делать анимации, записав все анимации персонажа в одном графическом файле. Стоит заранее заметить что огромные png-куски не скармливаются некоторыми браузерами. Так что без фоторедактора тут не обойтись. Как гласит поговорка: "Лучше меньше, да лучше".

Поиск по компонентам можно делать так:
Crafty('ComponentName')
Если компонентов на сцене много, нужно проходиться по всем ним следующим образом:
Crafty('ComponentName').each(function(obj) {})

Давайте создадим компонент Car который будем использоваться в PlayerCar.
Crafty.c('Car', {
  _speed: 0.0, // это приватный атрибут
  // В момент создания компонента, запускается функция init, если она существует
  init: function () {
    this
      .requires('2D, Canvas, Tween, Collision, Sprite') //компонуем разные компоненты
      .origin('top center') // изменяем позицию вращения относительно спрайта
      .attr({ _speed: 0.0 }) // еще один способ создания атрибута
  }
})

Хорошие практики

Для JavaScript рекомендуется не создавать новые свойства в глобальном объекте. Crafty позволяет добавлять новые объекты в свой объект прототипа. Это делается через Crafty.extend({ player: {} })

Создадим класс player со свойством name:
Crafty.extend({
  player: {
    _name: '',
    getName: function() {
      return this._name
    },
    setName: function(playerName) {
      this._name = playerName
    }
  }
}

Вот так мы сделали геттер и сеттер имени игрока. Теперь к player можно обратиться через Crafty.player

Изменяемый HTML

В Crafty.js рекомендуется делать объекты вызова цепочечными, для этого передаем в конце функции компонента контекст: return this.

Управляемый HTML код доступен из коробки: Например обычная кнопка делается так:
Crafty.c('Button', {
  _button: null, 
  init: function () {
    this.requires('HTML, Mouse')
    var button = document.createElement('button')
    button.style.color = '#333'
    button.style.backgroundColor = 'ghostwhite'
    button.style.padding = '4px 12px'
    button.style.border = 'none'
    this._button = button
  },
  setSize: function (width, height) {
    this._button.style.width = width || this.w + 'px'
    this._button.style.height = height || this.h + 'px'
    //делаем замещение HTML
    this.replace(this._button.outerHTML)
    return this
  },
  setText: function (text) {
    this._button.textContent = text
    this.replace(this._button.outerHTML)
    return this
  }
});

Инициализация кнопки:
Crafty.e('Button').setText('my Button').setSize(100, 100)

Текстовые шрифты

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

Создадим компонент для шрифта:
Crafty.c('DefaultFont', {
  init: function () { 
    this
      .requires("2D, Canvas, Text")
      .text('')
      .origin("center")
      .textColor('#FF0000')
      .textFont({
        size: '20px'
      })
  }
})

Мы можем наследовать обычный шрифт для генерации жирного шрифта, а также явно использовать familyFont. Сторонний шрифт предварительно надо установить через CSS Web Fonts.

Crafty.c('BoldFont', { 
  init: function () {
    this.requires('DefaultFont') 
      .textFont({
        size: '28px',
        type: 'normal',
        weight: 'bold',
        family: 'Lobster'
      })
  }
})

Работа с изображениями

Для добавления простой картинки на игровом холсте нужно добавить компонент Image, установить ширину и высоту, а также явно выбрать картинку через функцию .image. Эта функция имеет два параметра:
1 - Путь к ресурсу картинки
2 - Стиль отображения повторения.

this 
  .requires('2D, Canvas, Image')
  .attr({
    x: 0,
    y: 0,
    z: 0,
    w: 100, //ширина
    h: 100 //высота
  })
  .image('content/images/road_texture.jpg', 'repeat-y') 

Сцены

Сцены задаются через метод defineScene.

Crafty.defineScene('level', levelInit, levelOut)
function levelInit() {
  Crafty.background('white')
  Crafty.e('Track')
}
function levelOut() {
  Crafty('Delay').each(function () {
    this.destroy()
  })
})
Для перемещения между сценами используем метод scene:
Crafty.scene('sceneName')

Загрузка ресурсов

Хорошей практикой является загрузка ресурсов перед самой первой сценой. Ресурсами могут быть картинки, аудио, шрифты, видео.

Crafty.paths({ 
  images: "content/images/",
  audio: "content/audio/"
})

var assetsObj = {
  "audio": {
  "crash": ["crash/crash.wav"],
  "horn1": ["horn/horn1.mp3"],
  "music": ["music/music.ogg"]
}, 

"images": [ 
  "game_over.png",
  "menu.jpg",
  "controls/dpad.png",
  "road_texture.jpg",
  "speedometer/arrow.png",
  "speedometer/speedometer.png"
], 
"sprites": {
  "controls/buttons.png": { 
  "tile": 48,
  "tileh": 48,
  "map": {
    "pause": [0, 0],
    "soundOn": [1, 0],
    "soundOff": [2, 0],
    "play": [3, 0],
    "share": [4, 0],
    "fullscreen": [5, 0]
  }
},
"vehicles.png": {
  "tile": 128,
  "tileh": 284,
  "map": {
    playerCar: [0, 0], car1: [1, 0], car2: [2, 0], car3: [3, 0], car4: [4, 0]
  }
},
"tires.png": {
  "tile": 19,
  "tileh": 44,
  "map": {
    playerTire: [0, 0]
  }
},
"logo.png": { 
  "tile": 208,
  "tileh": 212,
  "map": {
    "logo": [0, 0]
  }
}; 
// вызываем загрузку ресурсов
function loadAssets() { 
  Crafty.load(assetsObj, function () {
      Crafty('LoadingText').text('Loading complete')
      Crafty.scene('menu')
    }, function (e) {
      Crafty('LoadingIndicator').w = Crafty.viewport.width / (100 / e.percent)
      Crafty('LoadingText').text('Loading: ' + '(' + e.loaded + '/' + e.total + ')')
    }, function (e) { //uh oh, error loading
      console.error(e)
  })
}

Итог

Финальная версия
Crafty замечательный игровой движок: он понятный, удобный, гибкий и мощный, к тому же он открытый и проверенный мною лично при создании HOG-игр. Помимо этого с ним можно создавать самые разные жанры. Жаль что такой крутой движок очень поздно пришел в мир WebGL и теперь ему очень тяжело угнаться за такими известными движками как Phaser или Cocos2d.

Исходный код игры доступен на гихабе:
https://github.com/qertis/CanvasRacer

Советую к ознакомлению: Создаем HTML5 игру