ระบบเครือข่ายสำหรับเกมกลยุทธ์แบบเรียลไทม์


16

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

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

ตอนนี้ฉันกำลังทำไคลเอ็นต์แบบ thin ซึ่งเพิ่งส่งแพ็กเก็ตไปยังเซิร์ฟเวอร์และรอการตอบกลับ อย่างไรก็ตามมีปัญหาหลายประการ

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

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

TL; DR สร้าง RTS ด้วย> 50 กิจกรรมต่อวินาทีฉันจะซิงโครไนซ์ไคลเอ็นต์ได้อย่างไร


คุณสามารถใช้สิ่งที่อีฟออนไลน์ทำและ "ชะลอ" เวลาเพื่ออนุญาตให้ทุกอย่างดำเนินการอย่างถูกต้อง
Ryan Erb

3
นี่คือลิงค์เชื่อมโยงไปยังรูปแบบไคลเอนต์ / เซิร์ฟเวอร์ของ Planetary Annihilation: forrestthewoods.ghost.io/ นี่เป็นทางเลือกแทนโมเดล lockstep ที่ดูเหมือนว่าจะทำงานได้ดีมากสำหรับพวกเขา
DallonF

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

คำตอบ:


12

เป้าหมายของคุณในการซิงโครไนซ์ 50 เหตุการณ์ต่อวินาทีด้วยเสียงแบบเรียลไทม์กับฉันเหมือนว่ามันไม่สมจริง นี่คือเหตุผลที่วิธีการล็อกขั้นตอนพูดคุยกันในบทความของนักธนู 1,500 คนก็พูดถึงแล้ว!

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


6

การกระทำของผู้เล่นทุกครั้งจะถูกกำหนดไว้อย่างไรก็ตามมีเหตุการณ์ที่เกิดขึ้นตามช่วงเวลาที่กำหนด

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

การให้เกมทำงานอย่างอิสระตามเวลาจริงมีข้อดีอื่น ๆ :

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

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


1
+1: อิงเฟรมเป็นตัวเลือกที่เหมาะสมไม่ใช่อิงตามเวลา คุณสามารถพยายามรักษาเฟรม N ต่อวินาทีได้แน่นอน การผูกปมเล็กน้อยดีกว่า desync แบบเต็ม
PatrickB

@PatrickB: ฉันเห็นว่าเกมหลายเกมใช้เวลา "จำลอง" ซึ่งไม่ได้เชื่อมโยงกับเฟรมวิดีโอ World of Warcraft อัปเดตสิ่งต่าง ๆ เช่นมานาทุก ๆ 100ms และค่าเริ่มต้นของป้อมปราการคนแคระคือ 10 เห็บต่อเฟรมวิดีโอ
Mooing Duck

@Minging Duck: ความคิดเห็นของฉันเฉพาะกับ RTS สำหรับสิ่งที่ข้อผิดพลาดเล็ก ๆ สามารถยอมรับและแก้ไขได้ในภายหลัง (เช่น MMORPGs, FPSs) แล้วการใช้ค่าต่อเนื่องไม่เพียง แต่ดี แต่สำคัญ อย่างไรก็ตามการจำลองที่กำหนดขึ้นมาซึ่งจะต้องมีการซิงก์ข้ามหลายเครื่อง? ติดกับเฟรม
PatrickB

4

ขั้นแรกเพื่อแก้ปัญหาเหตุการณ์ที่กำหนดไว้อย่าออกอากาศเหตุการณ์เมื่อเกิดขึ้นแต่เมื่อเริ่มกิจกรรม นั่นคือแทนที่จะส่งข้อความ "เพิ่มพลังงานของกระเบื้อง ( x , y )" ทุก ๆ วินาทีเพียงแค่ส่งข้อความเดียวว่า "เพิ่มพลังงานของกระเบื้อง ( x , y ) หนึ่งครั้งต่อวินาทีจนกว่ามันจะเต็มหรือจนกว่า ขัดจังหวะ". ไคลเอนต์แต่ละคนมีหน้าที่รับผิดชอบในการกำหนดตารางเวลาการอัปเดตภายในเครื่อง

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

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


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

  • ทำให้เกมของคุณเป็นแบบเลี้ยวต่อกันภายในแต่ละเทิร์นหรือ "ติ๊ก" การพูดประมาณ 1/50 วินาที (อันที่จริงแล้วคุณอาจหนีไปได้ด้วยการหมุน 1/10 วินาทีหรือนานกว่านั้น) การกระทำของผู้เล่นใด ๆ ที่เกิดขึ้นระหว่างเทิร์นเดี่ยวควรได้รับการปฏิบัติพร้อมกัน ข้อความทั้งหมดอย่างน้อยจากเซิร์ฟเวอร์ไปยังไคลเอนต์ควรติดแท็กด้วยหมายเลขเทิร์นเพื่อให้ลูกค้าแต่ละคนรู้ว่าเหตุการณ์ใดเกิดขึ้นในแต่ละเหตุการณ์

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

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

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

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


1
และซิงโครไนซ์ RNG เพื่อเริ่มและดึงเฉพาะจาก RNG ที่ซิงโครไนซ์เมื่อเซิร์ฟเวอร์แจ้งให้คุณทราบ Starcraft1 มีข้อผิดพลาดเป็นเวลานานที่เมล็ด RNG ไม่ได้รับการบันทึกในระหว่างการเล่นซ้ำดังนั้นการเล่นซ้ำจะเบี่ยงเบนไปจากเกมจริง
Mooing Duck

1
@MooingDuck: จุดดี ในความเป็นจริงฉันขอแนะนำให้ส่งเมล็ด RNG ปัจจุบันทุกรอบเพื่อให้ตรวจจับการซิงโครไนซ์ RNG ได้ทันที นอกจากนี้หากรหัส UI ของคุณต้องการการสุ่มอย่าดึงออกมาจากอินสแตนซ์ RNG เดียวกันกับที่ใช้สำหรับตรรกะของเกม
Ilmari Karonen

3

คุณควรทำให้เกมตรรกะของคุณเป็นอิสระจากเวลาจริงและทำให้มันเป็นแบบเลี้ยว ด้วยวิธีนี้คุณจะรู้ว่าสิ่งใดที่ "พลังงานเปลี่ยนกระเบื้อง" ในกรณีของคุณเทิร์นละแค่ 1 / 50th ของวินาที

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


1

ก่อนอื่นคุณต้องเข้าใจว่า PC float / double math ไม่ได้กำหนดไว้เว้นแต่คุณจะระบุให้ใช้ IEEE-754 อย่างเข้มงวดสำหรับการคำนวณของคุณ (จะช้า)

จากนั้นนี่คือวิธีที่ฉันจะใช้มัน: ไคลเอนต์เชื่อมต่อกับเซิร์ฟเวอร์และ sincronize เวลา (ดูแล latency ping!) (สำหรับการเล่นเกมที่ยาวนานอาจจำเป็นต้อง resync timestamp / turn)

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

ลูกค้าจะมี 2 "โลก": หนึ่งอยู่ในซิงค์กับเอนด์เทิร์นอีกอันถูกคำนวณโดยเริ่มจากเอนด์เทิร์นรวมการกระทำที่มาถึงคิวจนกระทั่งลูกค้าเปิด / ประทับเวลาปัจจุบัน

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

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

ฉันใช้ algoritm นี้ในเกมยิงแบบเรียลไทม์และใช้งานได้ดี (โดยมีไคลเอ็นต์ที่ไม่มีการแช่งการกระทำของตัวเอง แต่ด้วยเซิร์ฟเวอร์ ping ต่ำเป็น 20/50ms) นอกจากนี้เซิร์ฟเวอร์ X end-turn ทุกตัวยังส่งพิเศษ "ทั้งหมด แผนที่ลูกค้า "แพ็คเก็ตเพื่อแก้ไขค่าล่องลอย


โดยทั่วไปแล้วปัญหาทางคณิตศาสตร์ของ floating point สามารถหลีกเลี่ยงได้เช่นใน RTS คุณสามารถทำการจำลองและการเคลื่อนไหวด้วยจำนวนเต็ม / จุดคงที่และใช้ floating point สำหรับเลเยอร์การแสดงผลที่ไม่ส่งผลต่อเกม
Peteris

ด้วยจำนวนเต็มยากที่จะทำกระเบื้องแนวนอนเว้นแต่ว่ามันจะเป็นบอร์ดแปดด้าน ไม่มีการเร่งความเร็ว hw สำหรับจุดคงที่ดังนั้นอาจช้ากว่าการลอย ieee754
Lesto
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.