ระบบสแนปชอตของเกมจะใช้งานอย่างไรสำหรับเกมเรียลไทม์บนเครือข่าย


12

ฉันต้องการสร้างเกมแบบเรียลไทม์แบบมัลติ - เพลย์เยอร์ไคลเอ็นต์เซิร์ฟเวอร์เป็นโปรเจ็กต์สำหรับคลาสเครือข่ายของฉัน

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

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

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

อย่างไรก็ตามฉันไม่สามารถหาวิธีที่ดีที่จะใช้ระบบ snapshot ฉันพบว่ามันยากมากที่จะย้ายออกจากสถาปัตยกรรมการเขียนโปรแกรมผู้เล่นเดี่ยวและคิดว่าฉันจะเก็บสถานะของเกมในลักษณะที่:

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

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

คำนวณความแตกต่างของสแนปชอตได้อย่างไร

โดยทั่วไป: ระบบสแนปชอตของเกมจะมีการนำไปใช้อย่างไร


4
+1 นี่เป็นคำถามที่กว้างเกินไปสำหรับคำถามเดียว แต่ IMO เป็นหัวข้อที่น่าสนใจซึ่งสามารถครอบคลุมได้ในคำตอบ
Kromster

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

คุณได้อ่านสิ่งนี้แล้ว: fabiensanglard.net/quake3/network.php - การทบทวนรูปแบบเครือข่าย quake 3 รวมถึงการอภิปรายเกี่ยวกับการดำเนินการ
Steven

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

คำตอบ:


3

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

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

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

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

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

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


2

ก่อนอื่นคุณต้องรู้วิธีการแสดงข้อมูลที่เกี่ยวข้องของคุณในลักษณะที่สอดคล้องกับโปรโตคอล ขึ้นอยู่กับข้อมูลที่เกี่ยวข้องกับเกม ฉันจะใช้เกม RTS เป็นตัวอย่าง

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

ผู้เล่นจะต้องมีข้อมูลที่เกี่ยวข้องกับพวกเขา (เช่นทุกหน่วยที่มองเห็นได้):

  • พวกเขามีชีวิตอยู่หรือตาย?
  • พวกเขาเป็นประเภทอะไร?
  • พวกเขามีสุขภาพเหลืออยู่เท่าไหร่?
  • ตำแหน่งปัจจุบัน, การหมุน, ความเร็ว (ความเร็ว + ทิศทาง), เส้นทางในอนาคตอันใกล้ ...
  • กิจกรรม: โจมตีเดินสร้างแก้ไขรักษา ฯลฯ ...
  • ผลกระทบสถานะบัฟ / ดีบัฟ
  • และอาจเป็นสถิติอื่น ๆ เช่นมานะโล่และสิ่งที่ไม่?

ในตอนแรกผู้เล่นจะต้องได้รับสถานะเต็มก่อนที่เธอจะสามารถเข้าสู่เกม (หรือข้อมูลทั้งหมดที่เกี่ยวข้องกับผู้เล่นนั้น)

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

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

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

บางคำถามที่คุณไม่ได้ถามและฉันคิดว่าน่าสนใจคือ:

  1. ลูกค้าควรได้รับสแนปชอตพร้อมข้อมูลทั้งหมดตั้งแต่แรกไหม สิ่งที่เกี่ยวกับรายการนอกวิสัยทัศน์ของพวกเขา? แล้วหมอกแห่งสงครามในเกม RTS ล่ะ? หากคุณส่งข้อมูลทั้งหมดไคลเอ็นต์อาจถูกแฮ็กเพื่อแสดงข้อมูลที่ไม่ควรใช้กับเครื่องเล่น (ขึ้นอยู่กับมาตรการรักษาความปลอดภัยอื่น ๆ ที่คุณใช้) หากคุณส่งข้อมูลที่เกี่ยวข้องปัญหาจะได้รับการแก้ไข
  2. เมื่อใดจึงจำเป็นต้องส่งการเปลี่ยนแปลงแทนการส่งข้อมูลทั้งหมด พิจารณาแบนด์วิดท์ที่มีอยู่ในเครื่องที่ทันสมัยเราจะได้อะไรจากการส่ง "เดลต้า" แทนที่จะส่งข้อมูลทั้งหมดถ้าเป็นเช่นนั้นเมื่อไหร่?
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.