우수사례 - Google I/O 2013 실험

Thomas Reynolds
Thomas Reynolds

소개

컨퍼런스 등록이 시작되기 전에 Google I/O 2013 웹사이트에 대한 개발자의 관심을 유도하기 위해 Google은 터치 상호작용, 생성형 오디오, 탐색의 즐거움에 중점을 둔 일련의 모바일 우선 실험과 게임을 개발했습니다. 코드의 잠재력과 플레이의 힘에서 영감을 받은 이 상호작용형 환경은 새로운 I/O 로고를 누르면 'I'와 'O'가 내는 단순한 소리로 시작됩니다.

Organic Motion

I 및 O 애니메이션을 HTML5 상호작용에서 자주 볼 수 없는 흔들리는 유기적 효과로 구현하기로 했습니다. 재미있고 반응이 빠르게 느껴지도록 옵션을 조정하는 데 시간이 조금 걸렸습니다.

탄력 있는 물리 코드 예

이 효과를 얻기 위해 두 도형의 가장자리를 나타내는 일련의 점에 간단한 물리 시뮬레이션을 적용했습니다. 어느 도형이든 탭하면 모든 점이 탭 위치에서 밖으로 가속됩니다. 안쪽으로 늘어져서 안으로 들어가게 됩니다.

인스턴스화 시 각 지점은 무작위 가속량과 반발 '탄력'을 가지므로 다음 코드에서 볼 수 있듯이 균일하게 애니메이션되지 않습니다.

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 홈 모드입니다. 또한 이 구현에서 여러 가지 추가 옵션을 노출했습니다. '점 표시'를 사용 설정하면 물리 시뮬레이션과 힘이 작용하는 개별 점이 표시됩니다.

스킨 변경

홈 모드 모션에 만족한 후에는 8비트와 ASCII라는 두 가지 레트로 모드에도 동일한 효과를 적용하고자 했습니다.

이러한 리스키닝을 위해 홈 모드에서 동일한 캔버스를 사용하고 픽셀 데이터를 사용하여 두 효과를 각각 생성했습니다. 이 접근 방식은 장면의 각 픽셀이 검사 및 조작되는 OpenGL 프래그먼트 셰이더와 비슷합니다. 자세히 살펴보겠습니다.

캔버스 '셰이더' 코드 예

getImageData 메서드를 사용하여 캔버스의 픽셀을 읽을 수 있습니다. 반환된 배열에는 각 픽셀의 RGBA 값을 나타내는 픽셀당 4개의 값이 포함됩니다. 이러한 픽셀은 대규모 배열과 같은 구조로 연결됩니다. 예를 들어 2x2 캔버스의 imageData 배열에는 4개의 픽셀과 16개의 항목이 있습니다.

캔버스는 전체 화면이므로 화면이 1024x768이라고 가정하면(예: iPad) 배열에는 3,145,728개의 항목이 있습니다. 애니메이션이므로 이 전체 배열은 1초에 60번 업데이트됩니다. 최신 JavaScript 엔진은 프레임 속도를 일관되게 유지할 만큼 충분히 빠르게 이 정도의 데이터에 대한 루핑과 작업을 처리할 수 있습니다. (도움말: 이 데이터를 개발자 콘솔에 로깅하지 마세요. 브라우저 속도가 느려지거나 완전히 비정상 종료될 수 있습니다.)

다음은 Eightbit 모드가 홈 모드 캔버스를 읽고 픽셀을 확대하여 더 블록이 많은 효과를 내는 방법입니다.

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 셰이더 데모

아래에서는 Eightbit 오버레이를 삭제하고 그 아래에 있는 원래 애니메이션을 확인할 수 있습니다. 'kill screen' 옵션을 사용하면 소스 픽셀을 잘못 샘플링하여 우연히 발견한 이상한 효과가 표시됩니다. 결국 Eightbit 모드의 크기가 예상치 못한 가로세로 비율로 조정될 때 '반응형' 이스터 에그로 사용했습니다. 좋은 우연입니다.

캔버스 컴포지팅

여러 렌더링 단계와 마스크를 결합하여 얻을 수 있는 결과는 정말 놀랍습니다. Google에서는 각 공에 고유한 방사형 그라데이션이 있고 공이 겹치는 부분에는 이러한 그라데이션이 함께 섞이도록 요구하는 2D 메타볼을 빌드했습니다. (아래 데모에서 확인할 수 있습니다.)

이를 위해 두 개의 개별 캔버스를 사용했습니다. 첫 번째 캔버스는 메타볼 모양을 계산하고 그립니다. 두 번째 캔버스에서는 각 볼 위치에 방사형 그라데이션을 그립니다. 그런 다음 도형이 그라데이션을 마스크하고 최종 출력을 렌더링합니다.

컴포지션 코드 예시

다음은 모든 작업을 실행하는 코드입니다.

// 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 애니메이션, 웹 오디오 등) 덕분에 프로젝트를 개발하는 것이 매우 즐거웠습니다.

여기에서 확인할 수 있는 것보다 훨씬 더 많은 기능을 살펴볼 수 있습니다. I/O 로고를 계속 탭하면 올바른 시퀀스를 통해 더 많은 미니 실험, 게임, 기발한 시각 효과, 아침 식사 음식을 잠금 해제할 수 있습니다. 최상의 환경을 위해 스마트폰이나 태블릿에서 사용해 보시기 바랍니다.

시작하는 데 도움이 되는 조합은 O-I-I-I-I-I-I-I입니다. 지금 사용해 보기: google.com/io

오픈소스

Google은 코드 Apache 2.0 라이선스를 오픈소스로 공개했습니다. GitHub(http://github.com/Instrument/google-io-2013)에서 확인할 수 있습니다.

크레딧

개발자:

  • 토마스 레이놀즈
  • 브라이언 헤프터
  • 스테파니 해처
  • 폴 파닝

디자이너:

  • 댄 쉐터
  • 세이지 브라운
  • 카일 벡

프로듀서:

  • 에이미 파스칼
  • 안드레아 넬슨