11
\$\begingroup\$

I want to ask a question about how the information exchange between game engine parts should be implemented.

The engine is separated in four parts: logic, data, UI, graphics. In the beginning I made this exchange through the flags. For example if the new object is added in the data, the flag isNew in a object's class will be set as true. And after that the graphics part of the engine will check this flag, and will add the object into the game world.

Though, with this approach I was to write much code to process every flag of each kind of object.

I thought of using some event system, but I don't have enough experience to know whether this would be the right solution.

Is the event system the only appropriate approach, or should I use something else?

I’m using Ogre as the graphics engine, if that matters.

\$\endgroup\$
2
  • \$\begingroup\$ This is a very vague question. How your systems interact is going to be very coupled to how your systems are designed, and what kind of encapsulation you're ending up doing. But one thing stands out: "And after that the graphics part of the engine will check this flag, and will add the object into the game world." Why is the graphics part of the engine adding things to the world? It seems like the world should tell the graphics module what to render. \$\endgroup\$
    – Tetrad
    Feb 8, 2012 at 17:27
  • \$\begingroup\$ In the engine the "graphics" part controls the Ogre (for example, tells it to add object into the scene). But for doing that it also searches the "data" for the object which is new (and after that tells Ogre to add it into the scene) But I don't know whether this approach is right or wrong because of lack of experience. \$\endgroup\$
    – Userr
    Feb 9, 2012 at 5:43

2 Answers 2

21
\$\begingroup\$

My favorite game engine structure is the interface and object<->component model using messaging for communication between almost all parts.

You have multiple interfaces for main engine parts such as your scene manager, resource loader, audio, renderer, physics, etc.

I have the scene manager in charge of all objects in the 3D scene/world.

Object is a very atomic class, containing only a few things that are common to almost everything in your scene, in my engine the object class holds only position, rotation, a list of components, and a unique ID. Every object's ID is generated by a static int, so that no two objects will every have the same ID, this allows you to send messages to an object by its ID, rather than having to have a pointer to the object.

The list of components on the object is what gives that objects is main properties. For example, for something that you can see in the 3D world, you would give your object a render component that contains the information about the render mesh. If you want an object to have physics you would give it a physics component. If you want something to act as a camera, give it a camera component. The list of components can go on and on.

Communication between interfaces, objects, and components is key. In my engine I have a generic message class that contains only a unique ID, and a message type ID. The unique ID is the ID of the object you want the message to go to, and the message type ID is used by the object receiving the message so it knows what type of message it is.

Objects can handle the message if they need, and they can pass the message on to each of their components, and components will often do important things with the message. For example, if you want to change and object's position you send the object a SetPosition message, the object may update its position variable when it gets the message, but the render component may need to message to update the position of the render mesh, and the physics component may need the message to update the physics body's position.

Here is a very simple layout of scene manager, object, and component, and message flow, that I whipped up in about an hour, written in C++. When run it sets the position on an object, and the message passes through the render component, then retrieves the position from the object. Enjoy!

Also, I've written a C# version and Scala version of the below code for anyone that might be fluent in those rather than 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';
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ This code looks really nice. Reminds me of Unity. \$\endgroup\$
    – Tili
    Feb 9, 2012 at 8:51
  • \$\begingroup\$ I know this is an old answer, but I have a few questions. Wouldn't a 'real' game have hundreds of Message types, making a coding nightmare? Also, what do you do if you need (for example) the way the main character is facing to draw it correctly. Wouldn't you need to create a new GetSpriteMessage and send it in every single time you render? Doesn't this become too expensive? Just wondering! Thanks. \$\endgroup\$
    – you786
    May 3, 2012 at 7:29
  • \$\begingroup\$ In my last project we used XML to write the messages and a python script created all of the code for us during build time. You can separate into multiple XMLs for different message categories. You can create macros for message sending, making them almost as terse as a function call, if you needed the way a character was facing without messaging you'd still need to get the pointer to the component, and then know the function to call on it (if you weren't using messaging). RenderComponent can register with the renderer so you don't have to query it every frame. \$\endgroup\$
    – Nic Foster
    Jun 21, 2012 at 15:40
2
\$\begingroup\$

I think it is the best way to use Scene Manager and Interfaces. Have messaging implemented but I would use it as secondary approach. Messaging is good for inter-thread communication. Use abstraction (interfaces) wherever you can.

I don't know much about Ogre, so I'm speaking generally.

At core, you have main game loop. It gets input signals, calculates AI (from simple motion to complex AI and game logic), loads resources[, etc] and renders current state. This is basic example, so you can separate engine into these parts (InputManager, AIManager, ResourceManager, RenderManager). And you should have SceneManager which holds all objects that are present in game.

Every of these parts and their sub-parts have interfaces. So try to organize these parts to do their and only their job. They should use sub-parts that interact internally for the purpose of their parent-part. That way you wont get in embroil without chance of unrolling without total rewrite.

p.s. if you're using C++ consider using RAII pattern

\$\endgroup\$
1
  • 2
    \$\begingroup\$ RAII is not a pattern, it's a way of life. \$\endgroup\$ Apr 4, 2013 at 14:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .