Stavba Sokobanu z polymeru

Když jsem poprvé slyšel o Polymeru, myslel jsem na své staré Silverlight časy. Silverlight použil XHTML pro označení a C# pro kód. Polymer je podobný, ale Polymer používá HTML a Javascript. Podívejte se na tento skvělý článek pro úvod do Polymeru. V tomto tutoriálu vytvoříme klasickou hru Sokoban, využijeme webové komponenty a vynikající generátor Yeoman, generátor-polymer a publikujeme ji pomocí Bower.

Nastavení polymeru

Nastavení projektu Polymer je stejně jednoduché jako následující dva příkazy:

$ npm install generator-polymer -g
$ yo polymer

Požádá vás o zahrnutí některých standardních součástí. Protože žádné nepotřebujeme, můžete říci všem ne.

Toto je vygenerovaná struktura složek. Všechny vlastní prvky jsou v app/elements složka.

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

Chcete-li zahájit vývoj, spusťte grunt serve . Bude sloužit index.html a sledujte, jak se soubory v reálném čase znovu načítají, jak se mění. Toto je index.html , zahrnul jsem pouze základní části pro použití polymeru.

<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>

Zahrnujeme platform.js aktivovat Polymer a importovat elements.html který dále importuje všechny naše prvky. Všimněte si, že je zabaleno do build:vulcanized build block, který spojí všechny naše importované prvky do jednoho souboru. Nakonec v body přidáváme naše vlastní polymerové prvky. Zahrnul jsem poslední prvek, který budeme stavět, sokoban-ban , můžete jej nahradit jinými dílčími prvky a otestovat je při vytváření.

Vlastní prvek:sprite-el

První vlastní prvek, který postavíme, je prvek sprite, který poslouží jako základ pro všechny sprity, jako jsou boxy a náš přehrávač. Chcete-li přidat vlastní prvek, spusťte jediný příkaz.

$ yo polymer:el sprite-el

Tím se vytvoří elements/sprite-el podsložku a přidejte dva soubory, sprite-el.html a sprite-el.scss . Vloží také sprite-el.html v elements.html , v podstatě to dělá za vás.

Viz sprite-el.html vloženo do elements.html od Yeoman.

File: elements/elements.html

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

Prohlášení prvku

Pojďme definovat náš vlastní prvek 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>

Nejprve zahrneme polymer.html a otevřete polymer-element tag s sprite-el atribut name, který je povinný a musí obsahovat - . Dále máme dvě dílčí značky, template a script . template obsahuje označení pro náš vlastní prvek. V rámci script nazýváme Polymer funkce pro spuštění vlastního prvku. Další informace naleznete v dokumentaci.

Šablona prvku

V šabloně zahrneme styl sprite-el.css který je zkompilován Gruntem z sprite-el.scss .

Dále máme div s sprite třída a style atribut. style atribut definuje top , left , height , width a background , styl, který určuje polohu a hranice skřítka a jeho obrazu. Tyto styly zařazujeme do textu, protože pro tyto atributy stylu musíme použít datovou vazbu.

Vazba dat, publikované a vypočítané vlastnosti

Vlastnosti prvku lze svázat přímo s pohledem pomocí výrazů Polymer, jako je {{posY}} , {{frame.height}} , {{spriteUrl}} .

posX a posY jsou definovány pod computed vlastnost, což znamená, že se jedná o vypočítané vlastnosti. Jsou to dynamické vlastnosti, které se počítají na základě jiných hodnot vlastností. V našem případě závisí na position.x a position.y takže kdykoli position změny vlastností jsou přepočítány a aktualizovány také v pohledu.

spriteUrl a frame jsou zveřejněné vlastnosti. To znamená, že tuto vlastnost činíte součástí „veřejného rozhraní API“ prvku. Uživatelé prvku je tedy mohou měnit. Publikované vlastnosti jsou také vázány na data a jsou přístupné pomocí {{}} .

Vlastní prvek:box-el

Dalším vlastním prvkem je prvek box, který se bude skládat z našeho sprite-el a bude představovat krabice, stěny a zem. Pojďme obtěžovat Yeomana ještě jednou.

$ yo polymer:el box-el

Game Art a Sprite Frames

Veškeré herní umění je převzato z 1001.com a je licencováno CC-BY-SA 4.0. Všechny sprity a úplný zdrojový kód najdete na GitHubu.

Máme pět snímků sprite – B pro krabice BD pro tmavé rámečky T pro cíl W pro stěny a G pro zem. Ve skutečnosti je lepší definovat pohyblivé rámečky a sprity na pozadí v samostatných vrstvách, ale pro jednoduchost je všechny zahrneme do jednoho prvku. Každý snímek definuje polohu snímku v listu sprite a také jeho výšku a šířku.

Pojďme definovat náš vlastní prvek 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>

Dědičnost a složení

Krabice a prvky hráče budou používat základní prvek sprite. Existují dva způsoby, jak toho dosáhnout, pomocí dědičnosti nebo složení. Nebudeme prodlužovat sprite-el , ale raději použijte kompozici. Další informace o dědictví naleznete v tomto příspěvku na blogu a v této referenci.

Zahrnujeme sprite-el v naší šabloně a přiřaďte jí atributy. Pamatujte na publikované vlastnosti frame a position ? Zde je přiřadíme pomocí atributů.

Metody životního cyklu

Jedna vlastnost navíc box-el má jiné než publikované a vypočítané vlastnosti je ready metoda životního cyklu. ready metoda životního cyklu se volá, když je prvek plně připraven, v tomto zpětném volání můžeme přiřadit další vlastnosti, v našem případě je to boxCoords který používá frame vypočítaná vlastnost.

Vlastní prvek:sokoban-el

Naším posledním vlastním prvkem je samotná hra Sokoban. To se bude skládat z našeho player-el a krabicové, stěnové a pozemní prvky.

Herní model, herní ovladač a správce vstupu

Veškerá logika hry je uvnitř GameController typ. Generuje herní mapu a přímo manipuluje s herním modelem. Herním modelem jsou data vázaná na náš pohled, tedy polymerní prvek. Takže všechny změny v modelu provedené GameController se v zobrazení automaticky aktualizuje. V tomto článku se nebudu rozepisovat o logice hry, další podrobnosti si můžete prohlédnout v úplném zdrojovém kódu.

Zpracování uživatelského vstupu lze provést pomocí deklarativního mapování událostí. Ale přesto existují určitá upozornění. Viz tato otázka na Stack Overflow. Použil jsem tedy vlastní typ pro zpracování vstupu, KeyboardInputManager .

Pojďme definovat náš vlastní prvek 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>

Všimněte si dvou vlastností našeho polymerního prvku player a boxes , nastavili jsme je do našeho modelu. Pro účely testování je můžete ručně nastavit na pevně zakódované hodnoty, jak můžete vidět v komentovaném kódu.

Opakované šablony

boxes vlastnost je pole hodnot. Pro každou položku v poli můžeme vygenerovat jednu instanci šablony. Všimněte si použití template tag a repeat atribut pro iteraci přes pole polí. Další informace naleznete v dokumentaci.

Spouštění vlastních událostí

Můžete také spouštět vlastní události v rámci prvku Polymer pomocí fire metoda. V našem případě spustíme finished událost, kdy hra skončí. Události můžete poslouchat, jak je uvedeno níže.

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

Publikovat

Použili jsme generator-polymer pro vytvoření naší aplikace. K dispozici je také další generátor, prvek generátoru a šablona Polymer pro vytváření a publikování vlastních prvků. Jakmile vytvoříte svůj vlastní prvek pomocí generátoru, můžete jej publikovat pomocí Bower. Další informace o publikování naleznete v těchto vynikajících článcích zde a zde.

Nezapomeňte přidat web-component tag na váš bower.json . Jakmile jej publikujete Bowerovi, měl by být váš prvek dostupný v registru Bower. Nezapomeňte jej také odeslat na adresu customelements.io.

Zjistěte více a živá ukázka

V tomto tutoriálu jsme viděli Polymer v akci při stavbě Sokobanu. Obecně platí, že nemusíte vytvářet své vlastní prvky, můžete použít stávající a skládat je a vytvářet tak poutavější prvky. Navštivte galerii webových komponent na adrese customelements.io.

S Polymerem můžete udělat více, o čem jsme se nezabývali, jako jsou prvky stylů, pozorování vlastností atd. Další informace naleznete v příručce pro vývojáře API. Úplný zdrojový kód tohoto projektu najdete na GitHubu a na mém webu si můžete prohlédnout živou ukázku.