当我第一次听说Polymer时,就想到了我过去的Silverlight时代。 Silverlight使用XHTML进行标记,并使用C#进行代码。 Polymer与此类似,但是Polymer使用HTML和Javascript。 有关聚合物的介绍,请参见这篇出色的文章 。 在本教程中,我们将利用Web组件和出色的Yeoman生成器generator-polymer来构建经典的推箱子游戏,并使用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>
我们包含用来启用Polymer的platform.js
,并elements.html
进一步导入所有元素的elements.html。 请注意,它包装在build:vulcanized
构建块中,该块会将我们所有导入的元素连接到一个文件中。 最后,在body
添加自定义的Polymer元素。 我已经包含了我们将要构建的最后一个元素,即sokoban-ban
,您可以将其替换为其他子元素以在构建时对其进行测试。
自定义元素: sprite-el
我们将构建的第一个自定义元素是sprite元素,它将用作所有sprite的基础,例如盒子和播放器。 要添加自定义元素,请运行单个命令。
$ yo polymer:el sprite-el
这将创建elements/sprite-el
子文件夹,并添加两个文件sprite-el.html
和sprite-el.scss
。 它还会在elements.html
注入sprite-el.html
,基本上是为您做样板。
请参见由Yeoman注入到elements.html
的sprite-el.html
。
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
,并打开一个带有sprite-el
name属性的polymer-element
标签,该标签是必需的,并且必须包含-
。 接下来,我们有两个子标签, template
和script
。 template
包含我们自定义元素的标记。 在script
我们调用Polymer
函数启动自定义元素。 有关更多信息,请参阅文档 。
元素模板
在模板中,我们包括样式sprite-el.css
由咕噜从编译sprite-el.scss
。
接下来,我们有一个带有sprite
类和style
属性的div
。 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
,将代表盒子,墙壁和地面。 让我们再一次困扰Yeoman。
$ yo polymer:el box-el
游戏美术和精灵画面
所有游戏插图都来自1001.com,并已获得CC-BY-SA 4.0许可。 您可以在GitHub上找到所有sprites和完整的源代码。
我们有五个子画面框架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
,而是使用composition。 有关继承的更多信息,请参见此博客文章和此参考 。
我们在模板中包含sprite-el
,并为其分配属性。 还记得发布的属性frame
和position
吗? 在这里,我们通过属性分配它们。
生命周期方法
ready
生命周期方法是除已发布和计算的属性之外的一个额外属性box-el
。 当元素ready
时,将调用ready
生命周期方法,我们可以在此回调中分配额外的属性,在本例中,是frame
计算属性使用的boxCoords
。
自定义元素: sokoban-el
我们最终的自定义元素是推箱子游戏本身。 这将由我们的player-el
以及盒子,墙和地面元素组成。
游戏模型,游戏控制器和输入管理器
所有游戏逻辑都在GameController
类型内。 它生成游戏地图,并直接操纵游戏模型。 博弈模型是绑定到我们视图的数据,即聚合物元素。 因此, 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>
注意我们的Polymer元素player
和boxes
上的两个属性,我们将它们设置为模型。 您可以手动将它们设置为硬编码值,如在注释的代码中看到的那样,以进行测试。
迭代模板
boxes
属性是一个值数组。 我们可以为数组中的每个项目生成一个模板实例。 请注意template
标记和repeat
属性的使用,以遍历盒子的数组。 有关更多信息,请参见文档 。
触发自定义事件
您还可以使用fire
方法在Polymer元素内触发自定义事件。 在我们的情况下,游戏finished
时我们会触发一个finished
事件。 您可以如下所示监听事件。
document.querySelector('soko-ban')
.addEventListener('finished', function(e) {
alert('Congratz you have pushed all ' +
e.detail.target + ' boxes!');
});
发布它
我们使用generator-polymer
来构建我们的应用程序。 还有另一个生成器generator-element和Polymer样板模板,用于构建和发布自定义元素。 使用生成器构建自定义元素后,即可使用Bower发布它。 有关发布的更多信息,请在此处和此处查看这些优秀文章。
不要忘记将web-component
标签添加到bower.json
。 将其发布到Bower后,您的元素应该在Bower注册表中可用。 还要确保将其提交到customelements.io 。
了解更多和现场演示
在本教程中,我们通过构建Sokoban看到了Polymer在起作用。 通常,您不必构建自己的自定义元素,可以使用现有元素,将它们组成以构建更具吸引力的元素。 访问Web组件库,网址为customelements.io 。
您可以使用我们尚未介绍的Polymer来做更多事情,例如样式元素,观察属性等。有关更多信息,请访问API开发者指南 。 您可以在GitHub上找到该项目的完整源代码,并在我的网站上观看实时演示。