การเขียนเอ็นจิ้นการผจญภัยข้อความสำหรับผู้เล่นหลายคนใน Node.js (ตอนที่ 1)

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ เคยได้ยินการผจญภัยข้อความหรือไม่? ในบทความชุดนี้ Fernando Doglio อธิบายขั้นตอนการสร้างเอ็นจิ้นทั้งหมดที่สามารถให้คุณเล่นการผจญภัยแบบข้อความที่คุณและเพื่อน ๆ เพลิดเพลินได้ ถูกต้อง เราจะเพิ่มสีสันให้มากขึ้นด้วยการเพิ่มผู้เล่นหลายคนในประเภทการผจญภัยแบบข้อความ!

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

ถ้าเราต้องการหวนคิดถึงอดีต บางทีชื่อ Colossal Cave Adventure (หรือแค่ Adventure ที่มีชื่อเดิม) ก็ดังขึ้น นั่นคือเกมผจญภัยข้อความเกมแรกที่เคยทำ

ภาพการผจญภัยของข้อความจริงในสมัยก่อน
รูปภาพของการผจญภัยข้อความจริงในสมัยก่อน (ตัวอย่างขนาดใหญ่)

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

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

ส่วนอื่นๆ ของซีรีส์นี้

  • ส่วนที่ 2: การออกแบบเซิร์ฟเวอร์ Game Engine
  • ส่วนที่ 3: การสร้าง Terminal Client
  • ตอนที่ 4: การเพิ่มแชทในเกมของเรา

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

เป้าหมายของพวกเรา

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

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

เพียงเพื่อให้คุณเห็นภาพว่าสิ่งนี้จะออกมาเป็นอย่างไร นี่คือเป้าหมายของฉัน:

โครงร่างทั่วไปสำหรับ UI สุดท้ายของไคลเอนต์เกม
โครงร่างทั่วไปสำหรับ UI สุดท้ายของไคลเอนต์เกม (ตัวอย่างขนาดใหญ่)

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

หวังว่าในตอนท้าย คุณจะพบว่าตัวเองกำลังสร้างข้อความผจญภัยใหม่ๆ เพื่อลองเล่นกับเพื่อน ๆ!

ขั้นตอนการออกแบบ

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

องค์ประกอบสี่ประการที่ฉันต้องการกล่าวถึงในที่นี้พร้อมรายละเอียดที่เหมาะสมคือ:

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

เครื่องยนต์

เอ็นจิ้นเกมหรือเซิร์ฟเวอร์เกมจะเป็น REST API และจะจัดเตรียมฟังก์ชันที่จำเป็นทั้งหมด

ฉันเลือกใช้ REST API เพียงเพราะว่า - สำหรับเกมประเภทนี้ - ความล่าช้าที่เพิ่มโดย HTTP และลักษณะที่ไม่ตรงกันจะไม่ทำให้เกิดปัญหาใดๆ อย่างไรก็ตาม เราจะต้องไปเส้นทางอื่นสำหรับเซิร์ฟเวอร์การแชท แต่ก่อนที่เราจะเริ่มต้นกำหนดปลายทางสำหรับ API ของเรา เราต้องกำหนดว่าเอ็นจิ้นจะมีความสามารถอะไร ดังนั้นขอได้รับมัน

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

คำเกี่ยวกับการเคลื่อนไหว

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

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

การต่อสู้

การต่อสู้เป็นอีกแง่มุมที่สำคัญมากของเกมประเภทนี้ และเราจะต้องพิจารณาเพิ่มในเอ็นจิ้นของเรา ไม่เช่นนั้นเราจะพลาดความสนุกไปบ้าง

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

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

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

การโต้ตอบระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์

มาดูกันว่าไคลเอนต์ที่กำหนดจะโต้ตอบกับเซิร์ฟเวอร์ของเราอย่างไรโดยใช้ฟังก์ชันที่กำหนดไว้ก่อนหน้านี้ (ยังไม่คิดถึงปลายทาง แต่เราจะไปที่นั่นในไม่กี่วินาที):

(ตัวอย่างขนาดใหญ่)

การโต้ตอบเริ่มต้นระหว่างไคลเอนต์และเซิร์ฟเวอร์ (จากมุมมองของเซิร์ฟเวอร์) คือการเริ่มเกมใหม่และขั้นตอนสำหรับเกมมีดังนี้:

  1. สร้างเกมใหม่
    ลูกค้าร้องขอให้สร้างเกมใหม่จากเซิร์ฟเวอร์
  2. สร้างห้องสนทนา
    แม้ว่าชื่อจะไม่ได้ระบุชื่อไว้ แต่เซิร์ฟเวอร์ไม่ได้เป็นเพียงการสร้างห้องสนทนาในเซิร์ฟเวอร์แชทเท่านั้น แต่ยังตั้งค่าทุกอย่างที่จำเป็นเพื่อให้ผู้เล่นชุดหนึ่งสามารถเล่นผ่านการผจญภัยได้
  3. ส่งคืนข้อมูลเมตาของเกม
    เมื่อเซิร์ฟเวอร์สร้างเกมและมีห้องสนทนาสำหรับผู้เล่นแล้ว ลูกค้าจะต้องใช้ข้อมูลดังกล่าวสำหรับคำขอในภายหลัง ส่วนใหญ่จะเป็นชุดรหัสที่ลูกค้าสามารถใช้เพื่อระบุตัวตนและเกมปัจจุบันที่พวกเขาต้องการเข้าร่วม (เพิ่มเติมในอีกสักครู่)
  4. แชร์ ID เกมด้วยตนเอง
    ขั้นตอนนี้จะต้องทำโดยตัวผู้เล่นเอง เราสามารถคิดค้นกลไกการแบ่งปันบางอย่างได้ แต่ฉันจะปล่อยให้สิ่งนั้นอยู่ในรายการความปรารถนาสำหรับการปรับปรุงในอนาคต
  5. เข้าร่วมเกม .
    อันนี้ค่อนข้างตรงไปตรงมา เนื่องจากทุกคนมี ID เกม พวกเขาจะเข้าร่วมการผจญภัยโดยใช้แอปพลิเคชันไคลเอนต์ของพวกเขา
  6. เข้าร่วมห้องสนทนาของพวกเขา
    สุดท้าย แอปไคลเอนต์ของผู้เล่นจะใช้ข้อมูลเมตาของเกมเพื่อเข้าร่วมห้องสนทนาของการผจญภัย นี่เป็นขั้นตอนสุดท้ายที่จำเป็นก่อนเกม เมื่อเสร็จแล้วผู้เล่นก็พร้อมที่จะเริ่มการผจญภัย!
ลำดับการดำเนินการสำหรับเกมที่มีอยู่
ลำดับการดำเนินการสำหรับเกมที่มีอยู่ (ตัวอย่างขนาดใหญ่)

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

ขั้นตอนต่อไปนี้จะทำงานโดยเป็นส่วนหนึ่งของลูปเกม ซึ่งหมายความว่าจะทำซ้ำไปเรื่อยๆ จนกว่าเกมจะจบลง

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

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

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

สิ่งที่น่าสนใจที่นี่คือ HTTP (เราได้กล่าวไปแล้วว่าเซิร์ฟเวอร์จะเป็น REST API) ไม่อนุญาตให้มีพฤติกรรมประเภทนี้ ดังนั้น ตัวเลือกของเราคือ:

  1. ทำการโพลทุก ๆ X วินาทีจากไคลเอนต์
  2. ใช้ระบบการแจ้งเตือนบางประเภทที่ทำงานควบคู่ไปกับการเชื่อมต่อไคลเอ็นต์-เซิร์ฟเวอร์

จากประสบการณ์ของฉัน ฉันมักจะชอบตัวเลือกที่ 2 มากกว่า อันที่จริง ฉันจะ (และจะใช้สำหรับบทความนี้) ใช้ Redis สำหรับพฤติกรรมประเภทนี้

ไดอะแกรมต่อไปนี้แสดงให้เห็นถึงการขึ้นต่อกันระหว่างบริการ

การโต้ตอบระหว่างแอพไคลเอนต์และเอ็นจิ้นเกม
การโต้ตอบระหว่างแอปไคลเอนต์และเอ็นจิ้นเกม (ตัวอย่างขนาดใหญ่)

เซิร์ฟเวอร์แชท

ฉันจะทิ้งรายละเอียดการออกแบบโมดูลนี้ไว้สำหรับขั้นตอนการพัฒนา (ซึ่งไม่ได้เป็นส่วนหนึ่งของบทความนี้) ที่ถูกกล่าวว่ามีบางสิ่งที่เราสามารถตัดสินใจได้

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

  • จะมีเพียงหนึ่งห้องต่อปาร์ตี้
    เราจะไม่อนุญาตให้สร้างกลุ่มย่อย สิ่งนี้ไปควบคู่กันโดยไม่ปล่อยให้ปาร์ตี้แตกแยก บางทีเมื่อเราใช้การเพิ่มประสิทธิภาพนั้นแล้ว การอนุญาตให้สร้างกลุ่มย่อยและห้องสนทนาแบบกำหนดเองได้จะเป็นความคิดที่ดี
  • จะไม่มีข้อความส่วนตัว
    นี่เป็นเพียงเพื่อจุดประสงค์ในการทำให้เข้าใจง่าย แต่การแชทเป็นกลุ่มนั้นดีพอแล้ว เราไม่ต้องการข้อความส่วนตัวในตอนนี้ จำไว้ว่าเมื่อใดก็ตามที่คุณทำงานกับผลิตภัณฑ์ที่ทำงานได้ขั้นต่ำ พยายามหลีกเลี่ยงการใช้คุณลักษณะที่ไม่จำเป็น มันเป็นเส้นทางที่อันตรายและเป็นเส้นทางที่ยากจะออกไป
  • เราจะไม่ยืนยันข้อความ
    กล่าวอีกนัยหนึ่ง ถ้าคุณออกจากปาร์ตี้ คุณจะสูญเสียข้อความ สิ่งนี้จะทำให้งานของเราง่ายขึ้นอย่างมาก เนื่องจากเราไม่ต้องจัดการกับการจัดเก็บข้อมูลประเภทใด และไม่ต้องเสียเวลาตัดสินใจเลือกโครงสร้างข้อมูลที่ดีที่สุดในการจัดเก็บและกู้คืนข้อความเก่า ทุกอย่างจะอยู่ในความทรงจำ และจะคงอยู่ที่นั่นตราบเท่าที่ห้องสนทนายังเปิดใช้งานอยู่ เมื่อมันปิด เราก็บอกลาพวกเขาได้เลย!
  • การสื่อสารจะทำผ่านซ็อกเก็ต
    น่าเศร้าที่ลูกค้าของเราจะต้องจัดการกับช่องทางการสื่อสารสองช่องทาง: ช่อง RESTful สำหรับเอ็นจิ้นเกมและซ็อกเก็ตสำหรับเซิร์ฟเวอร์แชท ซึ่งอาจเพิ่มความซับซ้อนของลูกค้าได้เล็กน้อย แต่ในขณะเดียวกัน ก็จะใช้วิธีการสื่อสารที่ดีที่สุดสำหรับทุกโมดูล (ไม่มีจุดที่แท้จริงในการบังคับ REST บนเซิร์ฟเวอร์แชทของเรา หรือการบังคับซ็อกเก็ตบนเซิร์ฟเวอร์เกมของเรา วิธีการนั้นจะเพิ่มความซับซ้อนของรหัสฝั่งเซิร์ฟเวอร์ ซึ่งเป็นวิธีจัดการกับตรรกะทางธุรกิจด้วย ดังนั้นเรามาเน้นที่ด้านนั้นกันดีกว่า สำหรับตอนนี้.)

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

ลูกค้า

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

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

สถาปัตยกรรมระดับสูงขั้นสุดท้ายของการพัฒนาทั้งหมด
สถาปัตยกรรมระดับสูงขั้นสุดท้ายของการพัฒนาทั้งหมด (ตัวอย่างขนาดใหญ่)

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

ดังที่กล่าวไปแล้ว ฟังก์ชันการทำงานที่แอปพลิเคชันไคลเอนต์จะต้องนำไปใช้มีดังนี้:

  1. สร้างเกมใหม่
    เนื่องจากฉันต้องการทำสิ่งต่างๆ ให้เรียบง่ายที่สุดเท่าที่จะทำได้ การดำเนินการนี้จึงทำได้ผ่านอินเทอร์เฟซ CLI เท่านั้น UI จริงจะถูกใช้หลังจากเข้าร่วมเกมเท่านั้น ซึ่งจะนำเราไปสู่จุดต่อไป
  2. เข้าร่วมเกมที่มีอยู่
    ด้วยรหัสของเกมที่ส่งคืนจากจุดก่อนหน้า ผู้เล่นสามารถใช้รหัสดังกล่าวเพื่อเข้าร่วมได้ นี่คือสิ่งที่คุณควรจะทำได้โดยไม่ต้องใช้ UI ดังนั้นฟังก์ชันนี้จะเป็นส่วนหนึ่งของกระบวนการที่จำเป็นในการเริ่มใช้ UI แบบข้อความ
  3. แยกไฟล์คำจำกัดความของเกม
    เราจะพูดคุยกันเล็กน้อย แต่ลูกค้าควรจะสามารถเข้าใจไฟล์เหล่านี้ได้เพื่อที่จะรู้ว่าต้องแสดงอะไรและรู้วิธีใช้ข้อมูลนั้นอย่างไร
  4. โต้ตอบกับการผจญภัย
    โดยพื้นฐานแล้วสิ่งนี้ทำให้ผู้เล่นสามารถโต้ตอบกับสภาพแวดล้อมที่อธิบายไว้ในเวลาใดก็ได้
  5. รักษาสินค้าคงคลังสำหรับผู้เล่นแต่ละคน
    แต่ละอินสแตนซ์ของไคลเอ็นต์จะมีรายการในหน่วยความจำ รายการนี้กำลังจะถูกสำรองข้อมูล
  6. สนับสนุนการแชท
    แอปไคลเอนต์จำเป็นต้องเชื่อมต่อกับเซิร์ฟเวอร์แชทและล็อกผู้ใช้เข้าสู่ห้องสนทนาของปาร์ตี้

ข้อมูลเพิ่มเติมเกี่ยวกับโครงสร้างและการออกแบบภายในของลูกค้าในภายหลัง ในระหว่างนี้ มาจบขั้นตอนการออกแบบด้วยการเตรียมตัวครั้งสุดท้าย: ไฟล์เกม

เกม: ไฟล์ JSON

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

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

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

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

ตัวอย่างกราฟถ่วงน้ำหนัก
ตัวอย่างกราฟถ่วงน้ำหนัก (ตัวอย่างขนาดใหญ่)

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

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

กราฟตัวอย่างสำหรับดันเจี้ยนที่กำหนด
กราฟตัวอย่างสำหรับดันเจี้ยนที่กำหนด (ตัวอย่างขนาดใหญ่)

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

มาต่อกันที่ส่วน "ความสนุก" มาดูกันว่ากราฟจะเป็นอย่างไรในรูปแบบ JSON ทนกับฉันที่นี่; JSON นี้จะมีข้อมูลมากมาย แต่ฉันจะอ่านให้มากที่สุดเท่าที่จะทำได้:

 { "graph": [ { "id": "entrance", "name": "Entrance", "north": { "node": "1stroom", "distance": 1 } }, { "id": "1st room", "name": "1st Room", "south": {"node": "entrance", "distance": 1} , "north": { "node": "bigroom", "distance": 1} } , { "id": "bigroom", "name": "Big room", "south": { "node": "1stroom", "distance": 1}, "north": { "node": "bossroom", "distance": 2}, "east": { "node": "rightwing", "distance": 3} , "west": { "node": "leftwing", "distance": 3} }, { "id": "bossroom", "name": "Boss room", "south": {"node": "bigroom", "distance": 2} } { "id": "leftwing", "name": "Left Wing", "east": {"node": "bigroom", "distance": 3} } { "id": "rightwing", "name": "Right Wing", "west": { "node": "bigroom", "distance": 3 } } ], "game": { "win-condition": { "source": "finalboss", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } }, "lose-condition": { "source": "player", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } } }, "rooms": { "entrance": { "description": { "default": "You're at the entrance of the dungeon. There are two lit torches on each wall (one on your right and one on your left). You see only one path: ahead." }, "items": [ { "id": "littorch1", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch2", "name": "Lit torch on the left", "triggers": [ { "action": "grab", //grab Lit torch on the left "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" } ] }, "1stroom": { "description": { "default": "You're in a very dark room. There are no windows and no source of light, other than the one at the entrance. You get the feeling you're not alone here.", "conditionals": { "has light": "The room you find yourself in appears to be empty, aside from a single chair in the right corner. There appears to be only one way out: deeper into the dungeon." } }, "items": [ { "id": "chair", "name": "Wooden chair", "details": "It's a wooden chair, nothing fancy about it. It appears to have been sitting here, untouched, for a while now.", "subitems": [ { "id": "woodenleg", "name": "Wooden leg", "triggeractions": [ { "action": "break", "target": "chair"}, //break { "action": "throw", "target": "chair"} //throw ], "destination": "inventory", "damage": 2 } ] } ] }, "bigroom": { "description": { "default": "You've reached the big room. On every wall are torches lighting every corner. The walls are painted white, and the ceiling is tall and filled with painted white stars on a black background. There is a gateway on either side and a big, wooden double door in front of you." }, "exits": { "north": { "id": "bossdoor", "name": "Big double door", "status": "locked", "details": "A aig, wooden double door. It seems like something big usually comes through here."} }, "items": [] }, "leftwing": { "description": { "default": "Another dark room. It doesn't look like it's that big, but you can't really tell what's inside. You do, however, smell rotten meat somewhere inside.", "conditionals": { "has light": "You appear to have found the kitchen. There are tables full of meat everywhere, and a big knife sticking out of what appears to be the head of a cow." } }, "items": [ { "id": "bigknife", "name": "Big knife", "destination": "inventory", "damage": 10} ] }, "rightwing": { "description": { "default": "This appear to be some sort of office. There is a wooden desk in the middle, torches lighting every wall, and a single key resting on top of the desk." }, "items": [ { "id": "key", "name": "Golden key", "details": "A small golden key. What use could you have for it?", "destination": "inventory", "triggers": [{ "action": "use", //use on north exit (contextual) "target": { "room": "bigroom", "exit": "north" }, "effect": { "statusUpdate": "unlocked", "target": { "room": "bigroom", "exit": "north" } } } ] } ] }, "bossroom": { "description": { "default": "You appear to have reached the end of the dungeon. There are no exits other than the one you just came in through. The only other thing that bothers you is the hulking giant looking like it's going to kill you, standing about 10 feet from you." }, "npcs": [ { "id": "finalboss", "name": "Hulking Ogre", "details": "A huge, green, muscular giant with a single eye in the middle of his forehead. It doesn't just look bad, it also smells like hell.", "stats": { "hp": 10, "damage": 3 } } ] } } } { "graph": [ { "id": "entrance", "name": "Entrance", "north": { "node": "1stroom", "distance": 1 } }, { "id": "1st room", "name": "1st Room", "south": {"node": "entrance", "distance": 1} , "north": { "node": "bigroom", "distance": 1} } , { "id": "bigroom", "name": "Big room", "south": { "node": "1stroom", "distance": 1}, "north": { "node": "bossroom", "distance": 2}, "east": { "node": "rightwing", "distance": 3} , "west": { "node": "leftwing", "distance": 3} }, { "id": "bossroom", "name": "Boss room", "south": {"node": "bigroom", "distance": 2} } { "id": "leftwing", "name": "Left Wing", "east": {"node": "bigroom", "distance": 3} } { "id": "rightwing", "name": "Right Wing", "west": { "node": "bigroom", "distance": 3 } } ], "game": { "win-condition": { "source": "finalboss", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } }, "lose-condition": { "source": "player", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } } }, "rooms": { "entrance": { "description": { "default": "You're at the entrance of the dungeon. There are two lit torches on each wall (one on your right and one on your left). You see only one path: ahead." }, "items": [ { "id": "littorch1", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch2", "name": "Lit torch on the left", "triggers": [ { "action": "grab", //grab Lit torch on the left "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" } ] }, "1stroom": { "description": { "default": "You're in a very dark room. There are no windows and no source of light, other than the one at the entrance. You get the feeling you're not alone here.", "conditionals": { "has light": "The room you find yourself in appears to be empty, aside from a single chair in the right corner. There appears to be only one way out: deeper into the dungeon." } }, "items": [ { "id": "chair", "name": "Wooden chair", "details": "It's a wooden chair, nothing fancy about it. It appears to have been sitting here, untouched, for a while now.", "subitems": [ { "id": "woodenleg", "name": "Wooden leg", "triggeractions": [ { "action": "break", "target": "chair"}, //break { "action": "throw", "target": "chair"} //throw ], "destination": "inventory", "damage": 2 } ] } ] }, "bigroom": { "description": { "default": "You've reached the big room. On every wall are torches lighting every corner. The walls are painted white, and the ceiling is tall and filled with painted white stars on a black background. There is a gateway on either side and a big, wooden double door in front of you." }, "exits": { "north": { "id": "bossdoor", "name": "Big double door", "status": "locked", "details": "A aig, wooden double door. It seems like something big usually comes through here."} }, "items": [] }, "leftwing": { "description": { "default": "Another dark room. It doesn't look like it's that big, but you can't really tell what's inside. You do, however, smell rotten meat somewhere inside.", "conditionals": { "has light": "You appear to have found the kitchen. There are tables full of meat everywhere, and a big knife sticking out of what appears to be the head of a cow." } }, "items": [ { "id": "bigknife", "name": "Big knife", "destination": "inventory", "damage": 10} ] }, "rightwing": { "description": { "default": "This appear to be some sort of office. There is a wooden desk in the middle, torches lighting every wall, and a single key resting on top of the desk." }, "items": [ { "id": "key", "name": "Golden key", "details": "A small golden key. What use could you have for it?", "destination": "inventory", "triggers": [{ "action": "use", //use on north exit (contextual) "target": { "room": "bigroom", "exit": "north" }, "effect": { "statusUpdate": "unlocked", "target": { "room": "bigroom", "exit": "north" } } } ] } ] }, "bossroom": { "description": { "default": "You appear to have reached the end of the dungeon. There are no exits other than the one you just came in through. The only other thing that bothers you is the hulking giant looking like it's going to kill you, standing about 10 feet from you." }, "npcs": [ { "id": "finalboss", "name": "Hulking Ogre", "details": "A huge, green, muscular giant with a single eye in the middle of his forehead. It doesn't just look bad, it also smells like hell.", "stats": { "hp": 10, "damage": 3 } } ] } } } { "graph": [ { "id": "entrance", "name": "Entrance", "north": { "node": "1stroom", "distance": 1 } }, { "id": "1st room", "name": "1st Room", "south": {"node": "entrance", "distance": 1} , "north": { "node": "bigroom", "distance": 1} } , { "id": "bigroom", "name": "Big room", "south": { "node": "1stroom", "distance": 1}, "north": { "node": "bossroom", "distance": 2}, "east": { "node": "rightwing", "distance": 3} , "west": { "node": "leftwing", "distance": 3} }, { "id": "bossroom", "name": "Boss room", "south": {"node": "bigroom", "distance": 2} } { "id": "leftwing", "name": "Left Wing", "east": {"node": "bigroom", "distance": 3} } { "id": "rightwing", "name": "Right Wing", "west": { "node": "bigroom", "distance": 3 } } ], "game": { "win-condition": { "source": "finalboss", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } }, "lose-condition": { "source": "player", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } } }, "rooms": { "entrance": { "description": { "default": "You're at the entrance of the dungeon. There are two lit torches on each wall (one on your right and one on your left). You see only one path: ahead." }, "items": [ { "id": "littorch1", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch2", "name": "Lit torch on the left", "triggers": [ { "action": "grab", //grab Lit torch on the left "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" } ] }, "1stroom": { "description": { "default": "You're in a very dark room. There are no windows and no source of light, other than the one at the entrance. You get the feeling you're not alone here.", "conditionals": { "has light": "The room you find yourself in appears to be empty, aside from a single chair in the right corner. There appears to be only one way out: deeper into the dungeon." } }, "items": [ { "id": "chair", "name": "Wooden chair", "details": "It's a wooden chair, nothing fancy about it. It appears to have been sitting here, untouched, for a while now.", "subitems": [ { "id": "woodenleg", "name": "Wooden leg", "triggeractions": [ { "action": "break", "target": "chair"}, //break { "action": "throw", "target": "chair"} //throw ], "destination": "inventory", "damage": 2 } ] } ] }, "bigroom": { "description": { "default": "You've reached the big room. On every wall are torches lighting every corner. The walls are painted white, and the ceiling is tall and filled with painted white stars on a black background. There is a gateway on either side and a big, wooden double door in front of you." }, "exits": { "north": { "id": "bossdoor", "name": "Big double door", "status": "locked", "details": "A aig, wooden double door. It seems like something big usually comes through here."} }, "items": [] }, "leftwing": { "description": { "default": "Another dark room. It doesn't look like it's that big, but you can't really tell what's inside. You do, however, smell rotten meat somewhere inside.", "conditionals": { "has light": "You appear to have found the kitchen. There are tables full of meat everywhere, and a big knife sticking out of what appears to be the head of a cow." } }, "items": [ { "id": "bigknife", "name": "Big knife", "destination": "inventory", "damage": 10} ] }, "rightwing": { "description": { "default": "This appear to be some sort of office. There is a wooden desk in the middle, torches lighting every wall, and a single key resting on top of the desk." }, "items": [ { "id": "key", "name": "Golden key", "details": "A small golden key. What use could you have for it?", "destination": "inventory", "triggers": [{ "action": "use", //use on north exit (contextual) "target": { "room": "bigroom", "exit": "north" }, "effect": { "statusUpdate": "unlocked", "target": { "room": "bigroom", "exit": "north" } } } ] } ] }, "bossroom": { "description": { "default": "You appear to have reached the end of the dungeon. There are no exits other than the one you just came in through. The only other thing that bothers you is the hulking giant looking like it's going to kill you, standing about 10 feet from you." }, "npcs": [ { "id": "finalboss", "name": "Hulking Ogre", "details": "A huge, green, muscular giant with a single eye in the middle of his forehead. It doesn't just look bad, it also smells like hell.", "stats": { "hp": 10, "damage": 3 } } ] } } } { "graph": [ { "id": "entrance", "name": "Entrance", "north": { "node": "1stroom", "distance": 1 } }, { "id": "1st room", "name": "1st Room", "south": {"node": "entrance", "distance": 1} , "north": { "node": "bigroom", "distance": 1} } , { "id": "bigroom", "name": "Big room", "south": { "node": "1stroom", "distance": 1}, "north": { "node": "bossroom", "distance": 2}, "east": { "node": "rightwing", "distance": 3} , "west": { "node": "leftwing", "distance": 3} }, { "id": "bossroom", "name": "Boss room", "south": {"node": "bigroom", "distance": 2} } { "id": "leftwing", "name": "Left Wing", "east": {"node": "bigroom", "distance": 3} } { "id": "rightwing", "name": "Right Wing", "west": { "node": "bigroom", "distance": 3 } } ], "game": { "win-condition": { "source": "finalboss", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } }, "lose-condition": { "source": "player", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } } }, "rooms": { "entrance": { "description": { "default": "You're at the entrance of the dungeon. There are two lit torches on each wall (one on your right and one on your left). You see only one path: ahead." }, "items": [ { "id": "littorch1", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch2", "name": "Lit torch on the left", "triggers": [ { "action": "grab", //grab Lit torch on the left "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" } ] }, "1stroom": { "description": { "default": "You're in a very dark room. There are no windows and no source of light, other than the one at the entrance. You get the feeling you're not alone here.", "conditionals": { "has light": "The room you find yourself in appears to be empty, aside from a single chair in the right corner. There appears to be only one way out: deeper into the dungeon." } }, "items": [ { "id": "chair", "name": "Wooden chair", "details": "It's a wooden chair, nothing fancy about it. It appears to have been sitting here, untouched, for a while now.", "subitems": [ { "id": "woodenleg", "name": "Wooden leg", "triggeractions": [ { "action": "break", "target": "chair"}, //break { "action": "throw", "target": "chair"} //throw ], "destination": "inventory", "damage": 2 } ] } ] }, "bigroom": { "description": { "default": "You've reached the big room. On every wall are torches lighting every corner. The walls are painted white, and the ceiling is tall and filled with painted white stars on a black background. There is a gateway on either side and a big, wooden double door in front of you." }, "exits": { "north": { "id": "bossdoor", "name": "Big double door", "status": "locked", "details": "A aig, wooden double door. It seems like something big usually comes through here."} }, "items": [] }, "leftwing": { "description": { "default": "Another dark room. It doesn't look like it's that big, but you can't really tell what's inside. You do, however, smell rotten meat somewhere inside.", "conditionals": { "has light": "You appear to have found the kitchen. There are tables full of meat everywhere, and a big knife sticking out of what appears to be the head of a cow." } }, "items": [ { "id": "bigknife", "name": "Big knife", "destination": "inventory", "damage": 10} ] }, "rightwing": { "description": { "default": "This appear to be some sort of office. There is a wooden desk in the middle, torches lighting every wall, and a single key resting on top of the desk." }, "items": [ { "id": "key", "name": "Golden key", "details": "A small golden key. What use could you have for it?", "destination": "inventory", "triggers": [{ "action": "use", //use on north exit (contextual) "target": { "room": "bigroom", "exit": "north" }, "effect": { "statusUpdate": "unlocked", "target": { "room": "bigroom", "exit": "north" } } } ] } ] }, "bossroom": { "description": { "default": "You appear to have reached the end of the dungeon. There are no exits other than the one you just came in through. The only other thing that bothers you is the hulking giant looking like it's going to kill you, standing about 10 feet from you." }, "npcs": [ { "id": "finalboss", "name": "Hulking Ogre", "details": "A huge, green, muscular giant with a single eye in the middle of his forehead. It doesn't just look bad, it also smells like hell.", "stats": { "hp": 10, "damage": 3 } } ] } } }

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

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

คุณจะชนะโดยการฆ่ามันหรือแพ้โดยการถูกฆ่าโดยมัน

มาดูภาพรวมโดยละเอียดเพิ่มเติมของโครงสร้าง JSON ทั้งหมดและสามส่วนของโครงสร้างนี้

กราฟ

อันนี้จะมีความสัมพันธ์ระหว่างโหนด โดยพื้นฐานแล้ว ส่วนนี้แปลโดยตรงเป็นกราฟที่เราดูก่อนหน้านี้

โครงสร้างสำหรับส่วนนี้ค่อนข้างตรงไปตรงมา เป็นรายการโหนด โดยทุกโหนดประกอบด้วยแอตทริบิวต์ต่อไปนี้:

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

เกม

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

เพื่อให้ง่ายขึ้น ฉันได้เพิ่มเพียงสองเงื่อนไข:

  • คุณชนะด้วยการฆ่าบอส
  • หรือแพ้ด้วยการถูกฆ่า

ห้อง

นี่คือที่มาของ 163 บรรทัดส่วนใหญ่ และเป็นส่วนที่ซับซ้อนที่สุดของส่วนต่างๆ นี่คือที่ที่เราจะอธิบายห้องทั้งหมดในการผจญภัยของเราและทุกอย่างที่อยู่ภายในนั้น

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

มาดูกันว่าคุณสมบัติเหล่านี้สามารถทำอะไรกับเกมของเราได้บ้าง

คำอธิบาย

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

ดังนั้น การเลือกไอเท็มและใช้งานอาจก่อให้เกิดสภาวะโลกที่จะส่งผลกระทบต่อส่วนอื่น ๆ ของเกม

รายการ

สิ่งเหล่านี้เป็นตัวแทนของทุกสิ่ง” ที่คุณสามารถหาได้ภายในห้อง ทุกรายการใช้รหัสและชื่อเดียวกันกับที่โหนดในส่วนกราฟมี

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

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

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

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

สุดท้ายบางรายการจะมีคุณสมบัติ "ความเสียหาย" ดังนั้น หากคุณใช้ไอเท็มเพื่อโจมตี NPC ค่านั้นจะถูกใช้เพื่อลบพลังชีวิตออกจากพวกมัน

ทางออก

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

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

NPCs

ในที่สุด NPC จะเป็นส่วนหนึ่งของรายการอื่น โดยพื้นฐานแล้วเป็นรายการที่มีสถิติที่เอ็นจิ้นจะใช้เพื่อทำความเข้าใจว่าแต่ละรายการควรทำงานอย่างไร ที่เราได้กำหนดไว้ในตัวอย่างของเราคือ “hp” ซึ่งย่อมาจากจุดสุขภาพ และ “ความเสียหาย” ซึ่งเหมือนกับอาวุธ คือตัวเลขที่การโจมตีแต่ละครั้งจะหักออกจากสุขภาพของผู้เล่น

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

ในกรณีที่คุณยังไม่รู้ ประโยชน์หลักของการกำหนดเกมของเราในไฟล์เช่นนี้คือ เราจะสามารถสลับไฟล์ JSON ได้เหมือนกับที่คุณทำคาร์ทริดจ์ย้อนกลับไปในยุค Super Nintendo เพียงโหลดไฟล์ใหม่และเริ่มต้นการผจญภัยครั้งใหม่ ง่าย!

ปิดความคิด

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

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

ส่วนอื่นๆ ของซีรีส์นี้

  • ส่วนที่ 2: การออกแบบเซิร์ฟเวอร์ Game Engine
  • ส่วนที่ 3: การสร้าง Terminal Client
  • ตอนที่ 4: การเพิ่มแชทในเกมของเรา