กรณีศึกษา - Inside World Wide Maze

World Wide Maze คือเกมที่คุณใช้สมาร์ทโฟนเพื่อนำทางลูกบอลผ่านเขาวงกต 3 มิติที่สร้างขึ้นจากเว็บไซต์ต่างๆ เพื่อพยายามไปให้ถึงตามเป้าหมาย

เขาวงกตเวิลด์ไวด์

เกมมีการใช้คุณลักษณะของ HTML5 มากมาย ตัวอย่างเช่น เหตุการณ์ DeviceOrientation จะดึงข้อมูลการเอียงจากสมาร์ทโฟน ซึ่งจากนั้นจะส่งไปยัง PC ผ่าน WebSocket ที่ซึ่งผู้เล่นหาทางผ่านพื้นที่ 3 มิติที่สร้างโดย WebGL และ Web Workers

ในบทความนี้ เราจะอธิบายให้เข้าใจอย่างชัดเจนถึงวิธีการใช้งานฟีเจอร์เหล่านี้ กระบวนการพัฒนาโดยรวม และประเด็นสำคัญสำหรับการเพิ่มประสิทธิภาพ

DeviceOrientation

เหตุการณ์ DeviceOrientation (ตัวอย่าง) ใช้เพื่อเรียกข้อมูลการเอียงจากสมาร์ทโฟน เมื่อใช้ addEventListener กับเหตุการณ์ DeviceOrientation ระบบจะเรียกใช้โค้ดเรียกกลับที่มีออบเจ็กต์ DeviceOrientationEvent เป็นอาร์กิวเมนต์ในช่วงเวลาปกติ โดยช่วงเวลาจะแตกต่างกันไปตามอุปกรณ์ที่ใช้ ตัวอย่างเช่น ใน iOS + Chrome และ iOS + Safari จะมีการเรียกกลับทุก 1/20 วินาที ขณะที่ใน Android 4 + Chrome จะเรียกทุก 1/10 วินาทีโดยประมาณ

window.addEventListener('deviceorientation', function (e) {
  // do something here..
});

ออบเจ็กต์ DeviceOrientationEvent มีข้อมูลการเอียงสำหรับแต่ละแกน X, Y และ Z ในหน่วยองศา (ไม่ใช่เรเดียน) (อ่านเพิ่มเติมเกี่ยวกับ HTML5Rocks) อย่างไรก็ตาม ค่าการแสดงผลจะแตกต่างกันไปตามชุดค่าผสมของอุปกรณ์และเบราว์เซอร์ที่ใช้ ช่วงของมูลค่าผลตอบแทนจริงจะแสดงอยู่ในตารางด้านล่าง

การวางแนวของอุปกรณ์

ค่าด้านบนที่ไฮไลต์ด้วยสีฟ้าคือค่าที่ระบุไว้ในข้อกำหนด W3C รูปแบบที่ไฮไลต์ด้วยสีเขียวตรงกับข้อกำหนดเหล่านี้ ขณะที่ไฮไลต์ด้วยสีแดงจะแตกต่างออกไป น่าประหลาดใจที่มีเพียงชุดค่าผสม Android-Firefox เท่านั้นที่ส่งคืนค่าที่ตรงกับข้อกำหนดเฉพาะ อย่างไรก็ตาม หากต้องติดตั้งใช้งาน ก็ควรรองรับค่าที่เกิดขึ้นบ่อย World Wide Maze จึงใช้ค่าส่งคืนของ iOS เป็นมาตรฐานและปรับสำหรับอุปกรณ์ Android ให้สอดคล้องกัน

if android and event.gamma > 180 then event.gamma -= 360

อย่างไรก็ตาม เครื่องดังกล่าวยังคงไม่สนับสนุน Nexus 10 แม้ว่า Nexus 10 จะแสดงช่วงของค่าเดียวกับอุปกรณ์ Android อื่นๆ แต่ก็มีข้อบกพร่องที่ทำให้ค่าเบต้าและค่าแกมมากลับกัน ปัญหานี้จะได้รับการตรวจสอบแยกต่างหาก (บางทีอาจมีการตั้งเป็นค่าเริ่มต้นเป็นแนวนอน)

ดังที่แสดงในนี้ แม้ว่า API ที่เกี่ยวข้องกับอุปกรณ์จริงจะมีการตั้งค่าข้อมูลจำเพาะไว้ แต่ก็ไม่ได้เป็นการรับประกันว่าค่าที่แสดงผลจะตรงกับข้อมูลจำเพาะเหล่านั้น ดังนั้น การทดสอบโฆษณาในอุปกรณ์เป้าหมายทั้งหมดจึงเป็นสิ่งสำคัญ นอกจากนี้ยังหมายความว่าอาจมีการป้อนค่าที่ไม่คาดคิด ซึ่งต้องใช้วิธีแก้ปัญหาเฉพาะหน้า World Wide Maze จะแจ้งให้ผู้เล่นเกมเป็นครั้งแรกปรับเทียบอุปกรณ์ตามขั้นตอนที่ 1 ของบทแนะนำ แต่จะไม่สามารถปรับเทียบไปยังตำแหน่งศูนย์ได้ถูกต้องหากกล้องได้รับค่าการเอียงที่ไม่คาดคิด ฟีเจอร์ดังกล่าวจึงมีการจำกัดเวลาภายใน และจะแจ้งให้โปรแกรมเล่นเปลี่ยนไปใช้การควบคุมด้วยแป้นพิมพ์หากปรับเทียบภายในเวลาที่กำหนดไม่ได้

WebSocket

ใน World Wide Maze สมาร์ทโฟนและพีซีของคุณเชื่อมต่อกันผ่าน WebSocket และอุปกรณ์เหล่านี้จะเชื่อมต่อกันผ่านเซิร์ฟเวอร์ส่งต่อระหว่างอุปกรณ์ เช่น จากสมาร์ทโฟนต่อเซิร์ฟเวอร์ไปยัง PC เนื่องจาก WebSocket ไม่มีความสามารถในการเชื่อมต่อเบราว์เซอร์ระหว่างกันโดยตรง (การใช้ช่องทางข้อมูล WebRTC ช่วยให้เชื่อมต่อแบบเพียร์ทูเพียร์ได้โดยไม่ต้องมีรีเลย์เซิร์ฟเวอร์ แต่ในขณะที่ติดตั้งใช้งานวิธีการนี้ ใช้ได้กับ Chrome Canary และ Firefox Nightly เท่านั้น)

ฉันเลือกใช้ไลบรารีชื่อ Socket.IO (v0.9.11) ซึ่งมีฟีเจอร์สำหรับการเชื่อมต่ออีกครั้งในกรณีที่การเชื่อมต่อหมดเวลาหรือขาดการเชื่อมต่อ เราใช้ NodeJS ร่วมกับ NodeJS เนื่องจากชุดค่าผสม NodeJS + Socket.IO นี้แสดงประสิทธิภาพฝั่งเซิร์ฟเวอร์ที่ดีที่สุดในการทดสอบการใช้งาน WebSocket หลายรายการ

การจับคู่ด้วยตัวเลข

  1. คอมพิวเตอร์ของคุณเชื่อมต่อกับเซิร์ฟเวอร์
  2. เซิร์ฟเวอร์จะกำหนดหมายเลขให้ PC ของคุณแบบสุ่มและจำตัวเลขและ PC ผสมกัน
  3. จากอุปกรณ์เคลื่อนที่ของคุณ ระบุหมายเลขและเชื่อมต่อกับเซิร์ฟเวอร์
  4. หากหมายเลขที่ระบุตรงกับหมายเลขที่ได้รับจาก PC ที่เชื่อมต่ออยู่ อุปกรณ์เคลื่อนที่จะจับคู่กับ PC นั้น
  5. เกิดข้อผิดพลาดหากไม่มี PC ที่กำหนดไว้
  6. เมื่อข้อมูลมาจากอุปกรณ์เคลื่อนที่ ระบบจะส่งไปยัง PC ที่มีการจับคู่อุปกรณ์ไว้ และในทางกลับกัน

นอกจากนี้ คุณยังทำการเชื่อมต่อเริ่มต้นจากอุปกรณ์เคลื่อนที่แทนได้ด้วย ในกรณีนี้ อุปกรณ์จะกลับด้านอุปกรณ์

การซิงค์แท็บ

ฟีเจอร์การซิงค์แท็บเฉพาะของ Chrome ช่วยให้ขั้นตอนการจับคู่อุปกรณ์ง่ายดายยิ่งขึ้น หน้านี้ทำให้หน้าเว็บที่เปิดอยู่บน PC สามารถเปิดในอุปกรณ์เคลื่อนที่ได้อย่างง่ายดาย (และในทางกลับกันด้วย) พีซีจะใช้หมายเลขการเชื่อมต่อที่ออกโดยเซิร์ฟเวอร์และต่อท้าย URL ของหน้าเว็บโดยใช้ history.replaceState

history.replaceState(null, null, '/maze/' + connectionNumber)

หากเปิดใช้การซิงค์แท็บไว้ URL จะซิงค์หลังจากผ่านไป 2-3 วินาที และสามารถเปิดหน้าเดียวกันบนอุปกรณ์เคลื่อนที่ได้ อุปกรณ์เคลื่อนที่จะตรวจสอบ URL ของหน้าที่เปิดอยู่ และถ้ามีหมายเลขต่อท้าย ก็จะเริ่มเชื่อมต่อทันที คุณจึงไม่ต้องป้อนหมายเลขด้วยตนเองหรือสแกนคิวอาร์โค้ดด้วยกล้อง

เวลาในการตอบสนอง

เนื่องจากรีเลย์เซิร์ฟเวอร์อยู่ในสหรัฐอเมริกา การเข้าถึงเซิร์ฟเวอร์จากญี่ปุ่นจึงทำให้เกิดความล่าช้าประมาณ 200 มิลลิวินาทีก่อนที่ข้อมูลการเอียงของสมาร์ทโฟนจะไปถึง PC เวลาตอบสนองนั้นช้าลงอย่างชัดเจนเมื่อเทียบกับสภาพแวดล้อมในท้องถิ่นที่ใช้ระหว่างการพัฒนา แต่การแทรกตัวกรองแบบ Low-Pass (ฉันใช้ EMA) ช่วยปรับปรุงให้อยู่ในระดับที่ไม่สร้างความรำคาญ (ในทางปฏิบัติ จำเป็นต้องใช้ตัวกรองค่าความถี่ต่ำเพื่อการนำเสนอด้วย โดยแสดงผลค่าจากเซ็นเซอร์ตรวจจับการเอียงในปริมาณสัญญาณรบกวนที่มากพอ และการใช้ค่าเหล่านั้นกับหน้าจอซึ่งส่งผลให้เกิดการสั่นอย่างมาก) วิธีนี้ไม่ได้ผลกับการข้าม ซึ่งเห็นได้ชัดว่าเล่นช้า แต่แก้ปัญหาไม่ได้

เนื่องจากฉันคาดว่าจะมีปัญหาเกี่ยวกับเวลาในการตอบสนองตั้งแต่ต้น จึงได้พิจารณาการตั้งค่าเซิร์ฟเวอร์การส่งต่อทั่วโลกเพื่อให้ลูกค้าเชื่อมต่อกับเครือข่ายใกล้เคียงที่สุดได้ (ซึ่งช่วยลดเวลาในการตอบสนอง) แต่ฉันตัดสินใจใช้ Google Compute Engine (GCE) ซึ่งมีอยู่ในสหรัฐอเมริกาเท่านั้นในขณะนั้น จึงเป็นไปไม่ได้

ปัญหาอัลกอริทึม Nagle

โดยทั่วไปแล้ว อัลกอริทึมของ Nagle จะรวมไว้ในระบบปฏิบัติการเพื่อให้การสื่อสารมีประสิทธิภาพโดยการบัฟเฟอร์ในระดับ TCP แต่ฉันพบว่าฉันส่งข้อมูลแบบเรียลไทม์ไม่ได้ในขณะที่เปิดใช้อัลกอริทึมนี้อยู่ (โดยเฉพาะอย่างยิ่งเมื่อใช้ร่วมกับการรับทราบที่ล่าช้าเกี่ยวกับ TCF แม้ว่าจะไม่มีความล่าช้าของ ACK แต่ปัญหาเดียวกันก็ยังคงเกิดขึ้นหาก ACK เกิดความล่าช้าเป็นระดับหนึ่งเนื่องจากปัจจัยต่างๆ เช่น เซิร์ฟเวอร์ตั้งอยู่ต่างประเทศ)

ปัญหาเวลาในการตอบสนองของ Nagle ไม่ได้เกิดขึ้นกับ WebSocket ใน Chrome สำหรับ Android ซึ่งรวมถึงตัวเลือก TCP_NODELAY สำหรับการปิดใช้ Nagle แต่เกิดขึ้นกับ WebKit WebSocket ที่ใช้ใน Chrome สำหรับ iOS ซึ่งไม่ได้เปิดใช้ตัวเลือกนี้ (Safari ซึ่งใช้ WebKit เดียวกัน) ก็ประสบปัญหานี้เช่นกัน มีการรายงานปัญหานี้ไปยัง Apple ผ่าน Google และดูเหมือนว่าปัญหาได้รับการแก้ไขแล้วใน WebKit เวอร์ชันพัฒนา

เมื่อปัญหานี้เกิดขึ้น ข้อมูลการเอียงที่ส่งออกไปทุกๆ 100 มิลลิวินาทีจะถูกรวมเป็นส่วนๆ ซึ่งส่งไปถึง PC เท่านั้นทุกๆ 500 มิลลิวินาที เกมไม่สามารถทำงานภายใต้เงื่อนไขเหล่านี้ ดังนั้นจึงหลีกเลี่ยงเวลาในการตอบสนองนี้โดยให้ฝั่งเซิร์ฟเวอร์ส่งข้อมูลในช่วงเวลาสั้นๆ (ทุกๆ 50 มิลลิวินาที) ฉันเชื่อว่าการได้รับ ACK ในช่วงเวลาสั้นๆ เป็นการหลอกลวงอัลกอริทึมของ Nagle ให้คิดว่าการส่งข้อมูลออกไปนั้นไม่เป็นไร

อัลกอริทึม Nagle 1

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

อัลกอริทึม Nagle 2

ในทางตรงกันข้าม กราฟนี้แสดงผลลัพธ์ของการใช้เซิร์ฟเวอร์ในสหรัฐอเมริกา แม้ว่าช่วงเอาต์พุตสีเขียวจะคงที่ที่ 100 มิลลิวินาที แต่ช่วงเวลาอินพุตจะผันผวนระหว่างค่าต่ำสุดที่ 0 มิลลิวินาทีถึงสูงสุด 500 มิลลิวินาที ซึ่งหมายความว่า PC จะได้รับข้อมูลเป็นกลุ่ม

ALT_TEXT_HERE

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

เป็นข้อบกพร่อง

แม้ว่าเบราว์เซอร์เริ่มต้นใน Android 4 (ICS) จะมี WebSocket API แต่ก็เชื่อมต่อไม่ได้ ซึ่งส่งผลให้เกิดเหตุการณ์ Socket.IOconnect_failed ภายในหมดเวลา และฝั่งเซิร์ฟเวอร์ไม่สามารถยืนยันการเชื่อมต่อได้เช่นกัน (ฉันไม่ได้ทดสอบปัญหานี้กับ WebSocket เพียงอย่างเดียว ดังนั้นอาจเป็นปัญหา Socket.IO)

เซิร์ฟเวอร์การส่งต่อการปรับขนาด

เนื่องจากบทบาทของรีเลย์เซิร์ฟเวอร์ไม่ยุ่งยากอย่างนั้น การขยายและเพิ่มจำนวนเซิร์ฟเวอร์จึงไม่ใช่เรื่องยาก ตราบใดที่มี PC และอุปกรณ์เคลื่อนที่เครื่องเดียวกันเชื่อมต่อกับเซิร์ฟเวอร์เดียวกันเสมอ

ฟิสิกส์

การเคลื่อนที่ของลูกบอลในเกม (การกลิ้งลงล่าง ชนกับพื้น การชนกับกำแพง การเก็บไอเทม ฯลฯ) ทำได้โดยใช้เกมจำลองฟิสิกส์แบบ 3 มิติ ฉันใช้ Ammo.js ซึ่งเป็นพอร์ตของเครื่องมือฟิสิกส์ Bullet ที่ใช้กันอย่างแพร่หลายไปยัง JavaScript โดยใช้ Emscripten ควบคู่ไปกับ Physijs เพื่อใช้เป็น "Web Worker"

Web Worker

Web Workers คือ API สำหรับการเรียกใช้ JavaScript ในเทรดแยกต่างหาก JavaScript เปิดตัวเป็น Web Worker ที่ทำงานเป็นชุดข้อความแยกจากชุดข้อความที่ตั้งชื่อก่อน จึงสามารถทำงานหนักได้ในขณะที่ทำให้หน้าเว็บตอบสนองอยู่เสมอ Physijs ใช้ Web Workers ได้อย่างมีประสิทธิภาพเพื่อช่วยให้เครื่องมือฟิสิกส์แบบ 3D ที่ใช้แรงมากทำงานได้ราบรื่น World Wide Maze จัดการเครื่องมือฟิสิกส์และการแสดงภาพ WebGL ด้วยอัตราเฟรมที่ต่างกันอย่างสิ้นเชิง ดังนั้นแม้ว่าอัตราเฟรมจะลดลงในเครื่องที่มีสเปกต่ำเนื่องจากการโหลดการแสดงผล WebGL หนัก แต่ตัวเครื่องมือฟิสิกส์เองก็รักษาความละเอียดที่ 60 fps ได้ และไม่กระทบต่อการควบคุมเกม

FPS

รูปภาพนี้แสดงอัตราเฟรมใน Lenovo G570 ช่องด้านบนแสดงอัตราเฟรมของ WebGL (การแสดงภาพ) ส่วนช่องด้านล่างแสดงอัตราเฟรมสำหรับเครื่องมือฟิสิกส์ GPU เป็นชิป Intel HD Graphics 3000 ที่ผสานรวม ดังนั้นอัตราเฟรมการแสดงผลรูปภาพจึงไม่เท่ากับ 60 fps ตามที่คาดไว้ อย่างไรก็ตาม เนื่องจากเครื่องมือฟิสิกส์ได้อัตราเฟรมตามที่คาดหวังไว้ เกมเพลย์จึงไม่ได้แตกต่างจากประสิทธิภาพในเครื่องที่มีสเปคสูง

เนื่องจากเทรดที่มี Web Worker ที่ใช้งานอยู่ไม่มีออบเจ็กต์คอนโซล จึงต้องส่งข้อมูลไปยังเทรดหลักผ่าน postMessage เพื่อสร้างบันทึกการแก้ไขข้อบกพร่อง การใช้ console4Worker จะสร้างออบเจ็กต์คอนโซลที่เทียบเท่ากับออบเจ็กต์ใน Worker ทำให้ขั้นตอนการแก้ไขข้อบกพร่องง่ายขึ้นมาก

Service Worker

Chrome เวอร์ชันล่าสุดอนุญาตให้คุณตั้งค่าเบรกพอยท์เมื่อเปิดใช้ Web Worker ซึ่งมีประโยชน์สำหรับการแก้ไขข้อบกพร่องเช่นกัน โดยไปที่แผง "ผู้ปฏิบัติงาน" ในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์

การแสดง

บางครั้งขั้นตอนที่มีจำนวนรูปหลายเหลี่ยมสูงจะมีรูปหลายเหลี่ยมเกิน 100,000 รูป แต่ประสิทธิภาพจะไม่ลดลงมากนัก แม้ว่าจะสร้างขึ้นเป็น Physijs.ConcaveMesh ทั้งหมด (btBvhTriangleMeshShape ใน Bullet)

ในตอนแรก อัตราเฟรมจะลดลงเนื่องจากจำนวนวัตถุที่ต้องการการตรวจจับการชนเพิ่มขึ้น แต่การลดการประมวลผลที่ไม่จำเป็นใน Physijs มีประสิทธิภาพดีขึ้น การปรับปรุงครั้งนี้ทำมาจากส้อมของ Physijs รุ่นเดิม

วัตถุ Ghost

วัตถุที่ตรวจจับการชนได้ แต่ไม่กระทบต่อการชน จึงไม่มีผลกระทบกับวัตถุอื่นๆ จะเรียกว่า "วัตถุ Ghost" ในหัวข้อย่อย แม้ว่า Physijs จะไม่รองรับวัตถุ Ghost อย่างเป็นทางการ แต่คุณก็สามารถสร้างวัตถุเหล่านั้นได้ด้วยการปรับเปลี่ยน Flag หลังจากสร้าง Physijs.Mesh World Wide Maze ใช้วัตถุ Ghost ในการตรวจจับการชนของสิ่งของและจุดข้อมูล

hit = new Physijs.SphereMesh(geometry, material, 0)
hit._physijs.collision_flags = 1 | 4
scene.add(hit)

สำหรับ collision_flags 1 คือ CF_STATIC_OBJECT และ 4 คือ CF_NO_CONTACT_RESPONSE ลองค้นหาในฟอรัมของหัวข้อย่อย Stack Overflow หรือเอกสารประกอบหัวข้อย่อยเพื่อดูข้อมูลเพิ่มเติม เนื่องจาก Physijs เป็น Wrapper สำหรับ Ammo.js และ Ammo.js นั้นเหมือนกับ Bullet อยู่แล้ว ดังนั้นทุกๆ อย่างที่ทำได้ใน Bullet จะทำใน Physijs ได้เช่นกัน

ปัญหา Firefox 18

การอัปเดต Firefox จากเวอร์ชัน 17 เป็น 18 ได้เปลี่ยนวิธีแลกเปลี่ยนข้อมูลของ Web Workers และ Physijs ก็หยุดทำงาน มีการรายงานปัญหาใน GitHub และได้รับการแก้ไขแล้วหลังจากผ่านไป 2-3 วัน แม้ว่าประสิทธิภาพโอเพนซอร์สนี้จะทำให้ฉันประทับใจ แต่เหตุการณ์นี้ยังทำให้ฉันทราบด้วยว่า World Wide Maze ประกอบด้วยเฟรมเวิร์กโอเพนซอร์สต่างๆ มากมายได้อย่างไร ฉันเขียนบทความนี้โดยหวังว่าจะแสดงความคิดเห็นบางอย่างได้

asm.js

แม้ว่าเรื่องนี้จะไม่เกี่ยวกับ World Wide Maze โดยตรง แต่ Ammo.js ก็รองรับ asm.js ที่เปิดตัวไปเมื่อเร็วๆ นี้ของ Mozilla แล้ว (ไม่ใช่เรื่องน่าแปลกใจเพราะ asm.js นั้นสร้างขึ้นเพื่อเร่ง JavaScript ที่ Emscripten สร้างขึ้น และผู้สร้าง Emscripten ก็เป็นผู้สร้าง Ammo.js ด้วย) หาก Chrome รองรับ asm.js ด้วย การโหลดการประมวลผลของกลไกฟิสิกส์จะลดลงอย่างมาก ความเร็วเพิ่มขึ้นอย่างเห็นได้ชัดเมื่อทดสอบกับ Firefox Nightly บางทีจะเป็นการดีที่สุดหากเขียนส่วนที่ต้องการความเร็วมากขึ้นใน C/C++ แล้วถ่ายโอนไปยัง JavaScript โดยใช้ Emscripten

WebGL

สำหรับการติดตั้งใช้งาน WebGL ฉันใช้ไลบรารีที่พัฒนามากที่สุดอย่าง three.js (r53) แม้ว่าการแก้ไข 57 จะมีการเผยแพร่ไปแล้วในขั้นตอนหลังๆ ของการพัฒนา แต่ก็มีการเปลี่ยนแปลงสำคัญๆ กับ API ดังนั้น ฉันจึงอยู่กับการแก้ไขเดิมสำหรับการเผยแพร่

เอฟเฟกต์เรืองแสง

เอฟเฟกต์เรืองแสงที่เพิ่มลงในแกนหลักของลูกบอลและไอเทมจะมีการใช้เอฟเฟกต์การเรืองแสงที่เรียกว่า "Kawase Method MGF" เวอร์ชันธรรมดา ในขณะที่วิธีการ Kawase จะทำให้พื้นที่ที่สว่างทั้งหมดเบ่งบาน แต่เขาวงกต World Wide Maze ก็สร้างเป้าหมายในการแสดงภาพแยกต่างหากสำหรับบริเวณที่ต้องแสง เนื่องจากเราต้องใช้ภาพหน้าจอของเว็บไซต์สำหรับพื้นผิวพื้นที่งาน และเพียงแค่แยกพื้นที่ที่สว่างทั้งหมด ก็จะส่งผลให้เว็บไซต์ทั้งเว็บสว่างขึ้น เช่น เมื่อมีพื้นหลังสีขาว ฉันยังคิดจะประมวลผลทุกอย่างในรูปแบบ HDR ด้วย แต่ก็ตัดสินใจไม่เป็นเช่นนั้นเพราะการใช้งานอาจค่อนข้างซับซ้อน

Glow

ส่วนด้านซ้ายบนจะแสดงบัตรแรกที่แสดงพื้นที่เรืองแสงแยกกันแล้วจึงใช้การเบลอ ส่วนด้านขวาล่างจะแสดงบัตรผ่านที่ 2 โดยลดขนาดรูปภาพลง 50% และใช้การเบลอ ส่วนด้านขวาบนจะแสดงบัตรผ่านที่ 3 โดยรูปภาพจะลดลงอีกครั้ง 50% แล้วเบลอ จากนั้นนำทั้ง 3 ภาพมาวางซ้อนเพื่อสร้างรูปภาพผสมขั้นสุดท้ายซึ่งปรากฏที่ด้านซ้ายล่าง สำหรับการเบลอ ฉันใช้ VerticalBlurShader และ HorizontalBlurShader ที่รวมอยู่ใน third.js จึงยังพอมีเวลาสำหรับการเพิ่มประสิทธิภาพต่อไป

ลูกบอลสะท้อนแสง

การสะท้อนบนลูกบอลจะใช้ sample จาก third.js เส้นทางทั้งหมดจะแสดงผลจากตำแหน่งของลูกบอลและใช้เป็นแผนที่สภาพแวดล้อม จำเป็นต้องอัปเดตแผนที่สภาพแวดล้อมทุกครั้งที่ลูกบอลเคลื่อนที่ แต่เนื่องจากการอัปเดตที่ 60 fps ใช้เวลานาน จึงมีการอัปเดตทุกๆ 3 เฟรมแทน ผลที่ได้จะไม่ค่อยราบรื่นเท่าการอัปเดตทุกเฟรม แต่ความแตกต่างนี้แทบจะไม่มีให้เห็นเลย เว้นแต่จะมีการชี้ให้เห็น

ให้เฉดสี ให้เฉดสี...

WebGL ต้องใช้ตัวให้เฉดสี (Verex Shades Shader หรือตัวปรับแสงเงาส่วน) ในการแสดงผลทั้งหมด แม้ว่าโปรแกรมให้เฉดสีที่รวมอยู่ใน third.js จะมีเอฟเฟกต์ที่หลากหลายอยู่แล้ว แต่การเขียนของคุณเองนั้นหลีกเลี่ยงไม่ได้หากมีการลงเฉดสีและการเพิ่มประสิทธิภาพที่ซับซ้อนยิ่งขึ้น เนื่องจาก World Wide Maze ทำให้ CPU ทำงานด้วยระบบฟิสิกส์อยู่ตลอดเวลา ผมจึงพยายามใช้ GPU แทนโดยการเขียนให้มากที่สุดเท่าที่จะเป็นไปได้ด้วยภาษาแรเงา (GLSL) แม้ว่าการประมวลผลของ CPU (ผ่าน JavaScript) จะง่ายขึ้นก็ตาม โดยปกติเอฟเฟกต์คลื่นมหาสมุทรจะอาศัยตัวให้เฉดสี เช่นเดียวกับดอกไม้ไฟที่จุดเป้าหมาย และเอฟเฟกต์ตาข่ายที่ใช้เมื่อลูกบอลปรากฏขึ้น

ลูกบอลเฉดสี

ข้อมูลด้านบนมาจากการทดสอบเอฟเฟกต์ตาข่ายที่ใช้เมื่อลูกบอลปรากฏขึ้น ด้านซ้ายคือรูปที่ใช้ในเกม ซึ่งประกอบด้วยรูปหลายเหลี่ยม 320 รูป รูปตรงกลางใช้รูปหลายเหลี่ยมประมาณ 5,000 รูป และรูปขวาใช้รูปหลายเหลี่ยมประมาณ 300,000 รูป แม้จะมีรูปหลายเหลี่ยมจำนวนมากขนาดนี้ แต่การประมวลผลด้วยตัวปรับแสงเงาจะทำให้อัตราเฟรมคงที่ที่ 30 fps ได้

ตาข่ายกันแดด

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

poly2tri

โดยขั้นตอนจะอิงจากข้อมูลโครงร่างที่ได้รับจากเซิร์ฟเวอร์แล้วสร้างเป็นรูปหลายเหลี่ยมโดย JavaScript การสามเหลี่ยมซึ่งเป็นส่วนสำคัญของกระบวนการนี้มีการติดตั้งใช้งานอย่างไม่เหมาะสมโดย third.js และมักจะล้มเหลว ฉันจึงตัดสินใจผสานรวมไลบรารีสามเหลี่ยมอื่นๆ ที่ชื่อ poly2tri ด้วยตัวเอง ผลที่ได้คือ third.js เคยพยายามทำแบบเดียวกันนี้อย่างเห็นได้ชัด ฉันจึงลองใช้ฟีเจอร์นี้แค่แสดงความคิดเห็นบางส่วน ผลที่ได้คือข้อผิดพลาดลดลงอย่างมาก ทำให้เล่นได้หลายด่านมากขึ้น เกิดข้อผิดพลาดเป็นครั้งคราว และ poly2tri จัดการข้อผิดพลาดโดยการออกการแจ้งเตือนด้วยเหตุผลบางอย่าง ฉันจึงแก้ไขให้มีการใช้ข้อยกเว้นแทน

poly2tri

ภาพด้านบนแสดงให้เห็นว่าเส้นขอบสีน้ำเงินถูกทำเป็นสามเหลี่ยมและรูปหลายเหลี่ยมสีแดงที่สร้างขึ้น

การกรองแบบแอนไอโซทรอปิก

เนื่องจากการแมป MIP แบบไอโซทรอปิกมาตรฐานจะลดขนาดภาพทั้งบนแกนแนวนอนและแนวตั้ง การดูรูปหลายเหลี่ยมจากมุมเอียงจะทำให้พื้นผิวของระยะเขาวงกตเวิลด์ไวด์กว้างดูคล้ายกับพื้นผิวความละเอียดต่ำที่ยืดขยายในแนวนอน ภาพด้านขวาบนในหน้า Wikipedia นี้แสดงตัวอย่างที่ดี ในทางปฏิบัติแล้ว จำเป็นต้องใช้ความละเอียดในแนวนอนมากกว่า ซึ่ง WebGL (OpenGL) จะแก้ปัญหาโดยใช้วิธีการที่เรียกว่าการกรองแบบแอนไอโซทรอปิก ใน third.js การตั้งค่าที่มากกว่า 1 สําหรับ THREE.Texture.anisotropy จะเปิดใช้การกรองแบบแอนไอโซทรอปิก อย่างไรก็ตาม ฟีเจอร์นี้เป็นส่วนขยายและ GPU บางรายการอาจไม่รองรับ

เพิ่มประสิทธิภาพ

ดังที่บทความแนวทางปฏิบัติแนะนำของ WebGL ยังได้กล่าวถึงไว้ว่า วิธีที่สำคัญที่สุดในการปรับปรุงประสิทธิภาพของ WebGL (OpenGL) คือการลดการเรียกใช้ ในช่วงแรกของการพัฒนา World Wide Maze เกม สะพาน และแนวเขาทั้งหมดในเกมเป็นวัตถุแยกกัน ซึ่งบางครั้งก็ส่งผลให้เกิดการเรียกรวมกว่า 2,000 ครั้ง ทำให้ขั้นตอนที่ซับซ้อนเป็นเรื่องยุ่งยาก อย่างไรก็ตาม เมื่อฉันแพ็กวัตถุประเภทเดียวกันทั้งหมดลงในตาข่ายเดียวแล้ว การเรียกใช้การเรียกใช้จะลดลงเหลือประมาณ 50 ครั้ง ซึ่งจะช่วยปรับปรุงประสิทธิภาพได้อย่างมาก

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

การเพิ่มประสิทธิภาพ

ข้อมูลด้านบนคือผลการติดตามจากการสร้างแผนที่สภาพแวดล้อมสำหรับการสะท้อนของลูกบอล การแทรก console.time และ console.timeEnd ลงในตำแหน่งที่ดูจะมีความเกี่ยวข้องใน third.js จะทำให้เรามีกราฟที่มีลักษณะดังนี้ เวลาไหลจากซ้ายไปขวา และแต่ละเลเยอร์เป็นเหมือนการเรียกใช้สแต็ก การฝังคอนโซล.time ภายใน console.time ช่วยให้ทำการวัดเพิ่มเติมได้ กราฟด้านบนคือการเพิ่มประสิทธิภาพก่อนและด้านล่างคือหลังการเพิ่มประสิทธิภาพ ตามที่กราฟด้านบนแสดง ระบบเรียกใช้ updateMatrix (แต่คำจะถูกตัดออก) สำหรับการแสดงแต่ละครั้ง 0-5 ในระหว่างการเพิ่มประสิทธิภาพก่อนการเพิ่มประสิทธิภาพ ฉันแก้ไขเพื่อให้เรียกใช้เพียงครั้งเดียวแล้ว อย่างไรก็ตาม กระบวนการนี้จำเป็นต้องดำเนินการเมื่อวัตถุเปลี่ยนตำแหน่งหรือการวางแนวเท่านั้น

ปกติแล้วกระบวนการติดตามก็ใช้ทรัพยากรมาก ดังนั้นการแทรก console.time มากเกินไปอาจทำให้ประสิทธิภาพตามจริงที่เบี่ยงเบนไปอย่างมาก ทำให้ระบุพื้นที่สำหรับการเพิ่มประสิทธิภาพได้ยาก

ตัวปรับประสิทธิภาพ

ด้วยลักษณะของอินเทอร์เน็ต เกมอาจเล่นในระบบที่มีสเปคที่ต่างกันมาก Find Your Way to Oz ที่เปิดตัวไปเมื่อต้นเดือนกุมภาพันธ์ใช้คลาสชื่อ IFLAutomaticPerformanceAdjust ในการปรับขนาดเอฟเฟกต์ต่างๆ ตามความผันผวนของอัตราเฟรม ทำให้การเล่นเป็นไปอย่างราบรื่น World Wide Maze สร้างจากคลาส IFLAutomaticPerformanceAdjust เดียวกันและปรับขนาดเอฟเฟกต์กลับตามมาเพื่อให้เกมเพลย์เป็นไปอย่างราบรื่นมากที่สุด

  1. หากอัตราเฟรมต่ำกว่า 45 fps แผนที่สภาพแวดล้อมจะหยุดอัปเดต
  2. หากยังคงต่ำกว่า 40 fps ความละเอียดในการแสดงผลจะลดลงเหลือ 70% (50% ของอัตราส่วนพื้นผิว)
  3. หากยังคงต่ำกว่า 40 fps จะไม่มีการกำจัด FXAA (การป้องกันรอยหยัก)
  4. หากวิดีโอยังคงต่ำกว่า 30 fps เอฟเฟ็กต์การเรืองแสงจะถูกลบออก

หน่วยความจำรั่วไหล

การขจัดวัตถุอย่างเป็นระเบียบเป็นเรื่องยุ่งยากด้วย third.js แต่การปล่อยทิ้งไว้เฉยๆ จะทำให้หน่วยความจำรั่วไหลอย่างเห็นได้ชัด ฉันจึงคิดวิธีการตามด้านล่างนี้ @renderer อ้างถึง THREE.WebGLRenderer (การแก้ไขล่าสุดของ third.js ใช้วิธีการ Deallocation ที่แตกต่างกันเล็กน้อย ดังนั้นวิธีนี้อาจจะไม่ทํางานอย่างที่เป็นอยู่)

destructObjects: (object) =>
  switch true
    when object instanceof THREE.Object3D
      @destructObjects(child) for child in object.children
      object.parent?.remove(object)
      object.deallocate()
      object.geometry?.deallocate()
      @renderer.deallocateObject(object)
      object.destruct?(this)

    when object instanceof THREE.Material
      object.deallocate()
      @renderer.deallocateMaterial(object)

    when object instanceof THREE.Texture
      object.deallocate()
      @renderer.deallocateTexture(object)

    when object instanceof THREE.EffectComposer
      @destructObjects(object.copyPass.material)
      object.passes.forEach (pass) =>
        @destructObjects(pass.material) if pass.material
        @renderer.deallocateRenderTarget(pass.renderTarget) if pass.renderTarget
        @renderer.deallocateRenderTarget(pass.renderTarget1) if pass.renderTarget1
        @renderer.deallocateRenderTarget(pass.renderTarget2) if pass.renderTarget2

HTML

โดยส่วนตัวแล้ว ผมคิดว่าสิ่งที่ดีที่สุดเกี่ยวกับแอป WebGL คือความสามารถในการออกแบบเค้าโครงหน้าเว็บในแบบ HTML การสร้างอินเทอร์เฟซ 2 มิติ เช่น คะแนนหรือการแสดงข้อความใน Flash หรือ openFrameworks (OpenGL) เป็นเรื่องยุ่งยาก อย่างน้อย Flash จะมี IDE แต่ openFrameworks ได้ยากหากคุณไม่คุ้นเคย (การใช้ Cocos2D อาจทำให้ใช้งานง่ายกว่า) ในทางกลับกัน HTML จะช่วยให้ควบคุมการออกแบบฟรอนท์เอนด์ทุกด้านด้วย CSS ได้อย่างแม่นยำ เช่นเดียวกับตอนที่สร้างเว็บไซต์ แม้ว่าเอฟเฟกต์ที่ซับซ้อน เช่น อนุภาคที่ย่อตัวเป็นโลโก้จะเป็นไปไม่ได้ แต่เอฟเฟกต์ 3 มิติบางอย่างที่มีความสามารถของ CSS Transforms ก็เป็นไปได้ เอฟเฟกต์ข้อความ "เป้าหมาย" และ "TIME IS UP" ของ World Wide Maze เป็นภาพเคลื่อนไหวโดยใช้การปรับขนาดในการเปลี่ยน CSS (ใช้กับการเปลี่ยนผ่าน) (แน่นอนว่าการไล่ระดับพื้นหลังใช้ WebGL)

แต่ละหน้าในเกม (ชื่อ, RESULT, การจัดอันดับ ฯลฯ) มีไฟล์ HTML ของตัวเอง และเมื่อโหลดเหล่านี้เป็นเทมเพลตแล้ว ระบบจะเรียกใช้ $(document.body).append() ด้วยค่าที่เหมาะสมในเวลาที่เหมาะสม เกิดข้อขัดข้อง 1 ครั้งเนื่องจากไม่สามารถตั้งค่าเหตุการณ์เมาส์และแป้นพิมพ์ก่อนที่จะต่อท้ายได้ การพยายาม el.click (e) -> console.log(e) ก่อนต่อท้ายจึงไม่สำเร็จ

การทำให้เป็นสากล (i18n)

การทำงานในรูปแบบ HTML ก็สะดวกในการสร้างเวอร์ชันภาษาอังกฤษเช่นกัน ฉันเลือกใช้ i18next ซึ่งเป็นไลบรารีเว็บ i18n สำหรับความต้องการในการปรับให้เป็นสากล ซึ่งฉันนำมาใช้ได้ตามที่เป็นอยู่โดยไม่ต้องดัดแปลง

การแก้ไขและแปลข้อความในเกมทำได้ในสเปรดชีตของ Google เอกสาร เนื่องจาก i18next ต้องใช้ไฟล์ JSON ฉันจึงส่งออกสเปรดชีตไปยัง TSV แล้วแปลงไฟล์เหล่านั้นด้วยตัวแปลงที่กำหนดเอง ฉันได้ทำการอัปเดตหลายอย่างก่อนที่จะเปิดตัว ดังนั้นการทำให้กระบวนการส่งออกจากสเปรดชีตของ Google เอกสารเป็นแบบอัตโนมัติจะทำให้สิ่งต่างๆ ง่ายขึ้นมาก

ฟีเจอร์การแปลอัตโนมัติของ Chrome ก็ทำงานได้ตามปกติเช่นกันเนื่องจากหน้าเว็บสร้างด้วย HTML แต่บางครั้งอาจตรวจจับภาษาได้ไม่ถูกต้อง แต่กลับเข้าใจภาษาผิดๆ ไปโดยสิ้นเชิง (เช่น เวียดนาม) ดังนั้นฟีเจอร์นี้จึงปิดใช้อยู่ในขณะนี้ (ปิดใช้ได้โดยใช้เมตาแท็ก)

RequireJS

ฉันเลือก RequireJS เป็นระบบโมดูล JavaScript ของฉัน ซอร์สโค้ด 10,000 บรรทัดของเกมแบ่งออกเป็นคลาสประมาณ 60 คลาส (= ไฟล์กาแฟ) และคอมไพล์เป็นไฟล์ js แต่ละไฟล์ requiredJS โหลดแต่ละไฟล์ตามลำดับที่เหมาะสมตามทรัพยากร Dependency

define ->
  class Hoge
    hogeMethod: ->

คลาสที่กำหนดด้านบน (hoge.coffee) สามารถใช้ดังนี้

define ['hoge'], (Hoge) ->
  class Moge
    constructor: ->
      @hoge = new Hoge()
      @hoge.hogeMethod()

หากต้องการทำงาน จะต้องโหลด hoge.js ก่อน moge.js และเนื่องจาก "hoge" ถูกระบุเป็นอาร์กิวเมนต์แรกของ "define" hoge.js จะถูกโหลดก่อนเสมอ (ถูกเรียกกลับเมื่อ hoge.js โหลดเสร็จแล้ว) กลไกนี้เรียกว่า AMD และไลบรารีของบุคคลที่สามสามารถใช้กับการเรียกกลับประเภทเดียวกันได้ ตราบใดที่ไลบรารีรองรับ AMD แม้แต่แท็กที่ไม่ (เช่น third.js) ก็จะทำงานใกล้เคียงกันตราบใดที่มีการระบุ dependencies ไว้ล่วงหน้า

ขั้นตอนนี้คล้ายกับการนำเข้า AS3 ดังนั้นจึงไม่น่าจะแปลกอะไร หากสุดท้ายแล้วคุณจะต้องมีไฟล์ที่สัมพันธ์กันมากขึ้น วิธีนี้ก็ช่วยแก้ปัญหาได้

r.js

requiredJS ประกอบด้วยเครื่องมือเพิ่มประสิทธิภาพที่เรียกว่า r.js ซึ่งจะเป็นการรวมไฟล์ js หลักที่มีไฟล์ js ที่เกี่ยวข้องทั้งหมดไว้ในไฟล์เดียว จากนั้นลดขนาดโดยใช้ UglifyJS (หรือ Closure Compiler) ซึ่งจะลดจำนวนไฟล์และปริมาณข้อมูลทั้งหมดที่เบราว์เซอร์ต้องโหลด ขนาดไฟล์ JavaScript รวมสำหรับ World Wide Maze คือประมาณ 2 MB และสามารถลดเหลือประมาณ 1 MB ด้วยการเพิ่มประสิทธิภาพ r.js หากเผยแพร่เกมโดยใช้ gzip ได้ จำนวนนี้จะลดลงเหลือ 250 KB (GAE มีปัญหาที่ไม่อนุญาตให้ส่งไฟล์ gzip ที่มีขนาด 1 MB ขึ้นไป ระบบจึงกระจายเกมโดยไม่มีการบีบอัดเป็นข้อความธรรมดาขนาด 1 MB)

เครื่องมือสร้างขั้นตอน

ข้อมูลระยะจะสร้างขึ้นดังต่อไปนี้ โดยดำเนินการทั้งหมดบนเซิร์ฟเวอร์ GCE ในสหรัฐอเมริกา

  1. ระบบจะส่ง URL ของเว็บไซต์ที่จะแปลงเป็นขั้นตอนผ่าน WebSocket
  2. PhantomJS ถ่ายภาพหน้าจอ และดึงข้อมูลตำแหน่งแท็ก div และ img และแสดงผลในรูปแบบ JSON
  3. โปรแกรม C++ ที่กำหนดเอง (OpenCV, Boost) อ้างอิงจากภาพหน้าจอจากขั้นตอนที่ 2 และข้อมูลการกำหนดตำแหน่งขององค์ประกอบ HTML จะลบพื้นที่ที่ไม่จำเป็น สร้างเกาะ เชื่อมต่อเกาะกับสะพาน คำนวณแนวป้องกันและตำแหน่งสิ่งของ กำหนดจุดเป้าหมาย ฯลฯ ผลลัพธ์จะแสดงเป็นรูปแบบ JSON และกลับไปยังเบราว์เซอร์

PhantomJS

PhantomJS เป็นเบราว์เซอร์ที่ไม่ต้องใช้หน้าจอ โหลดหน้าเว็บได้โดยไม่ต้องเปิดหน้าต่าง ดังนั้นจึงใช้ในการทดสอบอัตโนมัติหรือจับภาพหน้าจอทางฝั่งเซิร์ฟเวอร์ได้ เครื่องมือเบราว์เซอร์คือ WebKit ซึ่งเป็นเครื่องมือเดียวกับที่ Chrome และ Safari ใช้ ดังนั้น การจัดวางและผลลัพธ์จากการทำงานของ JavaScript จึงมีความคล้ายคลึงกับเบราว์เซอร์มาตรฐาน

ด้วย PhantomJS จะมีการใช้ JavaScript หรือ CoffeeScript เพื่อเขียนกระบวนการที่คุณต้องการดำเนินการ การจับภาพหน้าจอทำได้ง่ายมากตามที่แสดงในตัวอย่างนี้ ฉันทำงานบนเซิร์ฟเวอร์ Linux (CentOS) ฉันจึงต้องติดตั้งแบบอักษรให้แสดงภาษาญี่ปุ่น (M+ FONTS) แต่ถึงอย่างนั้น การแสดงแบบอักษรจะมีการจัดการต่างจากใน Windows หรือ Mac OS ดังนั้น แบบอักษรเดียวกันอาจดูต่างกันในเครื่องอื่นๆ (แต่ต่างกันเพียงเล็กน้อยเท่านั้น)

การดึงข้อมูลตำแหน่งแท็ก img และ div นั้นจัดการด้วยวิธีเดียวกับในหน้ามาตรฐาน และยังใช้ jQuery ได้โดยไม่มีปัญหาอีกด้วย

stage_builder

ตอนแรกผมคิดว่าจะใช้ DOM มากกว่าเพื่อสร้างระยะ (คล้ายกับ Firefox เครื่องมือตรวจสอบ 3 มิติ) และพยายามทำบางอย่าง เช่น การวิเคราะห์ DOM ใน PhantomJS แต่ในที่สุด ผมก็ตัดสินใจเกี่ยวกับวิธีการประมวลผลรูปภาพ ฉันจึงเขียนโปรแกรม C++ ที่ใช้ OpenCV และ Boost ที่เรียกว่า "stage_builder" โดยมีการดำเนินการต่อไปนี้

  1. โหลดภาพหน้าจอและไฟล์ JSON
  2. แปลงรูปภาพและข้อความเป็น "เกาะ"
  3. สร้างสะพานเพื่อเชื่อมต่อเกาะ
  4. กำจัดสะพานที่ไม่จำเป็นในการสร้างเขาวงกต
  5. วางรายการขนาดใหญ่
  6. วางรายการขนาดเล็ก
  7. วางแนวรั้วป้องกัน
  8. เอาต์พุตข้อมูลการจัดตำแหน่งในรูปแบบ JSON

รายละเอียดของแต่ละขั้นตอนมีดังนี้

กำลังโหลดภาพหน้าจอและไฟล์ JSON

ระบบจะใช้ cv::imread ตามปกติเพื่อโหลดภาพหน้าจอ ฉันทดสอบไลบรารีหลายรายการสำหรับไฟล์ JSON แต่ picojson ดูเหมือนจะทำได้ง่ายที่สุด

การแปลงรูปภาพและข้อความเป็น "เกาะ"

บิลด์ของขั้นตอน

รูปภาพด้านบนเป็นภาพหน้าจอของส่วน News ใน aid-dcc.com (คลิกเพื่อดูขนาดจริง) องค์ประกอบรูปภาพและข้อความต้องแปลงเป็นเกาะ เราควรลบสีพื้นหลังสีขาวออก กล่าวคือ เป็นสีที่แพร่หลายที่สุดในภาพหน้าจอเพื่อแยกส่วนเหล่านี้ออกจากกัน เมื่อดำเนินการเสร็จแล้ว จะมีหน้าตาดังนี้

บิลด์ของขั้นตอน

ส่วนที่เป็นสีขาวคือเกาะที่เป็นไปได้

ข้อความละเอียดและคมชัดเกินไป เราจะทำให้หนาขึ้นด้วย cv::dilate, cv::GaussianBlur และ cv::threshold นอกจากนี้ เนื้อหารูปภาพก็ขาดหายไปด้วย เราจึงจะเติมสีขาวในพื้นที่เหล่านั้นตามการแสดงผลข้อมูลแท็ก img จาก PhantomJS รูปภาพที่ได้จะมีลักษณะดังนี้

บิลด์ของขั้นตอน

ตอนนี้ข้อความจะจับตัวเป็นก้อนกลมๆ ที่เหมาะสม และรูปภาพแต่ละรูปเกาะที่เหมาะสม

การสร้างสะพานเพื่อเชื่อมต่อเกาะ

เมื่อเกาะพร้อมแล้ว ก็จะมีสะพานเชื่อมเข้าด้วยกัน แต่ละเกาะจะมองหาเกาะที่อยู่ติดกัน ทางซ้าย ขวา ด้านบน และด้านล่าง แล้วเชื่อมสะพานไปยังจุดที่ใกล้ที่สุดของเกาะที่ใกล้ที่สุด ซึ่งผลที่ได้จะมีลักษณะดังต่อไปนี้

บิลด์ของขั้นตอน

ขจัดสะพานที่ไม่จำเป็นในการสร้างเขาวงกต

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

บิลด์ของขั้นตอน

การวางรายการขนาดใหญ่

แต่ละเกาะจะวางไอเทมขนาดใหญ่อย่างน้อย 1 ชิ้นตามขนาดของเกาะ โดยเลือกจากจุดที่อยู่ห่างจากขอบของเกาะมากที่สุด แม้ไม่ชัดเจน แต่จุดเหล่านี้จะแสดงเป็นสีแดงด้านล่าง

บิลด์ของขั้นตอน

จากจุดที่เป็นไปได้ทั้งหมด จุดแรกที่ด้านซ้ายบนถูกตั้งเป็นจุดเริ่มต้น (วงกลมสีแดง) จุดด้านขวาล่างเป็นเป้าหมาย (วงกลมสีเขียว) และเลือกได้สูงสุด 6 จุดสำหรับการแสดงรายการขนาดใหญ่ (วงกลมสีม่วง)

วางรายการขนาดเล็ก

บิลด์ของขั้นตอน

จำนวนชิ้นเล็กๆ ที่เหมาะสมจะวางเรียงกันตามเส้นในระยะห่างที่กำหนดไว้จากขอบของเกาะ ภาพด้านบน (ไม่ใช่จาก aid-dcc.com) แสดงเส้นตำแหน่งที่ฉายภาพเป็นสีเทา โดยมีระยะห่างจากขอบของเกาะอย่างสม่ำเสมอ จุดสีแดงบ่งบอกถึงตำแหน่งของรายการขนาดเล็ก เนื่องจากภาพนี้มาจากเวอร์ชันที่อยู่ระหว่างการพัฒนา รายการต่างๆ จะจัดวางเป็นเส้นตรง แต่เวอร์ชันสุดท้ายจะกระจายรายการต่างๆ ที่ผิดไปจากปกติเล็กน้อยที่ด้านใดด้านหนึ่งของเส้นสีเทา

การวางราวกันตก

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

บิลด์ของขั้นตอน

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

เอาต์พุตข้อมูลการจัดตำแหน่งในรูปแบบ JSON

ฉันใช้ picojson สำหรับเอาต์พุตด้วย การเขียนข้อมูลลงในเอาต์พุตมาตรฐานซึ่งผู้โทรจะได้รับ (Node.js)

การสร้างโปรแกรม C++ ใน Mac เพื่อให้ทำงานใน Linux

เกมนี้ได้รับการพัฒนาขึ้นบน Mac และติดตั้งใช้งานใน Linux แต่เนื่องจาก OpenCV และ Boost มีอยู่ในระบบปฏิบัติการทั้ง 2 ระบบ การพัฒนาเกมจึงไม่ใช่เรื่องยากหลังจากสร้างสภาพแวดล้อมแบบคอมไพล์ขึ้นแล้ว ฉันใช้เครื่องมือบรรทัดคำสั่งใน Xcode เพื่อแก้ไขข้อบกพร่องของบิลด์ใน Mac จากนั้นสร้างไฟล์กำหนดค่าโดยใช้ automake/autoconf เพื่อให้สามารถคอมไพล์บิลด์ใน Linux ได้ จากนั้นฉันก็ต้องใช้ "กำหนดค่า && สร้าง" ใน Linux เพื่อสร้างไฟล์ปฏิบัติการ ฉันพบข้อบกพร่องของ Linux โดยเฉพาะเนื่องจากความแตกต่างของเวอร์ชันของคอมไพเลอร์ แต่แก้ไขปัญหาได้ค่อนข้างง่ายโดยใช้ gdb

บทสรุป

เกมลักษณะนี้สามารถสร้างขึ้นด้วย Flash หรือ Unity ได้ ซึ่งจะก่อให้เกิดประโยชน์มากมาย อย่างไรก็ตาม เวอร์ชันนี้ไม่จำเป็นต้องใช้ปลั๊กอิน และคุณลักษณะการจัดวางของ HTML5 + CSS3 ก็ได้รับการพิสูจน์แล้วว่ามีประสิทธิภาพอย่างยิ่ง การมีเครื่องมือที่เหมาะสมสำหรับแต่ละงานเป็นสิ่งสำคัญอย่างยิ่ง โดยส่วนตัวแล้วผมแปลกใจที่เกมเวอร์ชันหนึ่งทำออกมาได้เป็น HTML5 ได้ดีเพียงใด และแม้ว่าตอนนี้จะยังขาดเกมนี้ในหลายๆ ด้าน แต่ผมหวังว่าจะได้เห็นพัฒนาการในอนาคต