簡介
將 Wordico 填字遊戲從 Flash 轉換為 HTML5 後,我們的第一項任務就是瞭解我們所知道,如何在瀏覽器中提供豐富的使用者體驗。雖然 Flash 為應用程式開發的各個方面提供一個全方位的 API (從向量繪圖、多邊形命中偵測到 XML 剖析),HTML5 提供了極為豐富的規格,並且支援不同的瀏覽器。我們還懷疑 HTML、文件專屬的語言,以及 CSS (以方塊為主的語言) 是否適合製作遊戲。遊戲會像在 Flash 中一樣,在不同的瀏覽器上平均顯示嗎?外觀和行為是否與 Flash 相同?Wordico 的答案為「是」。
Victor 是什麼媒介?
我們開發的原始版 Wordico 僅使用線條、曲線、填滿和漸層等向量圖形。我們進而成功地做出了十分精簡且可無限擴充的成果:
我們也利用 Flash 時間軸來建立擁有多個狀態的物件。舉例來說,我們針對 Space
物件使用了九個命名的主要畫面格:
但在 HTML5 中,我們使用點陣圖這個精靈:
為了從個別空間建立 15x15 遊戲板,我們疊代處理一個 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>
元素 (點陣圖繪圖介面) 繪製遊戲板一次繪製一個正方形。第一步是載入圖片 Sprite。載入後,我們會疊代版面配置標記法,每次疊代都會繪製圖片的不同部分:
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
則是相對於頁面左上角 (0,0) 的 x,y 座標,也稱為註冊點。
透過 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>
。捲動畫面是完全不需要的成本。
在本例中是一般的版面配置工作,也就是 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 Player。如果失敗,我們會為每個音檔建立 <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。特別是 WebSocket 提供了許多增強遊戲體驗的機會。
這項工具真棒!
我們使用 Google Web Toolkit (GWT) 來開發前端使用者介面和後端控制邏輯 (驗證、驗證、保存等)。JavaScript 本身是由 Java 原始碼編譯而成。例如,點函式是根據 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>
這些完整細節不在本文的討論範圍內,但建議您參考 GWT 提供的下一個 HTML5 專案。
使用 Java 的好處首先,設為嚴格輸入。動態輸入在 JavaScript 中非常實用 (例如,陣列可保留不同類型的值),但在大型且複雜的專案中可能會令人頭痛。第二,是重構能力想想看,變更 JavaScript 方法簽名在數千行程式碼中的方式 - 不容易!不過,有了優秀的 Java IDE,即可快速完成。最後是測試目的。為 Java 類別撰寫單元測試時的技術超越了「儲存並重新整理」的時間指定技術。
摘要
除了音訊問題外,HTML5 的表現遠遠超乎我們的預期。Wordico 不僅呈現和在 Flash 中一樣美觀的效果,更是流暢快速、反應靈敏。沒有 Canvas 和 CSS3 是我們辦不到的。我們下一個挑戰:針對行動裝置使用者調整 Wordico。