Статьи

Здание Сокобан с Полимером

Когда я впервые услышал о Polymer, я подумал о моих старых днях Silverlight. Silverlight использовал XHTML для разметки и C # для кода. Polymer похож, но Polymer использует HTML и Javascript. Смотрите эту прекрасную статью для введения в Polymer. В этом руководстве мы создадим классическую игру Sokoban, используя веб-компоненты и отличный генератор Yeoman, генератор-полимер , и опубликуем ее с помощью Bower.

Настройка Полимер

Настройка проекта Polymer так же проста, как следующие две команды:

$ npm install generator-polymer -g $ yo polymer 

Он попросит вас включить некоторые стандартные компоненты. Поскольку нам это не нужно, вы можете сказать «нет» всем.

Это сгенерированная структура папок. Все пользовательские элементы находятся в папке app/elements .

 . |-- Gruntfile.js |-- app | |-- elements | | |-- elements.html | | |-- soko-ban | | | |-- soko-ban.html | | | `-- soko-ban.scss | |-- index.html | |-- scripts | | |-- app.js |-- bower.json `-- package.json 

Чтобы начать разработку, запустите grunt serve . Он будет обслуживать index.html и будет следить за тем, чтобы файлы не перезагружались при их изменении Это index.html , я включил только основные части для использования Polymer.

 <html> <head> <script src="bower_components/platform/platform.js"></script> <!-- build:vulcanized elements/elements.vulcanized.html --> <link rel="import" href="elements/elements.html"> <!-- endbuild --> </head> <body unresolved> <div class="game-container"> <!-- insert your elements here --> <soko-ban></soko-ban> </div> <script src="scripts/app.js"></script> </body> </html> 

Мы включаем platform.js для включения Polymer и импортируем elements.html который далее импортирует все наши элементы. Обратите внимание, что он обернут в build:vulcanized блок сборки, который объединит все наши импортированные элементы в один файл. Наконец, в body мы добавляем наши пользовательские элементы Polymer. Я включил последний элемент, который мы будем создавать, sokoban-ban , вы можете заменить его другими подэлементами, чтобы проверить их при сборке.

Пользовательский элемент: sprite-el

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

 $ yo polymer:el sprite-el 

Это создаст подпапку elements/sprite-el и добавит два файла, sprite-el.html и sprite-el.scss . Он также sprite-el.html в elements.html , в основном выполняя шаблон для вас.

Смотрите sprite-el.html в elements.html Yeoman.

File: elements/elements.html

 <link rel="import" href="sprite-el/sprite-el.html"> 

Элемент Декларации

Давайте определим наш пользовательский элемент sprite-el .

 <link rel="import" href="../../bower_components/polymer/polymer.html"> <polymer-element name="sprite-el"> <template> <link rel="stylesheet" href="sprite-el.css"> <div class="sprite" style="top: {{posY}}px; left: {{posX}}px; height: {{frame.height}}px; width: {{frame.width}}px; background: url({{spriteUrl}}) {{frame.x}}px {{frame.y}}px"> </div> </template> <script> (function () { 'use strict'; Polymer({ publish: { spriteUrl: 'images/sprites.png', frame: { x: 0, y: 0 }, position: { x: 0, y: 0 }, computed: { posX: 'position.x * 64', posY: 'position.y * 64' } } }); })(); </script> </polymer-element> 

Сначала мы включаем polymer.html и открываем тег polymer-element с атрибутом имени sprite-el , который является обязательным и должен включать - . Далее у нас есть два вложенных тега, template и script . template содержит разметку для нашего пользовательского элемента. В script мы вызываем функцию Polymer для запуска пользовательского элемента. Для получения дополнительной информации см. Документацию .

Шаблон элемента

В шаблон мы включили стиль sprite-el.css , скомпилированный Grunt из sprite-el.scss .

Далее у нас есть div с классом sprite и атрибутом style . Атрибут style определяет top , left , height , width и background , стиль для определения положения и границ спрайта и его изображения. Мы включили эти стили встроенными, потому что мы должны использовать привязку данных для этих атрибутов стиля.

Связывание данных, опубликованные и вычисленные свойства

Свойства элемента могут быть связаны непосредственно с представлением с помощью выражений Polymer , таких как {{posY}} , {{frame.height}} , {{spriteUrl}} .

posX и posY определены в computed свойстве, которое указывает, что это вычисляемые свойства . Это динамические свойства, которые вычисляются на основе других значений свойств. В нашем случае они зависят от position.x и position.y поэтому всякий раз, когда изменяется свойство position они также пересчитываются и обновляются в представлении.

spriteUrl и frame являются опубликованными свойствами . Это означает, что вы делаете это свойство частью «открытого API» элемента. Таким образом, пользователи элемента могут изменить их. Опубликованные свойства также привязаны к данным и доступны через {{}} .

Таможенный элемент: box-el

Следующий пользовательский элемент — это элемент box, он будет состоять из нашего sprite-el и будет представлять коробки, стены и землю. Давай побеспокоим Йомана еще раз.

 $ yo polymer:el box-el 

Искусство игры и рамки спрайтов

Все игровое искусство взято с сайта 1001.com и лицензировано CC-BY-SA 4.0. Вы можете найти все спрайты и полный исходный код на GitHub .

У нас есть пять спрайтов — B для ящиков, BD для темных ящиков, T для мишени, W для стен и G для земли. На самом деле лучше определять движущиеся блоки и фоновые спрайты в отдельных слоях, но для простоты мы включаем их все в один элемент. Каждый кадр определяет положение кадра в спрайт-листе, а также его высоту и ширину.

Давайте определим наш пользовательский элемент box-el :

 <polymer-element name="box-el"> <template> <link rel="stylesheet" href="box-el.css"> <sprite-el frame="{{frame}}" position="{{model.position}}" style="height: {{frame.height}}px; width: {{frame.width}}px;"></sprite-el> </template> <script> (function () { 'use strict'; Polymer({ publish: { model: { position: { x: 0, y: 0 }, type: 'W' } }, computed: { frame: 'boxCoords[model.type]' }, ready: function() { this.boxCoords = { "B": { x:"-192", y:"0", width:"64", height:"64" }, "BD": { x:"-128", y:"-256", width:"64", height:"64" }, "T": { x:"-64", y:"-384", width:"32", height:"32" }, "W": { x:"0", y:"-320", width:"64", height:"64" }, "G": { x:"-64", y:"-256", width:"64", height:"64" } }; } }); })(); </script> </polymer-element> 

Наследование и состав

Элементы box и player будут использовать базовый элемент sprite. Есть два способа сделать это, используя наследование или композицию. Мы не будем расширять sprite-el , а будем использовать композицию. Для получения дополнительной информации о наследовании см. Этот пост в блоге и эту ссылку .

Мы включаем sprite-el в наш шаблон и присваиваем его атрибуты. Помните опубликованные свойства frame и position ? Здесь мы назначаем их через атрибуты.

Методы жизненного цикла

Одно дополнительное свойство box-el имеет не только опубликованные и вычисляемые свойства, но и метод ready жизненного цикла . Метод ready жизненного цикла вызывается, когда элемент полностью подготовлен, мы можем назначить дополнительные свойства в этом boxCoords , в нашем случае это boxCoords который используется в вычисляемом frame свойстве.

Таможенный элемент: sokoban-el

Наш последний элемент — сама игра Sokoban. Он будет состоять из элементов нашего player-el , элементов бокса, стены и земли.

Модель игры, игровой контроллер и менеджер ввода

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

Обработка пользовательского ввода может быть выполнена с использованием декларативного отображения событий . Но все же есть некоторые предостережения. Смотрите этот вопрос на переполнение стека . Поэтому я использовал пользовательский тип для обработки ввода, KeyboardInputManager .

Давайте определим наш пользовательский элемент soko-ban :

 <polymer-element name="soko-ban"> <template> <link rel="stylesheet" href="soko-ban.css"> <template repeat="{{box in boxes}}"> <box-el model="{{box}}"></box-el> </template> <player-el model="{{player}}" id="character"></player-el> </template> <script> (function () { 'use strict'; Polymer({ ready: function() { var controller = new GameController(); var model = controller.getModel(); /** Sample Model **/ /** this.player = { position: { x: 0, y: 0 } }; this.boxes = [ { type: 'W', position: { x: 10, y: 10 } }, { type: 'WD', position: { x: 10, y: 100 } } ]; */ this.player = model.player; this.boxes = model.boxes; var inputManager = new KeyboardInputManager(); var char = this.$.character; inputManager.on('move', function(val) { switch (val) { case KeyboardInputManager.Direction.UP: controller.move(GameController.Direction.UP); break; case KeyboardInputManager.Direction.RIGHT: controller.move(GameController.Direction.RIGHT); break; case KeyboardInputManager.Direction.DOWN: controller.move(GameController.Direction.DOWN); break; case KeyboardInputManager.Direction.LEFT: controller.move(GameController.Direction.LEFT); break; } if (controller.isGameOver()) { this.fire('finished', { target: model.target }); } }.bind(this)); } }); })(); </script> </polymer-element> 

Обратите внимание на два свойства нашего player и boxes Polymer, мы установили их для нашей модели. Вы можете вручную установить для них жестко закодированные значения, как вы можете видеть в прокомментированном коде, для целей тестирования.

Итерационные шаблоны

Свойство boxes является массивом значений. Мы можем сгенерировать один экземпляр шаблона для каждого элемента в массиве. Обратите внимание на использование тега template и атрибута repeat для перебора массива блоков. Смотрите документацию для получения дополнительной информации.

Увольнение пользовательских событий

Вы также можете запускать пользовательские события в вашем элементе Polymer, используя метод fire . В нашем случае мы запускаем finished событие, когда игра заканчивается. Вы можете прослушивать события, как показано ниже.

 document.querySelector('soko-ban') .addEventListener('finished', function(e) { alert('Congratz you have pushed all ' + e.detail.target + ' boxes!'); }); 

Опубликуйте это

Мы использовали generator-polymer для построения нашего приложения. Существует также еще один генератор, генератор-элемент и шаблонный шаблон Polymer для создания и публикации пользовательских элементов. После того, как вы создали свой пользовательский элемент с генератором, вы можете опубликовать его с помощью Bower. Для получения дополнительной информации о публикации см. Эти прекрасные статьи, здесь и здесь .

Не забудьте добавить тег web-component в ваш bower.json . После того, как вы опубликовали его в Bower, ваш элемент должен быть доступен в реестре Bower . Также обязательно отправьте его на customelements.io .

Узнайте больше и Live Demo

В этом уроке мы увидели Polymer в действии, создав Sokoban. Как правило, вам не нужно создавать свои собственные элементы, вы можете использовать существующие, составляя их для создания более привлекательных элементов. Посетите галерею веб-компонентов по адресу customelements.io .

Вы можете сделать больше с Polymer, который мы не рассмотрели, например, элементы стилей, свойства наблюдения и т. Д. Для получения дополнительной информации см. Руководство разработчика API . Вы можете найти полный исходный код этого проекта на GitHub и посмотреть живую демонстрацию на моем сайте .