1 คะแนน โดย GN⁺ 2023-09-18 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ‘Caves of Qud’ กำลังทำการพิสูจน์เชิงเทคนิคเพื่อลดการพึ่งพา Unity ที่มีมายาวนาน และ build/boot แกนเกมบน Godot โดยเป้าหมายแรกคือให้รันได้ในโหมด ASCII+tile โดยยังไม่มี VFX หรือ UI สมัยใหม่
  • งานถูกแบ่งเป็นการนำเข้า tile assets, การพอร์ต core C# assemblies และการจัดชุด rendering/input rig โดยไฟล์ BMP tile เก่า ๆ ถูกแปลงเป็น PNG แล้วโหลดเข้า Godot
  • ข้อผิดพลาด 5,641 รายการ ใน build แรกเริ่มลดลงเมื่อย้าย GeneratedCode, ConsoleLib, Genkit, Language, HistoryKit, Newtonsoft JSON, CodeDom ฯลฯ และค่อย ๆ จำกัดพื้นผิวการพึ่งพา UnityEngine ด้วย stub
  • จุดที่ใช้ Color, GameObject, AudioSource, Debug.Log และ Screen ของ Unity ถูกจัดการด้วย UnityEngineReplacer และคลาสทดแทน ส่วนเลเยอร์ chargen/presentation ที่ใกล้กับ PlayFab และ Unity UI ถูกถอดออกชั่วคราว หรือเก็บไว้เป็นตัวเลือกสำหรับแยกเป็น glue module
  • ผลลัพธ์คือแกนเกม C# ประมาณ 500,000 บรรทัด boot บน Godot ได้จนถึงขั้นส่ง frame และรอ input แล้ว แต่ยังเหลืองาน renderer/input harness รวมถึงการ rig VFX, sound และ UI

ขอบเขตงานย้ายจาก Unity ไป Godot

  • งานพอร์ตแบ่งออกเป็นสามสาย
    • การนำเข้า assets: งานนำ tile assets เข้าไปในโปรเจกต์ Godot
    • การพอร์ต core assemblies: งานย้าย XRL Application ซึ่งเป็น main game engine ที่ไม่ใช่ Unity และ GameManager ฝั่ง engine
    • การจัด rendering rig: งานเชื่อมต่อการแสดงผลบนหน้าจอและ input หลังจาก core boot แล้ว
  • สร้างโปรเจกต์ Godot “mobile” แล้วคัดลอก texture content ของ Qud ไปไว้ในโฟลเดอร์ root ของโปรเจกต์
  • เนื่องจาก Godot จัดการไฟล์ .bmp เก่าไม่ได้ จึงแปลงไฟล์ต่าง ๆ รวมถึง ASCII tiles เป็น PNG แบบ batch
  • หลังแปลงแล้ว assets โหลดได้ แต่ใช้เวลาประมาณ 30 วินาที กว่าตัวแสดงความคืบหน้าการนำเข้าจะปรากฏ

ย้าย core assembly พร้อมลด build errors

  • หลังคัดลอก XRL Application แล้ว ได้สร้าง C# project และ solution ใน Godot และในสถานะเริ่มต้นเกิดข้อผิดพลาด 5,641 รายการ
  • ไลบรารีทั่วไปอย่าง ConsoleLib และ Genkit ถูกย้ายมาทั้งชุด ส่วน Kobold ซึ่งเป็นโซลูชัน sprite/atlasing เก่า จะนำเข้าทีละไฟล์โดยตรวจสอบว่าจำเป็นหรือไม่
  • KoboldJSON เป็นไลบรารี JSON serialization แบบง่าย จึงย้ายมาตรง ๆ
  • เมื่อแทนที่การใช้ Color ทั่วทั้งโปรเจกต์ 85 จุด ข้อผิดพลาดลดลงเหลือ 4,700 รายการ
  • เมื่อเพิ่มโฟลเดอร์ GeneratedCode ที่ขาดอยู่ ผลลัพธ์จากการ generate โค้ดคลาสแยกตาม event ก็ถูกรวมเข้ามา และข้อผิดพลาดลดลงเหลือ 1,557 รายการ
    • Caves of Qud มีโครงสร้างที่ generate คลาสแยกตาม event ซึ่งมี boilerplate จำนวนมาก เพื่อให้ได้ประสิทธิภาพและ event surface ที่ strongly typed

จัดการ dependency ของ Unity และ glue layer

  • Embark Builder และ chargen สำหรับสร้างตัวละครมีโค้ด Unity UI จำนวนมาก จึงถอดออกจากการพิสูจน์เชิงเทคนิค ASCII ระยะแรก
    • ยังไม่มี console version แต่การตรวจสอบเบื้องต้นสามารถทดสอบด้วยการโหลด save game หรือเริ่มแบบสุ่มได้
  • โฟลเดอร์ Game ส่วนใหญ่เป็น glue ระหว่าง Unity กับเกม แต่ก็มีโฟลเดอร์ที่ไม่ใช่ glue อย่าง CodeGeneration ด้วย จึงเลือกแนวทางย้ายไปไว้ที่ root แยกต่างหากหรือโฟลเดอร์ Platform
  • เมื่อย้ายไลบรารี Language และ HistoryKit ข้อผิดพลาดลดลงเหลือ 454 รายการ และการที่มีการแยก game/glue ไว้บ้างแล้ว แม้ไม่สมบูรณ์ ก็ช่วยได้มาก
  • หมวดข้อผิดพลาดขนาดใหญ่ที่ยังเหลือคือ CodeDom/Roslyn, PlayFab, Harmony, ปัญหาบางส่วนเกี่ยวกับ compiler และ พื้นผิว UnityEngine ที่รั่วเข้ามาในเลเยอร์เกม
  • โค้ดที่เกี่ยวกับ PlayFab ถูก comment out ชั่วคราว และเก็บไว้เป็นตัวเลือกสำหรับยกขึ้นเป็น glue module

Stub ทดแทน Unity API และความแตกต่างของ Godot C#

  • Color32 เป็นชนิดสีที่อิง byte จึงเขียน implementation ทดแทน ได้อย่างรวดเร็ว
  • สร้างโฟลเดอร์ UnityEngineReplacer แล้ว implement เฉพาะพื้นผิวที่จำเป็นโดยดูจาก compile errors โดยไม่อ้างอิงเอกสาร Unity หรือ code reference
  • เมื่อสร้าง stub สำหรับ GameObject ข้อผิดพลาดแบบ “ไม่รู้จัก GameObject” ก็เปลี่ยนเป็นข้อผิดพลาดของ field และ method ที่จำเป็นจริง ๆ ทำให้เข้าใจ interface surface ที่เกมใช้งานได้
  • AudioSource ก็เพิ่ม member ที่ถูกเข้าถึงทีละตัวจาก error list จนได้ port interface ของ AudioSource ครบตามที่เกมใช้งานจริง
  • พื้นผิวอื่น ๆ ที่จัดการมีดังนี้
    • shim ทดแทน Debug.Log
    • implementation ทดแทน Screen
    • พอร์ตไลบรารี extension method IsNullOrEmpty
    • จัดการ reference ของ Math และความต่างระหว่าง PI/Pi
    • จัดการความต่างของชื่อพิกัดตัวพิมพ์ใหญ่ X, Y ใน Godot
    • แก้ปัญหา IDE import System.Drawing.Color, System.Numerics.Vector3 ผิด
  • Newtonsoft JSON แก้ได้โดยเพิ่ม NuGet package ใน Visual Studio และ CodeDom ก็แก้ด้วย NuGet package CodeDom

จน boot ทั้งหมดบน Godot ได้

  • เมื่อข้อผิดพลาดลดลงเหลือ 11 รายการ ปัญหาที่ยังเหลือคือโค้ดเกี่ยวกับ dynamic C# compilation ของ mod management ซึ่งไม่จำเป็นแต่ซับซ้อน จึงเหลือไว้จนท้ายสุด
  • จากนั้นจัดการขั้นตอน link และข้อผิดพลาดของ stub fields จนโค้ดประมาณ 500,000 บรรทัดของ C# build ได้
  • ขั้นถัดไปคือสร้าง renderer ขนาดเล็กและ input harness เพื่อ boot core assembly โดยมีเป้าหมายให้เกมรันได้ในโหมด ASCII+tile
  • สร้าง scene เปล่าใน Godot แล้วแนบ GameManager.cs กับ node พื้นฐาน โดย GameManager ต้องสืบทอดจาก Node และต้องใช้ partial code
  • ใช้ _Ready ของ Godot ในบทบาทที่สอดคล้องกับ Awake ของ Unity และ _Process ในบทบาทที่สอดคล้องกับ Update ของ Unity
  • ระหว่างการ initialize ได้ย้าย load path และ mod management และไล่ติดตามปัญหาที่ type resolver หา type จากชื่อไม่เจอด้วยวิธีแบบ printf
  • สาเหตุคือการจัดการ dynamic assembly
    • mod assembly เป็น dynamic จึงถูกยกเว้น dynamic assembly ออกจากการตรวจ main assembly
    • ใน Godot editor แม้แต่ main game assembly ก็เป็น dynamic จึงหลุดออกจากเป้าหมายการค้นหา type
  • หลังแก้ไขแล้วก็ boot ได้ทั้งหมด และแกนเกม 500kloc อยู่ในสถานะที่ส่ง frame และรอ input
  • งานที่เหลือถูกอธิบายว่า “just work” แต่ในความเป็นจริงยังมีงานจำนวนมากกับการ rig VFX, sound และ UI

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

 
GN⁺ 2023-09-18
ความคิดเห็นบน Hacker News
  • เกมนี้แทบทั้งหมดใช้ เอนจินของตัวเอง และดูเหมือนว่า Unity ถูกใช้แค่เป็นชั้น abstraction ของฮาร์ดแวร์กับกรอบสำหรับพอร์ตเท่านั้น ดังนั้นในแง่ความยากของการพอร์ต นี่น่าจะใกล้เคียงกับกรณีที่ดีที่สุด
    เกมลักษณะนี้มีมากกว่าที่คิด แต่แน่นอนว่าไม่ได้เป็นตัวแทนของเกม Unity ส่วนใหญ่

    • ใช่ เคยมีบทสัมภาษณ์หนึ่งที่บอกว่า Qud รันอยู่ใน Unity เหมือนแอปคอนโซล ในตอนนั้น
      หลังปรับปรุง UI แล้วยังเป็นแบบนั้นอยู่ไหมก็ไม่รู้ แต่ถ้าใช่ ผมก็แปลกใจมาตลอดว่าทำไมไม่พอร์ตไป MonoGame เพื่อประหยัดค่าใช้จ่าย
    • ผมมองว่านี่คือผลลัพธ์ที่องค์ประกอบพื้นฐานของ Unity แทบทั้งหมดไร้ประโยชน์ในระยะยาว
      Android ก็คล้ายกัน ระบบปฏิบัติการถูกใช้แค่เพื่อโหลดไลบรารีมาทดแทนสิ่งต่าง ๆ อย่างการตรวจจับกล้องหรือการเข้ารหัส จนวันหนึ่งก็รู้ตัวว่าระบบปฏิบัติการแทบจะเป็นเพียงชั้นโลหะบาง ๆ กับ bootloader เท่านั้น
  • https://nitter.net/unormal/status/1703163364229161236

    • เธรดที่กางออกอยู่ที่นี่: https://threadreaderapp.com/thread/1703163364229161236.html
    • โหลดไม่ขึ้น
      ให้ตายสิ Elon ตอนนี้จัดการกับ Twitter น่าหงุดหงิดจริง ๆ
  • ว้าว เจ๋งมาก ดีที่ได้เห็น กระบวนการพอร์ตแบบทีละขั้น และน่าประหลาดใจที่เวลาที่ใช้ก็ค่อนข้างสมเหตุสมผล

    • Godot มีฟีเจอร์น้อยกว่ามาก แต่ผมว่ามันเรียนรู้ง่ายจริง ๆ
    • อยากเห็นว่าเขาทำ บรรยากาศภาพ อันเป็นเอกลักษณ์ของ Caves of Qud ใน Godot อย่างไร
  • ถ้ามีโค้ดคัสตอมเยอะมากและใช้ฟีเจอร์ของ editor น้อย โครงสร้างแบบนี้อาจใช้ ไลบรารีเรนเดอร์ แทนเอนจินก็ได้

    • ถ้าอยากปล่อยเกมหลายแพลตฟอร์มพร้อมกัน การต้องจัดการโค้ดเรนเดอร์ เสียง และอินพุตแยกตามแพลตฟอร์มเองจะน่าเบื่อขึ้นอย่างรวดเร็ว
      ถ้ามีเอนจินเป็นฐาน คุณจะได้สิ่งเหล่านี้มาฟรี ๆ แต่ถ้าเป็น Unity ก็อาจมีค่าใช้จ่ายตามมา
    • เขาใช้ไลบรารีเรนเดอร์อยู่แล้ว ชื่อว่า “Unity”
  • ถ้าอยากรู้ว่า Brian คิดอย่างไร ลองดูวิดีโอนี้: https://www.youtube.com/watch?v=U03XXzcThGU
    เมื่อก่อนผมได้เรียนรู้จากที่นี่เยอะมาก

  • ทำให้นึกถึง ข้อถกเถียงเรื่อง TypeScript ของ DHH เมื่อเร็ว ๆ นี้
    ลองจินตนาการว่าต้องพอร์ตแบบนี้โดยไม่มี static types ทุกครั้งที่แก้ก็ต้อง build แล้วรันเพื่อหาจุด crash เวลาแบบนี้จะรู้สึกขอบคุณมากที่ทำไว้ด้วยภาษาที่ refactor ง่ายและสถาปัตยกรรมที่ดี

  • ไม่มีบัญชี Twitter เกิดอะไรขึ้นเหรอ?