คำแนะนำเกี่ยวกับการเชื่อมโยงระหว่างระบบคอมโพเนนต์ของเอ็นติตี้ใน C ++


10

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

for (entity x : listofentities) {
   for (system y : listofsystems) {
       if ((x.componentBitmask & y.bitmask) == y.bitmask)
             y.update(x, deltatime)
       }
 }

แต่ฉันคิดว่าระบบ bitmask จะปิดกั้นความยืดหยุ่นในกรณีที่ฝังภาษาสคริปต์ หรือมีรายการท้องถิ่นสำหรับแต่ละระบบจะเพิ่มการใช้หน่วยความจำสำหรับคลาส ฉันสับสนมาก


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

ใช้ bitmask ตัวอย่างเช่น int จะถือ 32 องค์ประกอบที่แตกต่างกันเท่านั้น ฉันไม่ได้หมายความว่าจะมีส่วนประกอบมากกว่า 32 ตัว แต่จะเกิดอะไรขึ้นถ้าฉันมี ฉันจะต้องสร้าง int อื่นหรือ 64 บิต int มันจะไม่เป็นแบบไดนามิก
deniz

คุณสามารถใช้ std :: bitset หรือ std :: vector <bool> ขึ้นอยู่กับว่าคุณต้องการให้มันเป็น dynamic-time หรือไม่
Benjamin Kloster

คำตอบ:


7

การมีรายการโลคัลสำหรับแต่ละระบบจะเพิ่มการใช้หน่วยความจำสำหรับคลาส

มันเป็นแบบดั้งเดิมtradeoff พื้นที่เวลา

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

ที่กล่าวว่าวิธีนี้อาจยังดีพอขึ้นอยู่กับเป้าหมายของคุณ

ถึงแม้ว่าหากคุณกังวลเรื่องความเร็ว แต่ก็มีอีกวิธีแก้ไขที่ควรพิจารณา

ทุกระบบควรมีรายการขององค์กรที่พวกเขาสนใจหรือไม่?

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

ตอนนี้วิธีการรักษา "รายการที่น่าสนใจ" เหล่านี้อาจไม่ชัดเจน ในส่วนของ data container นั้นstd::vector<entity*> targetsระบบของ class นั้นสมบูรณ์เพียงพอ ตอนนี้สิ่งที่ฉันทำคือ:

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

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

          for(auto sys = owner_world.systems.begin(); sys != owner_world.systems.end(); ++sys)
                  if((*sys)->components_signature.matches(new_signature) && !(*sys)->components_signature.matches(old_signature)) 
                          (*sys)->add(this);

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

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

รายการ std :: จะให้ O (1) ลบออก แต่ในอีกด้านหนึ่งคุณมีค่าใช้จ่ายหน่วยความจำเพิ่มเติมเล็กน้อย โปรดจำไว้ว่าเวลาส่วนใหญ่คุณจะประมวลผลเอนทิตีและไม่ลบออก - และสิ่งนี้จะทำได้เร็วขึ้นโดยใช้ std :: vector

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


5

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

class Entity {
  std::map<ComponentType, Component*> components;
};

เมื่อคุณพูดRigidBodyส่วนประกอบที่แนบมากับEntityคุณขอจากPhysicsระบบของคุณ ระบบสร้างองค์ประกอบและช่วยให้เอนทิตีเก็บตัวชี้ไป ระบบของคุณดูเหมือนว่า:

class PhysicsSystem {
  std::vector<RigidBodyComponent> rigidBodyComponents;
};

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

for(auto it = systems.begin(); it != systems.end(); ++it) {
  it->update();
}

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

for(auto it = rigidBodyComponents.begin(); it != rigidBodyComponents.end(); ++it) {
  it->update();
}

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

ณ จุดนี้คุณWorldจะวนupdateซ้ำผ่านระบบและเรียกใช้โดยไม่จำเป็นต้องทำซ้ำเอนทิตีเช่นกัน มัน (imho) การออกแบบที่ดีขึ้นเพราะจากนั้นความรับผิดชอบของระบบมีความชัดเจนมากขึ้น

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


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

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

ถ้าคุณบอกว่ามันจะดีกว่าสำหรับประสิทธิภาพฉันจะใช้รูปแบบของคุณ
deniz

@deniz ให้แน่ใจว่าคุณจริงโปรไฟล์รหัสของคุณต้นและมักจะระบุว่าการทำงานและไม่ได้สำหรับ engin โดยเฉพาะอย่างยิ่งของคุณ :)
pwny

ไม่เป็นไร :) ฉันจะทำครับทดสอบความเครียด
Deniz

1

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

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

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