簡介
為了在 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