คำแนะนำเกี่ยวกับการเชื่อมโยงระหว่างระบบคอมโพเนนต์ของเอ็นติตี้ใน 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.