個案研究 - 2013 年 Google I/O 大會實驗

Thomas Reynolds
Thomas Reynolds

簡介

為了在 Google I/O 2013 大會註冊開放前,讓開發人員對該網站產生興趣,我們開發了一系列以行動裝置為優先的實驗和遊戲,重點是觸控互動、生成式音訊和探索樂趣。這類互動體驗的靈感來自程式碼的潛力和遊戲的力量,因此在輕觸新 I/O 標誌時,一開始會依序播放「I」和「O」的簡單音效。

Organic Motion

我們決定以 HTML5 互動中不常見到的搖晃自然效果,實作 I 和 O 動畫。我們花了一些時間調整選項,讓使用者能感受到有趣的互動體驗。

Bouncy Physics 程式碼範例

為了實現這種效果,我們用簡單的物理模擬,模擬了兩個形狀的邊緣。使用者輕觸任一形狀時,所有點都會從輕觸的位置向外加速。在加回時,它們就向外伸出,

在例項化時,每個點都會取得隨機加速量和回彈「彈力」,因此不會以一致的方式進行動畫,如這段程式碼所示:

this.paperO_['vectors'] = [];

// Add an array of vector points and properties to the object.
for (var i = 0; i < this.paperO_['segments'].length; i++) {
 
var point = this.paperO_['segments'][i]['point']['clone']();
  point
= point['subtract'](this.oCenter);

  point
['velocity'] = 0;
  point
['acceleration'] = Math.random() * 5 + 10;
  point
['bounce'] = Math.random() * 0.1 + 1.05;

 
this.paperO_['vectors'].push(point);
}

接著,輕觸時,系統會使用以下程式碼從輕觸位置向外加速:

for (var i = 0; i < path['vectors'].length; i++) {
 
var point = path['vectors'][i];
 
var vector;
 
var distance;

 
if (path === this.paperO_) {
    vector
= point['add'](this.oCenter);
    vector
= vector['subtract'](clickPoint);
    distance
= Math.max(0, this.oRad - vector['length']);
 
} else {
    vector
= point['add'](this.iCenter);
    vector
= vector['subtract'](clickPoint);
    distance
= Math.max(0, this.iWidth - vector['length']);
 
}

  point
['length'] += Math.max(distance, 20);
  point
['velocity'] += speed;
}

最後,每個粒子都會在每個影格減速,並透過程式碼中的這個方法緩慢恢復平衡:

for (var i = 0; i < path['segments'].length; i++) {
 
var point = path['vectors'][i];
 
var tempPoint = new paper['Point'](this.iX, this.iY);

 
if (path === this.paperO_) {
    point
['velocity'] = ((this.oRad - point['length']) /
      point
['acceleration'] + point['velocity']) / point['bounce'];
 
} else {
    point
['velocity'] = ((tempPoint['getDistance'](this.iCenter) -
      point
['length']) / point['acceleration'] + point['velocity']) /
      point
['bounce'];
 
}

  point
['length'] = Math.max(0, point['length'] + point['velocity']);
}

自然動態示範

以下是 I/O 首頁模式,供您試用。我們也在這項實作中公開了許多其他選項。如果您開啟「顯示點」,就會看到物理模擬和力的作用點。

換皮

在滿意居家模式的動畫效果後,我們想將相同效果用於兩種復古模式:八位元和 ASCII。

為了完成這項重繪作業,我們使用了主畫面模式中的相同畫布,並使用像素資料產生這兩種效果。這種做法讓人聯想到 OpenGL 片段著色器,因為後者會檢查及操控場景的每個像素。讓我們來深入瞭解這個

畫布「著色器」程式碼範例

您可以使用 getImageData 方法讀取 Canvas 上的像素。傳回的陣列每個像素包含 4 個值,代表每個像素的 RGBA 值。這些像素會疊在一起,形成類似陣列的結構。舉例來說,2x2 畫布在 imageData 陣列中會有 4 個像素和 16 個項目。

我們的畫布是全螢幕,因此如果假設螢幕是 1024x768 (例如 iPad),則陣列會有 3,145,728 個項目。由於這是動畫,因此整個陣列會每秒更新 60 次。現代的 JavaScript 引擎可以快速處理這麼多資料的迴圈和操作,以維持一致的幀率。(提示:請勿嘗試將這些資料記錄到開發人員控制台,因為這會導致瀏覽器速度變慢或完全當機)。

下列是 Eightbit 模式如何閱讀主畫面模式的畫布,以及如何發光像素,使畫面看起來更絢麗的效果:

var pixelData = pctx.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height);

// tctx is the Target Context for the output Canvas element
tctx
.clearRect(0, 0, targetCanvas.width + 1, targetCanvas.height + 1);

var size = ~~(this.width_ * 0.0625);

if (this.height_ * 6 < this.width_) {
 size
/= 8;
}

var increment = Math.min(Math.round(size * 80) / 4, 980);

for (i = 0; i < pixelData.data.length; i += increment) {
 
if (pixelData.data[i + 3] !== 0) {
   
var r = pixelData.data[i];
   
var g = pixelData.data[i + 1];
   
var b = pixelData.data[i + 2];
   
var pixel = Math.ceil(i / 4);
   
var x = pixel % this.width_;
   
var y = Math.floor(pixel / this.width_);

   
var color = 'rgba(' + r + ', ' + g + ', ' + b + ', 1)';

    tctx
.fillStyle = color;

   
/**
     * The ~~ operator is a micro-optimization to round a number down
     * without using Math.floor. Math.floor has to look up the prototype
     * tree on every invocation, but ~~ is a direct bitwise operation.
     */

    tctx
.fillRect(x - ~~(size / 2), y - ~~(size / 2), size, size);
 
}
}

E8bit 著色器示範

在下方,我們移除了 Eightbit 疊加層,並在下方看到原始動畫。「kill screen」選項會顯示我們在錯誤取樣來源像素時意外發現的奇怪效果。當 Eightbit 模式縮小至不太可能出現的顯示比例時,我們最終會將它做為「回應式」復活節彩蛋。意外之福!

畫布合成

結合多個轉譯步驟和遮罩,可說是十分驚人。我們建立了 2D 元球,要求每個球都有自己的放射漸層,並在球重疊處將這些漸層混合在一起。(您可以在下方的示範中看到這一點)。

為達成這個目標,我們使用了兩張不同的畫布。第一個畫布會計算並繪製中繼球形狀。第二個畫布會在每個球的位置繪製放射漸層。接著,形狀會遮蓋漸層,然後我們會算繪最終輸出內容。

合成的程式碼範例

以下是執行所有操作的程式碼:

// Loop through every ball and draw it and its gradient.
for (var i = 0; i < this.ballCount_; i++) {
 
var target = this.world_.particles[i];

 
// Set the size of the ball radial gradients.
 
this.gradSize_ = target.radius * 4;

 
this.gctx_.translate(target.pos.x - this.gradSize_,
    target
.pos.y - this.gradSize_);

 
var radGrad = this.gctx_.createRadialGradient(this.gradSize_,
   
this.gradSize_, 0, this.gradSize_, this.gradSize_, this.gradSize_);

  radGrad
.addColorStop(0, target['color'] + '1)');
  radGrad
.addColorStop(1, target['color'] + '0)');

 
this.gctx_.fillStyle = radGrad;
 
this.gctx_.fillRect(0, 0, this.gradSize_ * 4, this.gradSize_ * 4);
};

接著,設定遮罩和繪製用的畫布:

// Make the ball canvas the source of the mask.
this.pctx_.globalCompositeOperation = 'source-atop';

// Draw the ball canvas onto the gradient canvas to complete the mask.
this.pctx_.drawImage(this.gcanvas_, 0, 0);
this.ctx_.drawImage(this.paperCanvas_, 0, 0);

結論

我們運用了多種技術和實作技術 (例如 Canvas、SVG、CSS 動畫、JS 動畫、Web Audio 等),讓這個專案的開發過程變得非常有趣。

除了這裡顯示的內容之外,還有更多內容等你探索。只要持續輕觸 I/O 標誌,並按照正確的順序操作,就能解鎖更多迷你實驗、遊戲、迷幻視覺效果,甚至是早餐食品。建議你在智慧型手機或平板電腦上試用,以獲得最佳體驗。

以下是讓你快速上手的組合:O-I-I-I-I-I-I-I。立即試用:google.com/io

開放原始碼

我們開放原始碼了 Apache 2.0 授權。您可以在我們的 GitHub 上找到這份文件:http://github.com/Instrument/google-io-2013

抵免額

開發人員:

  • Thomas Reynolds
  • Brian Hefter
  • 史蒂芬妮.哈徹爾 (Stefanie Hatcher)
  • 保羅法寧

設計師:

  • Dan Schechter
  • 鼠尾草棕
  • 凱爾.貝克 (Kyle Beck)

製作者:

  • 艾咪 (Amie Pascal)
  • Andrea Nelson