Giriş
Konferans kaydı açılmadan önce Google I/O 2013 web sitesine geliştirici ilgisini çekmek için dokunma etkileşimlerine, üretken sese ve keşfetmenin keyfine odaklanan bir dizi mobil öncelikli deneme ve oyun geliştirdik. Kodun potansiyelinden ve oyunun gücünden ilham alan bu etkileşimli deneyim, yeni I/O logosuna dokunduğunuzda "I" ve "O" sesleriyle başlar.
Organic Motion
I ve O animasyonlarını, HTML5 etkileşimlerinde sık görülmeyen titrek, organik bir efekt ile uygulamaya karar verdik. Eğlenceli ve duyarlı bir deneyim sunmak için seçenekleri belirlemek biraz zaman aldı.
Zıplayan Fizik Kodu Örneği
Bu efekti oluşturmak için iki şeklin kenarlarını temsil eden bir dizi noktada basit bir fizik simülasyonu kullandık. Herhangi bir şekle dokunulduğunda tüm noktalar, dokunma noktasından dışarı doğru hızlandırılır. Kollar geri çekilmeden önce uzayıp uzaklaşır.
Oluşturma sırasında her noktaya rastgele bir hızlanma miktarı ve geri sıçrama "esneklik" değeri verilir. Böylece, bu kodda görebileceğiniz gibi, noktalar tekdüze bir şekilde animasyonlu hareket etmez:
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);
}
Ardından, dokunulduğunda aşağıdaki kod kullanılarak dokunma noktasından dışarı doğru hızlandırılır:
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;
}
Son olarak, her parçacık her karede yavaşlatılır ve koddaki bu yaklaşımla yavaş yavaş dengeye döner:
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']);
}
Organik Hareket Demosu
Oynayabileceğiniz I/O ev modunu burada bulabilirsiniz. Bu uygulamada bir dizi ek seçenek de sunduk. "Puanları göster" özelliğini açarsanız fizik simülasyonunun ve kuvvetlerin etki ettiği her bir noktayı görürsünüz.
Yeniden deri kaplama
Ev modu hareketinden memnun kaldıktan sonra aynı efekti iki retro modda (Eightbit ve Ascii) kullanmak istedik.
Bu yeniden deriyi oluşturmak için ana moddaki aynı kanvası kullandık ve iki efektin her birini oluşturmak için piksel verilerini kullandık. Bu yaklaşım, sahnenin her pikseli incelenip değiştirildiği bir OpenGL fragman gölgelendiriciyi andırır. Bu konuyu daha ayrıntılı inceleyelim.
Tuval "Gölgelendirici" Kod Örneği
Bir kanvastaki pikseller getImageData
yöntemi kullanılarak okunabilir. Döndürülen dizi, her pikselin RGBA değerini temsil eden piksel başına 4 değer içerir. Bu pikseller, büyük bir dizi benzeri yapıda birbirine bağlanır. Örneğin, 2x2 boyutunda bir kanvasta imageData dizisinde 4 piksel ve 16 giriş bulunur.
Tuvalimiz tam ekran olduğundan, ekranın 1024x768 (iPad'de olduğu gibi) olduğunu varsayarsak dizi 3.145.728 girişe sahiptir. Bu bir animasyon olduğundan, dizideki tüm değerler saniyede 60 kez güncellenir. Modern JavaScript motorları, bu kadar çok veriyi döngüye alma ve işleme konusunda kare hızını tutarlı tutacak kadar hızlıdır. (İpucu: Bu verileri geliştirici konsoluna kaydetmeyi denemeyin. Bu işlem, tarayıcınızı yavaşlatarak tarama hızını düşürür veya tarayıcınızı tamamen kilitler.)
Sekiz bit modumuz, ana ekran modu kanvası okuyup pikselleri daha bloklu bir etki oluşturacak şekilde nasıl büyütür?
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 Demo
Aşağıda, Eightbit yer paylaşımını kaldırarak altındaki orijinal animasyonu görüyoruz. "Ekranı kapat" seçeneği, kaynak piksellerin yanlış örneklemesiyle karşılaştığımız tuhaf bir efekti gösterir. Sekiz bit modu, olası olmayan en boy oranlarına yeniden boyutlandırıldığında "duyarlı" bir paskalya yumurtası olarak kullanmaya karar verdik. Kazancınız kutlu olsun!
Tuval Birleştirme
Birden fazla oluşturma adımını ve maskeyi birleştirerek neler yapabileceğinizi görmek oldukça şaşırtıcı. Her topun kendi radyal gradyanına sahip olmasını ve bu gradanların, topun çakıştığı yerlerde birlikte harmanlanmasını gerektiren bir 2D meta top oluşturduk. (Bunu aşağıdaki denemede görebilirsiniz.)
Bunu yapmak için iki ayrı kanvas kullandık. İlk tuval, meta top şeklini hesaplar ve çizer. İkinci kanvas, her top pozisyonunda radyal gradyanlar çizer. Ardından şekil, gradyanları maskeler ve nihai çıkışı oluştururuz.
Kod Birleştirme Örneği
Her şeyi sağlayan kod şöyledir:
// 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);
};
Ardından, kanvası maskeleme için ayarlayın ve çizin:
// 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);
Sonuç
Kullandığımız tekniklerin ve uyguladığımız teknolojilerin (ör. tuval, SVG, CSS animasyonu, JS animasyonu, web sesi) çeşitliliği, projenin geliştirilmesini inanılmaz derecede eğlenceli hale getirdi.
Burada gördüğünüzden çok daha fazlasını keşfedebilirsiniz. I/O logosuna dokunmaya devam ederseniz doğru sıralar daha fazla mini denemenin, oyunun, ilginç görselin ve belki de kahvaltılık yiyeceklerin kilidini açar. En iyi deneyim için akıllı telefonunuzda veya tabletinizde denemenizi öneririz.
Başlamak için kullanabileceğiniz bir kombinasyon: O-I-I-I-I-I-I-I. Hemen deneyin: google.com/io
Açık Kaynak
Kodumuzu Apache 2.0 lisansı kapsamında açık kaynak olarak yayınladık. Bu dokümanı GitHub'da http://github.com/Instrument/google-io-2013 adresinde bulabilirsiniz.
Kredi
Geliştiriciler:
- Thomas Reynolds
- Brian Hefter
- Stefanie Hatcher
- Paul Farning
Tasarımcılar:
- Dan Schechter
- Adaçayı Kahverengi
- Kyle Beck
Yapımcılar:
- Amie Pascal
- Andrea Nelson