Пишем игру на HTML5 и JS. Часть 2.5
Фев 04
2011
beta, ИгроДел animation, effect, fade, gamedev, HTML5, JavaScript, sinus, ИгроДел No Comments
Эта статья — продолжение части 2, код в которой получился незавершёным. Сегодня мы сделаем следующее:
- Добавим в ядро возможность загружать ресурсы для модулей.
- Проверим работоспособность модуля zx.Image.
- Разберём простую анимацию.
Загрузка ресурсов ядром.
MindDefGame
Прежде всего, перенесём в ядро некоторые свойства из game.js. Например, canvas и его context. Также вынесем их инициализацию в ядро. Таким образом метод MindDefGame.initialize() упростится до:
initialize: function(cnv, w, h) { zx.go(cnv, w, h); this.x = 0; },
В методе MindDefGame.loop() исправим использование canvas и его контекста:
loop: function() { zx.ctx.globalAlpha = .017; zx.ctx.fillStyle = "#000"; zx.ctx.fillRect(0,0,zx.cnv.width,zx.cnv.height); zx.ctx.globalAlpha = 1; var clr = 'rgb(' + Math.round(Math.random()*255) + ',' + Math.round(Math.random()*255) + ',' + Math.round(Math.random()*255) + ')'; zx.ctx.fillStyle = clr; zx.ctx.fillRect(this.x, 0, 1, zx.cnv.height); this.x++; this.x %= zx.cnv.width; },
Кроме того, счётчик this.x теперь сбрасывается при достижении значения, равного ширине canvas’а, что позволяет полностью заполнить его цветными полосками.
Ядро.
Теперь перейдём к ядру. Первым сюда переезжает прототип объекта Function bind(), который раньше также был в game.js. Далее, я решил добавить несколько методов для создания HTML элемента и поиска его по тэгу:
zx.$create = function(tag) { return document.createElement(tag); }; zx.$el = function(tag) { return document.getElementsByTagName(tag); }; zx.$el0 = function(tag) { return zx.$el(tag)[0]; };
Рядом разместим свойства:
zx.cnv = null; // "холст" - объект Сanvas zx.ctx = null; // контекст (Сanvas.context) zx.res = []; // загружаемые ресурсы zx.ready = false; // готовность (всё загружено)
И опишем метод, загружающий ресурс:
// добавить ресурс zx.addRes = function (res) { if (res.src && !this.res[res.src]) { this.res[res.src] = res; // Хэш по полю src if (this.ready && res.load) { res.load(); // отложенная загрузка (runtime) } } };
Как видно, метод этот очень простой. Он выполняет отложенную загрузку. То есть сначала все модули сообщают ядру, какие и сколько ресурсов им нужны. А чуть позже ядро все их загружает. Это позволяет например показать, сколько % уже загружено (progress bar).
Первым делом, выполняется проверка, есть ли у ресурса свойство src, и не загружен ли уже ресурс с того же адреса. Если такого ресурса нет, то он добавляется в массив zx.res.
Далее проверяется флаг zx.ready — если он установлен, то ресурс загружается уже после старта игры, поэтому его нужно загрузить немедленно. Если у ресурса есть метод load(), то он загружается.
Теперь опишем показанный чуть ранее метод zx.go(), который как раз всё и запускает:
// запускаем ядро zx.go = function (cnv, w, h) { var c = document.getElementById(cnv); if (c) { c.width = w; c.height = h; zx.cnv = c; zx.ctx = c.getContext('2d'); zx.loadAll(); } };
На вход ему передаётся ID элемента canvas, а также желаемая ширина и высота для него. В результате его выполнения находится нужный canvas, размер его устанавливается в заданный, а также добывается его контекст. Ну и инициализируются соответствующие свойства ядра.
Ну а последний вызов (метод zx.loadAll()) как раз выполняет загрузку всех ресурсов:
// запускаем загрузку всех ресурсов zx.loadAll = function () { for (var i in zx.res) { var res = zx.res[i]; if (res && res.load && !res.loaded) { res.load(); } } zx.ready = true; };
Проверка работоспособности загрузчика и модуля zx.Image.
Добавим в MindDefGame.start() несколько изображений:
MindDefGame.start = function(cnv, w, h) { this.img0 = new zx.Image('img/0.gif?11'); this.img1 = new zx.Image('img/1.gif?12'); this.img2 = new zx.Image('img/2.jpg?23'); this.initialize(cnv, w, h); var fps = 60; this._interval = setInterval(this.loop.bind(MindDefGame), 1000/fps); };
Теперь выведем их на экран. Проверим все три варианта отрисовки:
- draw() — просто отрисовка в указанных координатах
- drawScale() — отрисовка с масштабированием
- drawChunk() — отрисовка фрагметна исходного изображения с масштабированием
Для этого в метод loop() допишем следующий код:
this.img0.draw(50, 50); var w = 60 + 20*Math.sin(this.x * Math.PI / zx.cnv.width*2); var h = 70 + 30*Math.cos(this.x * Math.PI / zx.cnv.width*2); this.img1.drawScale(250 - w/2, 150 - h/2, w, h); var x = 160-70/2*2.5 + 80*Math.sin(this.x * Math.PI / zx.cnv.width*8); var y = 300 + 50*Math.cos(this.x * Math.PI / zx.cnv.width*4); this.img2.drawChunk(x, y, 70, 70, 70, 70, 2.5);
Можете посмотреть результат работы этой простой HTML5 анимации.
Как работает анимация.
Анимация состоит из нескольких частей. Сначала разберём работу с изображением, т.к. код недалеко. Номера пунктов соответствуют номерам строк.
- Первое изображение неподвижно — оно просто орисовывается на «холсте» в координатах 50,50.
- Вторая картинка масштабируется. Сначала рассчитаем её ширину w.
- Затем вычислим высоту h.
- И нарисуем. Чтобы центр оставался неподвижным, из каждой координаты (x,y) вычтем половину ширины и высоты соответственно.
- Аналогично вычисляем координату x.
- И координату y.
- Выводим нижнюю правую четверть третьего изображения, при этом масштабируя результат. Размеры полного изображения 140×140, поэтому середина его находится в точке 70,70. Ну и размеры четверти тоже 70×70
Для плавности анимации используем sin и cos. Значение этих функций плавно меняется от -1 до 1. Поэтому если мы хотим, чтоб некое значение плавно менялось от -R до R, просто умножим на R. Для того, чтобы сместить центр из 0 в сторону, добавим start.
start + R*sin(x * 2*Pi / w)
Теперь разберёмся с частотой. У нас есть переменная-счётчик, в которой хранится номер текущей цветной линии. Меняется значение этой переменной от 0 до (cnv.width-1). Период же функций равен 2Пи. То есть если передать синусу значение x*2Pi/w, где x — счётчик, а w — его период, то возвращаемое им значение будет делать ровно один цикл синхронно со значением x.
Уменьшив w в два раза получим в два раза большую «скорость» синуса. То есть за один цикл вертикальных полосок синус сдалает два цикла. Какой-то странный текст получился, проще разобрать вживую, как оно работает, меняя значения :)
Теперь о полосках и затухании. Первые четыре строки имитируют эффект затухания:
zx.ctx.globalAlpha = .017; zx.ctx.fillStyle = "#000"; zx.ctx.fillRect(0,0,zx.cnv.width,zx.cnv.height); zx.ctx.globalAlpha = 1;
- Сначала устанавливаем «непрозрачности» маленькое значение (0.017), таким образом следующие нарисованные фигуры будут практически прозрачными.
- Выбираем цвет заливки чёрный (#000).
- Рисуем прямоугольник на весь холст. Получается, что он немного затемняет прошлую картинку.
- Восстанавливаем значение «непрозрачности».
Полоски ещё проще, смысла их отдельно разбирать нету.
Исходники, примеры.
Исходный код второй части можно посмотреть в svn.
Демонстрация работы.