[Blog] ความพยายามขั้นสุดของการพยายามใช้ WebSocket บน Unity WebGL (อ๊าาาากซ์~)
หากท่านทั้งหลายมาจากสายการพัฒนาเว็บไซต์ เชื่อว่า ทุกท่านคงรู้จักกับ WebSocket เป็นอย่างดี แต่กับ เขียนเกมกับ Unity มันหนังคนละม้วนจริง ๆ TT-TT (ผมอาจจะอ่อนไปเองด้วยครับ ~_~)
มาเริ่มกัน Blog นี้จะเล่าทั้งความทรมาน และ Gems ต่าง ๆ ที่ผมพบมาตลอดทาง เกริ่นกันก่อน ผมมี Project พิเศษที่ได้รับมาให้พัฒนาโปรแกรม ๆ หนึ่ง โดยมีองค์ประกอบต่าง ๆ คือ
1. RFID Reader แบบ UHF Long-Range
2. Single-Board Computer (ARMv8, RAM 4 GB, 1.5GHz)
ไอ้เราก็ชอบ Unity ในระดับคลั่งไคล้ซะด้วย ก็เลยตัดสินใจใช้ Client เป็นที่พัฒนามาจาก Unity
Plan A
แผน A ผมตั้งใจจะ Build ด้วย Native Linux Platform หลังจากทำมาถึงจุดหนึ่ง ก็เลยเริ่มลอง Proof of Concept ดูว่ามันจะเวิร์คไหม
ผลลัพท์ ไม่เวิร์คจร้าาาาาาาาาา~
เหตุผล คือ Unity แบบ Personal License "Not Support Embedded System" คร้าบบบบบบบ ต้องซึ้อ Industrial License เท่านั้น ถึงจะ Build ได้ ฮื่อออออ~ ผมไม่ได้ใช้งานขนาดนั้นนะ Unity~
เพราะ Single Board Computer ที่ผมใช้เป็น ARM ดังนั้น Unity จึงมองแยกออกจาก Linux ปกติธรรมดา แต่ถ้าเป็น Board Intel อย่าง Intel Atom ก็ไม่มีปัญหาอะไรครับ จุดนี้ทำให้ผมต้องทบทวนเรื่องตู้เกม Arcade ที่ผมอยากจะทำใหม่แล้วล่ะครับ (คนละ Project กันนะครับ)
สามารถดูรายละเอียด Feature ลับนี้เพิ่มเติมได้นะครับ
https://docs.unity.com/embeddedlinux/en/manual/Getting%20Started
ในฐานะ Developer เราจะหยุดแค่นี้ไม่ได้แผน B ต้องเข้า
Plan B
งั้นเราไปลุย Android Build กันดีกว่า ในความคุ้นเคย กับ Mobile Game ทางนี้น่าจะเป็นทางรอดของเรา จัดการ Flash SD Card เป็น Android 14 ซะ (LineageOS 21) ทำเสร็จสรรพ ปั๊บ! โยน APK ที่พึ่ง Switch จาก Linux มา Build Android Platform ทำการติดตั้งให้เรียบร้อย รัน!!!
Texture ขาดเป็นริ้ว ๆๆๆ เลยยย TT-TT
สาเหตุคือ ก็ไอ้ Single-Board Computer ที่ผมใช้มันไม่มี GPU (Proved ได้จาก ลองเอาใส่ Android Tablet เครื่องอื่นดู ทำงานได้ปกติดีครับ)
แต่เอาเข้าจริงหาก Solution นี้ทำงานได้ดี ผมจะติด Issue ต่อไป คือ การต้องเขียน Java Plugins เพื่อให้ Unity บน Android สามารถอ่าน Serial Data ผ่าน USB ได้
แผน C ต้องมาแล้ว
Plan C
ตอนนี้ผมเชื่อว่าท่านคงกำลังสงสัยว่าผมจะจบลงที่ตรงไหน 5555 มาลุ้นกันครับ (เผื่อใครอยากเป็น Software Developer หรือ Game Developer เตรียมตัวไว้ให้ดี ^^)
มาถึงแผน C เป็นแผนที่ผมหลีกเลี่ยงมาตั้งแต่แรก เพราะอยากให้ตัว Unity Client อ่าน Serial Data ได้ตรง ๆ เลยไม่ต้องผ่าน WebServer หรือ Components อื่น ๆ
แต่แล้ว Solution สุดท้ายที่ Unity Community แนะนำกันมา สำหรับ Single Board Computer ที่ Low Spec ซะเหลือเกินสำหรับงานด้าน Game หรือ Computer Vision นั่นก็ คือ!!!!!
" WebGL "
คือ ถ้าครั้งนี้ไม่สำเร็จ ก็ต้องไปเขียน WebApp หรือ Desktop App แบบเดิมละครับ
ใช่แหละครับ ยังไงมันก็ WORK! แต่งานที่ผมต้องมาทำเพิ่ม คือ Client - Server Architecture ครับ จำได้ไหมครับว่าผมมี RFID Reader ดังนั้นแปลว่า ผมจะต้องเขียนโปรแกรมตัวหนึ่งขึ้นมาเพื่ออ่าน Data Serial นี้ และอีกส่วนหนึ่ง คือ ตัวกลาง ที่เป็น WebServer ที่จะเชื่อมข้อมูลเข้าด้วยกัน เพราะต้องเอา RFID Tag ไปค้น Database ก่อนที่จะนำเสนอข้อมูลผ่าน Unity Client ครับ
BackEnd Side
เนื่องจากเริ่มเบื่อ Python เวลา Deployment ต้องไป setup environment เยอะแยะเต็มไปหมด (รู้ววววว์ว่ามี Docker) แต่มันมีวิธีที่ดีกว่านั้นครับ
ผมตัดสินใจใช้ภาษา Go เพราะหลังจากที่ผมทำงานเสร็จบน Local Machine ของผม (MacOS) ผมก็แค่ทำการ Cross-Compile ชี้ Target ไปที่ ARMv7 ได้ไฟล์ Binary Executable File มาแล้วก็โยนไป แล้วรันได้เลยยยยย!!! เย้!!!
โอเครสรุป ผมตัดสินใจใช้ Go Lang เข้ามาทำใน 2 ส่วน คือ
1. RFID Reader
2. WebSocket Server
เอ๊ะ! ทำไมใช้ WebSocket ไม่ใช้ RESTful APIs ล่ะ ก้ออออออออออ~
เนื้องานของเรามันควรจะเป็น RealTime Communication ไม่ต้องไป SetTimeOut เพื่อรอว่าข้อมูลมารึยัง...มารึยัง...มารึยัง... รอนาน ๆ มันก็ท้อนะครับ ยิ่งถ้าต้องยิงไปบน Network ก็จะเป็นการสร้าง Cost โดยใช่เหตุ ต่อให้ไม่ Online ก็รู้สึกขัดใจอยู่ดีครับ
จึงตัดสินใจนำ Event-Driven Architecture เข้ามา เอาจริง ๆ ตอนแรกก็แอบมอง WebHook ไว้แต่คิดว่ายังไม่ตอบโจทย์เท่าไหร่ เพราะอย่าลืมว่าเรามี Unity Client เป็นปัจจัยหลัก
ดังนั้น แล้วเราก็เลยเร่ิมลงมือสร้าง Reader และ WebSocket ด้วย Go กัน
เสร็จแล้ววว!!!
พูดตรง ๆ ส่วนนี้ไม่ได้มี Issue อะไรมากเท่าไหร่ในช่วงที่ทำการพัฒนาครับ ปัญหาน่ะเหรอ หลังจากนี้ต่างหาก - _ -
Unity WebGL WebSocket Listener
หลังจากที่ Data ของเราสามารถ Flow ออกมาผ่าน RFID Tag ผ่านออกมาเป็น TID เข้าสู่ WebSocket ของเราได้แล้ว Step ต่อไปของเรา คือ การ Boradcast ข้อมูลของเราออกไปสู่ Client ที่ได้ทำการ Listening อยู่
สำหรับการเขียน Connection บน Unity ผมเลือกใช้ System.Net.WebSockets ซึ่งเป็น Standard .Net Framework 4.x ที่ดูน่าจะเข้าที่ เข้าทาง ใช่ครับมันเวิร์คจริง ๆ ทำงานได้ดี Flow ทุกอย่าง ดูสวยงามดี จนผมแอบคิดไปว่า Project นี้น่าจะเสร็จก่อนเวลาที่คาดเอาไว้มาก ^^
เพราะหลังจากที่ทำการ Build เป็น WebGL แล้ว มันไม่เวิร์คคคคคคคค!!!!
System.Net.WebSocket มันไม่สามารถทำงานได้ตรง ๆ บน WebGL ซึ่งถ้าเป็น Platform อื่นสิ่งน้ีจะไม่มีปัญหาเลยครับ เพราะ WebGL ต้องใช้ JS WebSocket เท่านั้นครับ TT-TT
(คือ ตอนที่ผมทำจริงมันหาสาเหตุ หลายอย่างมาก ๆ นะครับ Test แล้ว Test อีก หาปมปัญหา หายังไงก็หาไม่เจอ ณ จุด ๆ นี้ AI ก็ยังไม่สามารถช่วยเราได้ดีขนาดนั้นนะครับ ต้องขอขอบคุณ Unity Community จริง ๆ ครับ เป็นการแสดงเบาะแส ต่าง ๆ ออกมาให้ผมได้เห็นเรื่อย ๆ)
มันท้อนะ เจอแบบนี้เข้าไป แต่ "ท้อ มีเอาไว้ให้ลิงถือเท่านั้นครับ"
ลุยกันต่อ สุดท้ายแล้วเราก็พบว่าเราต้องเขียน JS Plugin เข้าไปเพิ่มเติมเพื่อทำการสื่อสารข้อมูลผ่าน JS WebSocket แทน คือ ผมก็ลองผิด ลองถูกเขียน plugin อยู่นาน จนยอมแพ้ครับ
แต่แล้วผมก็ได้ Search จนไปพบเข้ากับ ผู้ที่มีปัญหาเดียวกันกับผม
(Unity แนะนำว่าลองไปซื้อ WebSocket Assets บน Assets Store ดูสิมีขายนะ 555+)
แต่ Endel Dreyer, ชาวบราซิล ก็ได้ช่วยชีวิตผมเอาไว้
เพราะ เขาได้สร้าง Native WebSockets ไว้ให้พวกเราใช้ได้ฟรี ๆ บน Github TT-TT น้ำตาต้องไหลแล้วครับวินาทีนี้
ที่สำคัญเลย คือ Native WebSocket สร้าง On top บน System.Net.WebSocket ทำให้เราไม่ต้องลง .DLL อะไรเพิ่มเติมเลย และตัวนี้มันใช้ได้กับทุก Target Platform ด้วยนะครับ (เขาว่าอย่างนั้น ผมยังไม่ได้ลองนะ)
ติดตั้งผ่าน Package Manager ด้วย Github URL แค่นั้นครับ เราก็สามารถใช้งานได้แล้ว
ทำการทดสอบบน Unity Editor ไม่มีปัญหา อันนี้ยังวางใจไม่ได้
Build and Run ด้วย WebGL แล้ว Test บน Local Machine ของผม
"ไม่มีปัญหา" จุดนี้เริ่มตาโต เพราะก่อนหน้านี้ Step นี้ยังไม่ผ่านเลย แต่!!! ขั้นตอนสุดท้ายยังไม่เสร็จ ก็จะยังวางหทัยไม่ได้
SCP ไปยังเครื่องเป้าหมายของเราผ่าน IP Address
ทำการ Run WebSocket ขึ้นมา และ Run Unity WebGL Client ของเราขึ้น
รอโหลด ๆๆๆ............................................
"เฮ้ยยยยยยย! เวิร์คแล้ววววววววว"
55555 และเรื่องราวทั้งหมด พร้อมวิธีการแก้ไขก็ขอจบลงแต่เพียงเท่านี้นะครับ ไว้มีประสบการณ์น่าระทึกใจ จะหยิบมาเล่าให้ฟังอีกบ่อย ๆ นะครับ