Bagaimana cara menerapkan interaksi antara bagian-bagian mesin?


10

Saya ingin mengajukan pertanyaan tentang bagaimana pertukaran informasi antara bagian-bagian mesin game harus dilaksanakan.

Mesin dipisahkan dalam empat bagian: logika, data, UI, grafik. Pada awalnya saya melakukan pertukaran ini melalui bendera. Misalnya jika objek baru ditambahkan dalam data, bendera isNewdi kelas objek akan ditetapkan sebagai true. Dan setelah itu bagian grafis dari mesin akan memeriksa bendera ini, dan akan menambahkan objek ke dunia game.

Padahal, dengan pendekatan ini saya harus menulis banyak kode untuk memproses setiap bendera dari setiap jenis objek.

Saya berpikir untuk menggunakan beberapa sistem acara, tetapi saya tidak memiliki pengalaman yang cukup untuk mengetahui apakah ini akan menjadi solusi yang tepat.

Apakah sistem acara satu-satunya pendekatan yang tepat, atau haruskah saya menggunakan sesuatu yang lain?

Saya menggunakan Ogre sebagai mesin grafis, jika itu penting.


Ini pertanyaan yang sangat samar. Bagaimana sistem Anda berinteraksi akan sangat digabungkan dengan bagaimana sistem Anda dirancang, dan jenis enkapsulasi yang akhirnya Anda lakukan. Tapi satu hal yang menonjol: "Dan setelah itu bagian grafis dari mesin akan memeriksa bendera ini, dan akan menambahkan objek ke dunia game." Mengapa bagian grafis dari mesin menambahkan sesuatu ke dunia ? Sepertinya dunia harus memberi tahu modul grafis apa yang harus dibuat.
Tetrad

Di mesin, bagian "grafis" mengontrol Ogre (misalnya, memintanya untuk menambahkan objek ke dalam adegan). Tetapi untuk melakukan itu juga mencari "data" untuk objek yang baru (dan setelah itu memberitahu Ogre untuk menambahkannya ke dalam adegan) Tapi saya tidak tahu apakah pendekatan ini benar atau salah karena kurangnya pengalaman.
Userr

Jawaban:


20

Struktur mesin permainan favorit saya adalah antarmuka dan objek <-> model komponen menggunakan pesan untuk komunikasi antara hampir semua bagian.

Anda memiliki banyak antarmuka untuk bagian-bagian mesin utama seperti manajer adegan, pemuat sumber daya, audio, renderer, fisika, dll.

Saya memiliki manajer adegan yang bertanggung jawab atas semua objek dalam adegan / dunia 3D.

Objek adalah kelas yang sangat atomik, hanya berisi beberapa hal yang umum untuk hampir semua yang ada dalam adegan Anda, di mesin saya kelas objek hanya memegang posisi, rotasi, daftar komponen, dan ID unik. ID setiap objek dihasilkan oleh int statis, sehingga tidak ada dua objek yang masing-masing akan memiliki ID yang sama, ini memungkinkan Anda untuk mengirim pesan ke objek dengan ID-nya, daripada harus memiliki pointer ke objek.

Daftar komponen pada objek adalah apa yang memberi objek itu sifat utama. Misalnya, untuk sesuatu yang dapat Anda lihat di dunia 3D, Anda akan memberikan objek Anda komponen render yang berisi informasi tentang render mesh. Jika Anda ingin objek memiliki fisika Anda akan memberikannya komponen fisika. Jika Anda ingin sesuatu bertindak sebagai kamera, berikan komponen kamera. Daftar komponen dapat terus dan terus.

Komunikasi antara antarmuka, objek, dan komponen adalah kuncinya. Di mesin saya, saya memiliki kelas pesan umum yang hanya berisi ID unik, dan ID jenis pesan. ID unik adalah ID objek yang ingin Anda kirimi pesan, dan ID jenis pesan digunakan oleh objek yang menerima pesan sehingga ia tahu jenis pesannya.

Objek dapat menangani pesan jika mereka membutuhkan, dan mereka dapat meneruskan pesan ke masing-masing komponennya, dan komponen akan sering melakukan hal-hal penting dengan pesan tersebut. Misalnya, jika Anda ingin mengubah dan posisi objek Anda mengirim objek pesan SetPosition, objek dapat memperbarui variabel posisinya ketika menerima pesan, tetapi komponen render mungkin perlu pesan untuk memperbarui posisi mesh render, dan komponen fisika mungkin memerlukan pesan untuk memperbarui posisi tubuh fisika.

Berikut ini adalah tata letak adegan manajer, objek, dan komponen, dan aliran pesan yang sangat sederhana, yang saya siapkan sekitar satu jam, ditulis dalam C ++. Ketika menjalankannya mengatur posisi pada objek, dan pesan melewati komponen render, lalu mengambil posisi dari objek. Nikmati!

Juga, saya telah menulis versi C # dan versi Scala dari kode di bawah ini untuk siapa saja yang mungkin lancar dalam hal itu daripada C ++.

#include <iostream>
#include <stdio.h>

#include <list>
#include <map>

using namespace std;

struct Vector3
{
public:
    Vector3() : x(0.0f), y(0.0f), z(0.0f)
    {}

    float x, y, z;
};

enum eMessageType
{
    SetPosition,
    GetPosition,    
};

class BaseMessage
{
protected: // Abstract class, constructor is protected
    BaseMessage(int destinationObjectID, eMessageType messageTypeID) 
        : m_destObjectID(destinationObjectID)
        , m_messageTypeID(messageTypeID)
    {}

public: // Normally this isn't public, just doing it to keep code small
    int m_destObjectID;
    eMessageType m_messageTypeID;
};

class PositionMessage : public BaseMessage
{
protected: // Abstract class, constructor is protected
    PositionMessage(int destinationObjectID, eMessageType messageTypeID, 
                    float X = 0.0f, float Y = 0.0f, float Z = 0.0f)
        : BaseMessage(destinationObjectID, messageTypeID)
        , x(X)
        , y(Y)
        , z(Z)
    {

    }

public:
    float x, y, z;
};

class MsgSetPosition : public PositionMessage
{
public:
    MsgSetPosition(int destinationObjectID, float X, float Y, float Z)
        : PositionMessage(destinationObjectID, SetPosition, X, Y, Z)
    {}
};

class MsgGetPosition : public PositionMessage
{
public:
    MsgGetPosition(int destinationObjectID)
        : PositionMessage(destinationObjectID, GetPosition)
    {}
};

class BaseComponent
{
public:
    virtual bool SendMessage(BaseMessage* msg) { return false; }
};

class RenderComponent : public BaseComponent
{
public:
    /*override*/ bool SendMessage(BaseMessage* msg)
    {
        // Object has a switch for any messages it cares about
        switch(msg->m_messageTypeID)
        {
        case SetPosition:
            {                   
                // Update render mesh position/translation

                cout << "RenderComponent handling SetPosition\n";
            }
            break;
        default:
            return BaseComponent::SendMessage(msg);
        }

        return true;
    }
};

class Object
{
public:
    Object(int uniqueID)
        : m_UniqueID(uniqueID)
    {
    }

    int GetObjectID() const { return m_UniqueID; }

    void AddComponent(BaseComponent* comp)
    {
        m_Components.push_back(comp);
    }

    bool SendMessage(BaseMessage* msg)
    {
        bool messageHandled = false;

        // Object has a switch for any messages it cares about
        switch(msg->m_messageTypeID)
        {
        case SetPosition:
            {               
                MsgSetPosition* msgSetPos = static_cast<MsgSetPosition*>(msg);
                m_Position.x = msgSetPos->x;
                m_Position.y = msgSetPos->y;
                m_Position.z = msgSetPos->z;

                messageHandled = true;
                cout << "Object handled SetPosition\n";
            }
            break;
        case GetPosition:
            {
                MsgGetPosition* msgSetPos = static_cast<MsgGetPosition*>(msg);
                msgSetPos->x = m_Position.x;
                msgSetPos->y = m_Position.y;
                msgSetPos->z = m_Position.z;

                messageHandled = true;
                cout << "Object handling GetPosition\n";
            }
            break;
        default:
            return PassMessageToComponents(msg);
        }

        // If the object didn't handle the message but the component
        // did, we return true to signify it was handled by something.
        messageHandled |= PassMessageToComponents(msg);

        return messageHandled;
    }

private: // Methods
    bool PassMessageToComponents(BaseMessage* msg)
    {
        bool messageHandled = false;

        auto compIt = m_Components.begin();
        for ( compIt; compIt != m_Components.end(); ++compIt )
        {
            messageHandled |= (*compIt)->SendMessage(msg);
        }

        return messageHandled;
    }

private: // Members
    int m_UniqueID;
    std::list<BaseComponent*> m_Components;
    Vector3 m_Position;
};

class SceneManager
{
public: 
    // Returns true if the object or any components handled the message
    bool SendMessage(BaseMessage* msg)
    {
        // We look for the object in the scene by its ID
        std::map<int, Object*>::iterator objIt = m_Objects.find(msg->m_destObjectID);       
        if ( objIt != m_Objects.end() )
        {           
            // Object was found, so send it the message
            return objIt->second->SendMessage(msg);
        }

        // Object with the specified ID wasn't found
        return false;
    }

    Object* CreateObject()
    {
        Object* newObj = new Object(nextObjectID++);
        m_Objects[newObj->GetObjectID()] = newObj;

        return newObj;
    }

private:
    std::map<int, Object*> m_Objects;
    static int nextObjectID;
};

// Initialize our static unique objectID generator
int SceneManager::nextObjectID = 0;

int main()
{
    // Create a scene manager
    SceneManager sceneMgr;

    // Have scene manager create an object for us, which
    // automatically puts the object into the scene as well
    Object* myObj = sceneMgr.CreateObject();

    // Create a render component
    RenderComponent* renderComp = new RenderComponent();

    // Attach render component to the object we made
    myObj->AddComponent(renderComp);

    // Set 'myObj' position to (1, 2, 3)
    MsgSetPosition msgSetPos(myObj->GetObjectID(), 1.0f, 2.0f, 3.0f);
    sceneMgr.SendMessage(&msgSetPos);
    cout << "Position set to (1, 2, 3) on object with ID: " << myObj->GetObjectID() << '\n';

    cout << "Retreiving position from object with ID: " << myObj->GetObjectID() << '\n';

    // Get 'myObj' position to verify it was set properly
    MsgGetPosition msgGetPos(myObj->GetObjectID());
    sceneMgr.SendMessage(&msgGetPos);
    cout << "X: " << msgGetPos.x << '\n';
    cout << "Y: " << msgGetPos.y << '\n';
    cout << "Z: " << msgGetPos.z << '\n';
}

1
Kode ini terlihat sangat bagus. Mengingatkan saya pada Unity.
Tili

Saya tahu ini adalah jawaban lama, tetapi saya punya beberapa pertanyaan. Bukankah permainan 'nyata' memiliki ratusan jenis Pesan, membuat mimpi buruk pengkodean? Juga, apa yang Anda lakukan jika Anda perlu (misalnya) cara karakter utama menghadap untuk menggambar dengan benar. Tidakkah Anda perlu membuat GetSpriteMessage baru dan mengirimkannya setiap kali Anda membuat? Bukankah ini menjadi terlalu mahal? Hanya ingin tahu! Terima kasih.
you786

Dalam proyek terakhir saya, kami menggunakan XML untuk menulis pesan dan skrip python membuat semua kode untuk kami selama waktu pembuatan. Anda dapat memisahkan menjadi beberapa XML untuk berbagai kategori pesan. Anda dapat membuat makro untuk pengiriman pesan, membuatnya hampir sesingkat panggilan fungsi, jika Anda membutuhkan cara karakter dihadapkan tanpa olahpesan Anda masih perlu mendapatkan pointer ke komponen, dan kemudian tahu fungsi untuk memanggil itu (jika Anda tidak menggunakan olahpesan). RenderComponent dapat mendaftar dengan renderer sehingga Anda tidak perlu meminta setiap frame.
Nic Foster

2

Saya pikir ini adalah cara terbaik untuk menggunakan Scene Manager dan Interfaces. Pesan sudah diimplementasikan tetapi saya akan menggunakannya sebagai pendekatan sekunder. Pesan bagus untuk komunikasi antar utas. Gunakan abstraksi (antarmuka) di mana pun Anda bisa.

Saya tidak tahu banyak tentang Ogre, jadi saya berbicara secara umum.

Pada intinya, Anda memiliki lingkaran permainan utama. Ia mendapat sinyal input, menghitung AI (dari gerakan sederhana hingga AI kompleks dan logika permainan), memuat sumber daya [, dll] dan menjadikan status saat ini. Ini adalah contoh dasar, sehingga Anda dapat memisahkan mesin menjadi bagian-bagian ini (InputManager, AIManager, ResourceManager, RenderManager). Dan Anda harus memiliki SceneManager yang menampung semua objek yang ada dalam game.

Setiap bagian ini dan sub-bagiannya memiliki antarmuka. Jadi cobalah untuk mengatur bagian-bagian ini untuk melakukan pekerjaan mereka dan hanya mereka. Mereka harus menggunakan sub-bagian yang berinteraksi secara internal untuk tujuan bagian induknya. Dengan begitu Anda tidak akan terlibat tanpa peluang membuka tanpa menulis ulang total.

ps jika Anda menggunakan C ++ pertimbangkan untuk menggunakan pola RAII


2
RAII bukanlah sebuah pola, ini adalah cara hidup.
Shotgun Ninja
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.