วิธีการเครือข่ายระบบนิติบุคคลนี้


33

ฉันได้ออกแบบระบบเอนทิตีสำหรับ FPS มันใช้งานได้ดังนี้:

เรามี "โลก" - วัตถุเรียกว่า GameWorld สิ่งนี้ถืออาร์เรย์ของ GameObject เช่นเดียวกับอาร์เรย์ของ ComponentManager

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

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

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

ComponentManager ยังดูแลการอัพเดทส่วนประกอบต่างๆเช่น PhysicsComponent ที่ฉันจะใช้ห้องสมุดภายนอก (ซึ่งทำทุกอย่างในโลกพร้อมกัน)

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

มาถึงปัญหาของฉันแล้วฉันจะลองใช้มันกับเกมที่มีผู้เล่นหลายคน ฉันไม่รู้ว่าจะเข้าใกล้สิ่งนี้อย่างไร

ประการแรก: สิ่งที่ลูกค้าควรมีตั้งแต่ต้น? ฉันควรเริ่มด้วยการอธิบายว่าเอ็นจิ้นผู้เล่นเดี่ยวจะกำหนดเอนทิตีที่จะสร้างได้อย่างไร

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

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

เมื่อคุณโหลดในระดับนั้นมันจะทำการติดตั้งเอนทิตีทั้งหมด ฟังดูง่ายใช่มั้ย

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

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

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

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

ฉันมีปัญหาใหญ่เข้ามาใกล้สิ่งนี้ ฉันจะขอบคุณถ้าคุณช่วยฉันในระหว่างทาง ฉันเปิดรับเคล็ดลับในการปรับปรุงการออกแบบระบบส่วนประกอบด้วยดังนั้นอย่ากลัวที่จะแนะนำสิ่งนั้น

คำตอบ:


13

นี่คือสัตว์ร้ายที่น่ากลัวสำหรับคำถามที่มีรายละเอียดมากมาย +1 นั่น แน่นอนพอที่จะช่วยคนที่สะดุดมัน

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

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

การเข้ารหัสเดลต้าหรือความแตกต่างของข้อมูล:เซิร์ฟเวอร์ดำเนินการกับข้อมูลที่ลูกค้ารู้และส่งเฉพาะความแตกต่างระหว่างข้อมูลเก่าและสิ่งที่ควรเปลี่ยนเป็นไคลเอนต์ เช่น pseudo-> ในตัวอย่างหนึ่งคุณอาจส่งข้อมูล "315 435 222 3546 33" เมื่อข้อมูลมีอยู่แล้ว "310 435 210 4000 40" บางตัวมีการเปลี่ยนแปลงเพียงเล็กน้อยและไม่มีการเปลี่ยนแปลงเลย! คุณจะส่ง (ในเดลต้า) "5 0 12 -454 -7" ซึ่งสั้นกว่ามาก

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

ข้อดี: (สามารถนึกถึงหนึ่งในตอนนี้เท่านั้น)

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

จุดด้อย:

  1. หากคุณกำลังอัปเดตระบบของคุณและต้องการเพิ่มข้อมูลเพิ่มเติมที่ควรแก้ไขผ่านเดลต้าคุณจะต้องสร้างฟังก์ชั่นใหม่เพื่อเปลี่ยนข้อมูล! (เช่นก่อนหน้านี้ "list.kill 30 ที่ 5" โอ้ฉันต้องเพิ่มวิธียกเลิกไปยังไคลเอนต์! "list.kill เลิกทำ")

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

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

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

ส่วนตัวนี้เป็นชั้นเชิงของสามัญสำนึก มันเป็นสิ่งที่ฉันคิดว่าฉันฉลาดในการคิดขึ้นมานานแล้วซึ่งกลายเป็นว่าใช้ตลอดเวลา

คำตอบ

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

ส่วนตัวฉันจะยกตัวอย่างข้อมูลบนไคลเอนต์เมื่อได้รับข้อมูลเกี่ยวกับมันจากเซิร์ฟเวอร์ (สิ่งที่คุณแนะนำ)

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

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

สำหรับวิธีที่มันควรรู้ว่าตัวแปรใดในเครือข่ายฉันอาจมีส่วนประกอบที่เป็นวัตถุย่อยอย่างแท้จริงและมอบส่วนประกอบที่คุณต้องการให้กับเครือข่าย ความคิดอื่นคือไม่เพียงAddComponent("whatever")แต่มีAddNetComponent("and what have you")เพียงเพราะมันฟังดูฉลาดเป็นการส่วนตัว


นี่เป็นคำตอบที่ยาวเหยียด! ฉันเสียใจอย่างมากเกี่ยวกับเรื่องนี้ ตามที่ฉันตั้งใจจะให้ความรู้เพียงเล็กน้อยเท่านั้นจากนั้น 2 เซ็นต์ของฉันเกี่ยวกับบางสิ่ง ดังนั้นฉันเข้าใจว่าอาจมีความจำเป็นเล็กน้อยที่จะต้องทราบ
Joshua Hedges

3

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

อันดับแรก +1 สำหรับคำถามที่เขียนไว้อย่างดีพร้อมรายละเอียดมากมายเพื่อตัดสินคำตอบโดย

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

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

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

หวังว่านี่จะช่วยได้


ดังนั้นสิ่งที่คุณกำลังพูดคือส่วนประกอบที่ควรเชื่อมต่อเครือข่ายควรใช้อินเตอร์เฟสบางประเภทเช่นนี้?: void SetNetworkedVariable (ชื่อสตริง, ค่า NetworkedVariable); NetworkedVariable GetNetworkedVariable (ชื่อสตริง); ตำแหน่งที่ NetworkedVariable ใช้เพื่อจุดประสงค์ในการแก้ไขและสิ่งอื่น ๆ ในเครือข่าย ฉันไม่ทราบวิธีระบุส่วนประกอบที่ใช้สิ่งนี้ ฉันสามารถใช้การระบุชนิดรันไทม์ แต่นั่นก็ดูน่าเกลียดสำหรับฉัน
Carter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.