1 คะแนน โดย GN⁺ 2024-05-09 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

บันทึกการพัฒนา "Machine" ของ xkcd

แนวคิดเริ่มต้น

  • ใช้เวลาคิดไอเดียจนถึงปลายเดือนมีนาคม แล้วตัดสินใจเลือกไอเดียในช่วงต้นเดือนเมษายน
  • "จะสร้างอุปกรณ์ขนาดยักษ์แบบแบ่งเป็นแผ่นย่อยได้ไหม คล้าย GIF ลูกบอลสีน้ำเงินที่ผู้ใช้ Something Awful ช่วยกันทำ โดยทุกคนช่วยกันคนละสี่เหลี่ยมเล็ก ๆ หนึ่งช่อง"
  • ตอนแรกเหมือนกับว่าไอเดียนี้เป็นรูปเป็นร่างครบถ้วนแล้ว แต่พอเริ่มคุยกันจริง ๆ ก็พบว่ายังมีเรื่องที่ต้องตัดสินใจอีกมาก
  • แต่ละคนมีภาพในหัวไม่เหมือนกันในประเด็นสำคัญ เช่น ลูกบอลมาจากไหน ทุกคนดูเครื่องเดียวกันหรือไม่ จุดประสงค์คืออะไร และผู้เล่นจะโต้ตอบอย่างไร

สิ่งที่เรียนรู้จากความพยายามก่อนหน้า

  • เคยมีประสบการณ์ทำคอมิกแบบอินเทอร์แอ็กทีฟที่เน้นคอนเทนต์จากผู้ใช้
    • Lorenz: เอ็กซ์ควิซิตคอร์ปัสที่ให้ผู้อ่านเขียนข้อความในพาเนลเพื่อช่วยต่อยอดมุกและเนื้อเรื่อง (สนุกมาก)
    • Collector's Edition: เกมที่ให้ผู้อ่านค้นหาสติกเกอร์ที่ซ่อนอยู่ในคลัง xkcd แล้วนำไปติดถาวรบนผืนผ้าใบร่วมกัน (ผลลัพธ์ไม่เป็นไปตามที่ตั้งใจ)
      • ถ้าเริ่มจากแผนที่ตรงกลางที่ว่างเปล่าในช่วงแรก จะนำไปสู่ความสับสน
      • แรงจูงใจในการวางสติกเกอร์มีไม่มาก ทำให้การกระทำรายบุคคลผลักดันโครงเรื่องได้ยาก และสุดท้ายก็มีแค่ลวดลายเรียบ ๆ
      • ไม่มีเนื้อเรื่องหรือเป้าหมายโดยรวม และความสัมพันธ์ระหว่างสติกเกอร์ก็ไม่ชัดเจน
  • ถ้าอยากให้ผืนผ้าใบแบบรวมหมู่ประสบความสำเร็จ ต้องมีตัวอย่างให้เห็นว่าสร้างอะไรได้บ้าง และต้องมีบริบทกับจุดมุ่งหมายร่วมกัน

การออกแบบข้อจำกัด

  • หลังจากตัดสินใจว่าจะสร้างเครื่องลูกบอลตกขนาดใหญ่ ก็ต้องเผชิญกับตัวเลือกมากเกินไป
  • ตัดสินใจให้ประกอบด้วยกริดขนาด 100x100
    • การจำลอง 10,000 ไทล์แบบเรียลไทม์ฝั่งไคลเอนต์ดูเสี่ยงเกินไป
    • ไม่แน่ใจว่าผู้เล่นจะสร้างส่วนย่อยของเครื่องที่ซับซ้อนโดยไม่สื่อสารกันโดยตรงได้อย่างไร และเมื่อรวมไทล์ที่แยกกันเข้าด้วยกันแล้วจะยังทำงานได้หรือไม่
  • หลังการทดลองทางความคิดหลายครั้ง จึงตั้งหลักการสำคัญไว้ 3 ข้อ:

1. เพิ่มอิสระในการแสดงออกของผู้เล่นให้สูงสุด แม้ต้องแลกกับความแม่นยำ

  • เครื่องควรคาดเดาได้มากแค่ไหน?
    • เคยพิจารณาทางเลือกอย่างการรันทั้งหมดบนเซิร์ฟเวอร์หรือการตรวจสอบทีละไทล์ แต่ยืนยันได้จากเอดิเตอร์ต้นแบบว่าทำรูปแบบการชนของลูกบอลที่วุ่นวายได้ง่ายมาก
    • ถ้าลูกบอลไม่เคลื่อนเป็นเส้นตรงโดยไม่มีสิ่งรบกวน ก็สร้างเครื่องที่คาดเดาไม่ได้ได้ง่าย
  • การเพิ่มความคาดเดาได้ของเครื่องขัดกับการเพิ่มอิสระของผู้เล่น
    • ระยะเวลาพัฒนาที่กระชั้นชิดก็ทำให้เอนเอียงไปทางแนวทางที่พึ่งการคาดการณ์/การจำลองน้อยกว่า
  • จึงตัดสินใจมอบอิสระในการสร้างอย่างยืดหยุ่นมากให้ผู้เล่น แม้จะรวมถึงเครื่องที่ไม่เป็นเชิงกำหนดอย่างรุนแรงหรือเครื่องที่พังด้วยก็ตาม
    • ต้องอาศัยการตรวจทานเชิงรุกเพื่อเช็กว่าตรงตามข้อจำกัดและลบคอนเทนต์ที่ไม่เหมาะสม

2. กำหนดข้อจำกัดที่เข้มงวดเพื่อส่งเสริมเครื่องที่เข้ากันได้และแทนที่กันได้

  • การยอมรับงานผ่านการตรวจทานและการมีเครื่องของผู้เล่นที่คาดเดาไม่ได้ กลับทำให้ต้องการความเป็นระเบียบมากขึ้น
  • ตอนแรกเคยคิดให้ช่องรับเข้า/ส่งออกเป็นรูปแบบอิสระทั้งหมด แต่ในขั้นตรวจทานก็พบว่าถ้าจำเป็นต้องเปลี่ยนไทล์ต้นทาง อาจทำให้เกิดความเสียหายเป็นวงกว้าง
  • จึงออกแบบข้อจำกัดที่แข็งแรงพอให้ผู้เล่นหลายคนสร้างแบบที่เข้ากันได้ในพื้นที่ไทล์เดียวกัน
    • ใช้หลัก Robustness: "สิ่งที่ส่งออกให้เคร่งครัด สิ่งที่รับเข้าให้ยืดหยุ่น"
  • เพื่อกำหนดข้อจำกัดด้านอินพุต/เอาต์พุต จำเป็นต้องมีแผนที่ของเครื่องทั้งหมดตั้งแต่ต้น
    • ใช้การสร้างแผนที่เพื่อปรับระดับความยากของเครื่อง (ตั้งแต่แบบ 1 อินพุต 1 เอาต์พุตง่าย ๆ ไปจนถึงการรวม 4 อินพุต 4 เอาต์พุตที่ซับซ้อน)
  • เพื่อให้มีฟีดแบ็กแบบเรียลไทม์ จึงจำกัดให้ไทล์ปล่อยลูกบอลออกด้วยอัตราใกล้เคียงกับที่รับเข้ามา
    • จำกัดเครื่องที่กลืนลูกบอลหรือหน่วงลูกบอล
    • ทดสอบความโกลาหลของไทล์ด้วยอัตราอินพุตแบบสุ่ม
  • จึงตั้งหลักไว้ว่า "ปล่อยให้เครื่องทำงานไปสักระยะ แล้วดูว่าโดยเฉลี่ยแล้วมันยังตรงตามข้อจำกัดหรือไม่"

3. เครื่องต้องเข้าสู่สภาวะคงตัวภายใน 30 วินาทีแรก

  • มีคำถามว่าผู้ตรวจทานต้องเฝ้าดูนานแค่ไหน
    • คำนวณเวลาในการตรวจทั้งเครื่อง (83.3 ชั่วโมงสำหรับ 10,000 ไทล์)
    • จึงตัดสินใจแบบตามดุลยพินิจว่าให้เข้าสู่สภาวะคงตัวภายใน 30 วินาที
  • ตั้งให้ลูกบอลหายไปหลังผ่านไป 30 วินาที
    • ตอนแรกยังไม่มีเวลาหมดอายุ ทำให้ระหว่างที่ผู้เล่นเรียนรู้เกม ลูกบอลสะสมจนเต็มหน้าจอ
    • เมื่อ rigid body ที่ทำงานอยู่มีจำนวนมากขึ้น ความเร็วของการจำลองฟิสิกส์ก็ลดลง
    • ลูกบอลกลายเป็นตัวเกะกะมากกว่าจะสนุก
  • การหมดอายุของลูกบอลยังช่วยให้เครื่องไม่สะสมความผิดพลาดไปเรื่อย ๆ ตามเวลา
    • ผู้ตรวจทานจึงดูเพียง 30 วินาทีก็พอจะเข้าใจได้ว่าลูกบอลส่วนใหญ่จะไปทางไหนบ้าง

การจำลองและความเหนือจริง

  • มีความท้าทายใหญ่ 2 อย่างในสถาปัตยกรรมของ Machine:
    1. ภายใต้ข้อจำกัดการออกแบบข้างต้น การเชื่อมไทล์ที่ต่างกันให้กลายเป็นเครื่องเดียวทั้งชุดจะใช้งานได้จริงหรือไม่?
      • จึงสร้างและแก้แผนที่ขนาดเล็กสองสามชุดเพื่อพิสูจน์
    2. ถ้าไม่สามารถรันเครื่องขนาดมหึมาแบบเรียลไทม์บนเซิร์ฟเวอร์หรือไคลเอนต์ได้ จะจะแสดงผลมันอย่างไร?

เป้าหมายคือทำให้สามารถเลื่อนหน้าจอเพื่อตามดูลูกบอลเพียงลูกเดียวได้

  • แม้จะไม่ได้จำลองทั้งเครื่องทั้งหมด พื้นที่รอบบริเวณที่ผู้เล่นมองอยู่ก็ยังต้องถูกจำลอง
  • ในช่วงแรกได้ทดสอบการจำลองเฉพาะพื้นที่ที่มองเห็นบนแผนที่ไร้ขอบเขต
    • มันทำงานได้ค่อนข้างดี แต่เมื่อเลื่อนหน้าจอ ไทล์จะเข้ามาในระบบจำลองด้วยสภาพเริ่มต้นว่างเปล่า ทำให้การไหลมีช่วงขาดหาย
  • แทนที่จะเป็นไทล์ว่าง มันต้องดูเหมือนมีการทำงานอยู่ก่อนแล้ว

ความท้าทายข้อที่สอง: ถ่ายสแนปช็อตของไทล์หลังจากเข้าสู่สภาวะคงตัวแล้วเท่านั้น เพื่อให้มันมีอยู่ก่อนจะถูกเห็นจากการเลื่อนหน้าจอไม่นาน

  • ในคอมิกเวอร์ชันสุดท้าย มุมมองที่ปิด display clipping (ปิด CSS overflow:hidden, contain:paint):
    • สังเกตเห็นสแนปช็อตไหม? ถ้าไม่ได้ตั้งใจดูมากเป็นพิเศษก็แทบไม่ทันสังเกต
    • มีเพียงไทล์ที่ถูกเรนเดอร์เท่านั้นที่มีอยู่ในระบบจำลองฟิสิกส์
    • การปรับแต่งการแสดงผล: มองเห็นเฉพาะลูกบอลในพื้นที่มุมมอง แต่จำลองบนช่วงไทล์ทั้งหมด
    • เพื่อทำให้ดูเหมือนด้านบนของเครื่อง จึงสร้างและป้อนลูกบอลที่แถวบนสุดของการจำลอง โดยอิงจากอัตราที่คาดไว้ในข้อจำกัดอินพุต
  • ผนวกการสร้างสแนปช็อตเข้ากับ UI สำหรับการตรวจทาน
    • ผู้ตรวจทานต้องรออย่างน้อย 30 วินาทีก่อนอนุมัติไทล์
    • เมื่อกดปุ่มอนุมัติ ก็จะสร้างสแนปช็อต
    • ผู้ตรวจทานสามารถใช้ดุลยพินิจรอเพิ่มอีกเล็กน้อยจนกว่าเครื่องจะอยู่ในสภาพที่ดูดี
  • วิธีสแนปช็อตทำงานได้ดีกว่าที่คาดไว้มาก
    • มันช่วยรีเซ็ตความผิดพลาดที่สะสมอยู่ในเครื่องได้ด้วย
    • ความประทับใจแรกของไทล์ที่เห็นระหว่างเลื่อนหน้าจอคือสภาพที่สะอาดและดีซึ่งผู้ตรวจทานพอใจแล้ว
    • ในความเป็นจริง ถ้าดูนาน ๆ เครื่องหลายตัวอาจพังหรือเสียรูปได้มาก แต่เมื่อสำรวจต่อไปก็จะเข้าสู่สแนปช็อตใหม่ จึงไม่มีทางได้เห็น
  • เครื่องที่เลื่อนดูในคอมิกไม่ใช่ของจริง มันคือความเหนือจริง
    • ทั้งหมดไม่ได้ถูกจำลองพร้อมกันในคราวเดียว แต่กลับให้ผลลัพธ์ที่ดีกว่า

การเรนเดอร์ลูกบอลหลายพันลูกด้วย React และ DOM

  • สร้างบนเอนจินฟิสิกส์ Rapier
    • เอกสารยอดเยี่ยม, API ที่สะอาดขององค์ประกอบพื้นฐานที่มีประโยชน์, และการ implement ด้วย Rust (รันเป็น WASM ในเบราว์เซอร์) ทำให้ได้ประสิทธิภาพที่น่าประทับใจ
    • ตอนแรกสนใจการรับประกันความเป็นเชิงกำหนดของ Rapier มาก แต่สุดท้ายไม่ได้ทำการจำลองฝั่งเซิร์ฟเวอร์
  • เขียน React context แบบกำหนดเอง <PhysicsContext> ครอบบน Rapier
    • สร้างวัตถุฟิสิกส์ของ Rapier และจัดการมันภายใน lifecycle ของ React component
    • ทำให้พัฒนา component แบบ "widget" สำหรับวัตถุแต่ละชิ้นที่วางได้และมีฟิสิกส์หรือพื้นผิวชนได้ง่าย
    • React ทำหน้าที่เป็น scene graph แบบง่าย ๆ และลุย ๆ
    • ทำให้การโหลด/ยกเลิกโหลดไทล์ตอนเลื่อนมุมมองง่ายขึ้น: เมื่อ unmount ไทล์ ฟิสิกส์และ DOM ทั้งหมดจะถูกเก็บกวาด
    • เป็นโบนัส ยังเชื่อม hot reloading เข้ากับ fast refresh ได้ง่าย (มีประโยชน์มากตอนจูนรูปร่างการชน)
  • ข้อดีอีกอย่างของแนวทาง React context:
    • ถ้าใช้ physics hook นอก <PhysicsContext> มันจะกลายเป็น noop
    • ใช้สิ่งนี้กับการเรนเดอร์พรีวิวไทล์แบบคงที่ใน UI ตรวจทาน
  • มองย้อนกลับไป น่าจะใช้ component แทน hook สำหรับการสร้างวัตถุ Rapier (แบบที่ react-three-rapier เลือกใช้)
    • เข้ากับ React diffing ได้ดีกว่า (เมื่อ dependency เปลี่ยน useEffect จะลบอินสแตนซ์เดิมแล้วสร้างใหม่)
  • Machine ถูกเรนเดอร์ด้วย DOM ทั้งหมด
    • ระหว่างพัฒนาในช่วงแรก เคยกังวลว่าอาจชนเพดานประสิทธิภาพของการเรนเดอร์ด้วย DOM
    • ถ้ามันช้าเกินไปก็คาดว่าจะเปลี่ยนไปใช้ PixiJS หรือ canvas แต่ก็อยากเห็นก่อนว่าจะดัน DOM ไปได้ไกลแค่ไหน
  • การปรับแต่งประสิทธิภาพการเรนเดอร์:
    • frame loop จะนำสไตล์ไปใช้กับ widget ที่มีการจำลองฟิสิกส์โดยตรง
      • React diff จะทำงานเฉพาะเวลาที่มีการเปลี่ยนแปลงเชิงโครงสร้างใน scene graph
    • ตอนแรก เรนเดอร์ลูกบอลด้วย React

1 ความคิดเห็น

 
GN⁺ 2024-05-09
ความเห็นจาก Hacker News

เมื่อรวบรวมความเห็นต่าง ๆ แล้ว สามารถสรุปได้ดังนี้:

  • "The Incredible Machine" ซึ่งเป็นกิจกรรมวันเอพริลฟูลส์ของ XKCD เป็นเกมปริศนาแบบร่วมมือกันที่จัดขึ้นตลอดวันที่ 1 เมษายน
    • ผู้ใช้สามารถไขปริศนาโดยใช้องค์ประกอบเครื่องจักรที่สร้างด้วยฟิสิกส์เอนจิน และยังสามารถสร้างปริศนาของตัวเองแล้วส่งเข้าไปได้ด้วย
    • แต่ดูเหมือนว่าจะมีผู้ใช้จำนวนมากที่งุนงง เพราะคำอธิบายเกี่ยวกับวิธีการเล่นมีไม่เพียงพอ
  • รูปแบบของเกมปริศนาคล้ายกับเกม DOS เก่าอย่าง "The Incredible Machine"
    • เป็นรูปแบบที่ต้องใช้ชิ้นส่วนเครื่องจักรที่มีอยู่อย่างจำกัดเพื่อทำเป้าหมายที่กำหนดให้สำเร็จ
  • ในกระบวนการพัฒนาได้ใช้ฟิสิกส์เอนจิน Rapier แต่ก็เคยเกิดการแครชจากข้อผิดพลาดแบบเรียกซ้ำ
  • มีความเห็นเสนอว่าหลังจบอีเวนต์ก็น่าจะมีฟังก์ชัน permalink สำหรับแชร์ปริศนาที่ตัวเองสร้างไว้ได้
    • เนื่องจากอาจติดปัญหาเรื่องพื้นที่จัดเก็บ จึงมีข้อเสนอให้เข้ารหัส JSON ด้วย Base64 แล้วส่งผ่าน URL parameter
  • มีการประเมินว่า การทำโปรเจ็กต์ขนาดนี้ให้เสร็จได้ภายในเวลาเพียง 3 สัปดาห์เป็นความสำเร็จที่น่าทึ่ง
  • บางคนก็แปลกใจ เพราะเคยคิดว่า XKCD ดำเนินการโดย Randall Munroe เพียงคนเดียว แต่ดูเหมือนว่าจะมีหลายคนเข้ามามีส่วนร่วม
  • สามารถดูข้อมูลรายละเอียดเกี่ยวกับอีเวนต์เพิ่มเติมได้จาก Reddit, Explain XKCD, Github repository เป็นต้น