はじめに
Google は、Google I/O 2013 ウェブサイトへの関心を高めるために、タッチ操作、生成音声、発見の楽しさに重点を置いたモバイル ファーストの実験とゲームを開発しました。コードのポテンシャルと遊びの力に着想を得たこのインタラクティブな体験は、新しい I/O ロゴをタップすると「I」と「O」のシンプルな発音から始まります。
オーガニック モーション
Google は、HTML5 インタラクションではめったに見られない、ぐらつきのあるオーガニックな効果で I と O のアニメーションを実装することにしました。オプションをダイヤルインして、楽しくリアクティブに感じられるようにするには、少し時間がかかりました。
Bouncy Physics のサンプルコード
この効果を実現するため、2 つの形状のエッジを表す一連の点で簡単な物理シミュレーションを行いました。いずれかの図形をタップすると、タップした位置からすべてのポイントが加速します。そこから引き伸ばされて、引き戻されます。
インスタンス化の際、各ポイントにはランダムな加速度量とリバウンドの「弾力」が与えられるため、次のコードのように、均一にアニメーション化されません。
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 ホームモードです。ぜひお試しください。この実装では、さまざまなオプションも公開されています。[ポイントを表示] をオンにすると、物理シミュレーションと力が作用している個々のポイントが表示されます。
リスキン
ホームモードのモーションに満足したら、同じ効果を 2 つのレトロモード(Eightbit と Ascii)にも使用したいと考えました。
この再スキンを実現するために、ホームモードと同じキャンバスを使用し、ピクセルデータを使用して 2 つの効果をそれぞれ生成しました。このアプローチは、シーンの各ピクセルが検査および操作される OpenGL フラグメント シェーダーによく似ています。詳しく見ていきましょう。
Canvas の「Shader」コードサンプル
キャンバス上のピクセルは、getImageData
メソッドを使用して読み取ることができます。返される配列には、各ピクセルの RGBA 値を表す 1 ピクセルあたり 4 つの値が含まれます。これらのピクセルは、巨大な配列のような構造でつながっています。たとえば、2x2 のキャンバスでは、imageData 配列に 4 ピクセルの 16 個のエントリがあります。
キャンバスは全画面なので、画面を(iPad のように)1024x768 と仮定すると、配列には 3,145,728 個のエントリが含まれます。これはアニメーションであるため、この配列全体が 1 秒間に 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 Shader のデモ
その下では、8bit オーバーレイを取り除き、その下に元のアニメーションが表示されています。「画面を強制終了」オプションを使用すると、ソースピクセルが誤ってサンプリングされ、偶発的な現象が発生します。Eightbit モードが想定外のアスペクト比にサイズ変更されたときに、これを「レスポンシブ」なイースター エッグとして使用することになりました。ハッピー アクシデント!
キャンバス合成
複数のレンダリング ステップとマスクを組み合わせることで、驚くほどの効果が得られます。2D メタボールを構築しました。このメタボールでは、各ボールに独自の放射状のグラデーションを設定し、ボールが重なる部分をブレンドする必要があります。(これは下のデモで確認できます)。
そのために、2 つの異なるキャンバスを使用しました。1 つ目のキャンバスでは、メタボールのシェイプを計算して描画します。2 つ目のキャンバスでは、ボールの各位置に放射状のグラデーションを描画します。次に、そのシェイプによってグラデーションがマスクされ、最終出力がレンダリングされます。
合成コードの例
すべての処理を行うコードは次のようになります。
// 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 Animation、JS Animation、Web Audio など)により、このプロジェクトの開発は驚くほど楽しくなりました。
ここに示すもの以外にも、探求すべきことはたくさんあります。I/O ロゴをタップし続けてください。正しいシーケンスでプレイすると、ミニテスト、ゲーム、トリッピーなビジュアル、場合によっては朝食用食品も解放されます。快適にご利用いただくには、スマートフォンやタブレットでご利用になることをおすすめします。
始めに O-I-I-I-I-I-I-I の組み合わせです。今すぐ試す: google.com/io
オープンソース
Google は、コードの Apache 2.0 ライセンスをオープンソース化しています。GitHub の http://github.com/Instrument/google-io-2013 から入手できます。
クレジット
デベロッパー:
- Thomas Reynolds 氏
- ブライアン・ヘフター
- Stefanie Hatcher 氏
- Paul Farning 氏
デザイナー:
- Dan Schechter 氏
- セージブラウン
- カイル・ベック
プロデューサー:
- エイミー パスカル
- アンドレア・ネルソン