引言
為協助開發人員在 2013 年 Google I/O 大會網站正式開幕之前引起開發人員的興趣,我們開發了一系列以行動裝置為主的實驗和遊戲,著重於觸控互動、生成式音訊,以及享受探索樂趣。這項互動體驗的靈感來自於程式碼的潛力,且強大的遊戲力量,會在輕觸新的 I/O 標誌時顯示「I」和「O」這類簡單的音效。
自然動作
我們決定以流暢自然的方式導入 I 和 O 動畫,而這在 HTML5 互動中通常不常見。多一點時間,
Bouncy 物理程式碼範例
為了達成這個效果,我們使用簡單的物理模擬來模擬代表兩個形狀的邊緣。輕觸任一形狀時,所有點都會從輕觸位置加速。他們再度伸展四肢之前,
在每個點執行個體化時,每個點都會獲得隨機的加速度金額,並且會重新繫結「獎勵」,因此不會統一套用動畫效果,如以下程式碼所示:
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 片段著色器,其中每個像素的像素都會經過檢查和操作。讓我們來深入探討這個問題
Canvas「著色器」程式碼範例
您可以使用 getImageData
方法讀取畫布上的像素。傳回的陣列包含 4 個每個像素的值,代表每個像素 RGBA 值。這些像素會聚集在類似陣列的架構中。舉例來說,2x2 畫布的 imageData 陣列中會有 4 像素,而 16 個項目。
我們的畫布是全螢幕,因此如果我們假設螢幕是 1024x768 (例如在 iPad 上),則陣列包含 3,145,728 個項目。由於這是動畫,因此整個陣列每秒更新 60 次。新型 JavaScript 引擎可以快速處理重複資料,並對這些資料採取行動,藉此維持影格速率的一致性。(提示:請勿將資料記錄到開發人員控制台,否則會導致瀏覽器無法檢索或完全當機)。
以下為八位元模式如何讀取居家模式畫布,然後高舉像素來提高方塊效果:
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);
}
}
Eightbit 著色器示範
下方,我們會去除 8 位元疊加層,然後在下方查看原始動畫。「殺害畫面」選項會錯誤地對來源像素進行取樣,從而顯示令人不解的效果。當八位元模式重新調整為不可能的顯示比例時,我們最後就會把它當做「回應式」彩蛋使用。事故愉快!
畫布合成
這個功能結合了多個轉譯步驟和遮罩,可達到的效果。我們打造了 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 動畫、網路音訊等),讓這個專案在開發過程中變得無比樂趣。
除了在這裡,你也能探索更多豐富功能。只要持續輕觸 I/O 標誌,正確的步驟就能解鎖更多迷你實驗、遊戲、三星視覺畫面,甚至一些早餐美食。建議你在智慧型手機或平板電腦上試用此版本,以獲得最佳體驗。
以下組合有助您踏出第一步:O-I-I-I-I-I-I-I。立即試用:google.com/io
開放原始碼
我們已開放程式碼 Apache 2.0 授權。您可以在 GitHub 中找到這項工具:http://github.com/Instrument/google-io-2013。
抵免額
開發人員:
- 湯瑪斯雷諾茲
- 布萊恩海夫特 (Brian Hefter)
- 史蒂芬妮哈特爾
- 保羅法寧
設計師:
- 施徹特 (Dan Schechter)
- 鼠尾草布朗
- 凱爾貝克
製作人:
- 阿米帕斯卡
- 尼爾森 (Andrea Nelson)