Creazione di una spada laser con Polymer

Screenshot con spada laser

Riepilogo

Come abbiamo usato Polymer per creare una spada laser ad alte prestazioni controllata da dispositivi mobile, modulare e configurabile. Esaminiamo alcuni dettagli chiave del nostro progetto https://lightsaber.withgoogle.com/ per aiutarti a risparmiare tempo quando crei il tuo prossimo video in cerca di assalti infuriati.

Panoramica

Se ti stai chiedendo cosa pensi di Polymer o WebComponenti, abbiamo pensato che sarebbe meglio iniziare condividendo un estratto di un vero progetto di lavoro. Ecco un esempio tratto dalla pagina di destinazione del nostro progetto https://lightsaber.withgoogle.com. È un normale file HTML ma contiene un po' di magia:

<!-- Element-->
<dom-module id="sw-page-landing">
    <!-- Template-->
    <template>
    <style>
        <!-- include elements/sw/pages/sw-page-landing/styles/sw-page-landing.css-->
    </style>
    <div class="centered content">
        <sw-ui-logo></sw-ui-logo>
        <div class="connection-url-wrapper">
        <sw-t key="landing.type" class="type"></sw-t>
        <div id="url" class="connection-url">.</div>
        <sw-ui-toast></sw-ui-toast>
        </div>
    </div>
    <div class="disclaimer epilepsy">
        <sw-t key="disclaimer.epilepsy" class="type"></sw-t>
    </div>
    <sw-ui-footer state="extended"></sw-ui-footer>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-page-landing.js"></script>
</dom-module>

Esistono quindi molte opzioni per creare un'applicazione basata su HTML5. API, framework, librerie, motori di gioco ecc. Nonostante tutte le scelte, è difficile ottenere una configurazione che sia una buona combinazione tra controllo su prestazioni elevate della grafica e struttura e scalabilità modulare pulite. Abbiamo scoperto che Polymer poteva aiutarci a mantenere il progetto organizzato pur consentendo ottimizzazioni delle prestazioni di basso livello. Abbiamo quindi realizzato con cura il modo in cui abbiamo suddiviso il nostro progetto in componenti per sfruttare al meglio le capacità di Polymer.

Modularità con polimero

Polymer è una libreria che offre molta potenza sul modo in cui il progetto viene creato a partire da elementi personalizzati riutilizzabili. Consente di utilizzare moduli autonomi e completamente funzionali contenuti in un singolo file HTML. Non solo contengono la struttura (markup HTML), ma anche la logica e gli stili incorporati.

Osserva il seguente esempio:

<link rel="import" href="bower_components/polymer/polymer.html">

<dom-module id="picture-frame">
    <template>
    <!-- scoped CSS for this element -->
    <style>
        div {
        display: inline-block;
        background-color: #ccc;
        border-radius: 8px;
        padding: 4px;
        }
    </style>
    <div>
        <!-- any children are rendered here -->
        <content></content>
    </div>
    </template>

    <script>
    Polymer({
        is: "picture-frame",
    });
    </script>
</dom-module>

Tuttavia, in un progetto più grande potrebbe essere utile separare questi tre componenti logici (HTML, CSS, JS) e unirli solo al momento della compilazione. Una cosa che abbiamo fatto è stato assegnare a ogni elemento del progetto una cartella separata:

src/elements/
|-- elements.jade
`-- sw
    |-- debug
    |   |-- sw-debug
    |   |-- sw-debug-performance
    |   |-- sw-debug-version
    |   `-- sw-debug-webgl
    |-- experience
    |   |-- effects
    |   |-- sw-experience
    |   |-- sw-experience-controller
    |   |-- sw-experience-engine
    |   |-- sw-experience-input
    |   |-- sw-experience-model
    |   |-- sw-experience-postprocessor
    |   |-- sw-experience-renderer
    |   |-- sw-experience-state
    |   `-- sw-timer
    |-- input
    |   |-- sw-input-keyboard
    |   `-- sw-input-remote
    |-- pages
    |   |-- sw-page-calibration
    |   |-- sw-page-connection
    |   |-- sw-page-connection-error
    |   |-- sw-page-error
    |   |-- sw-page-experience
    |   `-- sw-page-landing
    |-- sw-app
    |   |-- bower.json
    |   |-- scripts
    |   |-- styles
    |   `-- sw-app.jade
    |-- system
    |   |-- sw-routing
    |   |-- sw-system
    |   |-- sw-system-audio
    |   |-- sw-system-config
    |   |-- sw-system-environment
    |   |-- sw-system-events
    |   |-- sw-system-remote
    |   |-- sw-system-social
    |   |-- sw-system-tracking
    |   |-- sw-system-version
    |   |-- sw-system-webrtc
    |   `-- sw-system-websocket
    |-- ui
    |   |-- experience
    |   |-- sw-preloader
    |   |-- sw-sound
    |   |-- sw-ui-button
    |   |-- sw-ui-calibration
    |   |-- sw-ui-disconnected
    |   |-- sw-ui-final
    |   |-- sw-ui-footer
    |   |-- sw-ui-help
    |   |-- sw-ui-language
    |   |-- sw-ui-logo
    |   |-- sw-ui-mask
    |   |-- sw-ui-menu
    |   |-- sw-ui-overlay
    |   |-- sw-ui-quality
    |   |-- sw-ui-select
    |   |-- sw-ui-toast
    |   |-- sw-ui-toggle-screen
    |   `-- sw-ui-volume
    `-- utils
        `-- sw-t

La cartella di ogni elemento ha la stessa struttura interna, con directory e file separati per logica (file caffè), stili (file scss) e modello (file jade).

Ecco un esempio di elemento sw-ui-logo:

sw-ui-logo/
|-- bower.json
|-- scripts
|   `-- sw-ui-logo.coffee
|-- styles
|   `-- sw-ui-logo.scss
`-- sw-ui-logo.jade

E se esamini il file .jade:

// Element
dom-module(id='sw-ui-logo')

    // Template
    template
    style
        include elements/sw/ui/sw-ui-logo/styles/sw-ui-logo.css

    img(src='[[url]]')

    // Polymer element script
    script(src='scripts/sw-ui-logo.js')

Puoi vedere come sono organizzati in modo chiaro includendo stili e logica in file separati. Per includere i nostri stili nei nostri elementi Polymer, utilizziamo l'istruzione include di Jade, in modo da avere i contenuti effettivi del file CSS incorporato dopo la compilazione. L'elemento script sw-ui-logo.js verrà eseguito in fase di runtime.

Dipendenze modulari con Bower

Solitamente manteniamo le librerie e altre dipendenze a livello di progetto. Tuttavia, nella configurazione precedente noterai un bower.json nella cartella dell'elemento: dipendenze a livello di elemento. L'idea alla base di questo approccio è che, in una situazione in cui ci sono molti elementi con dipendenze diverse, possiamo assicurarci di caricare solo le dipendenze effettivamente utilizzate. Se rimuovi un elemento, non devi ricordarti di rimuovere la sua dipendenza, in quanto avrai rimosso anche il file bower.json che dichiara queste dipendenze. Ogni elemento carica in modo indipendente le dipendenze.

Tuttavia, per evitare duplicati di dipendenze, includiamo un file .bowerrc anche nella cartella di ogni elemento. Questo indica dove archiviare le dipendenze in modo da poter garantire che ne contenga solo una alla fine nella stessa directory:

{
    "directory" : "../../../../../bower_components"
}

In questo modo, se più elementi dichiarano THREE.js come dipendenza, dopo che la periferica lo installa per il primo elemento e inizia l'analisi del secondo, capirai che questa dipendenza è già installata e non verrà scaricata o duplicata di nuovo. Allo stesso modo, manterrà quei file delle dipendenze finché è presente almeno un elemento che li definisce ancora nel suo bower.json.

Uno script bash trova tutti i file bower.json nella struttura degli elementi nidificati. Quindi inserisce queste directory una alla volta ed esegue bower install in ciascuna di esse:

echo installing bower components...
modules=$(find /vagrant/app -type f -name "bower.json" -not -path "*node_modules*" -not -path "*bower_components*")
for module in $modules; do
    pushd $(dirname $module)
    bower install --allow-root -q
    popd
done

Modello Nuovo elemento rapido

Ogni volta che vuoi creare un nuovo elemento, occorre un po' di tempo: generare la cartella e la struttura di base dei file con i nomi corretti. Utilizziamo quindi Slush per scrivere un semplice generatore di elementi.

Puoi chiamare lo script dalla riga di comando:

$ slush element path/to/your/element-name

Viene così creato il nuovo elemento, inclusi tutta la struttura e i contenuti del file.

Abbiamo definito i modelli per i file di elemento, ad esempio il modello di file .jade ha il seguente aspetto:

// Element
dom-module(id='<%= name %>')

    // Template
    template
    style
        include elements/<%= path %>/styles/<%= name %>.css

    span This is a '<%= name %>' element.

    // Polymer element script
    script(src='scripts/<%= name %>.js')

Il generatore Slush sostituisce le variabili con i percorsi e i nomi effettivi degli elementi.

Utilizzo di Gulp per creare elementi

Gulp tiene sotto controllo il processo di compilazione. Nella nostra struttura, per creare gli elementi, abbiamo bisogno che Gulp segua questi passaggi:

  1. Compila i file .coffee degli elementi in .js
  2. Compila i file .scss degli elementi in .css
  3. Compila i file .jade degli elementi in .html, incorporando i file .css.

In modo più dettagliato:

Compilazione dei file .coffee degli elementi in .js in corso...

gulp.task('elements-coffee', function () {
    return gulp.src(abs(config.paths.app + '/elements/**/*.coffee'))
    .pipe($.replaceTask({
        patterns: [{json: getVersionData()}]
    }))
    .pipe($.changed(abs(config.paths.static + '/elements'), {extension: '.js'}))
    .pipe($.coffeelint())
    .pipe($.coffeelint.reporter())
    .pipe($.sourcemaps.init())
    .pipe($.coffee({
    }))
    .on('error', gutil.log)
    .pipe($.sourcemaps.write())
    .pipe(gulp.dest(abs(config.paths.static + '/elements')));
});

Per i passaggi 2 e 3 utilizziamo gulp e un plug-in per la bussola per compilare scss in .css e .jade in .html, in un approccio simile al precedente 2.

Inserimento di elementi in polimero

Per includere effettivamente gli elementi Polymer, utilizziamo le importazioni HTML.

<link rel="import" href="elements.html">

<!-- Polymer -->
<link rel="import" href="../bower_components/polymer/polymer.html">

<!-- Custom elements -->
<link rel="import" href="sw/sw-app/sw-app.html">
<link rel="import" href="sw/system/sw-system/sw-system.html">
<link rel="import" href="sw/system/sw-routing/sw-routing.html">
<link rel="import" href="sw/system/sw-system-version/sw-system-version.html">
<link rel="import" href="sw/system/sw-system-environment/sw-system-environment.html">
<link rel="import" href="sw/pages/sw-page-landing/sw-page-landing.html">
<link rel="import" href="sw/pages/sw-page-connection/sw-page-connection.html">
<link rel="import" href="sw/pages/sw-page-calibration/sw-page-calibration.html">
<link rel="import" href="sw/pages/sw-page-experience/sw-page-experience.html">
<link rel="import" href="sw/ui/sw-preloader/sw-preloader.html">
<link rel="import" href="sw/ui/sw-ui-overlay/sw-ui-overlay.html">
<link rel="import" href="sw/ui/sw-ui-button/sw-ui-button.html">
<link rel="import" href="sw/ui/sw-ui-menu/sw-ui-menu.html">

Ottimizzazione degli elementi polimerici per la produzione

Un progetto di grandi dimensioni può finire per avere molti elementi Polymer. Nel nostro progetto, ne abbiamo più di 50. Se consideri ogni elemento come un file .js separato e alcuni con librerie a cui viene fatto riferimento, il risultato diventa più di 100 file separati. Significa che il browser deve effettuare molte richieste, con una perdita di prestazioni. Analogamente a un processo concatenato e minimizzato che applichiamo a una build Angular, "vulcanizza" il progetto Polymer alla fine per la produzione.

Vulcanize è uno strumento Polymer che unisce l'albero delle dipendenze in un unico file HTML, riducendo il numero di richieste. Ciò è particolarmente utile per i browser che non supportano i componenti web in modo nativo.

CSP (Content Security Policy) e Polymer

Quando sviluppi applicazioni web sicure, devi implementare CSP. Il CSP è un insieme di regole che impediscono gli attacchi cross-site scripting (XSS): esecuzione di script da origini non sicure o esecuzione di script incorporati da file HTML.

Ora il file .html ottimizzato, concatenato e minimizzato generato da Vulcanize include tutto il codice JavaScript incorporato in un formato non conforme a CSP. Per risolvere questo problema, utilizziamo uno strumento chiamato Crisper.

Crisper suddivide gli script incorporati da un file HTML e li inserisce in un unico file JavaScript esterno per la conformità a CSP. Quindi passiamo il file HTML vulcanizzato tramite Crisper e finiamo con due file: elements.html e elements.js. All'interno di elements.html si occupa anche di caricare il elements.js generato.

Struttura logica dell'applicazione

In Polymer, gli elementi possono essere qualsiasi cosa, da un'utilità non visiva a elementi UI piccoli, autonomi e riutilizzabili (come i pulsanti) a moduli più grandi come "pagine" e persino alla scrittura di applicazioni complete.

Una struttura logica di primo livello dell&#39;applicazione
Una struttura logica di primo livello della nostra applicazione rappresentata con elementi Polymer.

Post-elaborazione con architettura polimerica e padre-figlio

In qualsiasi pipeline grafica 3D, c'è sempre un ultimo passaggio in cui gli effetti vengono aggiunti sopra l'intera immagine come una sorta di overlay. Si tratta della fase di post-elaborazione e prevede effetti come bagliori, raggi divinità, profondità di campo, bokeh, sfocature e così via. Gli effetti vengono combinati e applicati a diversi elementi a seconda di come viene creata la scena. In THREE.js potremmo creare uno shadower personalizzato per la post-elaborazione in JavaScript oppure possiamo fare questo con Polymer, grazie alla sua struttura padre-figlio.

Se guardi il codice HTML dell'elemento del nostro post-processore:

<dom-module id="sw-experience-postprocessor">
    <!-- Template-->
    <template>
    <sw-experience-effect-bloom class="effect"></sw-experience-effect-bloom>
    <sw-experience-effect-dof class="effect"></sw-experience-effect-dof>
    <sw-experience-effect-vignette class="effect"></sw-experience-effect-vignette>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-experience-postprocessor.js"></script>
</dom-module>

Gli effetti vengono specificati come elementi Polymer nidificati all'interno di una classe comune. Poi, in sw-experience-postprocessor.js facciamo questo:

effects = @querySelectorAll '.effect'
@composer.addPass effect.getPass() for effect in effects

Utilizziamo la funzionalità HTML e querySelectorAll di JavaScript per trovare tutti gli effetti nidificati come elementi HTML all'interno del post-elaborazione, nell'ordine in cui sono stati specificati. Le ripetiamo e le aggiungiamo al compositore.

Ora, supponiamo di voler rimuovere l'effetto DOF (profondità di campo) e modificare l'ordine di fioritura e vignettatura. Non devi fare altro che modificare la definizione del post-responsabile in modo simile a questo:

<dom-module id="sw-experience-postprocessor">
    <!-- Template-->
    <template>
    <sw-experience-effect-vignette class="effect"></sw-experience-effect-vignette>
    <sw-experience-effect-bloom class="effect"></sw-experience-effect-bloom>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-experience-postprocessor.js"></script>
</dom-module>

e la scena verrà eseguita senza modificare una sola riga di codice effettivo.

Loop di rendering e di aggiornamento in Polymer

Con Polymer possiamo anche affrontare il rendering e gli aggiornamenti del motore in modo elegante. Abbiamo creato un elemento timer che utilizza requestAnimationFrame e calcola valori come l'ora attuale (t) e il tempo delta - tempo trascorso dall'ultimo frame (dt):

Polymer
    is: 'sw-timer'

    properties:
    t:
        type: Number
        value: 0
        readOnly: true
        notify: true
    dt:
        type: Number
        value: 0
        readOnly: true
        notify: true

    _isRunning: false
    _lastFrameTime: 0

    ready: ->
    @_isRunning = true
    @_update()

    _update: ->
    if !@_isRunning then return
    requestAnimationFrame => @_update()
    currentTime = @_getCurrentTime()
    @_setT currentTime
    @_setDt currentTime - @_lastFrameTime
    @_lastFrameTime = @_getCurrentTime()

    _getCurrentTime: ->
    if window.performance then performance.now() else new Date().getTime()

Quindi, utilizziamo l'associazione di dati per associare le proprietà t e dt al nostro motore (experience.jade):

sw-timer(
    t='{ % templatetag openvariable % }t}}',
    dt='{ % templatetag openvariable % }dt}}'
)

sw-experience-engine(
    t='[t]',
    dt='[dt]'
)

Ascoltiamo le modifiche di t e dt nel motore e ogni volta che i valori cambiano, la funzione _update viene chiamata:

Polymer
    is: 'sw-experience-engine'

    properties:
    t:
        type: Number

    dt:
        type: Number

    observers: [
    '_update(t)'
    ]

    _update: (t) ->
    dt = @dt
    @_physics.update dt, t
    @_renderer.render dt, t

Se invece vuoi usare FPS, potresti rimuovere l'associazione dati di Polymer nel loop di rendering per risparmiare un paio di millisecondi necessari per notificare gli elementi alle modifiche. Abbiamo implementato gli osservatori personalizzati nel seguente modo:

sw-timer.coffee:

addUpdateListener: (listener) ->
    if @_updateListeners.indexOf(listener) == -1
    @_updateListeners.push listener
    return

removeUpdateListener: (listener) ->
    index = @_updateListeners.indexOf listener
    if index != -1
    @_updateListeners.splice index, 1
    return

_update: ->
    # ...
    for listener in @_updateListeners
        listener @dt, @t
    # ...

La funzione addUpdateListener accetta un callback e lo salva nel suo array di callback. Poi, nel loop di aggiornamento, ripetiamo ogni callback e lo eseguiamo direttamente con argomenti dt e t, ignorando l'associazione di dati o l'attivazione degli eventi. Quando un callback non deve più essere attivo, abbiamo aggiunto una funzione removeUpdateListener che consente di rimuovere un callback aggiunto in precedenza.

Una spada laser in THREE.js

THREE.js astrae i dettagli di basso livello di WebGL e ci permette di concentrarci sul problema. Il nostro problema è combattere gli Stormtrooper e abbiamo bisogno di un'arma. Allora costruiamo una spada laser.

La lama incandescente è ciò che distingue una spada da una vecchia arma a due mani. È costituito principalmente da due parti: la trave e il sentiero che si vede quando la si sposta. L'abbiamo costruita con una forma cilindrica luminosa e una scia dinamica che la segue man mano che il giocatore si muove.

The Blade - La lama

La lama è costituita da due lame secondarie. Interno ed esterno. Entrambi sono mesh THREE.js con i rispettivi materiali.

The Inner Blade

Per la lama interna abbiamo usato un materiale personalizzato con uno Shaker personalizzato. Prendiamo una linea creata da due punti e progettiamo la linea tra questi due punti su un piano. Questo aereo è quello che controlli quando combatti con il tuo cellulare, dà un senso di profondità e orientamento alla sciabola.

Per creare la sensazione di un oggetto luminoso rotondo, osserviamo la distanza ortogonale di un punto qualsiasi dell'aereo dalla linea principale che unisce i due punti A e B, come indicato di seguito. Più un punto è vicino all'asse principale, più luminoso è.

Bagliore della lama interna

L'origine riportata di seguito mostra come calcoliamo un valore vFactor per controllare l'intensità in vertex Shaper e poi utilizzarlo per fonderlo con la scena nel segmento Shader.

THREE.LaserShader = {

    uniforms: {
    "uPointA": {type: "v3", value: new THREE.Vector3(0, -1, 0)},
    "uPointB": {type: "v3", value: new THREE.Vector3(0, 1, 0)},
    "uColor": {type: "c", value: new THREE.Color(1, 0, 0)},
    "uMultiplier": {type: "f", value: 3.0},
    "uCoreColor": {type: "c", value: new THREE.Color(1, 1, 1)},
    "uCoreOpacity": {type: "f", value: 0.8},
    "uLowerBound": {type: "f", value: 0.4},
    "uUpperBound": {type: "f", value: 0.8},
    "uTransitionPower": {type: "f", value: 2},
    "uNearPlaneValue": {type: "f", value: -0.01}
    },

    vertexShader: [

    "uniform vec3 uPointA;",
    "uniform vec3 uPointB;",
    "uniform float uMultiplier;",
    "uniform float uNearPlaneValue;",
    "varying float vFactor;",

    "float getDistanceFromAB(vec2 a, vec2 b, vec2 p) {",

        "vec2 l = b - a;",
        "float l2 = dot( l, l );",
        "float t = dot( p - a, l ) / l2;",
        "if( t < 0.0 ) return distance( p, a );",
        "if( t > 1.0 ) return distance( p, b );",
        "vec2 projection = a + (l * t);",
        "return distance( p, projection );",

    "}",

    "vec3 getIntersection(vec4 a, vec4 b) {",

        "vec3 p = a.xyz;",
        "vec3 q = b.xyz;",
        "vec3 v = normalize( q - p );",
        "float t = ( uNearPlaneValue - p.z ) / v.z;",
        "return p + (v * t);",

    "}",

    "void main() {",

        "vec4 a = modelViewMatrix * vec4(uPointA, 1.0);",
        "vec4 b = modelViewMatrix * vec4(uPointB, 1.0);",
        "if(a.z > uNearPlaneValue) a.xyz = getIntersection(a, b);",
        "if(b.z > uNearPlaneValue) b.xyz = getIntersection(a, b);",
        "a = projectionMatrix * a; a /= a.w;",
        "b = projectionMatrix * b; b /= b.w;",
        "vec4 p = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
        "gl_Position = p;",
        "p /= p.w;",
        "float d = getDistanceFromAB(a.xy, b.xy, p.xy) * gl_Position.z;",
        "vFactor = 1.0 - clamp(uMultiplier * d, 0.0, 1.0);",

    "}"

    ].join( "\n" ),

    fragmentShader: [

    "uniform vec3 uColor;",
    "uniform vec3 uCoreColor;",
    "uniform float uCoreOpacity;",
    "uniform float uLowerBound;",
    "uniform float uUpperBound;",
    "uniform float uTransitionPower;",
    "varying float vFactor;",

    "void main() {",

        "vec4 col = vec4(uColor, vFactor);",
        "float factor = smoothstep(uLowerBound, uUpperBound, vFactor);",
        "factor = pow(factor, uTransitionPower);",
        "vec4 coreCol = vec4(uCoreColor, uCoreOpacity);",
        "vec4 finalCol = mix(col, coreCol, factor);",
        "gl_FragColor = finalCol;",

    "}"

    ].join( "\n" )

};

Il bagliore della lama esterna

Per il bagliore esterno, eseguiamo il rendering in un buffer di rendering separato, usiamo un effetto bloom post-elaborazione e lo fondiamo con l'immagine finale per ottenere il bagliore desiderato. L'immagine qui sotto mostra le tre diverse aree che devi avere per avere una sciabola di qualità. ovvero il nucleo bianco, il bagliore blu medio e il bagliore esterno.

Lama esterna

Sentiero con la spada laser

La scia della spada laser è la chiave di tutti gli effetti dell'originale della serie Star Wars. Abbiamo creato il sentiero con un ventaglio di triangoli generati dinamicamente in base al movimento della spada laser. Queste ventole vengono poi trasferite al postprocessore per ulteriori miglioramenti visivi. Per creare la geometria della ventola abbiamo un segmento lineare e, in base alla trasformazione e alla trasformazione di corrente precedenti, generiamo un nuovo triangolo nella rete mesh, rimuovendo la parte della coda dopo una certa lunghezza.

Sentiero con la spada laser a sinistra
Sciabola laser verso destra

Una volta ottenuta una mesh, gli assegniamo un materiale semplice e lo passiamo al postprocessore per ottenere un effetto uniforme. Utilizziamo lo stesso effetto Fioritura che abbiamo applicato al bagliore della lama esterna e otteniamo una scia fluida, come puoi vedere:

Il percorso completo

Illumina il sentiero

Per completare l'ultimo pezzo, abbiamo dovuto gestire il bagliore intorno al sentiero effettivo, che potrebbe essere creato in vari modi. Per motivi legati alle prestazioni, la nostra soluzione, che non entreremo nel dettaglio, è stata quella di creare uno Shader personalizzato per questo buffer che creasse un bordo uniforme intorno a un morsetto del renderbuffer. Quindi combiniamo questo output nel rendering finale, qui puoi vedere il bagliore che circonda la traccia:

Sentiero con incandescenza

Conclusione

Polymer è una libreria e un concetto potenti (come i componenti Webcomponenti in generale). La scelta è tua. Può essere qualsiasi cosa, da un semplice pulsante dell'interfaccia utente a un'applicazione WebGL di dimensioni standard. Nei capitoli precedenti ti abbiamo mostrato alcuni suggerimenti utili su come utilizzare in modo efficiente Polymer in produzione e su come strutturare moduli più complessi con un buon rendimento. Ti abbiamo anche mostrato come ottenere una bella spada laser in WebGL. Quindi, se combini tutte queste attività, ricordati di Vulcanizzare gli elementi Polymer prima di eseguire il deployment nel server di produzione e, se non dimentichi di usare Crisper se vuoi mantenere la conformità a CSP, la forza potrebbe essere tua.

Gameplay