Giới thiệu
Để thu hút sự quan tâm của nhà phát triển trên trang web Google I/O 2013 trước khi mở đăng ký tham dự hội nghị, chúng tôi đã phát triển một loạt các trò chơi và thử nghiệm ưu tiên thiết bị di động, tập trung vào các hoạt động tương tác bằng thao tác chạm, âm thanh tạo sinh và niềm vui khám phá. Lấy cảm hứng từ tiềm năng của mã nguồn và sức mạnh của khả năng chơi, trải nghiệm tương tác này bắt đầu bằng những âm thanh đơn giản của "I" và "O" khi bạn nhấn vào biểu trưng I/O mới.
Organic Motion
Chúng tôi quyết định triển khai ảnh động I và O theo hiệu ứng tự nhiên, lắc lư mà không thường thấy trong các hoạt động tương tác HTML5. Chúng tôi đã mất một chút thời gian để điều chỉnh các tuỳ chọn nhằm mang đến trải nghiệm thú vị và phản hồi nhanh chóng.
Ví dụ về mã vật lý bouncy
Để tạo hiệu ứng này, chúng tôi đã sử dụng một mô phỏng vật lý đơn giản trên một loạt các điểm đại diện cho các cạnh của hai hình dạng. Khi bạn nhấn vào một trong hai hình dạng, tất cả các điểm sẽ được tăng tốc từ vị trí nhấn. Các lò xo này kéo giãn ra và ra xa trước khi được kéo lại.
Khi tạo bản sao, mỗi điểm sẽ nhận được một lượng gia tốc ngẫu nhiên và độ "nảy" khi bật lại để không tạo ảnh động đồng nhất, như bạn có thể thấy trong mã này:
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);
}
Sau đó, khi được nhấn, các hạt sẽ được tăng tốc ra ngoài từ vị trí nhấn bằng mã tại đây:
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;
}
Cuối cùng, mỗi hạt được giảm tốc trên mỗi khung và từ từ trở về trạng thái cân bằng bằng phương pháp này trong mã:
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']);
}
Bản minh hoạ chuyển động không phải trả tiền
Sau đây là chế độ màn hình chính của I/O để bạn khám phá. Chúng tôi cũng đã giới thiệu một số tuỳ chọn bổ sung trong quá trình triển khai này. Nếu bật tuỳ chọn "hiển thị điểm", bạn sẽ thấy các điểm riêng lẻ mà lực và mô phỏng vật lý đang tác động.
Mua lại
Sau khi hài lòng với chuyển động của chế độ trang chủ, chúng tôi muốn sử dụng hiệu ứng tương tự cho hai chế độ hoài cổ: Eightbit và Ascii.
Để thực hiện việc đổi giao diện này, chúng tôi đã sử dụng cùng một canvas từ chế độ trang chủ và sử dụng dữ liệu pixel để tạo từng hiệu ứng trong số hai hiệu ứng. Phương pháp này gợi nhớ đến chương trình đổ bóng mảnh OpenGL, trong đó mỗi pixel của cảnh được kiểm tra và thao tác. Hãy cùng tìm hiểu kỹ hơn về vấn đề này.
Ví dụ về mã "Shader" (Bộ đổ bóng) của Canvas
Có thể đọc pixel trên Canvas bằng phương thức getImageData
. Mảng được trả về chứa 4 giá trị trên mỗi pixel, đại diện cho từng giá trị RGBA của pixel. Các pixel này được nối với nhau theo cấu trúc giống như một mảng lớn. Ví dụ: một canvas 2x2 sẽ có 4 pixel và 16 mục trong mảng imageData.
Canvas của chúng ta ở chế độ toàn màn hình, vì vậy, nếu giả sử màn hình có kích thước 1024x768 (như trên iPad), thì mảng sẽ có 3.145.728 mục nhập. Vì đây là ảnh động nên toàn bộ mảng này được cập nhật 60 lần/giây. Các công cụ JavaScript hiện đại có thể xử lý vòng lặp và hành động trên nhiều dữ liệu này một cách nhanh chóng để duy trì tốc độ khung hình nhất quán. (Lưu ý: đừng cố gắng ghi lại dữ liệu đó vào bảng điều khiển dành cho nhà phát triển vì việc này sẽ làm chậm trình duyệt của bạn hoặc khiến trình duyệt gặp sự cố hoàn toàn.)
Dưới đây là cách chế độ 8bit của chúng ta đọc canvas ở chế độ chính và tăng cường các pixel để tạo hiệu ứng chặn:
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);
}
}
Bản minh hoạ chương trình đổ bóng 8bit
Dưới đây, chúng ta sẽ xoá lớp phủ Eightbit và thấy ảnh động ban đầu bên dưới. Tuỳ chọn "kill screen" (tắt màn hình) sẽ cho bạn thấy một hiệu ứng lạ mà chúng tôi tình cờ phát hiện được khi lấy mẫu không chính xác các pixel nguồn. Cuối cùng, chúng tôi đã sử dụng nó làm một quả trứng phục sinh "đáp ứng" khi chế độ Eightbit được đổi kích thước thành tỷ lệ khung hình không phổ biến. Tai nạn vui vẻ!
Kết hợp Canvas
Bạn có thể làm được những điều khá tuyệt vời bằng cách kết hợp nhiều bước kết xuất và mặt nạ. Chúng tôi đã tạo một metaball 2D yêu cầu mỗi quả bóng có một độ dốc hình tròn riêng và các độ dốc đó được pha trộn với nhau khi các quả bóng chồng lên nhau. (Bạn có thể xem điều này trong bản minh hoạ bên dưới.)
Để thực hiện việc này, chúng ta đã sử dụng hai canvas riêng biệt. Canvas đầu tiên tính toán và vẽ hình dạng metaball. Một canvas thứ hai vẽ các hiệu ứng chuyển màu hình tròn tại mỗi vị trí của quả bóng. Sau đó, hình dạng sẽ che các hiệu ứng chuyển màu và chúng ta sẽ kết xuất kết quả cuối cùng.
Ví dụ về mã kết hợp
Dưới đây là mã giúp mọi thứ diễn ra:
// 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);
};
Sau đó, thiết lập canvas để che và vẽ:
// 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);
Kết luận
Việc sử dụng nhiều kỹ thuật và công nghệ (chẳng hạn như Canvas, SVG, Ảnh động CSS, Ảnh động JS, Âm thanh trên web, v.v.) đã giúp dự án trở nên thú vị hơn rất nhiều.
Thậm chí, còn có nhiều điều để khám phá hơn những gì bạn thấy ở đây. Hãy tiếp tục nhấn vào biểu trưng I/O và các trình tự chính xác sẽ mở ra nhiều thử nghiệm nhỏ, trò chơi, hình ảnh kỳ lạ và thậm chí có thể là một số món ăn sáng. Bạn nên dùng thử tính năng này trên điện thoại thông minh hoặc máy tính bảng để có trải nghiệm tốt nhất.
Sau đây là một tổ hợp để bạn bắt đầu: O-I-I-I-I-I-I-I. Hãy dùng thử ngay: google.com/io
Nguồn mở
Chúng tôi đã cung cấp mã nguồn mở giấy phép Apache 2.0 của mình. Bạn có thể tìm thấy mã này trên GitHub tại: http://github.com/Instrument/google-io-2013.
Ghi công
Nhà phát triển:
- Thomas Reynolds
- Brian Hefter
- Stefanie Hatcher
- Paul Farning
Nhà thiết kế:
- Dan Schechter
- Nâu xanh xám
- Kyle Beck
Nhà sản xuất:
- Amie Pascal
- Andrea Nelson