Thursday, November 3, 2011

Простая игра на HTML5 Canvas – часть 4

Часть 4а. РИСУЕМ ПЛАТФОРМЫ

Есть два типа платформ. Наш персонаж может прыгать по обычной (оранжевой) и зеленой (батут), который дает дополнительное ускорение и гипер-ультра-высокий прыжок. Всегда есть семь платформ на экране (я попробовал другое количество, от 4 до 10 и только 7 прекрасно работает с размером экрана заявленным в начале). Давайте создадим класс платформ (функции платформы будут наследовать).
var Platform = function(x, y, type){
//функция принимает позицию и тип платформы
var that=this;
that.firstColor = '#FF8C00';that.secondColor = '#EEEE00';that.onCollide = function(){player.fallStop();};//если тип платформы отличен от 1, то просто устанавливаем нужный цвет и вызываем функцию
//fallStop() определенную в прошлой части урока
if (type === 1) {//, но если тип равен '1', устанавливаем другие цвета и ускорение взлета jumpSpeed равным 50.
//после этого метод checkJump()примет '50' вместо '17' предусмотренных по умолчанию.
that.firstColor = '#AADD00';that.secondColor = '#698B22';that.onCollide = function(){
player.fallStop();player.jumpSpeed = 50;};}
that.x = ~~x;that.y = y;that.type = type;
return that;
};
Теперь необходимо создать функцию, которая будет генерировать все платформы и складывать их в platforms[] массив, который мы определим в ближайшее время. После этого нарисуем платформы на экране.
var nrOfPlatforms = 7,
platforms = [],platformWidth = 70,platformHeight = 20;//глобальные (пока) переменные не очень подходят для хранения размеров платформ, но
//в нашем случае они необходимы для вычисления столкновений, поэтому размещены здесь
//а не в свойствах класса платформ
var generatePlatforms = function(){var position = 0, type;//'position' это Y координата платформы на экране и вначале она равно нулю
for (var i = 0; i < nrOfPlatforms; i++) {type = ~~(Math.random()*5);
if (type == 0) type = 1;else type = 0;//Обычных платформ будет примерно в пять раз больше, чем батутов
platforms[i] = new Platform(Math.random()*(width-platformWidth),position,type);//случайно выбранная позиция по Xif (position < height - platformHeight)
position += ~~(height / nrOfPlatforms);}//и Y интервал
}();
//эта функция будет вызвана только один раз, перед стартом игры
Добавляем метод Draw () объекту платформы:
var Platform = function(x, y, type){(...)that.draw = function(){ctx.fillStyle = 'rgba(255, 255, 255, 1)';//Это важно заменить прозрачность в rgba на 1 (непрозрачно), потому что Google Chrome помнит
//предыдущее значение, которое мы устанавливали, рисуя облака (круги) на заднем фоне, в
//Firefox и Safari этого не происходит
 var gradient = ctx.createRadialGradient(that.x + (platformWidth/2), that.y + (platformHeight/2), 5, that.x + (platformWidth/2), that.y + (platformHeight/2), 45);
gradient.addColorStop(0, that.firstColor);gradient.addColorStop(1, that.secondColor);ctx.fillStyle = gradient;ctx.fillRect(that.x, that.y, platformWidth, platformHeight);//заполняем прямоугольную платформу градиентной заливкой
};
return that;
};
Платформы должна быть нарисованы на каждом кадре, так что обновление GameLoop () является обязательным.
var GameLoop = function(){(...)platforms.forEach(function(platform){platform.draw();
});
(...)
};

Часть 4б. СТОЛКНОВЕНИЯ 

Чудненько, но нет взаимодействия между ангелом и платформами. Одна маленькая функция исправит ситуацию. Позвольте мне представить checkCollision ():
var checkCollision = function(){platforms.forEach(function(e, ind){//проверяем каждую платформуif ((player.isFalling) &&
//только когда персонаж падает(player.X < e.x + platformWidth) &&
(player.X + player.width > e.x) &&
(player.Y + player.height > e.y) &&
(player.Y + player.height < e.y + platformHeight)//и находится прямо над платформой) {e.onCollide();}})}
Еще одно обновление основного цикла (подходящий момент, чтобы закомментировать функцию MoveCircles (), если платформы стоят на месте, почему фон движется? будет больше смысла, когда мы будем осуществлять прокрутку платформ и фона одновременно). Так GameLoop () функция должна выглядеть в настоящее время:
var GameLoop = function(){clear();//MoveCircles(5);DrawCircles();
if (player.isJumping) player.checkJump();if (player.isFalling) player.checkFall();
platforms.forEach(function(platform){platform.draw();});
checkCollision();
player.draw();gLoop = setTimeout(GameLoop, 1000 / 50);}
Результат этой части урока можно посмотреть по адресу: http://jsbin.com/igeta3/, а скачать исходники тут: http://guthub.com/michalbe/Simple-game-with-HTML5-Canvas. Думаю следующая часть будет последней, но кто знает;)
Копирайты.
Автор: Михал Будзинский https://twitter.com/#!/@michalbe

Автор перевода: Андрей Семенов https://twitter.com/#!/a_semenov79

Wednesday, November 2, 2011

Простая игра на HTML5 Canvas - часть 3


Часть 3а. Физика


Потому что физика в StH очень проста, нет никакой необходимости включать какой-то физический движок, как например Box2d (http://box2d-js.sourceforge.net/). Прыжки главного персонажа настолько просты, что реализуются в нескольких строках кода. Давайте разделим его на две несвязанные части - прыжки и падения. Когда объект начинает прыгать, он имеет некоторую начальную скорость, снижающуюся под действием силы тяжести. Это стадия заканчивается, когда эта скорость полностью сводится к нулю и гравитация начинает тянуть объект вниз с нарастающей силой. Это вторая часть прыжка - падение. Научим ангела, как себя вести в таких ситуациях, давайте расширим объект ‘player’ добавив несколько атрибутов

var  player = new (function(){
  var  that = this;
  that.image  = new Image();
  (...)
//новые атрибуты
  that.isJumping  = false;
  that.isFalling  = false;
//состояние объекта в булевых переменных (взлет или падение)?
  that.jumpSpeed  = 0;
  that.fallSpeed  = 0;
  //ускорение взлета и падения
  
  (...) //остальная часть кода
  })();
  Теперь  давайте внедрять методы ответственные за прыжки. Дальнейшее  расширение объекта ‘player’: 
  that.jump = function() {
  //инициализация прыжка
  if (!that.isJumping &&  !that.isFalling) {
  //сначала  проверим - объект не должен находиться в состоянии взлета или падения,
  //чтобы  не подпрыгивать не имея опоры (из воздуха)
  that.fallSpeed = 0;
  that.isJumping = true;
  that.jumpSpeed = 17;
  //начальная скорость
  }
  }
  that.checkJump = function() {
  //начинаем взлетать
  that.setPosition(that.X,  that.Y - that.jumpSpeed);
  //перемещаемся  вверх с приростом на jumpSpeed пикселей
  that.jumpSpeed--;
  //имитируем  гравитацию уменьшая ускорение взлета
  if (that.jumpSpeed == 0) {
  //и  если ускорение взлета упало до нуля, начинаем падать
  that.isJumping = false;
  that.isFalling = true;
  that.fallSpeed = 1;
  }
  }
  that.checkFall = function(){
  //почти  тоже, что и checkJump()
  if (that.Y < height -  that.height) {
  //проверяем,  не достигнут ли нижний край экрана и увеличиваем fallSpeed 
  //(ускорение  свободного падения)...
  that.setPosition(that.X,  that.Y + that.fallSpeed);
  that.fallSpeed++;
  } else {
  //.. иначе – отскок (снова взлетаем) 
  that.fallStop();
  }
  }
  that.fallStop = function(){
  //хватит  падать, пора снова взлетать
  that.isFalling = false;
  that.fallSpeed = 0;
  that.jump();    
  }

Это обязательно обновлять основные функции цикла для перерисовки позиции нашего персонажа во время прыжков и падений. Обновляем GameLoop (игровой цикл) таким кодом, прежде чем перерисовать персонаж:

if (player.isJumping) player.checkJump();
  if (player.isFalling) player.checkFall();

Я думаю, что код показанный выше достаточно прост для понимания. Последнее действие, которое мы должны выполнить, просто запустить первый прыжок, сразу после размещения персонажа на сцене. 

player.setPosition(~~((width-player.width)/2),  ~~((height - player.height)/2));
  player.jump(); //здесь

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

Часть 3b. Управление

Главный герой StH может двигаться только вбок. Он подпрыгивает автоматически, вверх/вниз движения будут зависеть от платформ. Пользователь может только командовать ангелу, переместиться влево или вправо. Опять же, это может быть достигнуто путем расширения объекта ‘player’ дополнительными методами. 

var player = new(function(){
  (...)
  that.moveLeft = function(){
  if (that.X > 0) {
  //проверим не вышли ли мы за  область экрана
  that.setPosition(that.X - 5, that.Y);
  }
  }
  that.moveRight = function(){
  if (that.X + that.width < width) {
  // проверим не вышли ли мы  за область экрана
  that.setPosition(that.X + 5, that.Y);
  }
  }
  (...)
  })();

Теперь привяжем функцию к положению курсора мыши (ангел будет следовать за ним).

document.onmousemove =  function(e){
  if (player.X + c.offsetLeft  > e.pageX) {
  //если мышь слева от персонажа сдвигаем его влево.
  player.moveLeft();
  } else if (player.X +  c.offsetLeft < e.pageX) {
  //иначе направо?
  player.moveRight();
  }
  }

Это все на сегодня. В следующей части ​​я покажу рисование платформ и контроль столкновений. Как обычно, результат этой части урока можно посмотреть по адресу: http://jsbin.com/uhaka3/, а скачать исходники тут: http://guthub.com/michalbe/Simple-game-with-HTML5-Canvas

Копирайты.

Автор: Михал Будзинский https://twitter.com/#!/@michalbe

Автор перевода: Андрей Семенов https://twitter.com/#!/a_semenov79

Saturday, October 29, 2011

Простая игра на HTML5 Canvas – часть 2


Персонаж

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

Давайте создадим объект, представляющий главного героя со всеми необходимыми атрибутами и методами. Назовем объект ‘player’ (англ. – игрок). Далее мной выбран не лучший способ описания объекта в JS, потому что все атрибуты являются доступными, а закрытые не применяются вообще. Но это простейшее решение, позволяющее уложиться в запланированные 10КБ, а главное что это работает. Если вам захочется узнать о закрытых атрибутах и методах, наследовании и т.п. прочитайте об этом в учебнике по JavaScript (на англ.языке можно прочитать тут - http://jibbering.com/faq/notes/closures/ ). Также важно помнить что если вы соберетесь уменьшить код с помощью онлайн инструментов, таких как ‘Closure Compiler’ (http://closure-compiler.appspot.com/home) что имена аргументов объектов он не сокращает. Именно поэтому я использую 2-х буквенные сокращения для описания объекта, например, ‘player.im’ вместо ‘player.image’. Итак, вот код, который добавляется в наш game.js:
var player = new (function(){
//создаем новый объект основанный на функции
//и присваиваем его переменной ‘player’

    var that = this;
//'that' – псевдоним этого объекта

//attributes
    that.image = new Image();
    that.image.src = "angel.png";
//создаем новый Image элемент и
//'angel.png' закачиваем в него

    that.width = 65;
//ширина одного кадра
    that.height = 95;
//высота одного кадра (рисунок в 2 раза выше, ведь в нем 2 кадра)

    that.X = 0;
    that.Y = 0;
//X&Y позиция объекта на холсте

//далее описываем методы
    that.setPosition = function(x, y){
    that.X = x;
    that.Y = y;
}

    that.draw = function(){
        try {
            ctx.drawImage(that.image, 0, 0, that.width, that.height, that.X, that.Y, that.width, that.height);
//отрисовываем наш персонаж функцией drawImage(объект-рисунок, X источника,
//Y источника, ширина источника, высота источника, X назначения (X позиция),
//Y назначения (Y позиция), ширина назначения, высота назначения)
        } catch (e) {
//Иногда, если изображение персонажа слишком велико и еще не успело
//загрузиться до вывода первого кадра, JavaScript выдаст ошибку и прекратит
//выполнение всего. Чтобы этого избежать, мы перехватываем ошибку и повторим
//попытку рисования в следующем кадре. Это незаметно для пользователей ведь
//частота кадров 50 в секунду.
        }
    }
})();
//мы сразу же применим функции описанные выше
//и создадим переменную ‘player’
//как новый объект

player.setPosition(~~((width-player.width)/2),  ~~((height - player.height)/2));
//наш персонаж готов, давайте переместим его
//в центр экрана,
//'~~' возвращает округленное вниз целое значение
//переменной с плавающей точкой, как это делает Math.floor()

Итак, теперь  необходимо перерисовывать нашего ангела на каждом кадре в игровом цикле с помощью  функции ‘player.draw’:
var GameLoop = function(){
    clear();
    MoveCircles(5);
    DrawCircles();
    player.draw();
    gLoop = setTimeout(GameLoop, 1000 / 50);
}

Ну а как насчет анимации? Спрайт (файл с изображением ангела) имеет 2 кадра, но только один из них перерисовывается на каждом кадре. Для того, чтобы анимировать наш персонаж потребуются дополнительные атрибуты и небольшие изменения метода ‘draw’:
var player = new (function(){
(...)
    that.frames = 1;
//нумерация кадров начинается с нуля
    that.actualFrame = 0;
//текущий кадр
    that.interval = 0;
//нам не прийдется переключать кадры
//в игровом цикле, ‘interval’ позаботится об этом

    that.draw = function(){
        try {
            ctx.drawImage(that.image, 0, that.height * that.actualFrame, that.width, that.height, that.X, that.Y, that.width, that.height);
//третий аргумент умножается на номер текущего кадра, чтобы показать нужную
//часть исходного изображения
        } catch (e) {};

        if (that.interval == 4 ) {
            if (that.actualFrame == that.frames) {
                that.actualFrame = 0;
            } else {
                that.actualFrame++;
            }
            that.interval = 0;
        }
    that.interval++;
//выше показана простая логика переключающая кадры каждые 4 итерации

    }
})();

Спасибо за внимание! Результат работы проделанной в данной части урока, как обычно, можно посмотреть по адресу: http://jsbin.com/orohe4/, а исходники скачать тут: http://github.com/michalbe/Simple-game-with-HTML5-Canvas/tree/master/part2/

КОПИРАЙТЫ.

Автор: Михал Будзинский https://twitter.com/#!/@michalbe
Автор перевода: Андрей Семенов https://twitter.com/#!/a_semenov79

Thursday, October 27, 2011

Простая игра на HTML5 Canvas - часть 1


Предисловие от автора перевода.

Итак, я наконец-то подготовил перевод статьи Михала Будзинского открывающей нам новый web-стандарт HTML5. Этот стандарт еще не имеет утвержденной спецификации и представлен на сайте w3.org только как рабочий черновик, но уже активно продвигается производителями браузеров. Новшества, вносимые новой версией HTML (язык разметки гипертекстов) сближают его с языками программирования. Благодаря активному развитию социальных сетей, «облачных» сервисов и т.п. все яснее прослеживается необходимость улучшения интеграции мультимедийного контента и новый тэг <canvas> стал одним из решений данного вопроса представленный консорциумом.
Движок игры написан на JavaScript и те, кто еще не знаком с этим языком программирования найдут здесь также замечательные примеры для его начального освоения.

Введение.

StH - очень простой клон Doodle Jump, но чтобы быть до конца честным, я был вдохновлен Icy Tower. Ну да ладно.
Цель игры состоит в том, чтобы управлять маленьким ангелом и скакать на двух видах платформ - оранжевые (обычные) и зеленые (трамплины). Игра заканчивается, когда ангел падает за нижний край экрана.
Я создал эту игру за 8 часов и позже, когда довольно долго играл, я обнаружил немного ошибок,  так что в этом уроке, я хочу исправить их все. Давайте приступим!

Фон.

Поскольку вся игра, включая изображения и сценарии (скрипты), не должна быть больше 10КБ, я не захотел использовать растровое изображение на фоне. Гораздо экономичнее рисовать используя функции рисования <canvas>.
Прежде всего, мы нуждаемся в небольшом HTML, ничего особенного, только один <canvas> элемент с некоторым уникальным id, немного CSS и несуществующий, пока еще, game.js:
<html>
  <head>
    <title> Простая игра с HTML5 Canvas </ title>
  <style>
  body {margin:0px; padding:0px; text-align: center}
  canvas {outline:0; border:1px solid # 000; margin: 0 auto}
  </ style>
  </ head>
  <body>
    <canvas id='c'></ canvas>
    <script src="game.js"></ script>
  </ body>
</ html>
Это и есть весь HTML, который нам понадобится в течение этого урока. Хорошо, теперь давайте создавать Javascript. Прежде всего мы должны создать немного глобальных (хотя, я знаю что это Глобальное зло =) переменных и изменить атрибуты (свойства) <canvas>. Этого будет достаточно:
var width = 320, //ширина canvas
    height = 500, //высота canvas

  c = document.getElementById('c'), //сам canvas

  ctx = c.getContext('2d');
//двумерный контектс canvas (сейчас только он поддерживается всеми
//браузерами)

c.width = width;
c.height = height;
//устанавливаем размеры canvas

Первое, что важно понять про <canvas>, это то, что невозможно просто перемещать объекты на его поверхности. Нужно, обязательно, на каждом кадре его очищать, целиком или частично. Поэтому давайте создадим функцию очистки – clear():
var clear = function(){
  ctx.fillStyle = '#d0e7f9';
//выбираем цвет заливки (чудесный голубой)
//Использование clearRect() вызывало ошибку, в 2-х строках ниже показан
//старый вариант
//ctx.clearRect(0, 0, width, height);
//очистка экрана
  ctx.beginPath();
//запускаем рисование
  ctx.rect(0, 0, width, height);
//рисуем прямоугольник из точки (0, 0) до (widthheight) заполняя весь наш
//холст <canvas>
  ctx.closePath();
//заканчиваем рисование
  ctx.fill();
//заполняем прямоугольник цветом выбранным ранее
}

Одноцветный фон скучен как ад, так что давайте нарисуем несколько облаков на нем. Скорее не правильные облака, а простые, полупрозрачные круги, подражающие облакам. Круги мы будем рисовать в случайных местах холста, каждый с различным размером и прозрачностью. Мы будем держать всю информацию о кругах в 2-ом массиве (в JS их нет, но лучший путь к решению этой проблемы помещение одного массива в другой).

var howManyCircles = 10, circles = [];

for (var i = 0; i < howManyCircles; i++)
  circles.push([Math.random() * width, Math.random() * height, Math.random() * 100, Math.random() / 2]);
//добавляем информацию о кругах
//в массив 'circles'. Это x и y позиция,
//радиус в диапазоне 0-100 и прозрачность
//в диапазоне 0-0.5 (0 это абсолютно прозрачно, 1 непрозрачно)

var DrawCircles = function(){
  for (var i = 0; i < howManyCircles; i++) {
    ctx.fillStyle = 'rgba(255, 255, 255, ' + circles[i][3] + ')';
//белый цвет с прозрачностью в rgba
    ctx.beginPath();
    ctx.arc(circles[i][0], circles[i][1], circles[i][2], 0, Math.PI * 2, true);
//arc(x, y, radius, startAngle, endAngle, anticlockwise)
    ctx.closePath();
    ctx.fill();
  }
};

Хорошо, но скучновато. Почему облака стоят на месте? Давайте сделаем маленькую функцию с одним аргументом, которая сдвигает облака вниз на заданное число пикселей, и, когда определенный круг скрывается за пределами холста, он перемещается  наверх с изменением координаты X, радиуса и прозрачности:
var MoveCircles = function(deltaY){
  for (var i = 0; i < howManyCircles; i++) {
    if (circles[i][1] - circles[i][2] > height) {
//круг достигший нижнего края
//меняет параметры
      circles[i][0] = Math.random() * width;
      circles[i][2] = Math.random() * 100;
      circles[i][1] = 0 - circles[i][2];
      circles[i][3] = Math.random() / 2;
    } else {
//сдвигаем круг на deltaY пикселей вниз
      circles[i][1] += deltaY;
    }
  }
};

Теперь, не в последнюю очередь, давайте создадим основной цикл игры и подключим в нем все, что уже создали. Каждый кадр будет очищать экран, перемещать круги на 5px вниз, рисовать их и после 1/50 секунды вызывать следующий кадр. Я использую два setTimeouts вместо одного setInterval, хотя незнаю почему:). Вроде бы раньше были некоторые проблемы с производительностью в IE, что ли. И не забудьте добавить gLoopв глобальные переменныеобъявленные вначале.
var width = 320, 
//ширина холста <canvas>
  height = 500, 
//высота холста <canvas>
  gLoop,
(...) //остальная часть кода находится здесь

var GameLoop = function(){
  clear();
  MoveCircles(5);
  DrawCircles();
  gLoop = setTimeout(GameLoop, 1000 / 50);
}
GameLoop();

Благодарю Луиса Гирибоуна за комментарии к уроку. Он помог разобраться в чем отличие setTimeOut от setInterval. SetInterval запускает функцию переданную ему в аргументе не дожидаясь окончания предыдущей итерации, а setTimeOut ждет ее завершения даже если вышло время переданное в качестве второго аргумента.
Так же хочу поблагодарить пользователя под ником Ped7g за выявленные ошибки.
Результат этой части урока можно посмотреть по адресу: http://jsbin.com/odoho3, а скачать исходники тут: http://github.com/michalbe/Simple-game-with-HTML5-Canvas

Копирайты.

Автор: Михал Будзинский https://twitter.com/#!/@michalbe
Автор перевода: Андрей Семенов https://twitter.com/#!/a_semenov79