Riepilogo
Come abbiamo utilizzato Polymer per creare un servizio WebGL ad alte prestazioni controllato da dispositivi mobili Spada laser modulare e configurabile. Esaminiamo alcuni dettagli chiave del nostro progetto https://lightsaber.withgoogle.com/ per aiutarti a risparmiare tempo quando ne creerai il tuo la prossima volta che ti imbatterai in un pacchetto di stormtrooper arrabbiati.
Panoramica
Se ti stai chiedendo cosa siano Polymer o WebComponents, abbiamo pensato che fosse meglio iniziare condividendo un estratto di un progetto funzionante reale. Ecco un esempio tratto dalla pagina di destinazione del nostro progetto https://lightsaber.withgoogle.com. È un normale file HTML, ma che contiene alcuni elementi magici:
<!-- 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>
Di conseguenza, oggi esistono molte opzioni per creare un'applicazione basata su HTML5. API, framework, librerie, motori di gioco ecc. Nonostante tutte le scelte disponibili, è difficile trovare un buon mix tra il controllo su grafiche ad alte prestazioni e una struttura modulare struttura e scalabilità. Abbiamo scoperto che Polymer poteva aiutarci a mantenere progetto organizzato, pur consentendo un rendimento di basso livello ottimizzazioni e abbiamo creato con cura il modo in cui abbiamo suddiviso il nostro progetto in componenti per sfruttare al meglio le capacità di Polymer.
Modularità con i polimeri
Polymer è una libreria che consente ha molta potenza sulle modalità di creazione del tuo progetto a partire da elementi personalizzati riutilizzabili. Consente di utilizzare moduli autonomi e completamente funzionali contenuti in un un singolo file HTML. Contengono non solo la struttura (markup HTML), ma anche stili e logica incorporati.
Dai un'occhiata all'esempio riportato di seguito:
<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>
Ma in un progetto più grande potrebbe essere utile separare questi tre (HTML, CSS, JS) e uniscili solo al momento della compilazione. Pertanto, abbiamo assegnato 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
Inoltre, la cartella di ogni elemento ha la stessa struttura interna con directory e file per la logica (file Caffè), gli stili (file CSS) 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
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 organizzate le cose in modo chiaro includendo stili
e logica da file separati. Per includere i nostri stili nei modelli Polymer
usiamo l'istruzione include
di Jade, quindi abbiamo un vero e proprio CSS incorporato
i contenuti dei file dopo la compilazione. L'elemento script sw-ui-logo.js
vengono eseguiti in fase di runtime.
Dipendenze modulari con Bower
In genere, manteniamo le librerie e altre dipendenze a livello di progetto.
Tuttavia, nella configurazione precedente noterai un bower.json
che si trova nella
cartella dell'elemento: le dipendenze a livello di elemento. L'idea alla base di questo approccio è che, in una situazione in cui sono presenti molti elementi con dipendenze diverse, possiamo assicurarci di caricare solo quelle effettivamente utilizzate. E se rimuovi un elemento, non devi ricordarti di
rimuovi la dipendenza perché avrai rimosso anche il file bower.json
che dichiara queste dipendenze. Ogni elemento carica in modo indipendente
e dipendenze correlate.
Tuttavia, per evitare una duplicazione di dipendenze, includiamo un file .bowerrc
anche nella cartella di ogni elemento. Questa informazione indica a bower dove archiviare
in modo da assicurarci che ne sia solo una alla fine
directory:
{
"directory" : "../../../../../bower_components"
}
In questo modo, se più elementi dichiarano THREE.js
come dipendenza, una volta
bower lo installa per il primo elemento e inizia ad analizzare il secondo.
si accorge che questa dipendenza è già installata
scaricarlo di nuovo o duplicarlo. Allo stesso modo, manterrà
purché esista almeno un elemento che lo definisce ancora
è bower.json
.
Uno script bash trova tutti i file bower.json
nella struttura degli elementi nidificati.
Quindi inserisce queste directory una per una ed esegue bower install
in
ognuno di essi:
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 rapido nuovo elemento
Ogni volta che vuoi creare un nuovo elemento occorre un po' di tempo: la generazione la struttura delle cartelle e dei file di base con i nomi corretti. Quindi usiamo Slush per scrivere un generatore di elementi semplice.
Puoi chiamare lo script dalla riga di comando:
$ slush element path/to/your/element-name
Viene creato il nuovo elemento, inclusi tutti i contenuti e la struttura del file.
Abbiamo definito i modelli per i file degli elementi, 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 di Slush sostituisce le variabili con percorsi e nomi di elementi effettivi.
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 i seguenti passaggi:
- Compila gli elementi
.coffee
file in.js
- Compila i file
.scss
degli elementi in.css
- Compila gli elementi
.jade
file in.html
, incorporamento dei file.css
.
In dettaglio:
Compilazione degli elementi .coffee
file in .js
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 Compass per compilare scss
in .css
e .jade
in .html
, in un approccio simile a quello descritto sopra.
Includere elementi polimeri
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 di Polymer 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 che ogni elemento ha una
file .js
separati e alcuni con librerie a cui viene fatto riferimento, diventa più di
100 file separati. Ciò significa che il browser deve effettuare molte richieste,
con una perdita di prestazioni. Analogamente a un processo di concatenazione e minimizzazione
si applicherebbe a una build Angular, "vulcanizziamo" il progetto Polymer
per la produzione.
Vulcanize è uno strumento Polymer che appiattisce la struttura ad albero delle dipendenze in un unico file HTML, riducendo il numero di richieste. Ciò è particolarmente utile per i browser che non supportare in modo nativo i componenti web.
CSP (Content Security Policy) e Polymer
Durante lo sviluppo di applicazioni web sicure, devi implementare CSP. CSP è un insieme di regole che prevengono 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
unico, ottimizzato, concatenato e minimizzato generato da Vulcanize contiene tutto il codice JavaScript in linea in un formato non conforme al CSP. Per risolvere il problema, utilizziamo uno strumento chiamato
Crisper.
Crisper suddivide gli script in linea di un file HTML e li inserisce in un unico file JavaScript esterno per la conformità al CSP. Quindi passiamo la formazione di
file HTML tramite Crisper e alla fine ci sono due file: elements.html
e
elements.js
. All'interno di elements.html
, si occupa anche di caricare
generato elements.js
.
Struttura logica dell'applicazione
In Polymer, gli elementi possono essere qualsiasi cosa, da un'utilità non visiva a piccoli elementi dell'interfaccia utente autonomi e riutilizzabili (come i pulsanti), a moduli più grandi come le "pagine" e persino la composizione di applicazioni complete.
Post-elaborazione con Polymer e architettura padre-figlio
In qualsiasi pipeline di grafica 3D, c'è sempre un ultimo passaggio in cui gli effetti vengono aggiunti all'intera immagine come una sorta di overlay. Questo è il fase di post-elaborazione e coinvolge effetti come bagliori, raggi profondità di campo, bokeh, sfocature e così via. Gli effetti vengono combinati e applicati elementi diversi a seconda di come è realizzata la scena. In THREE.js, potresti creare uno shaker personalizzato per la post-elaborazione in JavaScript possiamo farlo con Polymer, grazie alla sua struttura padre-figlio.
Se esamini il codice HTML dell'elemento del nostro post-processor:
<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 in una classe comune. Poi,
in sw-experience-postprocessor.js
lo facciamo:
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-processor, nell'ordine in cui sono stati specificati. Eseguiamo l'iterazione su questi elementi e li aggiungiamo al compositore.
Supponiamo ora di voler rimuovere l'effetto DOF (profondità di campo) e cambiare l'ordine degli effetti Bloom e Vignetta. Non dobbiamo fare altro che modificare la definizione del post-processor in modo da ottenere qualcosa di simile a quanto segue:
<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 vero e proprio.
Loop di rendering e loop di aggiornamento in Polymer
Con Polymer possiamo affrontare con eleganza anche il rendering e gli aggiornamenti del motore.
Abbiamo creato un elemento timer
che utilizza requestAnimationFrame
e calcola
come l'ora corrente (t
) e il tempo delta - tempo trascorso dal
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
alla nostra
motore (experience.jade
):
sw-timer(
t='{ % templatetag openvariable % }t}}',
dt='{ % templatetag openvariable % }dt}}'
)
sw-experience-engine(
t='[t]',
dt='[dt]'
)
Inoltre, ascoltiamo le modifiche di t
e dt
nel motore e ogni volta che viene eseguita
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 però sei in cerca di FPS, potresti rimuovere i dati di Polymer binding nel ciclo di rendering per risparmiare il paio di millisecondi necessari per la notifica elementi sulle 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
di callback di Google. Quindi, nel loop di aggiornamento, eseguiamo l'iterazione di ogni callback
lo eseguiamo direttamente con gli argomenti dt
e t
, aggirando l'associazione di dati
dell'attivazione di un evento. Quando non doveva più essere attivo il callback, abbiamo aggiunto
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 per risolvere il problema. Il nostro problema è che dobbiamo combattere gli Stormtrooper e ci serve un'arma. Costruiamo una spada laser.
La lama luminosa è ciò che distingue una spada laser da qualsiasi altra arma a due mani. È composta principalmente da due parti: la trave e il sentiero visibile quando lo si sposta. L'abbiamo costruita con una luminosa forma cilindrica e una scia dinamica che la segue mentre il giocatore si muove.
The Blade
La lama è composta da due lame secondarie. Uno interno e uno esterno. Entrambi sono mesh THREE.js con i rispettivi materiali.
Lama interna
Per la lama interna abbiamo utilizzato un materiale personalizzato con uno shaker personalizzato. Me prendere una linea creata da due punti e proiettare la linea tra questi due punti punti su un aereo. Questo aereo è fondamentalmente ciò che controlli quando con il tuo cellulare, ti dà un senso di profondità e orientamento alla sciabola.
Per creare la sensazione di un oggetto luminoso rotondo, osserviamo la distanza del punto ortogonale di qualsiasi punto del piano rispetto a quello principale linea che unisce i due punti A e B come indicato di seguito. Più un punto è vicino sull'asse principale, più luminoso è.
La fonte riportata di seguito mostra come calcoliamo un valore vFactor
per controllare l'intensità
in Vertex Shader per utilizzarlo poi per fondersi con la scena
Snippet 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" )
};
Bagliore della lama esterna
Per il bagliore esterno, il rendering viene eseguito in un buffer di rendering separato e utilizziamo un l'effetto bloom di post-elaborazione e si fonde con l'immagine finale per ottenere l'incandescenza desiderata. L'immagine di seguito mostra le tre diverse regioni in cui che bisogna avere se vuoi una sciabola decente. ossia il nucleo bianco, al centro bagliore blu scuro e il bagliore esterno.
Sentiero con spada laser
La scia della spada laser è la chiave per il pieno effetto dell'originale della serie Star Wars. Abbiamo creato il sentiero con un fan di triangoli generati dinamicamente in base al movimento della spada laser. Questi ventilatori vengono quindi vengono passate al post-processore per ulteriori miglioramenti visivi. Per creare il alla geometria di ventaglio abbiamo un segmento di linea, basato sulla trasformazione precedente e nella trasformazione attuale generiamo un nuovo triangolo nella mesh, dalla coda dopo una certa lunghezza.
Una volta ottenuta una mesh, le assegniamo un materiale semplice e la passiamo al post-processore per creare un effetto uniforme. Usiamo lo stesso effetto di fioritura che che abbiamo applicato al bagliore della pala esterna e ottenere una scia uniforme come puoi vedere:
Luce notturna lungo il percorso
Per completare il pezzo abbiamo dovuto gestire il bagliore intorno al creato in vari modi. La nostra soluzione non entrare in dettaglio qui, per motivi legati alle prestazioni era la creazione di un Shar per questo buffer che crea un bordo liscio attorno a un morsetto renderingbuffer. Combiniamo quindi questo output nel rendering finale, qui puoi osserva il bagliore che circonda il sentiero:
Conclusione
Polymer è una libreria e un concetto efficaci (come WebComponents generale). Sta a te decidere cosa farne. Può essere qualsiasi cosa, un semplice pulsante UI per un'applicazione WebGL di dimensioni standard. Nei capitoli precedenti ti abbiamo mostrato alcuni suggerimenti utili su come usare Polymer in modo efficiente in produzione e a strutturare moduli più complessi che beh. Ti abbiamo anche mostrato come creare una spada laser dall'aspetto accattivante in WebGL. Quindi, se combini tutto questo, ricorda di sottoporre a vulcanizzazione gli elementi Polymer prima della distribuzione sul server di produzione e, se non dimentichi di usare Crisper Se vuoi mantenere la conformità CSP, fai in modo che la forza sia con te.