ভূমিকা
যখন আমরা আমাদের Wordico ক্রসওয়ার্ড গেমটিকে Flash থেকে HTML5 তে রূপান্তর করি, তখন আমাদের প্রথম কাজটি ছিল ব্রাউজারে একটি সমৃদ্ধ ব্যবহারকারীর অভিজ্ঞতা তৈরি করার বিষয়ে আমরা যা জানতাম তার সমস্ত কিছু বাদ দেওয়া। যদিও ফ্ল্যাশ অ্যাপ্লিকেশন বিকাশের সমস্ত দিকগুলির জন্য একটি একক, ব্যাপক API অফার করেছে - ভেক্টর অঙ্কন থেকে বহুভুজ হিট সনাক্তকরণ থেকে XML পার্সিং পর্যন্ত - HTML5 বিভিন্ন ব্রাউজার সমর্থন সহ স্পেসিফিকেশনের একটি ঝাঁকুনি অফার করেছে৷ HTML, একটি নথি-নির্দিষ্ট ভাষা, এবং CSS, একটি বক্স-কেন্দ্রিক ভাষা, একটি গেম তৈরির জন্য উপযুক্ত কিনা তাও আমরা ভেবেছিলাম। গেমটি কি ব্রাউজার জুড়ে সমানভাবে প্রদর্শন করবে, যেমনটি ফ্ল্যাশে করেছিল, এবং এটি কি সুন্দরভাবে দেখতে এবং আচরণ করবে? Wordico জন্য, উত্তর ছিল হ্যাঁ.
আপনার ভেক্টর কি, ভিক্টর?
আমরা শুধুমাত্র ভেক্টর গ্রাফিক্স ব্যবহার করে Wordico-এর মূল সংস্করণ তৈরি করেছি: লাইন, বক্ররেখা, ফিলস এবং গ্রেডিয়েন্ট। ফলাফলটি অত্যন্ত কম্প্যাক্ট এবং অসীমভাবে মাপযোগ্য উভয়ই ছিল:
আমরা একাধিক স্টেট বিশিষ্ট বস্তু তৈরি করতে ফ্ল্যাশ টাইমলাইনের সুবিধাও নিয়েছি। উদাহরণস্বরূপ, আমরা Space
অবজেক্টের জন্য নয়টি নামযুক্ত কীফ্রেম ব্যবহার করেছি:
HTML5 এ, তবে, আমরা একটি বিটম্যাপড স্প্রাইট ব্যবহার করি:
পৃথক স্পেস থেকে 15x15 গেমবোর্ড তৈরি করতে, আমরা একটি 225-অক্ষরের স্ট্রিং স্বরলিপিতে পুনরাবৃত্তি করি যেখানে প্রতিটি স্থান একটি ভিন্ন অক্ষর দ্বারা প্রতিনিধিত্ব করা হয় (যেমন ট্রিপল অক্ষরের জন্য "t" এবং ট্রিপল শব্দের জন্য "T")। এটি ছিল ফ্ল্যাশে একটি সোজা অপারেশন; আমরা কেবল স্পেসগুলি স্ট্যাম্প আউট করেছি এবং সেগুলিকে একটি গ্রিডে সাজিয়েছি:
var spaces:Array = new Array();
for (var i:int = 0; i < 225; i++) {
var space:Space = new Space(i, layout.charAt(i));
...
spaces.push(addChild(space));
}
LayoutUtil.grid(spaces, 15);
HTML5 এ, এটা একটু বেশি জটিল। আমরা <canvas>
উপাদান ব্যবহার করি, একটি বিটম্যাপ অঙ্কন পৃষ্ঠ, গেমবোর্ডকে একবারে একটি বর্গক্ষেত্র আঁকতে। প্রথম ধাপ হল ইমেজ স্প্রাইট লোড করা। একবার এটি লোড হয়ে গেলে, আমরা লেআউট স্বরলিপির মাধ্যমে পুনরাবৃত্তি করি, প্রতিটি পুনরাবৃত্তির সাথে চিত্রের একটি ভিন্ন অংশ আঁকি:
var x = 0; // x coordinate
var y = 0; // y coordinate
var w = 35; // width and height of a space
for (var i = 0; i < 225; i++) {
if (i && i % 15 == 0) {
x = 0;
y += w;
}
var imageX = "_dDFtTqQxm".indexOf(layout.charAt(i)) * 70;
canvas.drawImage("spaces.png", imageX, 0, 70, 70, x, y, w, w);
x += w;
}
ওয়েব ব্রাউজারে ফলাফল এখানে। মনে রাখবেন যে ক্যানভাসে নিজেই একটি CSS ড্রপ শ্যাডো রয়েছে:
টাইল বস্তু রূপান্তর একটি অনুরূপ ব্যায়াম ছিল. ফ্ল্যাশে, আমরা পাঠ্য ক্ষেত্র এবং ভেক্টর আকার ব্যবহার করেছি:
HTML5-এ, আমরা রানটাইমে একটি একক <canvas>
উপাদানে তিনটি ইমেজ স্প্রাইট একত্রিত করি:
এখন আমাদের কাছে 100টি ক্যানভাস (প্রতিটি টাইলের জন্য একটি) এবং গেমবোর্ডের জন্য একটি ক্যানভাস রয়েছে। এখানে একটি "H" টাইলের জন্য মার্কআপ রয়েছে:
<canvas width="35" height="35" class="tile tile-racked" title="H-2"/>
এখানে সংশ্লিষ্ট CSS:
.tile {
width: 35px;
height: 35px;
position: absolute;
cursor: pointer;
z-index: 1000;
}
.tile-drag {
-moz-box-shadow: 1px 1px 7px rgba(0,0,0,0.8);
-webkit-box-shadow: 1px 1px 7px rgba(0,0,0,0.8);
-moz-transform: scale(1.10);
-webkit-transform: scale(1.10);
-webkit-box-reflect: 0px;
opacity: 0.85;
}
.tile-locked {
cursor: default;
}
.tile-racked {
-webkit-box-reflect: below 0px -webkit-gradient(linear, 0% 0%, 0% 100%,
from(transparent), color-stop(0.70, transparent), to(white));
}
আমরা CSS3 প্রভাব প্রয়োগ করি যখন টাইলটি টেনে আনা হয় (ছায়া, অস্বচ্ছতা এবং স্কেলিং) এবং যখন টাইলটি র্যাকে বসে থাকে (প্রতিফলন):
রাস্টার ছবি ব্যবহার করার কিছু সুস্পষ্ট সুবিধা রয়েছে। প্রথমত, ফলাফলটি পিক্সেল-সুনির্দিষ্ট। দ্বিতীয়ত, এই ছবিগুলি ব্রাউজার দ্বারা ক্যাশে করা যেতে পারে। তৃতীয়ত, একটু বাড়তি কাজের মাধ্যমে, আমরা নতুন টাইল ডিজাইন তৈরি করতে ছবিগুলিকে অদলবদল করতে পারি - যেমন একটি ধাতব টাইল - এবং এই নকশার কাজটি ফ্ল্যাশের পরিবর্তে ফটোশপে করা যেতে পারে।
খারাপ দিক? ইমেজ ব্যবহার করে, আমরা পাঠ্য ক্ষেত্রের প্রোগ্রামেটিক অ্যাক্সেস ছেড়ে দিই। ফ্ল্যাশে, রঙ বা প্রকারের অন্যান্য বৈশিষ্ট্য পরিবর্তন করা একটি সহজ অপারেশন ছিল; HTML5-এ, এই বৈশিষ্ট্যগুলিকে নিজেরাই চিত্রগুলিতে বেক করা হয়। (আমরা HTML পাঠ্য চেষ্টা করেছি, কিন্তু এটির জন্য প্রচুর অতিরিক্ত মার্কআপ এবং CSS প্রয়োজন। আমরা ক্যানভাস পাঠ্যও চেষ্টা করেছি, কিন্তু ফলাফলগুলি ব্রাউজার জুড়ে অসামঞ্জস্যপূর্ণ ছিল।)
অস্পষ্ট যুক্তি
আমরা যেকোনো আকারে ব্রাউজার উইন্ডোর সম্পূর্ণ ব্যবহার করতে চেয়েছিলাম - এবং স্ক্রলিং এড়াতে চাই। ফ্ল্যাশে এটি একটি তুলনামূলকভাবে সহজ অপারেশন ছিল, যেহেতু পুরো গেমটি ভেক্টরে আঁকা হয়েছিল এবং বিশ্বস্ততা না হারিয়ে উপরে বা নিচের দিকে ছোট করা যেতে পারে। কিন্তু এটি এইচটিএমএল এর মধ্যে trickier ছিল. আমরা CSS স্কেলিং ব্যবহার করার চেষ্টা করেছি কিন্তু একটি অস্পষ্ট ক্যানভাস দিয়ে শেষ করেছি:
আমাদের সমাধান হল যখনই ব্যবহারকারী ব্রাউজারটির আকার পরিবর্তন করে তখন গেমবোর্ড, র্যাক এবং টাইলস পুনরায় আঁকতে হয়:
window.onresize = function (evt) {
...
gameboard.setConstraints(boardWidth, boardWidth);
...
rack.setConstraints(rackWidth, rackHeight);
...
tileManager.resizeTiles(tileSize);
});
আমরা যেকোন স্ক্রীন আকারে খাস্তা ছবি এবং আনন্দদায়ক লেআউট দিয়ে শেষ করি:
পয়েন্ট পেতে
যেহেতু প্রতিটি টাইল একেবারে অবস্থান করে এবং অবশ্যই গেমবোর্ড এবং র্যাকের সাথে সঠিকভাবে সারিবদ্ধ হতে হবে, আমাদের একটি নির্ভরযোগ্য পজিশনিং সিস্টেম প্রয়োজন। গ্লোবাল স্পেসে (এইচটিএমএল পৃষ্ঠা) উপাদানগুলির অবস্থান পরিচালনা করতে আমরা দুটি ফাংশন, Bounds
এবং Point
ব্যবহার করি। Bounds
পৃষ্ঠায় একটি আয়তক্ষেত্রাকার এলাকা বর্ণনা করে, যখন Point
পৃষ্ঠার উপরের বাম কোণে (0,0), অন্যথায় নিবন্ধন পয়েন্ট হিসাবে পরিচিত একটি x,y স্থানাঙ্ক বর্ণনা করে।
Bounds
সাহায্যে, আমরা দুটি আয়তক্ষেত্রাকার উপাদানের ছেদ সনাক্ত করতে পারি (যেমন যখন একটি টাইল রাককে অতিক্রম করে) বা একটি আয়তক্ষেত্রাকার অঞ্চলে (যেমন একটি দ্বি-অক্ষরের স্থান) একটি নির্বিচারে বিন্দু রয়েছে (যেমন একটি টাইলের কেন্দ্র বিন্দু) . এখানে সীমানা বাস্তবায়ন:
// bounds.js
function Bounds(element) {
var x = element.offsetLeft;
var y = element.offsetTop;
var w = element.offsetWidth;
var h = element.offsetHeight;
this.left = x;
this.right = x + w;
this.top = y;
this.bottom = y + h;
this.width = w;
this.height = h;
this.x = x;
this.y = y;
this.midx = x + (w / 2);
this.midy = y + (h / 2);
this.topleft = new Point(x, y);
this.topright = new Point(x + w, y);
this.bottomleft = new Point(x, y + h);
this.bottomright = new Point(x + w, y + h);
this.middle = new Point(x + (w / 2), y + (h / 2));
}
Bounds.prototype.contains = function (point) {
return point.x > this.left &&
point.x < this.right &&
point.y > this.top &&
point.y < this.bottom;
}
Bounds.prototype.intersects = function (bounds) {
return this.contains(bounds.topleft) ||
this.contains(bounds.topright) ||
this.contains(bounds.bottomleft) ||
this.contains(bounds.bottomright) ||
bounds.contains(this.topleft) ||
bounds.contains(this.topright) ||
bounds.contains(this.bottomleft) ||
bounds.contains(this.bottomright);
}
Bounds.prototype.toString = function () {
return [this.x, this.y, this.width, this.height].join(",");
}
আমরা পৃষ্ঠায় বা মাউস ইভেন্টের যেকোনো উপাদানের পরম স্থানাঙ্ক (শীর্ষ-বাম কোণে) নির্ধারণ করতে Point
ব্যবহার করি। Point
দূরত্ব এবং দিকনির্দেশ গণনার পদ্ধতিও রয়েছে, যা অ্যানিমেশন প্রভাব তৈরির জন্য প্রয়োজনীয়। এখানে Point
বাস্তবায়ন:
// point.js
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.distance = function (point) {
var a = point.x - this.x;
var b = point.y - this.y;
return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}
Point.prototype.distanceX = function (point) {
return Math.abs(this.x - point.x);
}
Point.prototype.distanceY = function (point) {
return Math.abs(this.y - point.y);
}
Point.prototype.interpolate = function (point, pct) {
var x = this.x + ((point.x - this.x) * pct);
var y = this.y + ((point.y - this.y) * pct);
return new Point(x, y);
}
Point.prototype.offset = function (x, y) {
return new Point(this.x + x, this.y + y);
}
Point.prototype.vector = function (point) {
return new Point(point.x - this.x, point.y - this.y);
}
Point.prototype.toString = function () {
return this.x + "," + this.y;
}
// static
Point.fromElement = function (element) {
return new Point(element.offsetLeft, element.offsetTop);
}
// static
Point.fromEvent = function (evt) {
return new Point(evt.x || evt.clientX, evt.y || evt.clientY);
}
এই ফাংশনগুলি ড্র্যাগ-এন্ড-ড্রপ এবং অ্যানিমেশন ক্ষমতার ভিত্তি তৈরি করে। উদাহরণ স্বরূপ, আমরা Bounds.intersects()
ব্যবহার করি তা নির্ধারণ করার জন্য যে একটি টাইল গেমবোর্ডের একটি স্থানকে ওভারল্যাপ করে কিনা; টেনে আনা টাইলের দিক নির্ধারণ করতে আমরা Point.vector()
ব্যবহার করি; এবং আমরা একটি মোশন টুইন বা ইজিং ইফেক্ট তৈরি করতে টাইমারের সাথে Point.interpolate()
ব্যবহার করি।
প্রবাহের সাথে যান
ফ্ল্যাশে ফিক্সড সাইজের লেআউট তৈরি করা সহজ হলেও এইচটিএমএল এবং সিএসএস বক্স মডেলের মাধ্যমে ফ্লুইড লেআউট তৈরি করা অনেক সহজ। নিম্নলিখিত গ্রিড ভিউ বিবেচনা করুন, এর পরিবর্তনশীল প্রস্থ এবং উচ্চতা সহ:
অথবা চ্যাট প্যানেল বিবেচনা করুন. ফ্ল্যাশ সংস্করণে একাধিক ইভেন্ট হ্যান্ডলারের প্রয়োজন ছিল মাউস অ্যাকশনের প্রতিক্রিয়া জানাতে, স্ক্রোলযোগ্য এলাকার জন্য একটি মুখোশ, স্ক্রোল অবস্থান গণনার জন্য গণিত, এবং এটিকে একসাথে আঠালো করার জন্য অনেক অন্যান্য কোড।
এইচটিএমএল সংস্করণ, তুলনা করে, একটি নির্দিষ্ট উচ্চতা সহ একটি <div>
এবং ওভারফ্লো বৈশিষ্ট্যটি লুকিয়ে রাখা হয়েছে৷ স্ক্রোলিং আমাদের কিছুই খরচ.
এই ধরনের ক্ষেত্রে - সাধারণ লেআউট কাজগুলি - HTML এবং CSS ফ্ল্যাশকে ছাড়িয়ে যায়।
আপনি এখন আমাকে শুনতে পারেন?
আমরা <audio>
ট্যাগের সাথে লড়াই করেছি - এটি নির্দিষ্ট ব্রাউজারে বারবার ছোট শব্দের প্রভাবগুলি চালাতে সক্ষম ছিল না। আমরা দুটি সমাধানের চেষ্টা করেছি। প্রথমত, আমরা সাউন্ড ফাইলগুলিকে লম্বা করার জন্য মৃত বাতাস দিয়ে প্যাড করেছি। তারপরে আমরা একাধিক অডিও চ্যানেল জুড়ে প্লেব্যাকের বিকল্প চেষ্টা করেছি। কোন কৌশলই সম্পূর্ণ কার্যকর বা মার্জিত ছিল না।
শেষ পর্যন্ত আমরা আমাদের নিজস্ব ফ্ল্যাশ অডিও প্লেয়ার রোল করার এবং HTML5 অডিওকে ফলব্যাক হিসাবে ব্যবহার করার সিদ্ধান্ত নিয়েছি। এখানে ফ্ল্যাশে মৌলিক কোড আছে:
var sounds = new Array();
function playSound(path:String):void {
var sound:Sound = sounds[path];
if (sound == null) {
sound = new Sound();
sound.addEventListener(Event.COMPLETE, function (evt:Event) {
sound.play();
});
sound.load(new URLRequest(path));
sounds[path] = sound;
}
else {
sound.play();
}
}
ExternalInterface.addCallback("playSound", playSound);
জাভাস্ক্রিপ্টে, আমরা এমবেডেড ফ্ল্যাশ প্লেয়ার সনাক্ত করার চেষ্টা করি। যদি এটি ব্যর্থ হয়, আমরা প্রতিটি শব্দ ফাইলের জন্য একটি <audio>
নোড তৈরি করি:
function play(String soundId) {
var src = "/audio/" + soundId + ".mp3";
// Flash
try {
var swf = window["swfplayer"] || document["swfplayer"];
swf.playSound(src);
}
// or HTML5 audio
catch (e) {
var sound = document.getElementById(soundId);
if (sound == null || sound == undefined) {
var sound = document.createElement("audio");
sound.id = soundId;
sound.src = src;
document.body.appendChild(sound);
}
sound.play();
}
}
মনে রাখবেন যে এটি শুধুমাত্র MP3 ফাইলের জন্য কাজ করে - আমরা কখনই OGG সমর্থন করতে বিরক্ত করিনি। আমরা আশা করি শিল্প অদূর ভবিষ্যতে একটি একক বিন্যাসে স্থায়ী হবে।
ভোটের অবস্থান
গেমের অবস্থা রিফ্রেশ করার জন্য আমরা HTML5-এ একই কৌশল ব্যবহার করি যেমনটি আমরা ফ্ল্যাশে করেছি: প্রতি 10 সেকেন্ডে, ক্লায়েন্ট সার্ভারকে আপডেটের জন্য জিজ্ঞাসা করে। যদি শেষ ভোটের পরে গেমের অবস্থা পরিবর্তিত হয়, ক্লায়েন্ট পরিবর্তনগুলি গ্রহণ করে এবং পরিচালনা করে; অন্যথায়, কিছুই ঘটবে না। এই ঐতিহ্যগত পোলিং কৌশল গ্রহণযোগ্য, যদি বেশ মার্জিত না হয়। যাইহোক, গেমটি পরিপক্ক হওয়ার সাথে সাথে আমরা লং পোলিং বা WebSockets- এ স্যুইচ করতে চাই এবং ব্যবহারকারীরা নেটওয়ার্কে রিয়েল-টাইম ইন্টারঅ্যাকশন আশা করতে আসে। WebSockets, বিশেষ করে, গেম খেলা উন্নত করার অনেক সুযোগ উপস্থাপন করবে।
কি হাতিয়ার!
আমরা ফ্রন্ট-এন্ড ইউজার ইন্টারফেস এবং ব্যাক-এন্ড কন্ট্রোল লজিক (প্রমাণিকরণ, বৈধতা, অধ্যবসায় এবং আরও অনেক কিছু) বিকাশ করতে Google ওয়েব টুলকিট (GWT) ব্যবহার করেছি। জাভাস্ক্রিপ্ট নিজেই জাভা সোর্স কোড থেকে সংকলিত হয়। উদাহরণস্বরূপ, পয়েন্ট ফাংশন Point.java
থেকে অভিযোজিত হয়েছে:
package com.wordico.client.view.layout;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.DomEvent;
public class Point {
public double x;
public double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double distance(Point point) {
double a = point.x - this.x;
double b = point.y - this.y;
return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}
...
}
কিছু UI ক্লাসে সংশ্লিষ্ট টেমপ্লেট ফাইল থাকে যেখানে পৃষ্ঠার উপাদানগুলি ক্লাসের সদস্যদের জন্য "বাউন্ড" থাকে। উদাহরণস্বরূপ, ChatPanel.ui.xml
ChatPanel.java
এর সাথে মিলে যায়:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder
xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:w="urn:import:com.wordico.client.view.widget">
<g:HTMLPanel>
<div class="palette">
<g:ScrollPanel ui:field="messagesScroll">
<g:FlowPanel ui:field="messagesFlow"></g:FlowPanel>
</g:ScrollPanel>
<g:TextBox ui:field="chatInput"></g:TextBox>
</div>
</g:HTMLPanel>
</ui:UiBinder>
সম্পূর্ণ বিবরণ এই নিবন্ধের সুযোগের বাইরে, কিন্তু আমরা আপনাকে আপনার পরবর্তী HTML5 প্রকল্পের জন্য GWT পরীক্ষা করার জন্য উত্সাহিত করি।
কেন জাভা ব্যবহার করবেন? প্রথমত, কঠোর টাইপিংয়ের জন্য। যদিও ডাইনামিক টাইপিং জাভাস্ক্রিপ্টে দরকারী - উদাহরণস্বরূপ, বিভিন্ন ধরণের মান ধরে রাখার জন্য একটি অ্যারের ক্ষমতা - এটি বড়, জটিল প্রকল্পগুলিতে মাথাব্যথা হতে পারে। দ্বিতীয়ত, রিফ্যাক্টরিং ক্ষমতার জন্য। হাজার হাজার লাইন কোড জুড়ে আপনি কীভাবে জাভাস্ক্রিপ্ট পদ্ধতির স্বাক্ষর পরিবর্তন করবেন তা বিবেচনা করুন - সহজে নয়! কিন্তু একটি ভাল জাভা IDE এর সাথে এটি একটি স্ন্যাপ। অবশেষে, পরীক্ষার উদ্দেশ্যে। জাভা ক্লাসের জন্য লিখিত ইউনিট পরীক্ষা "সংরক্ষণ এবং রিফ্রেশ" এর সময়-সম্মানিত কৌশলকে হারায়।
সারাংশ
আমাদের অডিও সমস্যাগুলি ব্যতীত, HTML5 আমাদের প্রত্যাশাকে অনেক বেশি অতিক্রম করেছে৷ Wordico শুধু ফ্ল্যাশের মতোই সুন্দর দেখায় না, এটি প্রতিটি বিট তরল এবং প্রতিক্রিয়াশীল। আমরা ক্যানভাস এবং CSS3 ছাড়া এটি করতে পারতাম না। আমাদের পরবর্তী চ্যালেঞ্জ: মোবাইল ব্যবহারের জন্য Wordico মানিয়ে নেওয়া।