個案研究 - 打造 Stanisław Lem Google Doodle

Marcin Wichary
Marcin Wichary

大家好!

Google 首頁是很引人入勝的環境,方便你在當中編寫程式碼。但其中包含許多具挑戰性的限制:我們特別重視速度與延遲時間,才能滿足各種瀏覽器的需求和各種情況,...沒錯,就是帶來驚喜與樂趣。

我正在討論 Google Doodle,這是一種會不定期取代 Google 標誌的特殊插圖。 儘管我與筆和筆刷的關係長久以來 對重新訓練的定律產生了強烈的轉變 但我常常會促成互動才算互動

我和許多人共同編寫的互動式 Doodle (Pac-ManJules VerneWorld’s Fair) 和許多我所幫助的,都展現了未來與發展的特性,也就是能夠運用先進的網路功能,創造絕佳的天空運用體驗,而且他們擁有跨瀏覽器相容性的優勢。

我們從每個互動式 Doodle 中學到大量知識,而最近的 Stanisław Lem 迷你遊戲也不例外,其中有 17,000 行 JavaScript 程式碼,在 Doodle 史上首次嘗試許多事物。今天,我想和你分享那段程式碼。也許你可能會發現一些有趣的東西,或是指出我的錯誤,並稍微談論。

查看 Stanisław Lem doodle 程式碼 »

值得一提的是,Google 的首頁不不適合用於技術示範。我們透過 Doodle 慶祝特定人物和事件,並希望能運用最頂尖的藝術和可召集的最佳技術,為大家喝采,但永遠不必為科技產品注入能量。這表示請仔細檢視 HTML5 任何可用的部分,看看它是否有助於改善塗鴉、不造成乾擾,或使其過度覆蓋。

現在就來看看 Stanisław Lem Doodle 中他們發現的幾項新式網路技術,其中卻沒有這些技術。

透過 DOM 和畫布繪製圖形

畫布功能強大,並且專為這個 Doodle 要做的事而打造。不過,我們當時介紹的某些舊版瀏覽器並不支援,雖然我本來應該與組合出絕佳展望花的人共用辦公室,但還是決定改用其他方法。

我建立了可去除「rects」圖形基元的圖形引擎,然後在無法使用畫布時,使用畫布或 DOM 進行算繪。

這種做法會帶來一些有趣的挑戰。舉例來說,在 DOM 中移動或變更物件,會導致立即產生後果;以畫布來說,畫布會在所有內容同時繪製的特定時刻。(我決定只有一個畫布,然後清空,然後利用每個畫面的旋轉效果進行繪製,基本上,一隻手移動零件就好,但複雜性卻不夠複雜,因此需要分割為多個重疊的畫布,並選擇性地更新這些畫布。)

可惜的是,切換到畫布並不像使用 drawImage() 鏡像建立 CSS 背景一樣簡單:透過 DOM 組合內容時,您會失去許多免費功能,最重要的是搭配 Z-index 和滑鼠事件。

我已經用「飛機」概念除去 Z-index 了。這個 Doodle 定義了很多飛機,從遠處的天空,到各個東西前方的滑鼠指標,Duet 中的每個演員都必須決定屬於哪個飛機 (在飛機上運用 planeCorrection 進行小幅加上/減去校正)。

透過 DOM 轉譯,平面會直接轉譯為 Z-index。但如果透過畫布進行算繪,就必須先根據平面排序矩形,才能繪製矩形。由於每次執行這項作業都會產生費用,因此只有在新增演員或移動到其他飛機時,系統才會重新計算順序。

至於滑鼠事件,我同樣簡化這個過程...有點。針對 DOM 和畫布,我使用額外完全透明且 Z-index 高的浮動 DOM 元素,其函式僅對滑鼠過度/移位、點擊和輕觸動作做出反應。

我們想要的其中一項用途 是打破了第四面牆。上述引擎可讓我們結合畫布型演員與 DOM 業者。舉例來說,最後階段的爆炸會同時適用於宇宙物件的畫布,而在 Google 首頁的其他部分,則以 DOM 形式呈現。小鳥通常會像其他演員一樣在空中飛行,並被我們標記的遮罩裁剪,因此在射擊關卡期間決定保持安靜,並且坐在「好手氣」按鈕上。方法就是讓小鳥離開畫布,然後變成 DOM 元素 (反之亦然),希望能讓訪客完全看得懂。

畫面更新率

掌握目前的影格速率,並回應速度太慢 (速度太快!) 時,是 Google 引擎的重要一環。由於瀏覽器不會回報影格速率,因此我們必須自行計算。

我從開始使用 requestAnimationFrame,如果不支援前者,則會改回使用舊的 setTimeout。在某些情況下,requestAnimationFrame 會聰明地儲存 CPU,雖然我們自己正在進行部分操作 (如下方說明),但同樣也可讓系統取得比 setTimeout 更高的畫面更新率。

目前影格速率的計算方式相當簡單,但可能會有大幅變動,例如,當另一個應用程式卡住電腦一陣子時,它可能很快就會下降。因此,我們只計算每 100 個實體滴答的「滾動」(平均) 影格速率,然後做出決策。

有哪些決策?

  • 如果影格速率高於 60fps,我們就會節流播放。目前,部分 Firefox 版本的 requestAnimationFrame 沒有影格速率上限,也沒有浪費 CPU 的問題。請注意,實際上,我們最多只能將每秒 65 個影格數設為 65fps,因為在其他瀏覽器中,由於捨入錯誤會導致影格速率只有高於 60fps,我們不希望不小心啟動這項限縮功能。

  • 如果畫面更新率低於 10 FPS,我們會降低引擎的速度,而非捨棄影格。這才是「行不通」的主張,但我覺得比起速度較慢 (但仍維持一致的遊戲體驗),略過影格過度會令人感到困惑。這裡還有另一個不錯的副作用 - 如果系統運作速度緩慢,使用者不會遇到奇怪的跳動,因為引擎是在理想上趕著引擎。(我針對 Pac-Man 採用的做法略有不同,但最低畫面更新率是更好的做法)。

  • 最後,我們可以考慮在畫面更新率極低時簡化圖像。除了滑鼠指標以外,我們不會為 Lem Doodle 做出上述內容,但假設我們可能損失一些不必要的動畫,即使電腦速度較慢, Doodle 也能流暢顯示。

我們也提出實體刻度與邏輯滴答的概念。前者是來自 requestAnimationFrame/setTimeout。一般遊戲玩法的比率為 1:1,但如要快速快轉,只要在每個實體滴答中加入更多邏輯滴答 (上限是 1:5) 即可。這樣一來,我們就能為每個邏輯刻點進行所有必要的計算,但只需將最後一個條件指定為在螢幕上更新的項目即可。

基準化

假設某個畫佈在可用情況下,畫布的速度可能比 DOM 更快 (而且這是很早)。但事實並非如此。在測試期間,我們發現在 Mac 上使用 Opera 10.0–10.1,而在 Linux 上使用 Firefox 移動 DOM 元素時,實際速度會比較快。

在理想情況下,Doodle 會靜默地針對不同的圖形技術進行基準測試,例如使用 style.leftstyle.top 移動的 DOM 元素、在畫布上繪圖,甚至是使用 CSS3 轉換功能移動的 DOM 元素

然後切換至給予最高畫面更新率的那一個。我開始編寫程式碼的目的,但發現基準化方法不穩定,而且花費相當多的時間。首頁沒有時間,我們很重視速度,也希望 Doodle 可以即時顯示,而且您點選或輕觸後就能立即開始遊戲。

說到底,網頁開發有時難免會讓您忙於做事。我到後方有肩膀,確保沒有人看到,然後我才剛剛將 Opera 10 和 Firefox 硬式編碼在畫布上。到了下一個人生,我將會以 <marquee> 標記的形式使用標記。

節省 CPU

你知道嗎?有朋友到你家後,看了《Breaking Bad》頻道的最後一季,也對你刮目相看,然後把影片從 DVR 中刪除嗎?你不想讓那個傢伙,對嗎?

所以,這是有史以來最糟糕的比喻。但我們不希望對使用者造成肯定,因為我們允許進入他人的瀏覽器分頁才有權限處理;如果在這樣的情況下擾亂使用者的 CPU 週期,或是讓使用者分心,會導致我們感到不愉快。因此,如果沒有人使用 Doodle 遊戲 (沒有輕觸、點擊滑鼠、移動滑鼠或按鍵),我們會希望它最終會進入休眠模式。

何時?

  • 顯示在首頁 18 秒後 (稱為「景點模式」的街機遊戲)
  • 如果分頁有焦點,就會顯示 180 秒後
  • 如果分頁沒有聚焦,顯示該分頁在 30 秒後沒有焦點 (例如使用者切換成其他視窗,但目前可能仍在透過閒置分頁觀看 Doodle)
  • 如果分頁變得不可見 (例如,使用者在同一視窗中切換至其他分頁,如果看不到),就會立即產生停滯的循環

如何得知分頁目前是焦點?我們把自己放在 window.focuswindow.blur 上 我們如何知道分頁有顯示?我們已使用新的 Page Visibility API (網頁瀏覽權限 API),並回應適當的事件。

上述時間限制比平時還難以解決。我把這些圖案改寫成這個特定 Doodle 圖案,當中含有許多微光動畫 (主要是天空和鳥)。在理想情況下,遊戲中的逾時時間將限制在遊戲中的互動情形,比如在抵達球後,小鳥可以將現在應該睡覺的 Doodle 報告回報給 Doodle,但是我根本沒有實行這項限制。

由於天空一直在移動,當您入睡和喚醒 Doodle 時,這個階段不會只是停止或開始,而是在暫停之前減慢,並且可以視需要增加或減少邏輯滴答。

轉換、轉換、事件

一直以來,使用 HTML 的一大優勢就是讓自己更上一層樓:如果一般 HTML 和 CSS 組合的體驗不甚理想,您可以藉由疊加 JavaScript 來擴充它。不幸的是,通常必須從頭開始建立。CSS3 轉場效果固然很好,但您無法新增轉換類型,也無法使用轉場效果,執行樣式元素以外的操作。再舉一個例子:CSS3 轉換適用於 DOM,但當您移動到畫布時,就會突然出現。

這些問題等,也說明瞭 Lem doodle 有自己的轉換與轉換引擎的原因。沒錯,我知道的就是 2,000 秒等等 我內建的功能與 CSS3 一樣強大 不過不管是何種引擎,這些功能都會持續運作,並提供更多控管選項。

我一開始先使用簡單的動作 (事件) 系統,這是在不使用 setTimeout 的情況下觸發事件的時間軸,因為任何特定時間點的 Doodle 時間都會因為速度更快 (快轉) 而偏離實際時間、緩慢 (低影格速率或休眠以便節省 CPU),或完全停止 (等待圖片載入完成)。

轉場只是另一種動作除了基本的移動和旋轉以外,我們也支援相對移動 (例如向右移動 10 像素)、自訂項目 (例如變色),以及主要圖片動畫。

我剛才提到旋轉,而這些都是手動作業:我們針對需要旋轉的物件,從不同角度進行了旋轉。主要原因是 CSS3 和畫布旋轉功能導入了我們發現的視覺構件,而這些構件不僅因平台而異。

由於某些旋轉物件是連接到其他旋轉的物件 (例如機器人的手連接到下半臂,而它本身附加在旋轉的上臂上),因此我還需要以資料透視的方式建立不好男子的轉換來源。

這一切都要經過許多努力,最終涵蓋 HTML5 已處理的地面,但有時候原生支援不足以應付需求,這時就需要改造輪胎。

處理圖片和 Sprite

引擎不僅用於執行 Doodle,也能用於處理。我分享了上述部分偵錯參數:您可以在 engine.readDebugParams 中找到其餘的偵錯參數。

提示是一種眾所周知的技術,我們也常用於塗鴉。這不僅能節省位元組、縮短載入時間,還能更輕鬆地預先載入。 但也使得開發變得更困難,因為每次對圖像變更都需要重心 (大幅自動化,但還是很麻煩)。因此,引擎支援在原始映像檔上執行,並透過 engine.useSprites 執行實際工作環境的 Sprite,兩者都隨附在原始碼中。

小精靈 Doodle
Pac-Man doodle 使用的 Sprites。

此外,我們也支援在處理過程中預先載入圖片,如果圖片無法及時載入,就會暫停 Doodle 遊戲,並提供擬真進度列!(很抱歉,由於 HTML5 無法指出圖片檔案已載入多少,因此十分可靠)。

包含進度列載入圖形的載入圖片螢幕截圖。
含有進度列載入圖像的載入圖像螢幕截圖。

在某些場景中,我們只使用多個 Sprite,而非使用平行連線加快載入速度,只是因為 iOS 圖片的像素上限為 3/500 萬像素

HTML5 究竟在其中扮演什麼角色?上文內容並不完全相同,但我編寫來分割/裁剪的工具是全新的網路技術:畫布、bloba[下載]。 HTML 的一大優點是,它稍微減緩了先前必須在瀏覽器外執行的作業,唯一需要的是最佳化 PNG 檔案。

在遊戲之間儲存狀態

Lem 的世界總是瞬息萬變,還充滿真實感。他的故事通常從沒什麼說的解釋開始,第一個頁面以媒體形式開始,讀者必須四處一探究竟。

Cyberiad 的宗旨也不例外,我們想為 Doodle 複製這樣的感覺。我們會先盡量不清楚說明故事內容。另一個較大的部分是隨機化,也就是經過隨機化處理,也就是符合一本書籍的機制特性。此外,我們有很多輔助函式,可以處理許多地方的隨機性。

我們也想以其他方式加強重播。為此,我們需要知道 Doodle 之前完成多少次Cookie 是史上正確的技術解決方案,這是 Cookie,但不適用於 Google 首頁。每個 Cookie 都會增加每個網頁的酬載,而我們同樣非常在意速度和延遲時間。

幸好,HTML5 提供了很小的 Web Storage,讓我們可以儲存並喚起使用者播放一般播放次數以及使用者最後播放的場景,而且觀看時間遠比 Cookie 允許更多。

我們如何處理這些資訊?

  • 這裡會顯示快轉按鈕 這樣就能壓縮使用者先前看過的剪輯畫面
  • 我們在最後一期會針對
  • 也能稍微增加拍攝等級的難度
  • 我們能從第三次和後續的戲劇中,顯示不同的故事

您可以利用許多偵錯參數控制這項設定:

  • ?doodle-debug&doodle-first-run – 假裝這是首次跑步
  • ?doodle-debug&doodle-second-run – 假裝第二次跑步
  • ?doodle-debug&doodle-old-run:假裝是舊的跑步

觸控式裝置

我們希望 Doodle 裝置在觸控裝置上也能呈現出真實的感覺,因為大多數新世代的裝置都強大且功能強大,讓 Doodle 也能順利執行,透過輕觸功能體驗遊戲的過程,會比點按按鈕好上許多。

必須對使用者體驗進行一些預警調整。起初,在傳達方塊/非互動部分時,滑鼠指標是唯一發生的位置。我們稍後會在右下角新增一個指標,因此您不必單獨仰賴滑鼠指標 (因為觸控裝置上並沒有這些指標)。

正常 忙碌 可點按 曾點閱過
處理中
處理中一般指標
處理中忙碌指標
處理中可點選的指標
處理中點擊的指標
最終
最終正常指標v
最終忙碌指標
最終可點擊的指標
最終點擊的指標
開發期間的滑鼠遊標,以及最終對等項目。

大多數功能都能立即使用。不過,我們快速對觸控體驗進行不穩定的測試,顯示有兩個問題:有些目標太難按下,而且由於我們只是覆寫滑鼠點擊事件,所以忽略快速輕觸動作。

我們擁有單獨的可點選透明 DOM 元素,對這項工作有許多幫助,因為我可以單獨調整這些元素的大小,不必配合視覺效果。我針對觸控式裝置推出了額外的 15 像素邊框間距,並在建立可點擊的元素時使用。(我為滑鼠環境另外添加了 5 像素的邊框間距,只是為了讓 Mr. Fitts 更加滿意)。

至於其他問題,我只是確保附加並測試正確的觸控啟動和結束處理常式,而非依賴滑鼠點選。

我們也使用較新的樣式屬性移除 WebKit 瀏覽器預設新增的部分觸控功能 (輕觸醒目顯示、輕觸摘要)。

我們如何偵測執行 Doodle 的特定裝置是否支援觸控功能?遲鈍。我們不去費心找出答案,而是用我們合併的 IQ 來刪除裝置在第一次接觸開始事件後支援觸控動作的情形。

自訂滑鼠遊標

但並非所有事物都是以觸控的方式提供。我們的指導原則之一 就是盡可能在 Doodle 的世界中 盡可能增加題材小型側欄 UI (快轉、問號)、工具提示,甚至是滑鼠指標。

如何自訂滑鼠遊標?部分瀏覽器允許藉由連結至自訂的圖片檔來變更滑鼠遊標。不過,系統並未妥善支援這個做法,也有一些限制。

如果不存在,該怎麼辦?那麼,為什麼不把滑鼠遊標變成 Doodle 中的另一個演員?這種做法雖然沒有問題,但主要有幾點注意事項:

  • 您必須先移除原生的滑鼠遊標
  • 最好能將滑鼠指標與「真實」指標保持同步

前者有點棘手,CSS3 允許 cursor: none,但某些瀏覽器不支援此功能。我們必須進行某些體操:使用空白的 .cur 檔案做為備用方案、為部分瀏覽器指定具體行為,甚至能視情況以硬式編碼的方式加入其他變數。

另一端在表面上相對較為複雜,但滑鼠指標只是 Doodle 宇宙的另一部分,也會沿用所有問題。最大的問題是什麼?如果塗鴉的影格速率很低,滑鼠指標的影格速率就會偏低,而這也產生了惡劣的結果,因為滑鼠指標是手掌的自然延伸,所以無論如何,都能感受到回應的靈敏度。(過去曾使用 Commodore Amiga 的人,現在也很緊張)。

這個問題有一個較為複雜的解決方式,就是將滑鼠遊標與一般更新迴圈分離。我們做到了,在另一個不需睡覺的世界中比較簡單的解決方案? 如果滾動式影格速率低於 20fps,就會還原為原生滑鼠遊標。(這時就適合採用累計影格速率如果我們回應目前的影格速率,而且發生速度變化約為每秒 20 個影格數,使用者就會看見隱藏並顯示所有時間的自訂滑鼠遊標)。歸納出以下問題:

畫面更新率範圍 行為
>10fps 放慢遊戲速度,避免更多影格遭到捨棄。
10 - 20 FPS 使用原生滑鼠遊標,而非自訂滑鼠遊標。
20 - 60 fps 正常作業。
>60fps 節流,確保影格速率不超出這個值。
與影格速率相關的行為摘要。

對了,在 Mac 上我們的滑鼠遊標是深色,在 PC 上是白色。原因何在?因為平台戰爭需要燃料,甚至在虛構世界中也要補足。

結論

這並不是完美的引擎,它是與 Lem 塗鴉一起開發,對它而言非常特別。沒錯。正如 Don Knuth 的說法,「前產最佳化是一切的根基」,我不相信先獨自編寫引擎,而且日後只採用它,這項做法也正如理所說。我的案例中,程式碼被丟棄、數個部分被重寫,還有許多被注意到的常見片段,而不是一件事實。但總而言之,我們能夠達成想要的任務 慶祝 Stanisław Lem 的生涯,以及 Daniel Mróz 繪製的圖畫

希望以上說明能為我們做出的幾個設計選擇和優缺點,以及我們如何在特定的實際情境中使用 HTML5。現在,請試用原始碼,試用看看,告訴我們您的想法。

我自己做過下列活動,這是最近幾天的直播, 直到俄羅斯的 2011 年 11 月 23 日早期,也就是首次看到 Lem 塗鴉的時區。也許是傻氣,但就像塗鴉一樣,某些看似微不足道的東西可能有更深層的涵義,對引擎來說,這個計數器真的是很好的「壓力測試」。

Lem Doodle 宇宙倒數計時器的螢幕截圖。
世界倒數計時器中的 Lem Doodle 螢幕截圖。

有別於 Google Doodle 的生活 可以觀看 Google Doodle 的生活JavaScript 的每一行程式碼都希望這 5 分鐘能順利運作。祝你使用愉快!