Sokoban bouwen met polymeer

Toen ik voor het eerst over Polymer hoorde, dacht ik aan mijn oude Silverlight-dagen. Silverlight gebruikte XHTML voor opmaak en C# voor de code. Polymer is vergelijkbaar, maar Polymer gebruikt HTML en Javascript. Zie dit uitstekende artikel voor een inleiding tot Polymer. In deze tutorial zullen we het klassieke Sokoban-spel bouwen, gebruikmakend van het gebruik van webcomponenten en een uitstekende Yeoman-generator, generator-polymeer, en deze publiceren met Bower.

Polymeer instellen

Het opzetten van een Polymer-project is zo eenvoudig als de volgende twee opdrachten:

$ npm install generator-polymer -g
$ yo polymer

Het zal u vragen om enkele standaardcomponenten op te nemen. Omdat we er geen nodig hebben, kun je tegen iedereen nee zeggen.

Dit is de gegenereerde mappenstructuur. Alle aangepaste elementen staan ​​in app/elements map.

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

Voer grunt serve uit om de ontwikkeling te starten . Het wordt geserveerd index.html en bekijk de bestanden voor live-herladen als ze veranderen. Dit is de index.html , Ik heb alleen de essentiële onderdelen toegevoegd om Polymer te gebruiken.

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

We nemen platform.js op om Polymer in te schakelen en elements.html te importeren dat verder al onze elementen importeert. Merk op dat het verpakt is in een build:vulcanized buildblok dat al onze geïmporteerde elementen in één bestand samenvoegt. Eindelijk in de body we voegen onze aangepaste polymeerelementen toe. Ik heb het laatste element toegevoegd dat we gaan bouwen, sokoban-ban , kunt u deze vervangen door de andere subelementen om ze tijdens het bouwen te testen.

Aangepast element:sprite-el

Het eerste aangepaste element dat we zullen bouwen, is een sprite-element, dit zal dienen als basis voor alle sprites, zoals dozen en onze speler. Voer een enkele opdracht uit om een ​​aangepast element toe te voegen.

$ yo polymer:el sprite-el

Hiermee wordt de elements/sprite-el . gemaakt submap en voeg twee bestanden toe, sprite-el.html en sprite-el.scss . Het zal ook sprite-el.html . injecteren in elements.html , in feite de standaardtekst voor je doen.

Zie sprite-el.html geïnjecteerd in elements.html door Yeoman.

File: elements/elements.html

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

Elementverklaring

Laten we ons aangepaste element sprite-el definiëren .

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

Eerst voegen we polymer.html . toe , en open een polymer-element tag, met sprite-el name-attribuut, dat vereist is en een - . moet bevatten . Vervolgens hebben we twee sub-tags, template en script . template bevat opmaak voor ons aangepaste element. Binnen script we noemen de Polymer functie om het aangepaste element op te starten. Zie de documentatie voor meer info.

Elementsjabloon

In de sjabloon nemen we de stijl sprite-el.css op dat is samengesteld door Grunt van sprite-el.scss .

Vervolgens hebben we een div met een sprite klasse, en style attribuut. style attribuut definieert top , left , height , width , en background , styling om de positie en grenzen van de sprite en zijn afbeelding te bepalen. We nemen deze stijlen inline op omdat we gegevensbinding moeten gebruiken voor deze stijlkenmerken.

Databindende, gepubliceerde en berekende eigenschappen

Eigenschappen op het element, kunnen direct in de weergave worden gebonden, met polymeerexpressies, zoals {{posY}} , {{frame.height}} , {{spriteUrl}} .

posX en posY zijn gedefinieerd onder computed eigenschap, wat aangeeft dat dit berekende eigenschappen zijn. Het zijn dynamische eigenschappen die worden berekend op basis van andere eigenschapswaarden. In ons geval zijn ze afhankelijk van position.x en position.y dus wanneer position eigenschapswijzigingen ze worden ook opnieuw berekend en bijgewerkt in de weergave.

spriteUrl en frame zijn gepubliceerde eigenschappen. Dat betekent dat u die eigenschap onderdeel maakt van de "openbare API" van het element. De gebruikers van het element kunnen ze dus wijzigen. Gepubliceerde eigenschappen zijn ook gegevensgebonden en zijn toegankelijk via {{}} .

Aangepast element:box-el

Het volgende aangepaste element is een dooselement, dit zal bestaan ​​uit onze sprite-el , en zal de dozen, muren en de grond vertegenwoordigen. Laten we Yeoman nog een keer lastig vallen.

$ yo polymer:el box-el

Game Art en Sprite Frames

Alle game-art is afkomstig van 1001.com en heeft een CC-BY-SA 4.0-licentie. Je kunt alle sprites en volledige broncode vinden op GitHub.

We hebben vijf sprite-frames - B voor dozen, BD voor donkere dozen, T voor doel, W voor muren, en G voor grond. Het is eigenlijk beter om verhuisdozen en achtergrondsprites in afzonderlijke lagen te definiëren, maar voor de eenvoud nemen we ze allemaal op in één element. Elk frame definieert de framepositie in het sprite-blad, evenals de hoogte en breedte.

Laten we ons aangepaste element box-el definiëren :

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

Overerving en samenstelling

De box en de player-elementen zullen het basis sprite-element gebruiken. Er zijn twee manieren om dit te doen, met behulp van overerving of samenstelling. We zullen sprite-el niet verlengen , maar gebruik liever compositie. Zie deze blogpost en deze referentie voor meer informatie over overerving.

We bevatten sprite-el in onze sjabloon en wijs de kenmerken ervan toe. Onthoud de gepubliceerde eigenschappen frame en position ? Hier wijzen we ze toe via attributen.

Levenscyclusmethoden

Een extra eigenschap box-el andere dan gepubliceerde en berekende eigenschappen heeft is ready levenscyclus methode. ready lifecycle-methode wordt aangeroepen wanneer het element volledig is voorbereid, we kunnen extra eigenschappen toewijzen in deze callback, in ons geval is het boxCoords die wordt gebruikt door frame berekende eigenschap.

Aangepast element:sokoban-el

Ons laatste aangepaste element is het Sokoban-spel zelf. Dit zal bestaan ​​uit onze player-el , en doos-, muur- en grondelementen.

Spelmodel, gamecontroller en invoerbeheer

Alle spellogica zit in GameController type. Het genereert de spelkaart en manipuleert rechtstreeks het spelmodel. Het spelmodel is data gebonden aan onze visie, dat is het polymeerelement. Dus alle wijzigingen aan het model gemaakt door GameController wordt automatisch bijgewerkt in de weergave. Ik zal niet in detail treden over de spellogica in dit artikel, je kunt de volledige broncode bekijken voor meer details.

Het verwerken van gebruikersinvoer kan worden gedaan met behulp van declarative event mapping. Maar toch zijn er enkele kanttekeningen. Zie deze vraag op Stack Overflow. Dus ik gebruikte een aangepast type om invoer te verwerken, KeyboardInputManager .

Laten we ons aangepaste element soko-ban definiëren :

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

Let op de twee eigenschappen van ons polymeerelement player en boxes , hebben we ze ingesteld op ons model. Je kunt ze handmatig instellen op hard gecodeerde waarden, zoals je kunt zien in de becommentarieerde code, voor testdoeleinden.

Iteratieve sjablonen

De boxes eigenschap is een reeks waarden. We kunnen een enkele sjablooninstantie genereren voor elk item in de array. Let op het gebruik van de template tag en repeat attribuut om de reeks vakken te herhalen. Zie de documentatie voor meer informatie.

Aangepaste gebeurtenissen activeren

U kunt ook aangepaste gebeurtenissen binnen uw Polymer-element activeren met behulp van de fire methode. In ons geval vuren we een finished evenement wanneer het spel is afgelopen. Je kunt naar evenementen luisteren zoals hieronder weergegeven.

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

Publiceer het

We gebruikten generator-polymer voor het bouwen van onze applicatie. Er is ook nog een generator, een generatorelement en een Polymer-boilerplate-sjabloon voor het bouwen en publiceren van aangepaste elementen. Nadat u uw aangepaste element met de generator hebt gebouwd, kunt u het publiceren met Bower. Bekijk deze uitstekende artikelen hier en hier voor meer informatie over publiceren.

Vergeet niet de web-component . toe te voegen tag naar uw bower.json . Zodra u het naar Bower hebt gepubliceerd, zou uw element beschikbaar moeten zijn in het Bower-register. Zorg er ook voor dat u het verzendt naar customelements.io.

Meer informatie en live demo

In deze tutorial hebben we Polymer in actie gezien door Sokoban te bouwen. Over het algemeen hoef je niet je eigen aangepaste element te bouwen, je kunt bestaande elementen gebruiken om ze samen te stellen om boeiendere elementen te bouwen. Bezoek de galerij met webcomponenten op customelements.io.

U kunt meer doen met Polymer die we niet hebben behandeld, zoals stijlelementen, observatie-eigenschappen, enz. Ga voor meer informatie naar de API-ontwikkelaarsgids. Je kunt de volledige broncode voor dit project vinden op GitHub en een live demo bekijken op mijn site.