Статьи

Разработка кроссплатформенной игры HTML5: часть 1

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

Часть 1: заставить игру выглядеть великолепно и хорошо работать на всех платформах

Часть 2: Обработка различных типов ввода каждой платформы

Часть 3: Обеспечение безопасности вашей игры.

Часть 1 охватывает:

  • CSS3 медиа-запросы для пользовательского интерфейса
  • Масштабирование игры
  • Retina отображает
  • «Полный экран» на мобильном телефоне
  • «Установка» игры на мобильный
  • Улучшение FPS за счет использования субканваз.

CSS3 Media Queries для пользовательского интерфейса

К настоящему времени вы, вероятно, хорошо знакомы с медиа-запросами CSS — это отличный способ адаптировать свой сайт или игру к экранам разных размеров. Они будут просто полезны для пользовательского интерфейса ваших игр, так как сама игра, скорее всего, будет работать внутри элемента canvas.

Для наших игр и сайта мы обычно просто настраиваем макеты шириной менее 800 пикселей. Вот как это делается:

@media screen and (max-width: 800px) {
    /* Whatever CSS you want here */
}

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

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

В Word Wars у нас есть некоторый JavaScript, который определяет, является ли представление пейзажным или портретным (если ширина окна меньше высоты окна, это портрет), и мы добавляем класс .portrait к элементу <html>. Затем в нашем CSS у нас есть некоторые специфические свойства для дочерних элементов html.portrait (например, скрытие определенных элементов, их масштабирование по-разному). Если вы посмотрите на
http://wordwars.clay.ioна мобильном устройстве (или просто уменьшите размер браузера) вы увидите, как мы показываем информационную панель и таймер вверх для портрета и влево для ландшафта.
Вот немного кода для определения портрета и добавления класса портрета:

Портрет
Пейзаж

window.onresize = function() // this needs to be called onorientationchange as well
{
    var htmlTag = document.getElementsByTagName('html')[0];
    if(window.innerWidth < window.innerHeight)
        htmlTag.className += ' portrait';
    else
        htmlTag.className = htmlTag.className.replace(' portrait', '');
}

Масштабирование игры

Достаточно легко масштабировать элемент canvas до полной ширины окна. Трудная часть — масштабирование всего содержимого внутри.

Чтобы масштабировать холст до полной ширины и высоты, просто убедитесь, что ваши <html> и <body> не имеют отступов или полей, и немного JavaScript, вы можете установить ширину и высоту холста на полную и обновлять их, когда Изменение размера окна (обратите внимание, что изменение ориентации на мобильном устройстве не вызывает изменение размера, только onorientationchange):

window.onresize = function() // You can alternatively add an event listener for onresize
{
    var canvas = document.getElementById(‘canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}
window.onload = function()
{
    window.onresize(); // Call onload to set the width & height initially
}

Когда вы добавляете элементы в контекст холста, вы указываете высоту и ширину, например: <code> ctx.fillRect (0, 0, 100, 100); </ code> рисует прямоугольник размером 100 на 100 пикселей, который совсем не жидкий

Чтобы сделать эту жидкость, вам нужно масштабировать в соответствии с высотой и / или шириной окна. Например, если вы хотите, чтобы элемент занимал 10% экрана по вертикали и 10% по горизонтали, вы должны использовать:

var rectangle = { // Be sure to update this onresize
    width: window.innerWidth / 10,
    height: window.innerHeight / 10
};

Как заставить игру хорошо выглядеть на экранах Retina

Другая проблема, с которой приходится сталкиваться, — заставить игру хорошо выглядеть на экранах сетчатки. Для этого мы делаем две вещи. Сначала мы устанавливаем ширину CSS и высоту элемента canvas как window.innerWidth и window.innerHeight. Кроме того, мы устанавливаем атрибуты width и height элемента canvas, чтобы они были значениями, умноженными на devicePixelRatio. Во-вторых, мы умножаем размер каждого элемента, нарисованного на холсте, на отношение пикселей устройства (обычно это 1, но для таких устройств, как iPhone 4, это 2). Следуя приведенному выше примеру, мы получим:

var devicePixelRatio = window.devicePixelRatio || 1; // > 1 for retina displays
var canvas = document.getElementById(‘canvas');
/* Be sure to update all these values onresize */
// Width & height attributes (scaled according to pixel ratio)
canvas.width = window.innerWidth * devicePixelRatio;
canvas.height = window.innerHeight * devicePixelRatio;
// CSS width & height
canvas.style.width = window.innerWidth;
canvas.style.height = window.innerHeight;
var rectangle = {
    width: devicePixelRatio * window.innerWidth / 10,
    height: devicePixelRatio * window.innerHeight / 10
};

«Полный экран» на мобильном телефоне

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

Еще одна вещь, которую стоит рассмотреть, чтобы попытаться удержать больше мобильных пользователей, — это предложить пользователям «установить» вашу игру как закладку на iOS (которая добавляет значок на домашний экран). Другое преимущество этих закладок заключается в том, что когда ваша игра открывается с главного экрана, нижняя панель в Safari не отображается, поэтому появляется больше ресурсов.

Для этого вам нужно указать значок 57×57 (в <head>):

<!-- Works for iOS and Android -->
<link rel="apple-touch-icon-precomposed" href="images/apple-touch-icon.png" />

Этот фрагмент кода JavaScript, написанный моим партнером Джо, покажет подсказку для установки. Не стесняйтесь использовать этот фрагмент в своей игре!
Конечно, если вы разместите свою игру на
clay.io , мы позаботимся обо всем этом для вас.

Сохранить iOS игроков

Улучшение FPS с помощью субканваз

В нашей самой последней игре Word Wars FPS в мобильном Safari поначалу был ужасным.

Поскольку каждая плитка в нашей игре имеет градиенты и что-то, что не должно выглядеть хорошо, перерисовка этих градиентов 60 раз в секунду действительно сильно загружает процессор. Word Wars имеет четыре типа плиток: серый (невыбранный), синий (выбранный), зеленый (правильный), красный (неправильный). Решением нашей низкой частоты кадров было то, что для каждого типа, когда мы изначально загружаем страницу, мы рисуем их и сохраняем их в подканале и используем эту подкану в drawImage (). Вот посмотрите, как мы это делаем:

var insetCircle = document.createElement('canvas');
var redCircle   = document.createElement('canvas');
var blueCircle  = document.createElement('canvas');
var greenCircle = document.createElement('canvas');
var renderTile = function(circle, color1, color2, outglow1, outglow2, strokeColor)
{
    circle.width = size; // size is something we set elsewhere for fluid width & height
    circle.height = size;
    var innerGlow = ctx.createRadialGradient(0, 0, size/2-4, 0, 0, size/2); // Gradient for looks
    innerGlow.addColorStop(0, 'rgba(255,255,255,0)');
    innerGlow.addColorStop(1, 'rgba(255,255,255,.2)');
    var cctx = circle.getContext('2d');
    cctx.translate(size/2, size/2); // center of circle
    var grdRedLin  = cctx.createLinearGradient(0, -size/2, 0, size/2);
    grdRedLin.addColorStop(0, color1);
    grdRedLin.addColorStop(1, color2);
    cctx.save();
    outerGlow(outglow1, outglow2, cctx);
    cctx.beginPath();
    cctx.arc(0, 0, size/2, 0, 2*Math.PI);
    cctx.strokeStyle = strokeColor;
    cctx.lineWidth = 4;
    cctx.stroke();   
    cctx.fillStyle = grdRedLin;
    cctx.fill();
    cctx.fillStyle = innerGlow;
    cctx.fill();
}
var renderAllTiles = function() {
    // Render each tile in its own canvas
    renderTile(redCircle, '#a52222', '#7e0101', '#a52222', 'rgba(165, 34, 34, 0)', '#520101');
    renderTile(blueCircle, '#1d97c9', '#0a7cab', '#7bb5c5', 'rgba(123, 181, 197, 0)', '#105a78');
    renderTile(greenCircle, '#1dc924', '#0faf15', '#1dc924', 'rgba(29, 201, 36, 0)', '#107814');
    insetCircle.width = size;
    insetCircle.height = size;
    var cctx = insetCircle.getContext('2d');
    cctx.translate(size/2+50, size/2+50);
    cctx.lineWidth = Math.floor(size * 0.1);
    cctx.strokeStyle = 'rgba(255,255,255,.25)';
    // draw the rest of the tile
    cctx.beginPath();
    cctx.arc(0, 0, size/2, 0, 2*Math.PI);   
    cctx.stroke();
    var grd = ctx.createRadialGradient(0, 0, 10, 0, 0, size/2);
    grd.addColorStop(0, 'rgba(255,255,255,.1)');
    grd.addColorStop(1, '#a1aaae');
    cctx.fillStyle = grd;
    cctx.fill();
}
renderAllTiles();

// Actually rendering them (render() is called with requestAnimationFrame())
var circles = [insetCircle, blueCircle, greenCircle, redCircle]; // These correspond to tile states
var render = function()
{
    // ...
    ctx.drawImage(circles[tiles[i].state], Math.floor(-size/2), Math.floor(-size/2), size, size);
    // ...
}

Приведенный выше код визуализирует каждый из 4 типов плиток на своем собственном холсте, и когда нам нужно отобразить его на главном холсте, мы ссылаемся на субканву в ctx.drawImage (). Затем мы добавляем что-нибудь динамическое поверх плиток (в нашем случае это буквы).

Вот и все к части 1. Часто проверяйте части 2 и 3, где я расскажу об обработке различных типов ввода, а также о безопасности для игр с JavaScript и бэкэндом.

Если вы заинтересованы в разработке игр на HTML5, посмотрите API разработчика clay.io
где мы заботимся о достижениях, списках лидеров, обработке платежей, социальной интеграции, входе пользователей в систему, многопользовательских комнатах, скриншотах и ​​нескольких вещах, которые я упомянул в этом посте. Если у вас есть какие-либо отзывы об API или идеи о том, как мы можем сделать вашу жизнь проще, дайте мне знать в комментариях!