Ghi chú phát triển "Machine" của xkcd
Ý tưởng ban đầu
- Sau khi suy nghĩ về ý tưởng đến cuối tháng 3, họ chốt ý tưởng vào đầu tháng 4
- "Liệu có thể tạo ra một cỗ máy khổng lồ dạng lát ghép như ảnh GIF blueball do người dùng Something Awful làm ra không? Mỗi người chỉ cần đóng góp một hình vuông nhỏ"
- Lúc đầu, ý tưởng có vẻ như đã hoàn chỉnh, nhưng khi thực sự bắt đầu bàn bạc thì họ nhận ra vẫn còn rất nhiều quyết định phải đưa ra
- Họ có những hình dung khác nhau về các phần cốt lõi như bóng xuất phát từ đâu, mọi người có nhìn cùng một cỗ máy hay không, mục tiêu là gì, và người chơi sẽ tương tác như thế nào
Những điều rút ra từ các thử nghiệm trước
- Họ từng có kinh nghiệm làm truyện tranh tương tác xoay quanh nội dung do người dùng tạo ra
- Lorenz: một exquisite corpse, nơi độc giả viết lời thoại cho các khung tranh để phát triển trò đùa và câu chuyện (rất vui)
- Collector's Edition: một trò chơi nơi độc giả tìm sticker ẩn trong kho lưu trữ xkcd và dán vĩnh viễn lên một canvas chia sẻ (không đạt được kết quả như mong muốn)
- Bắt đầu từ một bản đồ trung tâm trống rỗng khiến mọi thứ rơi vào hỗn loạn
- Thiếu động lực cho việc đặt sticker nên rất khó để hành động cá nhân thúc đẩy cốt truyện tiến lên, và cuối cùng chỉ tạo ra các mẫu đơn giản
- Không có câu chuyện tổng thể hay mục tiêu rõ ràng, và quan hệ giữa các sticker cũng không rõ ràng
- Để một canvas tập thể thành công, cần cho người dùng thấy ví dụ về những gì họ có thể tạo ra, đồng thời phải có bối cảnh và mục tiêu chung
Thiết kế các điều kiện ràng buộc
- Sau khi quyết định làm một cỗ máy thả bi cỡ lớn, họ phải đối mặt với quá nhiều lựa chọn
- Họ quyết định dùng lưới kích thước 100x100
- Việc mô phỏng thời gian thực 10.000 ô ở phía client có vẻ quá rủi ro
- Họ cũng không chắc người chơi có thể tạo ra các phân khu của một cỗ máy phức tạp mà không cần giao tiếp trực tiếp, và liệu các ô tách rời có hoạt động khi ghép lại hay không
- Sau nhiều thí nghiệm tư duy, họ đặt ra 3 nguyên tắc cốt lõi:
1. Tối đa hóa khả năng biểu đạt của người chơi, ngay cả khi phải hy sinh độ chính xác
- Cỗ máy cần phải dễ dự đoán đến mức nào?
- Họ cũng cân nhắc chạy toàn bộ ở phía server hoặc kiểm chứng từng ô riêng lẻ, nhưng qua trình chỉnh sửa nguyên mẫu, họ xác nhận rằng rất dễ tạo ra các kiểu va chạm bóng hỗn loạn
- Nếu bóng không thể di chuyển thẳng mà không bị cản trở, thì rất dễ tạo ra một cỗ máy không thể dự đoán
- Việc tăng tính dễ dự đoán của cỗ máy mâu thuẫn với mức độ tự do của người chơi
- Tiến độ phát triển gấp gáp cũng khiến họ nghiêng về cách tiếp cận cần ít dự đoán/mô phỏng hơn
- Họ quyết định trao cho người chơi quyền tự do chế tạo rất linh hoạt, kể cả những cỗ máy cực kỳ phi định tính hoặc bị hỏng
- Điều này đòi hỏi khâu kiểm duyệt chủ động để bảo đảm đáp ứng các ràng buộc và loại bỏ nội dung không phù hợp
2. Đưa ra các ràng buộc nghiêm ngặt để khuyến khích những cỗ máy tương thích và có thể thay thế cho nhau
- Việc chấp nhận kiểm duyệt và các cỗ máy khó đoán của người chơi lại khiến họ cần nhiều trật tự hơn
- Ban đầu họ cân nhắc để đầu vào/đầu ra hoàn toàn tự do, nhưng trong quá trình kiểm duyệt họ nhận ra rằng nếu cần thay một ô ở giai đoạn đầu, có thể gây ra gián đoạn trên diện rộng
- Họ thiết kế các ràng buộc đủ mạnh để nhiều người chơi có thể tạo ra các thiết kế tương thích trong cùng một không gian ô
- Áp dụng nguyên tắc Robustness: "gửi dữ liệu một cách bảo thủ, nhận dữ liệu một cách khoan dung"
- Để đặt ràng buộc đầu vào/đầu ra, ngay từ đầu họ đã cần bản đồ của toàn bộ cỗ máy
- Việc tạo bản đồ cũng giúp điều chỉnh độ khó của cỗ máy (từ đơn giản kiểu 1 đầu vào 1 đầu ra đến hợp nhất phức tạp 4 đầu vào 4 đầu ra)
- Để cung cấp phản hồi theo thời gian thực, họ giới hạn để mỗi ô thải bóng ra với tốc độ gần giống tốc độ mà nó nhận vào
- Hạn chế những cỗ máy nuốt bóng hoặc trì hoãn bóng
- Kiểm tra hỗn loạn các ô bằng tốc độ đầu vào ngẫu nhiên
- Họ xác lập nguyên tắc: "cho cỗ máy chạy một lúc và xác nhận rằng về trung bình nó có đáp ứng các ràng buộc hay không"
3. Cỗ máy phải đạt trạng thái ổn định trong 30 giây đầu tiên
- Điều này đặt ra câu hỏi người kiểm duyệt cần phải quan sát trong bao lâu
- Họ tính thời gian cần thiết để kiểm duyệt toàn bộ cỗ máy (83,3 giờ với 10.000 ô)
- Rồi quyết định có phần tùy ý rằng cỗ máy phải đi vào trạng thái ổn định trong vòng 30 giây
- Họ thiết lập để bóng biến mất sau 30 giây
- Lúc đầu không có thời gian hết hạn, nên trong khi người chơi đang học cách chơi, bóng cứ tích tụ và lấp đầy màn hình
- Khi số lượng rigid body đang hoạt động tăng lên, tốc độ mô phỏng vật lý giảm xuống
- Tình huống lúc này là bóng gây cản trở nhiều hơn là tạo niềm vui
- Việc cho bóng hết hạn giúp cỗ máy không tích lũy lỗi theo thời gian
- Người kiểm duyệt chỉ cần quan sát 30 giây là có thể nắm được phần lớn bóng có thể đi về đâu
Mô phỏng và tính siêu thực
- Có hai thách thức lớn trong kiến trúc của Machine:
- Liệu việc nối các ô dị biệt thành một cỗ máy hoàn chỉnh với các ràng buộc thiết kế ở trên có hoạt động được không?
- Họ kiểm chứng điều này bằng cách tạo và giải vài bản đồ nhỏ
- Nếu không thể chạy cỗ máy khổng lồ theo thời gian thực ở server hoặc client, thì phải hiển thị nó như thế nào?
Mục tiêu là cho phép cuộn và theo dõi một quả bóng duy nhất
- Dù toàn bộ cỗ máy không được mô phỏng, khu vực xung quanh nơi người chơi đang nhìn vẫn phải được mô phỏng
- Ban đầu họ thử chỉ mô phỏng vùng đang nhìn thấy trên một bản đồ vô hạn
- Cách này hoạt động khá tốt, nhưng khi cuộn, các ô bước vào mô phỏng với trạng thái trống ban đầu nên tạo ra khoảng đứt trong dòng chảy
- Thay vì các ô trống, chúng phải trông như đã có hoạt động sẵn
Thách thức thứ hai: chỉ chụp snapshot của ô sau khi nó đã đạt trạng thái ổn định, để nó chỉ tồn tại ngay trước khi được nhìn thấy khi cuộn
- Trong truyện tranh hoàn chỉnh, họ dùng chế độ xem tắt display clipping (CSS overflow:hidden, contain:paint bị vô hiệu hóa):
- Bạn có nhận ra các snapshot không? Nếu không quan sát đặc biệt kỹ thì rất khó nhận ra
- Chỉ các ô được render mới tồn tại trong mô phỏng vật lý
- Tối ưu hiển thị: chỉ bóng trong vùng nhìn thấy mới được hiển thị, nhưng việc mô phỏng diễn ra trên toàn bộ phạm vi ô
- Để giả lập phần trên cùng của cỗ máy, họ tạo và cấp bóng ở hàng trên cùng của vùng mô phỏng, dựa trên tốc độ dự kiến của các ràng buộc đầu vào
- Tích hợp việc tạo snapshot vào UI kiểm duyệt
- Người kiểm duyệt phải chờ ít nhất 30 giây trước khi phê duyệt một ô
- Khi nhấn nút phê duyệt thì snapshot sẽ được tạo
- Tùy theo phán đoán, người kiểm duyệt có thể chờ thêm một chút cho đến khi cỗ máy ở trạng thái trông đẹp mắt
- Cách tiếp cận bằng snapshot hoạt động tốt hơn kỳ vọng
- Nó còn mang lại hiệu ứng tích cực là đặt lại các lỗi đã tích tụ trong cỗ máy
- Ấn tượng đầu tiên về ô mà người xem thấy khi cuộn luôn là trạng thái sạch sẽ, đẹp mắt mà người kiểm duyệt đã hài lòng
- Trên thực tế, nếu quan sát đủ lâu thì nhiều cỗ máy có thể hỏng hoặc tan vỡ, nhưng khi tiếp tục khám phá, người xem sẽ bước vào một snapshot mới nên sẽ không thấy điều đó
- Cỗ máy cuộn trong truyện tranh không phải là thật. Nó mang tính siêu thực
- Toàn bộ hệ thống không được mô phỏng cùng lúc, nhưng điều đó lại tạo ra kết quả tốt hơn
Render hàng nghìn quả bóng bằng React và DOM
- Được xây dựng dựa trên engine vật lý Rapier
- Hiệu năng rất ấn tượng nhờ tài liệu xuất sắc, API gọn gàng với các primitive hữu ích, và phần triển khai bằng Rust (chạy dưới dạng WASM trong trình duyệt)
- Ban đầu họ bị thu hút bởi cam kết tính quyết định của Rapier, nhưng cuối cùng không mô phỏng ở phía server
- Họ viết một React context tùy chỉnh
<PhysicsContext> trên Rapier
- Tạo các đối tượng vật lý Rapier và quản lý chúng trong vòng đời của component React
- Giúp dễ phát triển các component "widget" cho từng đối tượng có thể đặt được với bề mặt vật lý hoặc va chạm
- React đóng vai trò như một scene graph đơn giản và hơi bẩn
- Đơn giản hóa việc tải/gỡ ô khi cuộn khung nhìn: khi một ô unmount, toàn bộ vật lý và DOM đều được dọn sạch
- Như một phần thưởng thêm, việc nối hot reloading với fast refresh trở nên dễ dàng hơn (rất hữu ích khi tinh chỉnh hình va chạm)
- Một ưu điểm khác của cách tiếp cận bằng React context:
- Nếu các physics hook không nằm trong
<PhysicsContext> thì chúng sẽ trở thành noop
- Điều này được dùng để render bản xem trước ô tĩnh trong UI kiểm duyệt
- Họ ước gì đã dùng component thay vì hook để tạo các đối tượng Rapier (cách mà react-three-rapier áp dụng)
- Cách đó phù hợp hơn với React diffing (khi dependency thay đổi,
useEffect sẽ xóa instance cũ rồi tạo lại)
- Machine được render hoàn toàn bằng DOM
- Trong giai đoạn phát triển ban đầu, họ lo rằng render bằng DOM sẽ chạm trần hiệu năng
- Nếu quá chậm, họ dự tính sẽ chuyển sang PixiJS hoặc canvas, nhưng trước tiên muốn xem DOM có thể được đẩy xa đến đâu
- Tối ưu hiệu năng render:
- Vòng lặp frame áp style trực tiếp lên các widget có mô phỏng vật lý
- React diff chỉ chạy khi scene graph có thay đổi về cấu trúc
- Ban đầu họ render bóng bằng React
1 bình luận
Ý kiến trên Hacker News
Tổng hợp nhiều ý kiến có thể tóm tắt như sau:
Rapier, nhưng cũng từng xảy ra crash do lỗi đệ quy