পলিমার দিয়ে একটি লাইটসাবার তৈরি করা

Lightsaber স্ক্রিনশট

সারসংক্ষেপ

আমরা কীভাবে পলিমার ব্যবহার করে একটি উচ্চ-কর্মক্ষমতা সম্পন্ন WebGL মোবাইল নিয়ন্ত্রিত Lightsaber তৈরি করেছি যা মডুলার এবং কনফিগারযোগ্য। আমরা আমাদের প্রকল্পের কিছু মূল বিবরণ পর্যালোচনা করি https://lightsaber.withgoogle.com/ পরের বার যখন আপনি রাগান্বিত Stormtroopers একটি প্যাকেজ আপনার নিজের তৈরি করার সময় আপনাকে সময় বাঁচাতে সাহায্য করতে।

ওভারভিউ

আপনি যদি ভাবছেন যে পলিমার বা ওয়েব কম্পোনেন্টগুলি কী তা আমরা মনে করি একটি বাস্তব কাজের প্রকল্প থেকে একটি নির্যাস ভাগ করে শুরু করা ভাল হবে৷ এখানে আমাদের প্রকল্প https://lightsaber.withgoogle.com এর ল্যান্ডিং পৃষ্ঠা থেকে নেওয়া একটি নমুনা রয়েছে৷ এটি একটি নিয়মিত HTML ফাইল কিন্তু এর ভিতরে কিছু জাদু আছে:

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

তাই আজকাল আপনি যখন একটি HTML5 ভিত্তিক অ্যাপ্লিকেশন তৈরি করতে চান তখন সেখানে অনেক পছন্দ রয়েছে৷ এপিআই, ফ্রেমওয়ার্ক, লাইব্রেরি, গেম ইঞ্জিন ইত্যাদি। সমস্ত পছন্দ থাকা সত্ত্বেও গ্রাফিক্সের উচ্চ কার্যক্ষমতা এবং পরিষ্কার মডুলার কাঠামো এবং স্কেলেবিলিটির উপর নিয়ন্ত্রণের মধ্যে একটি ভাল মিশ্রণ যা একটি সেটআপ পাওয়া কঠিন। আমরা দেখেছি যে পলিমার আমাদেরকে প্রকল্পটি সংগঠিত রাখতে সাহায্য করতে পারে যখন এখনও নিম্ন-স্তরের কর্মক্ষমতা অপ্টিমাইজেশানের জন্য অনুমতি দেয় এবং আমরা পলিমারের ক্ষমতার সর্বোত্তম সুবিধার জন্য আমাদের প্রকল্পকে উপাদানগুলিতে বিভক্ত করার উপায়টি যত্ন সহকারে তৈরি করেছি।

পলিমার সহ মডুলারিটি

পলিমার হল একটি লাইব্রেরি যা আপনার প্রোজেক্টকে কীভাবে পুনর্ব্যবহারযোগ্য কাস্টম উপাদানগুলি থেকে তৈরি করা হয়েছে তার উপর অনেক শক্তি দেয়৷ এটি আপনাকে একটি একক HTML ফাইলে থাকা স্বতন্ত্র, সম্পূর্ণ কার্যকরী মডিউলগুলি ব্যবহার করতে দেয়। এগুলিতে কেবল কাঠামো (HTML মার্কআপ) নয়, ইনলাইন শৈলী এবং যুক্তিও রয়েছে।

নীচের উদাহরণটি দেখুন:

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

কিন্তু একটি বড় প্রকল্পে এই তিনটি যৌক্তিক উপাদান (এইচটিএমএল, সিএসএস, জেএস) আলাদা করা সহায়ক হতে পারে এবং শুধুমাত্র কম্পাইলের সময় তাদের একত্রিত করতে পারে। সুতরাং আমরা একটি জিনিস করেছি প্রকল্পের প্রতিটি উপাদানকে তার নিজস্ব আলাদা ফোল্ডার দেওয়া:

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

এবং প্রতিটি উপাদানের ফোল্ডারে লজিক (কফি ফাইল), স্টাইল (scss ফাইল) এবং টেমপ্লেট (জেড ফাইল) এর জন্য পৃথক ডিরেক্টরি এবং ফাইলগুলির সাথে একই অভ্যন্তরীণ কাঠামো রয়েছে।

এখানে একটি উদাহরণ sw-ui-logo উপাদান:

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

এবং আপনি যদি .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')

আপনি আলাদা ফাইল থেকে শৈলী এবং যুক্তি অন্তর্ভুক্ত করে জিনিসগুলি কীভাবে পরিষ্কার উপায়ে সংগঠিত হয় তা দেখতে পারেন। আমাদের পলিমার উপাদানগুলিতে আমাদের শৈলীগুলি অন্তর্ভুক্ত করতে আমরা জেডের include বিবৃতি ব্যবহার করি, তাই সংকলনের পরে আমাদের কাছে প্রকৃত ইনলাইন CSS ফাইল সামগ্রী রয়েছে। sw-ui-logo.js স্ক্রিপ্ট উপাদান রানটাইমে কার্যকর হবে।

বোওয়ার সহ মডুলার নির্ভরতা

সাধারণত আমরা লাইব্রেরি এবং অন্যান্য নির্ভরতা প্রকল্প স্তরে রাখি। তবে উপরের সেটআপে আপনি একটি bower.json লক্ষ্য করবেন যা উপাদানের ফোল্ডারে রয়েছে: উপাদান স্তর নির্ভরতা। এই পদ্ধতির পিছনে ধারণা হল যে এমন পরিস্থিতিতে যেখানে আপনার কাছে বিভিন্ন নির্ভরতা সহ প্রচুর উপাদান রয়েছে আমরা কেবলমাত্র সেই নির্ভরতাগুলি লোড করতে নিশ্চিত করতে পারি যা আসলে ব্যবহৃত হয়। এবং যদি আপনি একটি উপাদান অপসারণ করেন, তাহলে আপনাকে তার নির্ভরতা মুছে ফেলার কথা মনে রাখতে হবে না কারণ আপনি bower.json ফাইলটিও সরিয়ে ফেলবেন যা এই নির্ভরতাগুলি ঘোষণা করে। প্রতিটি উপাদান স্বাধীনভাবে এর সাথে সম্পর্কিত নির্ভরতাগুলিকে লোড করে।

যাইহোক, নির্ভরতার নকল এড়াতে আমরা প্রতিটি উপাদানের ফোল্ডারে একটি .bowerrc ফাইলও অন্তর্ভুক্ত করি। এটি বোয়ারকে কোথায় নির্ভরতা সঞ্চয় করতে হবে তা বলে দেয় যাতে আমরা নিশ্চিত করতে পারি যে একই ডিরেক্টরির শেষে শুধুমাত্র একটি আছে:

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

এইভাবে যদি একাধিক উপাদান THREE.js একটি নির্ভরতা হিসাবে ঘোষণা করে, একবার বোওয়ার এটি প্রথম উপাদানটির জন্য ইনস্টল করে এবং দ্বিতীয়টি পার্স করা শুরু করে, তবে এটি বুঝতে পারবে যে এই নির্ভরতা ইতিমধ্যেই ইনস্টল করা আছে এবং এটি পুনরায় ডাউনলোড বা নকল করবে না। একইভাবে, এটি সেই নির্ভরতা ফাইলগুলিকে ততক্ষণ পর্যন্ত রাখবে যতক্ষণ না অন্তত একটি উপাদান থাকে যা এখনও এটির bower.json এ এটিকে সংজ্ঞায়িত করে।

একটি ব্যাশ স্ক্রিপ্ট নেস্টেড এলিমেন্ট স্ট্রাকচারে সমস্ত bower.json ফাইল খুঁজে পায়। তারপরে এটি একের পর এক এই ডিরেক্টরিগুলিতে প্রবেশ করে এবং তাদের প্রতিটিতে bower install সম্পাদন করে:

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

দ্রুত নতুন উপাদান টেমপ্লেট

আপনি যখনই একটি নতুন উপাদান তৈরি করতে চান তখন এটি কিছুটা সময় নেয়: সঠিক নাম সহ ফোল্ডার এবং মৌলিক ফাইল কাঠামো তৈরি করা। তাই আমরা একটি সাধারণ উপাদান জেনারেটর লিখতে Slush ব্যবহার করি।

আপনি কমান্ড লাইন থেকে স্ক্রিপ্ট কল করতে পারেন:

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

এবং নতুন উপাদান তৈরি করা হয়, সমস্ত ফাইল গঠন এবং বিষয়বস্তু সহ।

আমরা উপাদান ফাইলগুলির জন্য টেমপ্লেটগুলি সংজ্ঞায়িত করেছি, যেমন .jade ফাইল টেমপ্লেটটি দেখতে নিম্নরূপ:

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

স্লাশ জেনারেটর আসল উপাদান পাথ এবং নাম দিয়ে ভেরিয়েবল প্রতিস্থাপন করে।

উপাদান তৈরি করতে Gulp ব্যবহার করা

গুলপ নির্মাণ প্রক্রিয়া নিয়ন্ত্রণে রাখে। এবং আমাদের কাঠামোতে, উপাদানগুলি তৈরি করতে আমাদের নিম্নলিখিত পদক্ষেপগুলি অনুসরণ করতে Gulp প্রয়োজন:

  1. উপাদানগুলির .coffee ফাইলগুলি .js এ কম্পাইল করুন৷
  2. উপাদানগুলির .scss ফাইলগুলিকে .css এ কম্পাইল করুন
  3. এলিমেন্টের .jade ফাইল .html এ কম্পাইল করুন, .css ফাইল এম্বেড করুন।

আরো বিস্তারিত:

উপাদানগুলির .coffee ফাইলগুলি .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')));
});

ধাপ 2 এবং 3 এর জন্য আমরা gulp এবং একটি কম্পাস প্লাগইন ব্যবহার করি scss থেকে .css এবং .jade থেকে .html কম্পাইল করার জন্য, উপরের 2 এর অনুরূপ পদ্ধতিতে।

পলিমার উপাদান সহ

আসলে পলিমার উপাদান অন্তর্ভুক্ত করতে আমরা 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">

উৎপাদনের জন্য পলিমার উপাদান অপ্টিমাইজ করা

একটি বড় প্রকল্পে প্রচুর পলিমার উপাদান থাকতে পারে। আমাদের প্রকল্পে, আমাদের পঞ্চাশটিরও বেশি রয়েছে। আপনি যদি বিবেচনা করেন যে প্রতিটি উপাদানের একটি পৃথক .js ফাইল রয়েছে এবং কিছু লাইব্রেরি রয়েছে, এটি 100 টিরও বেশি পৃথক ফাইলে পরিণত হয়। এর মানে হল কার্যক্ষমতা হ্রাস সহ ব্রাউজারকে অনেক অনুরোধ করতে হবে। একইভাবে আমরা একটি কৌণিক বিল্ডে আবেদন করব একটি সংযুক্ত এবং ছোট প্রক্রিয়ার জন্য, আমরা উৎপাদনের জন্য পলিমার প্রকল্পটিকে "ভালকানাইজ" করি।

ভলকানাইজ হল একটি পলিমার টুল যা নির্ভরতা গাছকে একটি একক HTML ফাইলে সমতল করে, অনুরোধের সংখ্যা কমিয়ে দেয়। এটি বিশেষত এমন ব্রাউজারগুলির জন্য দুর্দান্ত যেগুলি নেটিভভাবে ওয়েব উপাদানগুলিকে সমর্থন করে না।

সিএসপি (কন্টেন্ট সিকিউরিটি পলিসি) এবং পলিমার

নিরাপদ ওয়েব অ্যাপ্লিকেশন বিকাশ করার সময় আপনাকে CSP প্রয়োগ করতে হবে। সিএসপি হল নিয়মের একটি সেট যা ক্রস-সাইট স্ক্রিপ্টিং (এক্সএসএস) আক্রমণ প্রতিরোধ করে: অনিরাপদ উত্স থেকে স্ক্রিপ্ট চালানো, বা এইচটিএমএল ফাইল থেকে ইনলাইন স্ক্রিপ্ট চালানো।

এখন ভলকানাইজ দ্বারা উত্পন্ন একটি, অপ্টিমাইজ করা, সংযুক্ত এবং ছোট করা .html ফাইলটিতে সমস্ত জাভাস্ক্রিপ্ট কোড ইনলাইন একটি নন সিএসপি কমপ্লায়েন্ট ফর্ম্যাটে রয়েছে৷ এটি মোকাবেলার জন্য আমরা ক্রিসপার নামে একটি টুল ব্যবহার করি।

ক্রিস্পার একটি HTML ফাইল থেকে ইনলাইন স্ক্রিপ্টগুলিকে বিভক্ত করে এবং CSP সম্মতির জন্য একটি একক, বহিরাগত জাভাস্ক্রিপ্ট ফাইলে রাখে। তাই আমরা ভলকানাইজড এইচটিএমএল ফাইলটি ক্রিসপারের মাধ্যমে পাস করি এবং দুটি ফাইল দিয়ে শেষ করি: elements.html এবং elements.jselements.html এর ভিতরে এটি জেনারেট করা elements.js লোড করার যত্ন নেয়।

অ্যাপ্লিকেশন লজিক্যাল স্ট্রাকচার

পলিমারে, উপাদানগুলি একটি নন-ভিজ্যুয়াল ইউটিলিটি থেকে ছোট, স্বতন্ত্র এবং পুনঃব্যবহারযোগ্য UI উপাদান (যেমন বোতাম) থেকে "পৃষ্ঠাগুলি" এর মতো বড় মডিউল এবং এমনকি সম্পূর্ণ অ্যাপ্লিকেশন কম্পোজ করা পর্যন্ত যেকোনো কিছু হতে পারে।

অ্যাপ্লিকেশনের একটি শীর্ষ-স্তরের যৌক্তিক কাঠামো
পলিমার উপাদানের সাথে উপস্থাপিত আমাদের অ্যাপ্লিকেশনের একটি শীর্ষ-স্তরের যৌক্তিক কাঠামো।

পলিমার এবং পিতা-মাতা-শিশু আর্কিটেকচারের সাথে পোস্টপ্রসেসিং

যেকোনো 3D গ্রাফিক্স পাইপলাইনে, সর্বদা একটি শেষ ধাপ থাকে যেখানে পুরো ছবির উপরে এক ধরনের ওভারলে হিসাবে প্রভাব যুক্ত করা হয়। এটি হল পোস্ট-প্রসেসিং ধাপ, এবং এতে আলোকসজ্জা, গড-রশ্মি, ক্ষেত্রের গভীরতা, বোকেহ, অস্পষ্টতা ইত্যাদির মতো প্রভাব জড়িত। দৃশ্যটি কীভাবে তৈরি করা হয়েছে সে অনুযায়ী প্রভাবগুলি একত্রিত এবং বিভিন্ন উপাদানে প্রয়োগ করা হয়। THREE.js-এ আমরা জাভাস্ক্রিপ্টে পোস্ট-প্রসেসিংয়ের জন্য একটি কাস্টম শেডার তৈরি করতে পারি বা আমরা পলিমার দিয়ে এটি করতে পারি, এর পিতামাতা-সন্তান কাঠামোর জন্য ধন্যবাদ।

আপনি যদি আমাদের পোস্ট-প্রসেসরের উপাদান HTML কোড দেখেন:

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

আমরা একটি সাধারণ শ্রেণীর অধীনে নেস্টেড পলিমার উপাদান হিসাবে প্রভাবগুলি নির্দিষ্ট করি। তারপর, sw-experience-postprocessor.js এ আমরা এটি করি:

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

আমরা HTML বৈশিষ্ট্য এবং JavaScript এর querySelectorAll ব্যবহার করি পোস্ট প্রসেসরের মধ্যে এইচটিএমএল উপাদান হিসাবে নেস্ট করা সমস্ত প্রভাবগুলি খুঁজে বের করার জন্য, সেগুলি যে ক্রমে নির্দিষ্ট করা হয়েছিল। তারপর আমরা সেগুলিকে পুনরাবৃত্তি করি এবং সেগুলিকে কম্পোজারে যুক্ত করি।

এখন, ধরা যাক আমরা DOF (ডেপথ অফ ফিল্ড) প্রভাব সরিয়ে ফেলতে চাই এবং ব্লুম এবং ভিননেট প্রভাবগুলির ক্রম পরিবর্তন করতে চাই। আমাদের যা করতে হবে তা হল পোস্ট-প্রসেসরের সংজ্ঞাটি এমন কিছুতে সম্পাদনা করা:

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

এবং দৃশ্যটি প্রকৃত কোডের একটি লাইন পরিবর্তন না করেই চলবে।

পলিমারে লুপ এবং আপডেট লুপ রেন্ডার করুন

পলিমারের সাহায্যে আমরা সুন্দরভাবে রেন্ডারিং এবং ইঞ্জিন আপডেটের কাছেও যেতে পারি। আমরা একটি timer উপাদান তৈরি করেছি যা requestAnimationFrame ব্যবহার করে এবং বর্তমান সময় ( t ) এবং শেষ ফ্রেম ( 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()

তারপর, আমরা আমাদের ইঞ্জিনে t এবং dt বৈশিষ্ট্যগুলিকে আবদ্ধ করতে ডেটা বাইন্ডিং ব্যবহার করি ( experience.jade ):

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

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

এবং আমরা ইঞ্জিনে t এবং dt এর পরিবর্তনগুলি শুনি এবং যখনই মানগুলি পরিবর্তিত হয়, _update ফাংশনটি বলা হবে:

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

আপনি যদি FPS-এর জন্য ক্ষুধার্ত হন তবে, আপনি পরিবর্তনগুলি সম্পর্কে উপাদানগুলিকে অবহিত করার জন্য প্রয়োজনীয় কয়েকটি মিলিসেকেন্ড বাঁচাতে রেন্ডার লুপে পলিমারের ডেটা বাইন্ডিং সরিয়ে দিতে চাইতে পারেন। আমরা নিম্নরূপ কাস্টম পর্যবেক্ষক প্রয়োগ করেছি:

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 ফাংশন একটি কলব্যাক গ্রহণ করে এবং এটিকে তার কলব্যাক অ্যারেতে সংরক্ষণ করে। তারপর, আপডেট লুপে, আমরা প্রতিটি কলব্যাকের উপর পুনরাবৃত্তি করি এবং ডাটা বাইন্ডিং বা ইভেন্ট ফায়ারিংকে বাইপাস করে সরাসরি dt এবং t আর্গুমেন্টের সাথে এটি কার্যকর করি। একবার একটি কলব্যাক আর সক্রিয় থাকার জন্য নয়, আমরা একটি removeUpdateListener ফাংশন যোগ করেছি যা আপনাকে আগের যোগ করা কলব্যাকটি সরাতে দেয়।

THREE.js-এ একটি লাইটসাবার

THREE.js WebGL-এর নিম্ন স্তরের বিশদ বিমূর্ত করে এবং আমাদের সমস্যার উপর ফোকাস করার অনুমতি দেয়। এবং আমাদের সমস্যা হল স্টর্মট্রুপারদের সাথে লড়াই করা এবং আমাদের একটি অস্ত্র দরকার। তাই আসুন একটি লাইটসাবার তৈরি করি।

চকচকে ব্লেড হল যে কোনও পুরানো দুই হাতের অস্ত্র থেকে লাইটসাবারকে আলাদা করে। এটি প্রধানত দুটি অংশ দিয়ে তৈরি: মরীচি এবং ট্রেইল যা এটি সরানোর সময় দেখা যায়। আমরা এটিকে একটি উজ্জ্বল সিলিন্ডার আকৃতি এবং একটি গতিশীল ট্রেইল দিয়ে তৈরি করেছি যা প্লেয়ারের নড়াচড়ার সাথে সাথে এটিকে অনুসরণ করে।

কাটার যন্ত্র

ব্লেড দুটি সাব ব্লেড দিয়ে তৈরি। একটি অভ্যন্তরীণ এবং একটি বাইরের। উভয়ই তাদের নিজ নিজ উপকরণ সহ THREE.js মেশ।

ইনার ব্লেড

অভ্যন্তরীণ ব্লেডের জন্য আমরা একটি কাস্টম শেডার সহ একটি কাস্টম উপাদান ব্যবহার করেছি। আমরা দুটি বিন্দু দ্বারা তৈরি একটি লাইন নিই এবং একটি সমতলে এই দুটি বিন্দুর মধ্যে রেখাটি প্রজেক্ট করি। এই প্লেনটি মূলত যা আপনি নিয়ন্ত্রণ করেন যখন আপনি আপনার মোবাইলের সাথে লড়াই করেন, এটি সাবারকে গভীরতা এবং অভিযোজনের ধারনা দেয়।

একটি বৃত্তাকার প্রদীপ্ত বস্তুর অনুভূতি তৈরি করার জন্য আমরা নীচের মত A এবং B দুটি বিন্দুকে যুক্ত করে মূল লাইন থেকে সমতলের যেকোনো বিন্দুর অর্থোগোনাল বিন্দুর দূরত্ব দেখি। একটি বিন্দু মূল অক্ষের যত কাছে থাকবে তত উজ্জ্বল হবে।

অভ্যন্তরীণ ব্লেড উজ্জ্বল

নীচের উত্সটি দেখায় যে কীভাবে আমরা ভার্টেক্স শেডারে তীব্রতা নিয়ন্ত্রণ করতে একটি vFactor গণনা করি এবং তারপরে এটিকে ফ্র্যাগমেন্ট শেডারের দৃশ্যের সাথে মিশ্রিত করতে ব্যবহার করি।

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

};

আউটার ব্লেড গ্লো

বাইরের উজ্জ্বলতার জন্য আমরা একটি পৃথক রেন্ডারবাফারে রেন্ডার করি এবং একটি পোস্ট-প্রসেসিং ব্লুম প্রভাব ব্যবহার করি এবং পছন্দসই আভা পেতে চূড়ান্ত চিত্রের সাথে মিশ্রিত করি। নীচের ছবিটি তিনটি ভিন্ন অঞ্চল দেখায় যেগুলি আপনার প্রয়োজন যদি আপনি একটি শালীন সাবার চান। যথা সাদা কোর, মধ্যম নীল-ইশ আভা এবং বাইরের আভা।

বাইরের ব্লেড

লাইটসেবার ট্রেইল

স্টার ওয়ার্স সিরিজে দেখা আসলটির মতো পূর্ণ প্রভাবের চাবিকাঠি হল লাইটসাবারের পথ। আমরা আলোকসজ্জার গতিবিধির উপর ভিত্তি করে গতিশীলভাবে উত্পন্ন ত্রিভুজগুলির একটি পাখা দিয়ে পথ তৈরি করেছি। এই ফ্যানগুলিকে পরবর্তী ভিজ্যুয়াল বর্ধনের জন্য পোস্টপ্রসেসরে পাঠানো হয়। ফ্যানের জ্যামিতি তৈরি করার জন্য আমাদের একটি লাইন সেগমেন্ট আছে এবং এর আগের ট্রান্সফর্ম এবং বর্তমান ট্রান্সফর্মের উপর ভিত্তি করে আমরা জালের মধ্যে একটি নতুন ত্রিভুজ তৈরি করি, একটি নির্দিষ্ট দৈর্ঘ্যের পরে লেজের অংশটি ছেড়ে দিই।

Lightsaber ট্রেইল বাম
Lightsaber ট্রেইল ডান

একবার আমাদের কাছে একটি জাল হয়ে গেলে আমরা এটিতে একটি সাধারণ উপাদান বরাদ্দ করি এবং একটি মসৃণ প্রভাব তৈরি করতে এটি পোস্টপ্রসেসরে প্রেরণ করি। আমরা একই ব্লুম এফেক্ট ব্যবহার করি যা আমরা বাইরের ব্লেড গ্লোতে প্রয়োগ করেছি এবং আপনি দেখতে পাচ্ছেন এমন একটি মসৃণ লেজ পেতে পারেন:

পুরো পথ

লেজ চারপাশে আলোকিত

চূড়ান্ত অংশটি সম্পূর্ণ হওয়ার জন্য আমাদের প্রকৃত পথের চারপাশে উজ্জ্বলতা পরিচালনা করতে হয়েছিল, যা বিভিন্ন উপায়ে তৈরি করা যেতে পারে। আমাদের সমাধান যে আমরা এখানে বিস্তারিতভাবে যেতে পারি না, কার্যক্ষমতার কারণে এই বাফারের জন্য একটি কাস্টম শেডার তৈরি করা ছিল যা রেন্ডারবাফারের একটি ক্ল্যাম্পের চারপাশে একটি মসৃণ প্রান্ত তৈরি করে। তারপরে আমরা চূড়ান্ত রেন্ডারে এই আউটপুটটি একত্রিত করি, এখানে আপনি লেজটিকে ঘিরে থাকা আভা দেখতে পাবেন:

আভা সঙ্গে ট্রেইল

উপসংহার

পলিমার একটি শক্তিশালী লাইব্রেরি এবং ধারণা (সাধারণভাবে ওয়েব কম্পোনেন্টগুলির মতো)। আপনি এটি দিয়ে কী তৈরি করবেন তা কেবল আপনার উপর নির্ভর করে। এটি একটি সাধারণ UI বোতাম থেকে একটি পূর্ণ আকারের WebGL অ্যাপ্লিকেশন পর্যন্ত যেকোনো কিছু হতে পারে৷ পূর্ববর্তী অধ্যায়ে আমরা আপনাকে কিছু টিপস এবং কৌশল দেখিয়েছি কিভাবে পলিমার উৎপাদনে দক্ষতার সাথে ব্যবহার করা যায় এবং কীভাবে আরও জটিল মডিউল গঠন করা যায় যেগুলিও ভাল কাজ করে। আমরা আপনাকে দেখিয়েছি কিভাবে WebGL-এ একটি সুন্দর লুকিং লাইটসেবার অর্জন করা যায়। সুতরাং আপনি যদি সেগুলি একত্রিত করেন, তাহলে প্রোডাকশন সার্ভারে স্থাপন করার আগে আপনার পলিমার উপাদানগুলিকে ভলকানাইজ করতে ভুলবেন না এবং আপনি যদি CSP অনুগত থাকতে চান তবে ক্রিস্পার ব্যবহার করতে ভুলবেন না, শক্তি আপনার সাথে থাকতে পারে!

খেলা খেলা