Polimer ile Işın Kılıcı Oluşturma

Işın kılıcı ekran görüntüsü

Özet

Modüler ve yapılandırılabilir olan, yüksek performanslı, mobil kontrollü bir Işık Kılıcı oluşturmak için Polymer'i nasıl kullandık? Bir dahaki sefere bir yığın öfkeli Fırtına Birlikleriyle karşılaştığınızda kendi işinizi oluştururken zamandan tasarruf etmenize yardımcı olmak için https://lightsaber.withgoogle.com/ projemizin bazı önemli ayrıntılarını gözden geçiriyoruz.

Genel bakış

Polymer veya WebBileşenlerinin hangisi olduğunu merak ediyorsanız gerçek bir projeden bir örneği paylaşarak en iyi seçenek olacağını düşündük. Burada, https://lightsaber.withgoogle.com projemizin açılış sayfasından alınan bir örneği görebilirsiniz. Bu, normal bir HTML dosyasıdır ancak içinde bazı sihirli unsurlar vardır:

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

Bu nedenle, günümüzde HTML5 tabanlı bir uygulama oluşturmak için kullanabileceğiniz birçok seçenek mevcut. API'ler, Çerçeveler, Kitaplıklar, Oyun Motorları vb. bulunur. Tüm seçeneklere rağmen yüksek grafik performansı kontrolü ile derli toplu yapı ve ölçeklenebilirlik arasında iyi bir karma kuruluma sahip olmak zordur. Polymer'in, düşük seviyeli performans optimizasyonlarına olanak tanırken projeyi düzenli tutmamıza yardımcı olabileceğini gördük ve Polymer'in özelliklerinden en iyi şekilde yararlanmak için projemizi bileşenlere ayırma yöntemimizi dikkatli bir şekilde belirledik.

Polimer ile Modülerlik

Polimer, projenizin yeniden kullanılabilir özel öğelerden nasıl oluşturulduğu konusunda büyük bir güce sahip olan bir kitaplıktır. Tek bir HTML dosyasında yer alan, bağımsız, tam işlevli modülleri kullanmanıza olanak tanır. Yalnızca yapıyı (HTML işaretlemesi) değil, aynı zamanda satır içi stilleri ve mantığı da içerirler.

Aşağıdaki örneği inceleyin:

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

Ancak daha büyük bir projede bu üç mantıksal bileşeni (HTML, CSS, JS) ayırmak ve yalnızca derleme sırasında birleştirmek yararlı olabilir. Böylece, projedeki her öğeye ayrı bir klasör koyduk:

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

Ayrıca her öğenin klasörü, mantık (kahve dosyaları), stiller (scss dosyaları) ve şablon (yeşim dosyası) için ayrı dizinler ve dosyalarla aynı dahili yapıya sahiptir.

Aşağıda örnek bir sw-ui-logo öğesi verilmiştir:

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

.jade dosyasını incelerseniz:

// 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')

Ayrı dosyalardan stiller ve mantığı ekleyerek öğelerin nasıl düzenli bir şekilde düzenlendiğini görebilirsiniz. Stillerimizi Polimer öğelerimize eklemek için Jade'in include ifadesini kullanırız. Böylece, derlemeden sonra gerçek satır içi CSS dosyası içeriklerimiz olur. sw-ui-logo.js komut dosyası öğesi, çalışma zamanında yürütülür.

Bower ile Modüler Bağımlılıklar

Normalde kitaplıkları ve diğer bağımlılıkları proje düzeyinde tutarız. Ancak yukarıdaki kurulumda, öğenin klasöründe bir bower.json olduğunu fark edeceksiniz: öğe düzeyinde bağımlılıklar. Bu yaklaşımın ardında yatan fikir, farklı bağımlılıkları olan çok sayıda öğenizin olduğu bir durumda yalnızca gerçekten kullanılan bağımlılıkları yükleyebilmemizdir. Bir öğeyi kaldırırsanız bu bağımlılıkları bildiren bower.json dosyasını da kaldırmış olacağınızdan bu öğenin bağımlılığını kaldırmayı hatırlamanız gerekmez. Her öğe, kendisiyle ilişkili bağımlılıkları bağımsız olarak yükler.

Bununla birlikte, bağımlılıkların tekrarlanmasını önlemek için her bir öğenin klasörüne bir .bowerrc dosyası da ekleriz. Bu, bower'a bağımlılıkların nereye depolanacağını bildirir; böylece aynı dizinin sonunda yalnızca bir tane bulunduğundan emin olabiliriz:

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

Bu şekilde, birden fazla öğe THREE.js öğesini bağımlılık olarak tanımlarsa Bower bunu ilk öğe için yükleyip ikinci öğeyi ayrıştırmaya başladığında, bu bağımlılığın zaten yüklü olduğunu ve tekrar indirmeyeceğini veya yinelemeyeceğini fark eder. Benzer şekilde, bu bağımlılık dosyalarını bower.json içinde hâlâ tanımlayan en az bir öğe olduğu sürece saklar.

Bash komut dosyası, iç içe yerleştirilmiş öğeler yapısında tüm bower.json dosyalarını bulur. Ardından, bu dizinlere tek tek girer ve her birinde bower install işlemini yürütür:

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

Hızlı Yeni Öğe Şablonu

Her yeni öğe oluşturmak istediğinizde, klasörü ve temel dosya yapısını doğru adlarla oluşturmak biraz zaman alır. Bu nedenle, basit bir öğe oluşturucu yazmak için Slush'u kullanırız.

Komut dosyasını komut satırından çağırabilirsiniz:

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

Yeni öğe de tüm dosya yapısı ve içeriği dahil olacak şekilde oluşturulur.

Öğe dosyaları için şablonlar tanımladık. Örneğin .jade dosya şablonu aşağıdaki gibi görünür:

// 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')

Slush oluşturucu, değişkenleri gerçek öğe yolları ve adlarıyla değiştirir.

Öğe Oluşturmak için Gulp'ı Kullanma

Gulp, derleme sürecinin kontrol altında kalmasını sağlar. Yapımızda bu öğeleri oluşturmak için Gulp'a şu adımları izlememiz gerekiyor:

  1. Öğelerin .coffee dosyalarını .js dosyasında derleyin
  2. Öğelerin .scss dosyalarını .css dosyasında derleyin
  3. .css dosyalarını yerleştirerek öğelerin .jade dosyalarını .html üzerinde derleyin.

Daha ayrıntılı olarak:

Öğelerin .coffee dosyaları .js için derleniyor

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')));
});

2. ve 3. adımlarda, yukarıdaki 2'ye benzer bir yaklaşımla scss ve .css için .jade to .html derlemek için yumak ve bir pusula eklentisi kullanırız.

Polimer Öğeleri Dahil

Polimer öğelerini eklemek için HTML içe aktarma işlemlerini kullanırız.

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

Polimer öğelerini üretim için optimize etme

Büyük bir projede çok sayıda Polymer öğe bulunabilir. Projemizde elliden fazlası var. Her öğenin ayrı bir .js dosyasına ve bazı kitaplıkların referans verildiğini düşünürseniz bu öğe 100'den fazla ayrı dosya haline gelir. Bu, tarayıcının yapması gereken çok fazla istek ve performans kaybı anlamına gelir. Angular derlemesine uygulayacağımız birleştirme ve küçültme işlemine benzer şekilde, üretimin sonunda Polymer projesini "vulkanize" alırız.

Vulcanize, bağımlılık ağacını tek bir HTML dosyası halinde birleştiren ve istek sayısını azaltan bir Polymer aracıdır. Bu, özellikle web bileşenlerini yerel olarak desteklemeyen tarayıcılar için çok iyidir.

CSP (İçerik Güvenliği Politikası) ve Polymer

Güvenli web uygulamaları geliştirirken CSP'yi uygulamanız gerekir. CSP, siteler arası komut dosyası (XSS) saldırılarını önleyen bir kural grubudur. Güvenli olmayan kaynaklardan komut dosyası yürütme veya HTML dosyalarından satır içi komut dosyaları yürütme.

Artık Vulcanize tarafından oluşturulan optimize edilmiş, birleştirilmiş ve küçültülmüş .html dosyasında tüm JavaScript kodu, CSP ile uyumlu olmayan bir biçimde satır içinde bulunmaktadır. Bu sorunu gidermek için Crisper adlı bir araç kullanıyoruz.

Crisper, satır içi komut dosyalarını bir HTML dosyasından böler ve CSP uyumluluğu için bunları tek bir harici JavaScript dosyasına yerleştirir. Bu yüzden, vulkanize edilmiş HTML dosyasını Crisper üzerinden iletiriz ve sonuçta iki dosya elde ederiz: elements.html ve elements.js. elements.html içinde, oluşturulan elements.js öğesinin yüklenmesi de kolaylaşır.

Uygulamanın Mantıksal Yapısı

Polimer'de öğeler, görsel olmayan bir yardımcıdan küçük, bağımsız ve yeniden kullanılabilir kullanıcı arayüzü öğelerine (düğmeler gibi) "sayfalar" gibi daha büyük modüllere ve hatta tam uygulamalar oluşturmaya kadar her şey olabilir.

Uygulamanın üst düzey mantıksal yapısı
Polymer öğeleriyle temsil edilen uygulamamızın üst düzey mantıksal yapısı.

Polimer ve Üst Alt Öğe Mimari ile İşleme Sonrası

Herhangi bir 3D grafik ardışık düzeninde, efektlerin her zaman bir tür yer paylaşımlı resim olarak resmin üzerine eklendiği bir son adım vardır. Bu, işleme sonrası adımıdır ve parıltılar, ilah ışınları, alan derinliği, bokeh, bulanıklık gibi efektleri içerir. Efektler, sahnenin oluşturulma şekline bağlı olarak birleştirilir ve farklı öğelere uygulanır. THREE.js'de, JavaScript'te son işleme için özel bir gölgelendirici oluşturabilir veya üst-alt yapısı sayesinde bunu Polymer ile yapabiliriz.

Son işlemcimizin öğe HTML koduna bakarsanız:

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

Efektleri, ortak bir sınıf altında iç içe yerleştirilmiş Polimer öğeleri olarak belirtiriz. Ardından, sw-experience-postprocessor.js uygulamasında şunu yaparız:

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

Son işlemci içinde HTML öğeleri olarak iç içe yerleştirilmiş tüm efektleri, belirtildikleri sırayla bulmak için HTML özelliğini ve JavaScript'in querySelectorAll kodunu kullanırız. Ardından bunları yinelemeye ve besteciye ekleriz.

Şimdi de DOF (Alan Derinliği) etkisini kaldırıp çiçek ve vinyet efektlerinin sırasını değiştirmek istediğimizi düşünelim. Tek yapmamız gereken son işlemcinin tanımını aşağıdaki gibi düzenlemektir:

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

Böylece sahne, tek bir kod satırı bile değiştirilmeden çalışır.

Polymer'de döngü ve güncelleme döngüsü oluşturma

Polymer ile oluşturma ve motor güncellemelerine de zarif bir yaklaşabiliyoruz. requestAnimationFrame kullanan ve geçerli zaman (t) ve delta zamanı - son kareden geçen süre (dt) gibi değerleri hesaplayan bir timer öğesi oluşturduk:

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

Daha sonra, t ve dt özelliklerini motorumuza (experience.jade) bağlamak için veri bağlamayı kullanırız:

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

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

Ayrıca, motordaki t ve dt değişikliklerini dinliyoruz ve değerler her değiştiğinde _update işlevi çağrılır:

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

Ancak FPS'yi kullanmak istiyorsanız öğelere değişiklikler hakkında bilgi vermek için gereken birkaç milisaniyeden tasarruf etmek amacıyla, Polymer'in oluşturma döngüsündeki veri bağlamasını kaldırmak isteyebilirsiniz. Özel gözlemcileri aşağıdaki şekilde uyguladık:

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

addUpdateListener işlevi, bir geri çağırmayı kabul eder ve bunu geri çağırma dizisine kaydeder. Daha sonra, güncelleme döngüsünde her geri çağırmayı yineliyoruz ve veri bağlamayı veya etkinlik tetiklemeyi atlayarak bu geri çağırmayı doğrudan dt ve t bağımsız değişkenleriyle yürütüyoruz. Bir geri çağırmanın artık etkin olması amaçlanmadığında, daha önce eklenmiş olan bir geri çağırmayı kaldırmanıza olanak tanıyan bir removeUpdateListener işlevi ekledik.

THREE.js'de Işın Kılıcı

THREE.js, WebGL'nin alt düzey ayrıntılarını soyutlayarak soruna odaklanmamızı sağlar. Bizim sorunumuz Fırtına Birlikleriyle savaşmak ve bir silaha ihtiyacımız var. Haydi bir ışın kılıcı yapalım.

Işıltılı bıçak, ışın kılıcını diğer iki elle kullanılan silahtan ayırıyor. Esas olarak iki parçadan oluşur: hareket ettirildiğinde görünen kiriş ve iz. Bu oyunu, parlak bir silindir şekli ve oyuncu hareket ettikçe takip eden dinamik bir patikayla oluşturduk.

Bıçak

Bıçak iki alt kanattan oluşur. Bir iç ve bir dış dünya. Her ikisi de ilgili malzemeleriyle birlikte THREE.js örgüleridir.

İç bıçak

İç bıçak için özel bir gölgelendiriciyle özel bir malzeme kullandık. İki noktanın oluşturduğu bir çizgiyi alıp bu iki nokta arasındaki çizgiyi düzlemde yansıtırız. Bu uçak esasen cep telefonunuzla savaşırken kontrol ettiğiniz şeydir ve kılıcınıza derinlik ve yön hissi verir.

Yuvarlak şekilde parlayan bir nesne hissi yaratmak için, aşağıda olduğu gibi A ve B noktalarını birleştiren ana çizgiyle düzlem üzerindeki herhangi bir noktanın dik nokta mesafesine bakarız. Bir nokta ana eksene ne kadar yakın olursa o kadar parlak olur.

İç bıçak parlaklığı

Aşağıdaki kaynak, köşe gölgelendiricideki yoğunluğu kontrol etmek ve daha sonra bunu parça gölgelendiricideki sahneyle harmanlamak üzere kullanmak için bir vFactor'yi nasıl hesapladığımızı göstermektedir.

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

};

Dış Bıçak Işıltısı

Dış parlaklık için ayrı bir oluşturma arabelleği oluşturur ve işleme sonrası çiçeklenme efekti kullanırız. Ardından, istenen parlaklığı elde etmek için nihai resimle harmanlanırız. Aşağıdaki resimde, düzgün bir kılıç istiyorsanız sahip olmanız gereken üç farklı bölge gösterilmektedir. Beyaz çekirdek, ortadaki mavimsi parlaklık ve dıştaki parlaklık.

Dış bıçak

Işın Kılıcı Yolu

Yıldız Savaşları serisinde görüldüğü üzere, ışın kılıcının izini tüm etkinin anahtarıdır. Işın kılıcının hareketine dayalı dinamik olarak oluşturulan bir üçgen fanıyla yolu çizdik. Ardından bu fanlar, görsel iyileştirme için son işlemciye iletilir. Hayran geometrisini oluşturmak için bir çizgi segmentimiz vardır. Önceki dönüşüm ve geçerli dönüşüme dayalı olarak, ağda yeni bir üçgen oluşturur ve belirli bir sürenin sonunda kuyruk kısmı ayrılır.

Soldan ışın kılıcı izi
Sağdaki ışın kılıcı izi

Bir ağ oluşturduktan sonra ona basit bir malzeme atıyoruz ve pürüzsüz bir efekt oluşturmak için son işlemciye iletiyoruz. Dış bıçağın parlamasına uyguladığımız çiçek efektinin aynısını uygularız ve aşağıda gördüğünüz gibi düzgün bir iz elde ederiz:

Tam patika

Patika etrafında ışıldır

Son parçanın tamamlanması için, birçok farklı şekilde oluşturulabilen gerçek patikanın çevresindeki parıltıyı tutmamız gerekiyordu. Performansla ilgili nedenlerden dolayı, bu tampon için oluşturucu arabelleği kıvrımının çevresinde yumuşak bir kenar oluşturan özel bir gölgelendirici oluşturmak üzere burada ayrıntıya girmeyeceğimiz çözümümüz vardı. Ardından bu sonucu son oluşturmada bir araya getiriyoruz. Burada yolu çevreleyen parıltıyı görebilirsiniz:

Işıltılı patika

Sonuç

Polimer güçlü bir kitaplık ve kavramdır (Genel olarak WebBileşenleri ile aynıdır). Bununla ne yapacağınız yalnızca size bağlıdır. Basit bir kullanıcı arayüzü düğmesinden tam boyutlu WebGL uygulamasına kadar her şey olabilir. Önceki bölümlerde, Polymer'i üretimde verimli bir şekilde nasıl kullanabileceğinize ve iyi performans gösteren daha karmaşık modülleri nasıl yapılandıracağınıza dair bazı ipuçları ve püf noktaları gösterdik. Ayrıca size WebGL'de nasıl güzel görünen bir ışın kılıcının yaratılacağını da göstermiştik. Yani tüm bunları bir araya getirirseniz üretim sunucusuna dağıtmadan önce Polymer öğelerinizi Vulcanize işlemi yapmayı unutmayın. CSP ile uyumlu olmaya devam etmek istiyorsanız Crisper'ı kullanmayı unutmayın. Bu işin gücü sizde olabilir.

Oyun oynanışı