Пишем игру на HTML5 и JS. Часть 2.5

No Comments

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

  1. Добавим в ядро возможность загружать ресурсы для модулей.
  2. Проверим работоспособность модуля zx.Image.
  3. Разберём простую анимацию.

Загрузка ресурсов ядром.

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 анимации.

Как работает анимация.

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

  1. Первое изображение неподвижно — оно просто орисовывается на «холсте» в координатах 50,50.
  2. Вторая картинка масштабируется. Сначала рассчитаем её ширину w.
  3. Затем вычислим высоту h.
  4. И нарисуем. Чтобы центр оставался неподвижным, из каждой координаты (x,y) вычтем половину ширины и высоты соответственно.
  5. Аналогично вычисляем координату x.
  6. И координату y.
  7. Выводим нижнюю правую четверть третьего изображения, при этом масштабируя результат. Размеры полного изображения 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;
  1. Сначала устанавливаем «непрозрачности» маленькое значение (0.017), таким образом следующие нарисованные фигуры будут практически прозрачными.
  2. Выбираем цвет заливки чёрный (#000).
  3. Рисуем прямоугольник на весь холст. Получается, что он немного затемняет прошлую картинку.
  4. Восстанавливаем значение «непрозрачности».

Полоски ещё проще, смысла их отдельно разбирать нету.

Исходники, примеры.

Исходный код второй части можно посмотреть в svn.

Демонстрация работы.

Leave a Reply

You must be logged in to post a comment.