Introdução
Para despertar o interesse dos desenvolvedores no site da Google I/O 2013 antes da abertura das inscrições para a conferência, desenvolvemos uma série de experimentos e jogos com foco em dispositivos móveis, com foco em interações por toque, áudio generativo e a alegria da descoberta. Inspirada no potencial do código e no poder do jogo, essa experiência interativa começa com os sons simples de "I" e "O" quando você toca no novo logotipo do I/O.
Movimento orgânico
Decidimos implementar as animações de I e O em um efeito orgânico e instável que não é comum em interações HTML5. A seleção das opções para tornar a experiência divertida e responsiva levou um pouco de tempo.
Exemplo de código de física bouncy
Para conseguir esse efeito, usamos uma simulação de física simples em uma série de pontos que representam as bordas das duas formas. Quando uma das formas é tocada, todos os pontos são acelerados para fora do local do toque. Eles se estendem e se afastam antes de serem puxados de volta.
Na instanciação, cada ponto recebe uma quantidade de aceleração aleatória e uma "elasticidade" de rebote para que não sejam animados de maneira uniforme, como você pode conferir neste código:
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);
}
Em seguida, quando tocadas, elas são aceleradas para fora da posição da batida usando o código aqui:
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;
}
Por fim, cada partícula é desacelerada em cada frame e retorna lentamente ao equilíbrio com essa abordagem no código:
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']);
}
Demonstração de movimento orgânico
Este é o modo inicial do I/O para você se divertir. Também disponibilizamos várias outras opções nessa implementação. Se você ativar a opção "Mostrar pontos", vai aparecer os pontos individuais em que a simulação de física e as forças estão agindo.
Reskinning
Quando estivermos satisfeitos com o movimento do modo "Em casa", queríamos usar o mesmo efeito para dois modos retrô: Eightbit e Ascii.
Para fazer essa reskinning, usamos a mesma tela do modo inicial e os dados de pixel para gerar cada um dos dois efeitos. Essa abordagem lembra um sombreador de fragmento OpenGL em que cada pixel da cena é inspecionado e manipulado. Vamos nos aprofundar nisso.
Exemplo de código de tela "Shader"
Os pixels em uma tela podem ser lidos usando o método getImageData
. A matriz retornada contém quatro valores por pixel, representando cada valor RGBA de pixel. Esses pixels estão agrupados em uma enorme estrutura semelhante a uma matriz. Por exemplo, uma tela 2x2 teria 4 pixels e 16 entradas na matriz imageData.
Nossa tela é em tela cheia. Portanto, se fingirmos que a tela é 1024x768 (como em um iPad), a matriz terá 3.145.728 entradas. Como se trata de uma animação, toda a matriz é atualizada 60 vezes por segundo. Os mecanismos modernos de JavaScript podem processar loops e atuar em muitos dados com rapidez suficiente para manter a taxa de frames consistente. Dica: não tente registrar esses dados no console do desenvolvedor, porque isso vai deixar seu navegador lento ou fazer com que ele trave.
Confira como o modo Eightbit lê a tela do modo de início e aumenta os pixels para ter um efeito mais em blocos:
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);
}
}
Demonstração de sombreador de 8 bits
Abaixo, removemos a sobreposição de 8 bits e mostramos a animação original abaixo. A opção "eliminar tela" mostrará um efeito estranho que encontramos ao fazer a amostragem incorreta dos pixels de origem. Acabamos usando isso como um ovo de Páscoa "responsivo" quando o modo de 8 bits é redimensionado para proporções improváveis. Feliz acidente!
Composição de tela
É incrível o que você pode fazer combinando várias etapas de renderização e máscaras. Criamos uma metaesfera 2D, que exige que cada esfera tenha seu próprio gradiente radial e que esses gradientes sejam mesclados onde as esferas se sobrepõem. Confira isso na demonstração abaixo.
Para isso, usamos duas telas separadas. A primeira tela calcula e desenha a forma da metabola. Uma segunda tela desenha gradientes radiais em cada posição da bola. Em seguida, a forma mascara os gradientes e renderizamos a saída final.
Exemplo de código de composição
Confira o código que faz tudo acontecer:
// 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);
};
Em seguida, configure a tela para mascarar e desenhar:
// 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);
Conclusão
A variedade de técnicas e tecnologias que implementamos (como Canvas, SVG, animação CSS, animação JS, Web Audio etc.) tornou o projeto incrivelmente divertido de desenvolver.
Há muito mais a explorar do que você vê aqui. Continue tocando no logotipo da I/O e as sequências corretas vão desbloquear mais miniexperimentos, jogos, visuais psicodélicos e talvez até alguns alimentos para o café da manhã. Para ter a melhor experiência, sugerimos que você teste o recurso no smartphone ou tablet.
Aqui está uma combinação para você começar: O-I-I-I-I-I-I. Faça um teste: google.com/io
Código aberto
Liberamos o código sob a licença Apache 2.0. Confira no nosso GitHub: http://github.com/Instrument/google-io-2013.
Créditos
Desenvolvedores:
- Thomas Reynolds
- Brian Hefter
- Stefanie Hatcher
- Paul Farning
Designers:
- Dan schechter
- Verde-acinzentado
- Kyle Beck
Produtores:
- Amie Pascal
- Andrea nelson