Когда я впервые услышал о 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 и посмотреть живую демонстрацию на моем сайте .