مقدمة
عندما حوّلنا لعبة كلمات المرور Wordico من Flash إلى HTML5، كانت مهمتنا الأولى هي نسيان كل ما عرفناه عن توفير تجربة غنية للمستخدم في المتصفّح. في حين أنّ Flash قدّم واجهة برمجة تطبيقات واحدة وشاملة لجميع جوانب تطوير التطبيقات، بدءًا من الرسم المتجه إلى رصد النقاط المضبوطة للأشكال المضلّعة إلى تحليل XML، قدّم HTML5 مجموعة متنوعة من المواصفات مع دعم متباين للمتصفّحات. فكّرنا أيضًا في ما إذا كانت لغة HTML، وهي لغة خاصة بالوثائق، ولغة CSS، وهي لغة تركّز على المربّعات، مناسبة لإنشاء لعبة. هل ستظهر اللعبة بشكل موحّد في جميع المتصفّحات، كما كانت تظهر في Flash، وهل ستبدو بنفس الجودة؟ بالنسبة إلى Wordico، كانت الإجابة نعم.
ما هو الرسم المتجه يا فيكتور؟
لقد طوّرنا الإصدار الأصلي من Wordico باستخدام الرسومات المتجهّة فقط: الخطوط والمنحنيات وعمليات الملء والتدرّجات. وكانت النتيجة حلاً مكثّفًا للغاية وقابلًا للتوسّع إلى ما لا نهاية:
استفدنا أيضًا من مخطط الوقت في Flash لإنشاء عناصر لها حالات متعددة. على سبيل المثال، استخدمنا تسعة لقطات رئيسية مُسمّاة للكائن Space
:
في HTML5، نستخدم رسومًا متحركة بتنسيق مخطّط النقطة:
لإنشاء لوحة اللعبة 15×15 من مساحات فردية، نكرّر ترميز سلسلة من 225 حرفًا يتم فيها تمثيل كل مساحة بحرف مختلف (مثل "t" للحرف الثلاثي و "T" للكلمة الثلاثية). كانت هذه عملية مباشرة في Flash، حيث أزلنا المسافات ببساطة ورتبناها في شبكة:
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:
كان تحويل عنصر المربّع عملية مشابهة. في Flash، استخدمنا الحقول النصية والأشكال المتجهة:
في 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 عند سحب المربّع (الظلام والشفافية والتكبير/التصغير) وعند وضع المربّع على الرفّ (الانعكاس):
يُعدّ استخدام الصور النقطية ذا بعض المزايا الواضحة. أولاً، تكون النتيجة دقيقة بدقة بكسل. ثانيًا، يمكن أن يخزّن المتصفّح هذه الصور في ذاكرة التخزين المؤقت. ثالثًا، يمكننا استبدال الصور لإنشاء تصاميم جديدة للوحات، مثل لوحة معدنية، ويمكن تنفيذ عمل التصميم هذا في Photoshop بدلاً من Flash.
ما هي السلبيات؟ باستخدام الصور، نتخلّى عن إمكانية الوصول الآلي إلى الحقول النصية. في Flash، كانت عملية تغيير اللون أو السمات الأخرى للنوع بسيطة، ولكن في HTML5، يتم دمج هذه السمات في الصور نفسها. (لقد جرّبنا نص HTML، ولكنّه كان يتطلّب الكثير من الترميز الإضافي وCSS. حاولنا أيضًا استخدام نص اللوحة، ولكن كانت النتائج غير متّسقة على مستوى المتصفّحات.)
المنطق الضبابي
أردنا الاستفادة إلى أقصى حد من نافذة المتصفّح بأي حجم، وتجنّب الانتقال للأعلى أو للأسفل. كانت هذه عملية بسيطة نسبيًا في Flash، لأنّه تم رسم اللعبة بأكملها باستخدام أشكال هندسية، ويمكن تكبيرها أو تصغيرها بدون فقدان الدقّة. لكنّ الأمر كان أكثر تعقيدًا في HTML. حاولنا استخدام ميزة التكبير/التصغير في CSS ولكن انتهى بنا المطاف بخلفية مموهة:
يتمثل الحلّ الذي نقدّمه في إعادة رسم لوحة اللعبة والرفّ واللوحات كلما غيّر المستخدم حجم المتصفّح:
window.onresize = function (evt) {
...
gameboard.setConstraints(boardWidth, boardWidth);
...
rack.setConstraints(rackWidth, rackHeight);
...
tileManager.resizeTiles(tileSize);
});
وبذلك نحصل على صور واضحة وتنسيقات جميلة على أي حجم شاشة:
الدخول في صلب الموضوع
بما أنّ كلّ مربّع يتم وضعه بشكل مطلق ويجب أن يكون مُحاذيًا بدقة للوحة اللعب والرف، نحتاج إلى نظام تحديد موضع موثوق. نستخدم دالتَين، Bounds
وPoint
، للمساعدة في إدارة موضع العناصر في المساحة الشاملة (صفحة HTML). يصف Bounds
منطقة مستطيلة على الصفحة، في حين يصف Point
إحداثيات x,y بالنسبة إلى أعلى يمين الصفحة (0,0)، والتي تُعرف أيضًا باسم نقطة التسجيل.
باستخدام Bounds
، يمكننا رصد تقاطع عنصرَين مستطيلَين (مثلاً عندما يمرّ مربّع على الرف) أو ما إذا كانت منطقة مستطيلة (مثلاً مساحة حرفَين مزدوجَين) تحتوي على نقطة عشوائية (مثل النقطة المركزية لمربّع). في ما يلي تنفيذ 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()
مع موقّت لإنشاء تأثير انتقال الحركة أو تأثير التخفيف.
شخصية انسيابية
على الرغم من أنّه من الأسهل إنشاء تنسيقات بحجم ثابت في Flash، إلا أنّه من الأسهل إنشاء تنسيقات قابلة للتكيّف باستخدام HTML ونموذج مربّعات CSS. فكِّر في طريقة عرض الشبكة التالية التي تتغيّر فيها قيم العرض والارتفاع:
أو يمكنك استخدام لوحة المحادثة. كان إصدار Flash يتطلّب معالجات أحداث متعددة للردّ على إجراءات الماوس، بالإضافة إلى قناع للمنطقة التي يمكن التمرير فيها، وعمليات حسابية لاحتساب موضع التمرير، والكثير من الرموز البرمجية الأخرى لتجميعها معًا.
في المقابل، يُعدّ الإصدار بتنسيق HTML مجرد <div>
بارتفاع ثابت وقيمة hidden لسمة overflow. لا يكلف التمرير أي تكلفة.
في مثل هذه الحالات، أي مهام التنسيق العادية، تتفوق HTML وCSS على Flash.
هل يمكنك سماعي الآن؟
واجهنا بعض الصعوبات في استخدام علامة <audio>
، إذ لم تكن قادرة على تشغيل تأثيرات صوتية قصيرة بشكل متكرر في بعض المتصفحات. لقد جرّبنا حلّين بديلين. أولاً، أضفنا إلى الملفات الصوتية فواصل صمت لجعلها أطول. بعد ذلك، جرّبنا التبديل بين تشغيل المحتوى في قنوات صوتية متعددة. ولم تكن أيّ من الطريقتَين فعّالة أو أنيقة تمامًا.
في النهاية، قرّرنا طرح مشغّل صوت Flash الخاص بنا واستخدام الصوت بتنسيق HTML5 كخيار احتياطي. في ما يلي الرمز البرمجي الأساسي في Flash:
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);
في JavaScript، نحاول رصد مشغّل Flash المضمّن. إذا تعذّر ذلك، سننشئ عقدة <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 كما استخدمناها في Flash لإعادة تحميل حالة اللعبة: كل 10 ثوانٍ، يطلب العميل من الخادم آخر المعلومات. إذا تغيّرت حالة اللعبة منذ الاستطلاع الأخير، يتلقّى العميل التغييرات ويعالجها، وإلا لن يحدث شيء. إنّ أسلوب الاستطلاع التقليدي هذا مقبول، وإن لم يكن أنيقًا تمامًا. ومع ذلك، نريد التبديل إلى الاستطلاع الطويل أو WebSockets مع نضوج اللعبة وتوقّع المستخدمين التفاعل في الوقت الفعلي عبر الشبكة. وتوفر بروتوكولات WebSockets، على وجه الخصوص، العديد من الفرص لتحسين تجربة اللعب.
أداة رائعة.
لقد استخدمنا Google Web Toolkit (GWT) لتطوير كلّ من واجهة المستخدم في الواجهة الأمامية ومنطق التحكّم في الواجهة الخلفية (المصادقة والتحقّق من الصحة والثبات وما إلى ذلك). ويتم تجميع JavaScript نفسها من رمز مصدر Java. على سبيل المثال، تمّ اقتباس الدالة Point من 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));
}
...
}
تحتوي بعض فئات واجهة المستخدم على ملفات نماذج مقابلة يتم فيها "ربط" عناصر الصفحة بأعضاء الفئة. على سبيل المثال، يتوافق 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>
لا يتناول هذا المقال التفاصيل الكاملة، ولكننا ننصحك بالاطّلاع على GWT لمشروعك التالي باستخدام HTML5.
لماذا يجب استخدام Java؟ أولاً، للكتابة الصارمة. على الرغم من أنّ الكتابة الديناميكية مفيدة في JavaScript، مثل قدرة المصفوفة على تخزين قيم من أنواع مختلفة، إلا أنّها قد تتسبب في صداع في المشاريع الكبيرة والمعقدة. ثانيًا، لإمكانيات إعادة التشكيل. فكِّر في كيفية تغيير توقيع طريقة JavaScript في آلاف سطور الرمز البرمجي، فهذا ليس بالأمر السهل. ولكن باستخدام بيئة تطوير متكاملة جيدة لتطبيقات Java، يمكنك إجراء ذلك بسهولة. أخيرًا، لأغراض الاختبار. إنّ كتابة اختبارات الوحدة لفئات Java تفوق أسلوب "الحفظ والتحديث" القديم.
ملخّص
باستثناء المشاكل المتعلقة بالصوت، تجاوزت تقنية HTML5 توقعاتنا بشكل كبير. لا يتميز Wordico بمظهره الجميل كما في Flash فحسب، بل يتميز أيضًا بسلاسة استخدامه وسرعة استجابته. لم يكن بإمكاننا تنفيذ ذلك بدون Canvas وCSS3. كان التحدي التالي هو تكييف Wordico لاستخدامه على الأجهزة الجوّالة.