จะรักษาโครงสร้างข้อมูลให้ตรงกันบนเครือข่ายได้อย่างไร?


12

บริบท

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

ป้อนคำอธิบายรูปภาพที่นี่

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

var actionGroup = actionManager.CreateGroup();
actionGroup.Add(new TalkAction("Guybrush", "Hello there!");
actionGroup.Add(new WalkAction("Guybrush", new Vector2(300, 300));
actionGroup.Add(new SetAnimationAction("Guybrush", "Sit"));

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

ปัญหา

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

var actionManager = new List<List<string>>();

ฉันจะเก็บเนื้อหาของโครงสร้างข้อมูลด้านบนให้ตรงกันระหว่างผู้เล่นทุกคนได้อย่างไร

นอกจากคำถามหลักแล้วฉันยังมีข้อกังวลอื่นอีกสองสามข้อที่เกี่ยวข้อง (เช่นความหมายที่เป็นไปได้ทั้งหมดของปัญหาเดียวกันข้างต้น):

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

5
ข้อผูกมัด "อ่านนี้ก่อน" ลิงค์gafferongames.com/networking-for-game-programmersซึ่งไม่ใช่เทคนิคมากแม้จะมีรหัสบางส่วน แต่มันเป็นคำพูดและอธิบายตัวเลือกของคุณค่อนข้างดี ยิ่งไปกว่านั้นมันจะช่วยให้คุณมีความเท่าเทียมกับทุกคนที่อ่านลิงค์นี้และเป็นสถานที่ที่มีความสุข
Patrick Hughes

@PatrickHughes ขอบคุณสำหรับลิงค์! ฉันจะอ่านมันทั้งหมดแน่ ๆ
David Gouveia

มีแพ็คเกจบางอย่างที่จัดการสิ่งนี้ให้คุณ ฉันไม่รู้ว่ามันมีประสิทธิภาพหรือรวดเร็วแค่ไหน แต่ NowJS ( nowjs.com ) เป็นตัวอย่างที่น่าสนใจ จากมุมมองของนักพัฒนาคุณมีโครงสร้างข้อมูลร่วมกันระหว่างไคลเอนต์และเซิร์ฟเวอร์ เปลี่ยนหนึ่งการเปลี่ยนแปลงอื่น ๆ
ทิมโฮลท์

คำตอบ:


9

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

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

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

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

2:ไคลเอนต์ส่งคำขอไปยังเซิร์ฟเวอร์ซึ่งจะออกอากาศคำขอทุกคนรวมถึงลูกค้าที่มาคำขอ

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

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

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

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

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

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

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

จะเกิดอะไรขึ้นถ้าฉันเพิ่มการกระทำมากเกินไปในคราวเดียวนั่นจะไม่ทำให้เกิดปัญหากับการเชื่อมต่อหรือไม่ มีวิธีใดบ้างที่จะบรรเทาได้

ครั้งแรกสิ่งที่ชัดเจน อย่าส่งสตริงหรือวัตถุที่เป็นอนุกรม อย่าส่งข้อความเร็วเท่าที่ตัวเกมกำลังอัพเดท ส่งเป็นชิ้น ๆ (เช่น 10 ครั้งต่อวินาที) จะมีข้อผิดพลาด (โดยเฉพาะอย่างยิ่งการปัดเศษข้อผิดพลาด) ที่เซิร์ฟเวอร์ / ไคลเอนต์จะต้องแก้ไขข้อผิดพลาดเป็นครั้งคราว (ดังนั้นทุกวินาทีเซิร์ฟเวอร์ส่งทุกอย่าง [ทุกอย่าง = ข้อมูลทั้งหมดที่สำคัญเพื่อให้สิ่งต่าง ๆ ในเกมของคุณถูกต้อง state] ซึ่งลูกค้าจะยึดและ / หรือคำนึงถึงการแก้ไขสถานะ)แก้ไข: หนึ่งในเพื่อนร่วมงานของฉันกล่าวว่าหากคุณใช้ UDP [ซึ่งคุณควรถ้าคุณมีข้อมูลจำนวนมาก] แสดงว่าไม่มีการสั่งซื้อเครือข่าย การส่งมากกว่า 10 แพ็คเก็ตที่สองเพิ่มการเปลี่ยนแปลงของแพ็กเก็ตที่มาถึงตามลำดับ ฉันจะดูว่าฉันสามารถอ้างสิทธิ์ที่ สำหรับแพ็กเก็ตที่ไม่เรียบร้อยคุณสามารถทิ้งหรือเพิ่มลงใน message / move ของระบบซึ่งออกแบบมาเพื่อจัดการกับการเปลี่ยนแปลงในอดีตเพื่อแก้ไขสถานะปัจจุบัน

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

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

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

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


ขอบคุณมากสำหรับการตอบสนองอย่างละเอียดซึ่งครอบคลุมความกังวลทั้งหมดของฉัน so every second, the server sends everything ... which the client then snaps toเพียงหนึ่งคำถามเกี่ยวกับส่วนหนึ่งที่คุณบอกว่า ฉันสามารถส่งข้อมูลจำนวนมากเช่นนี้พร้อมกันได้หรือไม่? ขนาดสูงสุดที่เหมาะสมคืออะไร
David Gouveia

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

นอกจากนี้ยังไม่มีกฎที่ยากที่คุณควรส่งก้อนใหญ่หนึ่งก้อนฉันเพิ่งยกตัวอย่างเช่น
Samaursa

@ Samaursa - ฉันจะไม่เห็นด้วยกับ "ถ้าคุณใช้ UDP [ที่คุณควรถ้าคุณมีข้อมูลจำนวนมาก]" เนื่องจากเป็นโปรแกรมใหม่สำหรับการเขียนโปรแกรมเครือข่าย TCP จะดูแลเรื่องยากที่สุดสำหรับคุณในราคาที่ถูกกว่ามาก ในกรณีของพวกเขาฉันจะใช้ TCP จนกว่ามันจะกลายเป็นคอขวด ณ จุดนั้นพวกเขาจะมีความเข้าใจที่ดีขึ้นเกี่ยวกับวิธีการที่แอพใช้งานเครือข่าย
Chris

@Chris: ฉันจะไม่เห็นด้วย :) ... การตัดสินใจระหว่างการใช้ TCP และ UDP นั้นเหมือนกับ (imo) การตัดสินใจเลือก Python และ C ++ สำหรับการพัฒนา ในทางทฤษฎีอาจฟังดูโอเค แต่ในทางปฏิบัติมันยากที่จะเปลี่ยน (อีกครั้งนั่นคือ imo)
Samaursa
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.