การออกแบบเกมที่ใช้องค์ประกอบ


16

ฉันกำลังเขียนปืน (เช่น 1942, กราฟิก 2D แบบคลาสสิก) และฉันต้องการใช้การอนุมัติตามส่วนประกอบ จนถึงตอนนี้ฉันคิดถึงการออกแบบต่อไปนี้:

  1. องค์ประกอบแต่ละเกม (เรือเหาะขีปนาวุธเพิ่มพลังศัตรู) เป็นเอนทิตี

  2. แต่ละหน่วยงานคือชุดของส่วนประกอบที่สามารถเพิ่มหรือลบได้ในเวลาทำงาน ตัวอย่างเช่นตำแหน่ง, Sprite, Health, IA, Damage, BoundingBox เป็นต้น

แนวคิดคือ Airship, Projectile, Enemy, Powerup ไม่ใช่คลาสเกม เอนทิตีถูกกำหนดโดยส่วนประกอบที่เป็นเจ้าของเท่านั้น (และสามารถเปลี่ยนแปลงได้ในช่วงเวลา) ดังนั้นผู้เล่นเรือเหาะเริ่มต้นด้วย Sprite, ตำแหน่ง, สุขภาพและส่วนประกอบอินพุต Powerup มี Sprite, Position, BoundingBox และอื่น ๆ

หลักวนรอบจัดการเกม "ฟิสิกส์" นั่นคือส่วนประกอบต่าง ๆ มีปฏิสัมพันธ์กันอย่างไร:

foreach(entity (let it be entity1) with a Damage component)
    foreach(entity (let it be entity2) with a Health component)
    if(the entity1.BoundingBox collides with entity2.BoundingBox)
    {
        entity2.Health.decrease(entity1.Damage.amount());
    }

foreach(entity with a IA component)
    entity.IA.update(); 

foreach(entity with a Sprite component)
    draw(entity.Sprite.surface()); 

...

คอมโพเนนต์ถูกฮาร์ดโค้ดในแอปพลิเคชัน C ++ หลัก เอนทิตีสามารถกำหนดได้ในไฟล์ XML (ส่วน IA ในไฟล์ lua หรือ python)

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

  1. ให้ส่วนประกอบรับเอนทิตี้ของมัน

  2. ให้เอนทิตี้รับส่วนประกอบประเภท "type"

  3. สำหรับทุกหน่วยงานทำอะไรสักอย่าง

  4. สำหรับส่วนประกอบของเอนทิตีทั้งหมดให้ทำบางสิ่ง (เช่น: ทำให้เป็นอันดับ)

ฉันกำลังคิดถึงสิ่งต่อไปนี้:

class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component {...};
class Position : public Component {...};
class IA : public Component {... virtual void update() = 0; };

// I don't remember exactly the boost::fusion map syntax right now, sorry.
class Entity
{
   int id; // entity id
   boost::fusion::map< pair<Sprite, Sprite*>, pair<Position, Position*> > components;
   template <class C> bool has_component() { return components.at<C>() != 0; }
   template <class C> C* get_component() { return components.at<C>(); }
   template <class C> void add_component(C* c) { components.at<C>() = c; }
   template <class C> void remove_component(C* c) { components.at<C>() = 0; }
   void serialize(filestream, op) { /* Serialize all componets*/ }
...
};

std::list<Entity*> entity_list;

ด้วยการออกแบบนี้ฉันสามารถรับ # 1, # 2, # 3 (ด้วยการเพิ่ม :: fusion :: algorithm algorithm) และ # 4 ทุกอย่างก็โอ (1) (ตกลงไม่เหมือนกัน แต่ก็ยังเร็วมาก)

นอกจากนี้ยังมีการอนุมัติ "ทั่วไป" เพิ่มเติม:

class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component { static const int type_id = 0; };
class Position : public Component { static const int type_id = 1; };

class Entity
{
   int id; // entity id
   std::vector<Component*> components;
   bool has_component() { return components[i] != 0; }
   template <class C> C* get_component() { return dynamic_cast<C> components[C::id](); } // It's actually quite safe
...
};

การอนุมัติอีกอย่างหนึ่งก็คือการกำจัดคลาส Entity: Component แต่ละประเภทมีชีวิตอยู่ในรายการของตัวเอง ดังนั้นจึงมีรายการ Sprite, รายการสุขภาพ, รายการความเสียหายเป็นต้นฉันรู้ว่าพวกเขาอยู่ในเอนทิตีตรรกะเดียวกันเพราะรหัสนิติบุคคล สิ่งนี้ง่ายกว่า แต่ช้ากว่า: ส่วนประกอบของ IA นั้นจำเป็นต้องเข้าถึงส่วนประกอบของเอนทิตีอื่น ๆ ทั้งหมดและจะต้องค้นหารายการของส่วนประกอบอื่น ๆ ในแต่ละขั้นตอน

คุณคิดว่าการอนุมัติแบบไหนดีกว่ากัน? คือ :: แผนที่ฟิวชั่นที่เหมาะที่จะใช้ในทางนั้น?


2
ทำไมต้องลงคะแนน เกิดอะไรขึ้นกับคำถามนี้
Emiliano

คำตอบ:


6

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

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

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


คุณหมายถึงอะไรโดย "มุ่งเน้นข้อมูล"
Emiliano

มีข้อมูลจำนวนมากใน Google แต่นี่เป็นบทความที่ดีที่โผล่ขึ้นมาซึ่งควรให้ภาพรวมระดับสูงแล้วตามด้วยการอภิปรายตามที่เกี่ยวข้องกับระบบส่วนประกอบ: gamesfromwithin.com/data-oriented-design , gamedev net / topic / …
Skyler York

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

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

-1

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

// declare components here------------------------------
class component
{
};

class health:public component
{
public:
    int value;
};

class boundingbox:public component
{
public :
    int left,right,top,bottom;
    bool collision(boundingbox& other)
    {
        if (left < other.right || right > other.left)
            if (top < other.bottom || bottom > other.top)
                return true;
        return false;
    }
};

class damage : public component
{
public:
    int value;
};

// declare enteties here------------------------------

class entity
{
    virtual int id() = 0;
    virtual int size() = 0;
};

class aircraft :public entity, public health,public boundingbox
{
    virtual int id(){return 1;}
    virtual int size() {return sizeof(*this);};
};

class bullet :public entity, public damage, public boundingbox
{
    virtual int id(){return 2;}
    virtual int size() {return sizeof(*this);};
};

int main()
{
    entity* gameobjects[3];
    gameobjects[0] = new aircraft;
    gameobjects[1] = new bullet;
    gameobjects[2] = new bullet;
    for (int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if (dynamic_cast<boundingbox*>(gameobjects[i]) && dynamic_cast<boundingbox*>(gameobjects[j]) &&
                dynamic_cast<boundingbox*>(gameobjects[i])->collision(*dynamic_cast<boundingbox*>(gameobjects[j])))
                if (dynamic_cast<health*>(gameobjects[i]) && dynamic_cast<damage*>(gameobjects[j]))
                    dynamic_cast<health*>(gameobjects[i])->value -= dynamic_cast<damage*>(gameobjects[j])->value;
}

ใน Approch นี้ทุกองค์ประกอบเป็นฐานสำหรับเอนทิตีเพื่อให้องค์ประกอบมันเป็นตัวชี้ยังเป็นนิติบุคคล! สิ่งที่สองที่คุณขอคือการเข้าถึงส่วนประกอบของเอนทิตี้บางอย่างโดยตรง เมื่อฉันต้องการเข้าถึงความเสียหายในหนึ่งในเอนทิตีของฉันฉันใช้dynamic_cast<damage*>(entity)->valueดังนั้นหากentityมีองค์ประกอบความเสียหายมันจะคืนค่า หากคุณไม่แน่ใจว่าentityมีความเสียหายขององค์ประกอบหรือไม่คุณสามารถตรวจสอบif (dynamic_cast<damage*> (entity))ค่าส่งคืนของdynamic_castเป็น NULL ได้เสมอหากการส่งสัญญาณไม่ถูกต้องและตัวชี้เดียวกัน แต่มีประเภทที่ร้องขอหากถูกต้อง เพื่อทำบางสิ่งกับสิ่งentitiesที่มีบางอย่างที่componentคุณสามารถทำได้เช่นด้านล่าง

for (int i=0;i<enteties.size();i++)
    if (dynamic_cast<component*>(enteties[i]))
        //do somthing here

หากมีคำถามอื่นฉันยินดีที่จะตอบ


ทำไมฉันถึงลงคะแนน? เกิดอะไรขึ้นกับโซลูชันของฉัน
Ali1S232

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

ก่อนอื่นคำถามจะถามถึงโครงสร้างที่ทุกอินสแตนซ์ขององค์ประกอบเกี่ยวข้องกับเอนทิตี้เดียวเท่านั้นและคุณสามารถเปิดใช้งานและปิดใช้งานคอมโพเนนต์ได้โดยการเพิ่มbool isActiveคลาสไปยังฐานที่เป็นตัวแทนเท่านั้น ยังคงมีความจำเป็นสำหรับการแนะนำของส่วนประกอบที่ใช้งานได้เมื่อคุณกำหนด enteties แต่ฉันไม่คิดว่าเป็นปัญหาและยังคงมีการปรับปรุงคุณ componnent seprate (จำ Somthing เช่นdynamic_cast<componnet*>(entity)->update().
Ali1S232

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

ในขณะที่ฉันยอมรับว่าเป็นไปได้ที่จะใช้มันด้วยวิธีนี้ฉันไม่คิดว่ามันเป็นความคิดที่ดี นักออกแบบคุณไม่สามารถเขียนวัตถุเองได้เว้นแต่คุณจะมีคลาสüberหนึ่งคลาสที่สืบทอดส่วนประกอบที่เป็นไปได้ทั้งหมด และในขณะที่คุณสามารถเรียกการอัปเดตได้เพียงองค์ประกอบเดียวมันจะไม่มีเลย์เอาต์ในหน่วยความจำที่ดีในโมเดลที่ประกอบด้วยส่วนประกอบทั้งหมดของอินสแตนซ์ประเภทเดียวกันสามารถเก็บไว้ในหน่วยความจำได้อย่างใกล้ชิด คุณยังต้องพึ่งพา RTTI ซึ่งเป็นสิ่งที่มักปิดในเกมเนื่องจากเหตุผลด้านประสิทธิภาพ รูปแบบวัตถุที่เรียงลำดับที่ดีแก้ไขส่วนใหญ่
โมฆะ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.