คิดเล่น ๆ: multi-thread architecture design สำหรับเกมแบบ visual novel

จริง ๆ มันเริ่มจากมีน้องคนนึงไปโพสต์ในกลุ่มบน Facebook ประมาณว่า เขาจะทำเกม Visual Novel แล้วจะทำตัว interpreter binary ไฟล์ยังไง ก็เลยตอบไปว่า … ใช้ภาษาสคริปท์ที่มีอยู่แล้วเถอะ สบายกว่าเยอะ !

แล้วก็มานั่งคิด เกมแบบอื่นเขาใช้ภาษาสคริปท์อย่างเช่น Lua กัน เพราะเป็นภาษาที่เขียนง่าย เร็ว เล็ก และ bind ได้ง่าย แต่ว่าพอเอามาใช้กับเกมอย่าง Visual Novel ถ้าใช้ Architecture เดิม ๆ อาจจะไม่เหมาะก็ได้

ที่ว่าไม่เหมาะเนี่ย มันเป็นยังไง … หน้าตาคร่าว ๆ ของตัวโปรแกรมหลักของเกมจะเป็นแบบนี้ครับ

while true do:  
    get_input();
    process_game_logic();  
    update_screen();  
end.  

ถ้าใครเขียนโปรแกรมพวก GUI ที่ใช้ API ระดับล่าง ๆ (เช่น Win32) จะรู้สึกว่ามันคล้ายกัน ต่างกันตรง get_input() เนี่ย มันจะไม่ block คำสั่งจนกว่าจะมี input ครับ คือถ้าไม่มี input มันก็จะ return ค่าออกมาว่า ไม่มี input ต่างกับโปรแกรม GUI ที่จะรอจนกว่าจะมี input ก่อน (เพราะถ้าไม่มี input ก็ไม่รู้จะทำอะไรนั่นเอง)

แล้วมันยังไงกับ Visual Novel ผมคิดว่า สคริปท์ของ Visual Novel ในส่วนของตัว Game มันควรจะเป็น…ประมาณนี้

translate girl1.school_uniform.stance 400 400  
add_image girl1.school_uniform.stance  
message "Girl: Oh, hi, how are you doing?"  
result = choice "I: Great! how about you?", "I: Not so well, I have a cold".  
if result = 1 then  
    message "Girl: I'm doing good, thanks!"  
else
    message "Oh, sorry to hear that. Get better soon!"  
end.  

หรืออะไรทำนองนี้ (อ่านรู้เรื่องไหมนี่)

คือทุกครั้งที่มีคำสั่ง message ขึ้นมา ตัวเกมมันควรจะหยุดให้คนอ่านอ่านข้อความจนจบ … และอาจจะต้องมี option อื่น ๆ ด้วย (ไม่พูดถึงนะ) แต่ถ้าเราเอาโค๊ดทุกอย่างไปใส่ในฟังก์ชั่น process_game_logic แล้วล่ะก็ …มันจะกลายเป็นว่ามีแต่ภาพสาวน้อยกับข้อความขึ้นมาว่าอะไรไม่รู้ (ฮา)… เพราะมันทำงานรวดเดียวตั้งแต่ต้นยันจบ !

แต่ถ้าเราทำ message ให้หยุดรอ จะกลายเป็นว่าตัวเกมจะค้างจนกว่า user จะกดปุ่มใด ๆ … ค้างชนิดที่ว่าโปรแกรมจะหยุดทำงานไปเลย กดปิดก็ไม่ได้ จนกว่า user จะกดผ่าน …

วิธีที่ทำได้อย่างหนึ่งคือ ให้ process_game_logic อ่านทีละบรรทัดแล้วทำตาม คือ interpret คำสั่งกันไป ในกรณีที่เราสร้างภาษาสคริปทืเองมันก็ทำได้แหละ (แต่จะกลายเป็นงานช้างไป อย่างที่บอก)

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

ทางออกเหรอ … แยกเอา process_game_logic() ออกมาเป็นอีก thread นึงไง …

ทีนี้เราก็จะมีสอง thread ละ

Thread แรกก็จะมีหน้าตาแบบนี้

while true do:  
    get_input();  
    update_screen();  
end.  

Thread ที่สอง … มีแค่  process_game_logic(); เท่านั้น โดยจะทำการ sync ดึงค่า input จาก thread แรกเข้ามา แล้วส่ง output กลับไปที่ thread แรกเมื่อ process เสร็จแล้ว

ถ้าเราเอาสคริปท์ข้างบนมาใช้ ก็แค่ว่า เขียนโปรแกรมให้คำสั่ง message และคำสั่ง choice มีการหยุดรอจนกว่าจะมี input ถามว่าตรงนี้ทำได้ยังไง … ก็คือ ใช้ condition variable ร่วมกันระหว่างสอง thread เมื่อถึงคำสั่ง message ตัว thread ที่สองจะสั่งบล๊อค cv แล้วรอจนกว่า thread แรกจะปลดล๊อคให้ เท่านั้นเอง

การออกแบบในเชิง multi-thread แบบนี้สามารถเอาไปใช้กับเกมแบบเดิม ๆ ได้ด้วย ก็คือ เราก็เขียนโค๊ดใน process_game_logic() เป็น infinite loop นั่นเอง

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *

This site uses Akismet to reduce spam. Learn how your comment data is processed.