Studium przypadku – eksperyment Google I/O 2013

Thomas Reynolds
Thomas Reynolds

Wprowadzenie

Aby zachęcić deweloperów do korzystania z witryny Google I/O 2013 przed rozpoczęciem rejestracji na konferencję, opracowaliśmy serię eksperymentów i gier na urządzenia mobilne, które koncentrują się na interakcjach dotykowych, generatywnej ścieżce dźwiękowej i przyjemności odkrywania. Ta interaktywna zabawa zainspirowana potencjałem kodu i mocy gry rozpoczyna się od prostych dźwięków „I” i „O” po dotknięciu nowego logo I/O.

Organic Motion

Postanowiliśmy zastosować animacje liter „I” i „O” w postaci efektu wibracji, który nie jest często spotykany w interakcjach HTML5. Wybór opcji, które sprawią, że gra będzie sprawiać przyjemność i będzie dynamiczna, zajął trochę czasu.

Przykładowy kod fizyki Bouncy Physics

Aby uzyskać ten efekt, zastosowaliśmy prostą symulację fizyki na serii punktów reprezentujących krawędzie 2 kształtów. Gdy klikniesz dowolny kształt, wszystkie punkty przyspieszą w kierunku od miejsca kliknięcia. Rozciągają się i odsuwają, a potem są wciągane z powrotem.

Podczas tworzenia każdy punkt otrzymuje losową wartość przyspieszenia i odbijania, dzięki czemu nie animuje się równomiernie, jak widać w tym kodzie:

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);
}

Następnie po dotknięciu przyspieszane są w kierunku przeciwnym do położenia kliknięcia. Kod ten wygląda tak:

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;
}

W każdej klatce każda cząstka jest spowalniana i powoli powraca do równowagi. W tym celu w kodzie stosujemy takie podejście:

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']);
}

Prezentacja bezpłatnych wyników wyszukiwania

Oto tryb I/O Home, z którym możesz się pobawić. Wprowadziliśmy też kilka dodatkowych opcji tego wdrożenia. Jeśli włączysz opcję „Pokaż punkty”, zobaczysz poszczególne punkty, na które działają siły i symulacja fizyki.

Zmiana skóry

Gdy byliśmy zadowoleni z ruchu w trybie domowym, chcieliśmy użyć tego samego efektu w 2 trybach retro: Eightbit i Ascii.

Aby przeprowadzić tę zmianę, użyliśmy tego samego tła z trybu domowego i danych pikseli, aby wygenerować oba efekty. To podejście przypomina shader fragmentu OpenGL, w którym każdy piksel sceny jest sprawdzany i modyfikowany. Przyjrzyjmy się temu bliżej.

Przykład kodu Canvas „Shader”

Pixele na płótnie można odczytać za pomocą metody getImageData. Zwrócona tablica zawiera 4 wartości na piksel, które reprezentują wartości RGBA każdego piksela. Te piksele są połączone w ogromną strukturę podobną do tablicy. Na przykład kanwa 2 x 2 będzie miała 4 piksele i 16 elementów w tablicy imageData.

Nasze tworzywo zajmuje cały ekran, więc jeśli założymy, że ekran ma rozmiar 1024 x 768 (jak na iPadzie), tablica będzie mieć 3 145 728 elementów. To jest animacja, więc cała tablica jest aktualizowana 60 razy na sekundę. Współczesne mechanizmy JavaScript są w stanie obsłużyć zapętlanie dużej ilości danych i wykorzystywanie ich na tyle szybko, aby utrzymać stałą liczbę klatek. (Wskazówka: nie próbuj rejestrować tych danych w konsoli programisty, ponieważ może to spowolnić indeksowanie lub całkowicie zawiesić przeglądarkę).

Oto jak nasz tryb Eightbit odczytuje płótno w trybie domowym i rozbija piksele w celu uzyskania efektu blockier:

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);
 
}
}

Demonstracja shadera ośmiobitowego

Poniżej usuwamy nakładkę Eightbit i widzimy oryginalną animację. Opcja „kill screen” spowoduje wyświetlenie dziwnego efektu, który uzyskaliśmy przez nieprawidłowe próbkowanie pikseli źródłowych. Wykorzystaliśmy go jako „responsywną” niespodziankę, gdy rozmiar trybu Eightbit został zmieniony na mało prawdopodobne. Szczęśliwy wypadek

Kompilowanie w Canvas

To niesamowite, co można osiągnąć, łącząc wiele etapów renderowania i masek. Utworzyliśmy metakulkę 2D, która wymaga, aby każda kula miała własny gradient promienisty, a gradienty te były łączone w miejscach, w których kule się nakładają. (możesz to zobaczyć na poniższym filmie demonstracyjnym).

Aby to zrobić, użyliśmy 2 oddzielnych obszarów roboczych. Pierwsze tworzywo oblicza i rysuje kształt metaballa. Drugie tworzywo rysuje gradienty promieniowe w każdej pozycji piłki. Następnie kształt maskuje gradienty, a my renderujemy ostateczny wynik.

Przykładowy kod komponowania

Oto kod, który powoduje, że wszystko działa:

// 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);
};

Następnie skonfiguruj obszar roboczy pod kątem maskowania i rysowania:

// 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);

Podsumowanie

Dzięki różnorodności technik i technologii, których mogliśmy użyć (takich jak Canvas, SVG, animacja CSS, animacja JS, dźwięk internetowy itp.), praca nad projektem była niesamowicie przyjemna.

Jest jeszcze więcej do odkrycia niż to, co widzisz na ekranie. Klikaj na logo I/O, a właściwe sekwencje odblokowują więcej minieksperymentów, gier, ekstrawaganckich efektów wizualnych, a może nawet dań śniadaniowych. Aby zapewnić sobie jak najlepsze wrażenia, spróbuj użyć aplikacji na smartfonie lub tablecie.

Na początek może skorzystać z tych kombinacji: O-I-I-I-I-I-I-I. Wypróbuj to teraz: google.com/io

Oprogramowanie typu open source

Udostępniliśmy kod open source naszej licencji Apache 2.0. Znajdziesz go na GitHubie pod adresem http://github.com/Instrument/google-io-2013.

Środki

Programiści:

  • Thomas Reynolds
  • Brian Hefter
  • Stefanie Hatcher
  • Paul Farning

Projektanci:

  • Dan Schechter
  • Szałwia brązowa
  • Kyle'a Becka

Producenci:

  • Amie Pascal
  • Andrea Nelson