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 koncentrowały się na interakcjach dotykowych, generatywnym dźwięku i przyjemności odkrywania. Ta interaktywna zabawa, zainspirowana potencjałem kodu i mocą zabawy, rozpoczyna się od prostych dźwięków „I” i „O”, które słychać po kliknię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 przyjemna i szybka, zajął trochę czasu.

Przykład kodu fizyki odbicia

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ą, zanim zostaną wciągnięte 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, gdy zostaną dotknięte, przyspieszają na zewnątrz od miejsca dotknięcia, używając do tego kodu:

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ć. W ramach tej implementacji udostępniliśmy też wiele dodatkowych opcji. Jeśli włączysz opcję „Pokaż punkty”, zobaczysz poszczególne punkty, na które działają siły i symulacja fizyki.

Zmiana wyglądu

Gdy byliśmy zadowoleni z ruchu w domu, postanowiliśmy użyć tego samego efektu w dwóch trybach retro: Eightbit i Ascii.

Aby przeprowadzić tę zmianę, użyliśmy tego samego tła z trybu domowego i wykorzystaliśmy dane pikseli do wygenerowania obu efektów. 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 „Shader” na potrzeby kanwy

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. Ponieważ jest to animacja, cały ten tablica jest aktualizowana 60 razy na sekundę. Nowoczesne silniki JavaScript mogą obsługiwać pętle i działania na tak dużej ilości danych wystarczająco szybko, aby utrzymać stałą liczbę klatek na sekundę. (Wskazówka: nie próbuj rejestrować tych danych w konsoli programisty, ponieważ spowoduje to spowolnienie indeksowania przez przeglądarkę lub całkowite zawieszenie jej działania).

Oto jak tryb 8-bitowy odczytuje płótno trybu domowego i powiększy piksele, aby uzyskać efekt blokowania:

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. Ostatecznie wykorzystaliśmy je jako „responsywne” jajko wielkanocne, gdy rozmiary w trybie Eightbit zostaną dopasowane do nietypowych formatów obrazu. Szczęśliwy wypadek

Kompilowanie w Canvas

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

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

Przykład kodu do tworzenia kompozycji

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 płótno do maskowania i narysuj:

// 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 użyliśmy (np. 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 logo I/O, a właściwe sekwencje odblokują więcej minieksperymentów, gier, dziwacznych wizualizacji, a być może nawet jakieś śniadaniowe przekąski. Aby uzyskać najlepsze wrażenia, spróbuj użyć aplikacji na smartfonie lub tablecie.

Oto kombinacja, która może Ci pomóc: O-I-I-I-I-I-I-I. Wypróbuj to teraz: google.com/io

Oprogramowanie typu open source

Udostępniliśmy kod na licencji Apache 2.0. Znajdziesz go w naszym repozytorium GitHub 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 Beck

Producenci:

  • Amie Pascal
  • Andrea Nelson