Canvas
3 May 2011

Пятнашки на LibCanvas

Недавно на Хабре была статья про пятнашки на Canvas.
Отличная статья, уверен, новички найдут в ней много полезного. К сожалению, в комментариях высказались о немного завышеном потреблении процессора.
Это не от недостатка технологии, а от недостаточного опыта и удобных инструментов.
В этом топике я расскажу, как, при помощи LibCanvas, сделать эту игру совершенно нетребовательной к процессору и отлично выглядящей.

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





Dirty Rectangles


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

Нам достаточно зарисовать старую клетку и очистить новую (по сути, можно было бы постоянно сохранять предыдущее место отрисовки фишки, но это не так критично).

var Tile = atom.Class({
	[...]
	redraw: function () {
		this.libcanvas.ctx
			.clearRect( this.lastPositionRectangle )
			.clearRect( this.field.emptyRectangle );
		this.draw()
	},
	[...]
})


Итак, теперь у нас есть код перерисовки фишки. Допустим, раньше наше приложение перерисовывало каждый кадр. Код передвижения фишки выглядел приблизительно так:

var Tile = atom.Class({
	[...]
	move: function (point) {
		// Блокируем поле, чтобы, пока не закончится передвижение, никто не двигал фишки
		this.field.blocked = true;
		this.animate({
			time: 150,
			props: { x: point.x, y: point.y },
			onFinish: function () {
				// Разблокируем поле
				this.field.blocked = false;
			},
			fn: 'sine-out'
		});
	},
	[...]
})


Мы отключаем автоматическую перерисовку каждого кадра и добавляем код, который заставляет каждую фишку перерисовывать себя при движении:
var Tile = atom.Class({
	[...]
	move: function (point) {
		// Блокируем поле, чтобы, пока не закончится передвижение, никто не двигал фишки
		this.field.blocked = true;
		this.animate({
			time: 150,
			props: { x: point.x, y: point.y },
			onProccess: this.redraw.bind(this),
			onFinish: function () {
				// Разблокируем поле
				this.field.blocked = false;
				this.redraw();
			},
			fn: 'sine-out'
		});
	},
	[...]
})


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

Буферизация


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

Для своего приложения можно грубо считать, что (program) == 'Бездействие системы'

Исправим это досадное недоразумение предварительной отрисовкой фишки в буфер. Создадим новый скрытый холст, отрисуем фишку в него и далее будем отрисовывать сам холст вместо вызова кучи тяжелых функций. Я для этого использую плагин atom.Class.Mutators.Generators — простой способ единожды сгенерировать объект и далее брать из кеша.
Допустим, раньше у нас был следующий код, который отрисовывал фишку:
var Tile = atom.Class({
	[...]
	draw: function () {
		this.callHardDrawFunctions( this.libcanvas.ctx );
	}
	[...]
})


Сменим его на следующий код:
var Tile = atom.Class({
	[...]
	Generators: {
		buffer: function () {
			var buffer = LibCanvas.buffer( this.shape, true );
			this.callHardDrawFunctions( buffer.ctx );
			return buffer;
		}
	},
	draw: function () {
		this.libcanvas.ctx.drawImage({
			image: this.buffer,
			draw : this.shape
		});
	}
	[...]
})


Да, оно стало немного менее изящно, но, зато¸ мы достигли цели, отрисовка наших пятнашек очень быстра и совершенно нетребовательна к ресурсам:


До введения буфера


После введения буфера

Заключение


Программируйте и наслаждайтесь результатом)

+55
5k 81
Comments 43