はじめに
カンファレンスの登録が開始される前に、Google I/O 2013 ウェブサイトでデベロッパーの関心を高めるために、タップ操作、生成音声、発見の喜びに焦点を当てた一連のモバイル ファーストのテストとゲームを開発しました。コードの可能性と遊びの力にインスパイアされたこのインタラクティブな体験は、新しい I/O ロゴをタップすると「I」と「O」のシンプルな音声から始まります。
Organic Motion
Google は、I と O のアニメーションを、HTML5 の操作ではあまり見られない、ぐらつきのある有機的な効果で実装することにしました。楽しく、事後対応的な印象を与えるためにオプションを設定する作業は、少し時間がかかりました。
バウンシー フィジックス コードの例
この効果を実現するために、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 ホームモードは次のとおりです。また、この実装では、多くの追加オプションも公開されています。[ポイントを表示] をオンにすると、物理シミュレーションと力が作用している個々のポイントが表示されます。
スキンケア
ホームモードのモーションに満足したら、Eightbit と Ascii の 2 つのレトロモードでも同じエフェクトを使いたいと考えました。
この再調整を行うため、ホームモードと同じキャンバスを使用し、ピクセルデータを使用して 2 つの効果をそれぞれ生成しました。このアプローチは、シーンの各ピクセルを検査、操作する OpenGL フラグメント シェーダーを連想させるものです。さらに詳しく見ていきましょう。
Canvas「Shader」のコードサンプル
キャンバス上のピクセルは、getImageData
メソッドを使用して読み取ることができます。返される配列には、各ピクセルの RGBA 値を表すピクセルあたり 4 つの値が含まれています。これらのピクセルは、巨大な配列のような構造につなぎ合わされています。たとえば、2x2 のキャンバスでは、imageData 配列に 4 つのピクセルと 16 個のエントリがあります。
キャンバスはフルスクリーンなので、画面が 1024x768(iPad など)であると仮定すると、配列には 3,145,728 個のエントリがあります。これはアニメーションであるため、この配列全体が 1 秒に 60 回更新されます。最新の JavaScript エンジンは、フレームレートを一定に保つために、この量のデータのループ処理と処理を十分な速さで処理できます。(ヒント: ブラウザの動作が遅くなるか、完全にクラッシュするため、そのデータをデベロッパー コンソールに記録しないでください)。
8 ビット モードでは、ホームモードのキャンバスを読み取り、ピクセルを拡大してブロック状の効果を実現します。
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);
}
}
8 ビット シェーダーのデモ
以下では、Eightbit のオーバーレイを削除し、その下の元のアニメーションを確認します。[画面を消す] オプションを選択すると、ソース ピクセルが正しくサンプリングされなかったために発生した奇妙な効果が表示されます。最終的には、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 アニメーション、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)にあります。
クレジット
デベロッパー:
- トーマス・レイノルズ
- ブライアン・ヘフター
- Stefanie Hatcher
- Paul Farning 氏
デザイナー:
- Dan Schechter
- セージブラウン
- カイル・ベック(Kyle Beck)氏
プロデューサー:
- エイミー・パスカル
- Andrea Nelson 氏