Пятнашки на 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
    		});
    	}
    	[...]
    })
    


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


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


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

    Заключение


    Программируйте и наслаждайтесь результатом)
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 43

      0
      В некотором смысле края после перемещения странно отображаются в FireFox и Chome (в других не проверял)
        +2
        Простите, забыл пушнуть обновлённый код. Исправил)
        +1
        Результат 2 раза попробовал собрать, не получилось. Оба раза получалась несобираемая комбинация предложенная Ноем Чепменом — это спецом такой прикол сделан? :)
          0
          Нет. На самом деле я мало отдавал сил самому геймплею. Мне, как автору фреймворка, куда интереснее технологическая составляющая, потому кое-какие ошибки в алгоритмах игры и геймплее могут быть) И реализация таких вещей, как очки или корректная генерация, бывает, остаются за бортом. Если у кого будет желание довести игру до ума — буду рад)
          +1
          да, это несобираемые пятнашки :)
            0
            Хатеть приз
              0
              habrastorage.org/storage/9e93b10a/414ad2a2/735b859d/4a944d3f.png

              Я не понимаю как вставлять изображения, хабр их просто отказывается отображать через img
              0
              Спасибо, я благодаря Вам узнал что это все таки были несобираемые комбинации а не мои кривые руки!
              +1
              Отлично, что сказать.
              В своей реализации SwellFoop (которую недавно обсуждали с Вами) при использовании atom.js и libcanvas.js вышло бы гораздо меньше кода. Теперь точно переделаю, появился стимул :)
                0
                Будут вопросы — обязательно обращайтесь)
                +2
                Я не устану высказывать вам благодарности за ваши посты. Спасибо!
                  +1
                  Спасибо, приятно слышать)
                  0
                  Наш ответ «процессорам надому»! :)
                  Atom-то, кстати, развиваете?
                  0
                  А нельзя буферизацию примитива, aka display list и некий глобальный clipRect зашить в сам libCanvas
                  В том смысле — а что будет если у меня две фишки анимируются в одной области?
                  Надо найти изменяемые области, найти все объекты в этой области, настроить правильный клипинг и отрисовать в нужной последовательности.
                  Руками это сделать не так чтобы просто :(
                    0
                    Вообще не так тяжело. Необходимо придумать красивый интерфейс и можно сделать.
                    Когда изменяется какой-то объект — проверяем все другие объекты на пересечение object.shape, если найдены — еще и так далее.
                    Впринципе, штук 10 строчек кода. Я подумаю)
                    +3
                    Собрал и… и где поздравления? Система абсолютно никак не среагировала, что все расставлено правильно.
                    К канвасу отношения не имеет, а тестерам приятно!
                    0
                    Неделя пятнашек на Хабре
                      +2
                      Дык напокупали себе тёмных изданий Portal 2 :)
                      0
                      В чём профит использования Canvas для данной задачи?
                        +1
                        Обучение, высокое быстродействие, настраиваемый внешний вид.
                          0
                          Мне нравится аргумент обучение.
                          А по поводу быстродействия — у меня не плавные движения.
                          Думаю, обычный html отработал бы быстрее.
                            0
                            А вы попробуйте))
                            пс. какой браузер?
                              0
                              хорошо, попробую :-)

                              Firefox 4.0.1, Ubuntu 11.04
                              ноутбук с двухядерным Celeron
                            0
                            По поводу быстродействия и внешнего вида: а не лучше было бы сделать 1в1 тоже самое на CSS3 (градиенты+транзишены) + минимум яваскрипта, которому нужно только менять классы?

                            *задумался о реализации*
                              0
                              А вы попробуйте) Градиенты — это очень тугое место.
                                +1
                                Просю :)

                                olostan.name/temp/fifteen.html (сорсы gist.github.com/954605 )

                                Как и хотелось — вся анимация — транзишенами. Градиенты — цсс (генерил на www.colorzilla.com/gradient-editor/ ). Прошу обратить внимание на плавный зум в хроме :)

                                Проверял работу в хроме и фф4.

                                Стоит сделать топик о том, как я делал это? :)
                                  +1
                                  Хе, увлекся :)

                                  Сделал сохранение состояние в хеше. Т.е. можно

                                  1. передавать другим ссылки, типа «а ты можешь решить вот такой расклад
                                  2. кнопки в браузере назад-вперед работают.
                                  3. детект на победу

                                  код написан «на коленке» (точнее gedit'ом) так что прошу сильно не придерется :)
                                    0
                                    Прикольно, в современных браузерах работает приятно, но я готов Вас огорчить)

                                    Старые браузеры (например Fx3.6, Opera10.6) — работает некорректно (решение в топике работает корректно даже в Fx3.5). Но это не так важно и через два года будет уже неактуально, согласен)

                                    Но основная проблема, которая вылезет только в реальном приложении и не отображается в тестовом примере — другая.
                                    Restyle-reflow-repaint-restyle-reflow-repaint-restyle-reflow-repaint.

                                    Производительность очень зависит от вёрстки

                                    В то же время решение из топика вызывать только repaint.
                                      0
                                      Ну я делал без единого «закоса» для оптимизации — чисто как «пруф-оф-коцепт» идеи сделать то же самое на CSS3.

                                      К примеру, вместо того, чтоб менять класс (как я сделал — из «col1» -> «col2» при перемещении из колонки 1 в 2) делать реальный здвиг (менять «left») и убрать live который там не нужен ваабще.

                                      В общем оптимизировать там много чего, но идея использовать встроенные функции браузера для анимации имхо ничего так.
                                        0
                                        Ну дык, как ты не оптимизируй, всё-равно от restyle-reflow-repaint для передвижения элемента не избавишься) Сейчас то оно нормально работает и при слабой вёрстке будет нормально работать, но стоит сделать вёрстку такой, чтобы reflow занимал существенное время — fps сразу упадёт и проц будет загружен.

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

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

                                          ПС. Под iOS (iPad2) немного фликает прорисовка в канвас-реализации, при чем все поле — может это как-то пофиксить можно?
                                            0
                                            Reflow будет независим от браузера. То есть когда что-то меняется в игре — не перерисовывается весь сайт.

                                            Что значит «фликает»? У меня на Wildfire работает вменяемо, думаю, что iPad помощнее будет.
                                  +1
                                  думаю, это сделано в первую очередь для демонстрации и пиара libcanvas, автором которой является топикстартер:) Разумеется, есть много способов сделать пятнашки, но суть поста в том, что с помощью этой библиотеки, решить эту задачу можно быстро и просто.
                              0
                              Все эти затирания следов и отрисовка поверх напоминает мне первые программы на турбо паскале с использованием graph.tpu :)
                                +1
                                Ну, оно везде так, даже сейчас) Просто кое-где автоматизированно)
                                  0
                                  Зачем затирать? SetWriteMode (XORPut) :-)

                                  Кстати, брать кусочек из шаблона — это удобно даже в том смысле, что потом можно будет картинку на фон повесить.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое