如何实现发动机零件之间的相互作用?


10

我想问一个问题,如何实现游戏引擎各部分之间的信息交换。

引擎分为四个部分:逻辑,数据,UI,图形。一开始我是通过标志进行这种交换的。例如,如果将新对象添加到数据中,则isNew对象类中的标志将设置为true。之后,引擎的图形部分将检查此标志,并将对象添加到游戏世界中。

但是,使用这种方法,我要编写很多代码来处理每种对象的每个标志。

我曾考虑使用某种事件系统,但是我没有足够的经验来知道这是否是正确的解决方案。

事件系统是唯一合适的方法,还是我应该使用其他方法?

如果重要的话,我正在使用Ogre作为图形引擎。


这是一个非常模糊的问题。系统之间的交互方式将与系统的设计方式以及最终要进行的封装方式紧密相关。但是有一件事情引人注目:“然后,引擎的图形部分将检查该标志,并将对象添加到游戏世界中。” 为什么引擎的图形部分将事物添加到世界中?似乎世界应该告诉图形模块要渲染什么。
Tetrad 2012年

在引擎中,“图形”部分控制食人魔(例如,告诉食人魔将对象添加到场景中)。但是为此,它还会在“数据”中搜索新对象(然后告诉Ogre将其添加到场景中),但是由于缺乏经验,我不知道这种方法是对还是错。
Userr 2012年

Answers:


20

我最喜欢的游戏引擎结构是使用消息传递进行几乎所有部分之间的通信的接口和对象<->组件模型。

您具有用于主机部件的多个接口,例如场景管理器,资源加载器,音频,渲染器,物理等。

我让场景管理器负责3D场景/世界中的所有对象。

对象是一个非常原子的类,仅包含场景中几乎所有事物所共有的一些内容,在我的引擎中,对象类仅包含位置,旋转,组件列表和唯一ID。每个对象的ID是由一个静态int生成的,因此,没有两个对象都具有相同的ID,这使您可以通过其ID向对象发送消息,而不必具有指向该对象的指针。

对象上的组件列表使对象成为主要属性。例如,对于3D世界中可以看到的东西,可以为对象提供一个渲染组件,其中包含有关渲染网格物体的信息。如果您希望某个对象具有物理特性,则可以给它一个物理特性。如果要让某些东西充当照相机,请为其提供照相机组件。组件列表可以继续。

接口,对象和组件之间的通信是关键。在我的引擎中,我有一个通用的消息类,该类仅包含唯一的ID和消息类型ID。唯一ID是您希望消息转到的对象的ID,消息类型ID由接收消息的对象使用,因此它知道消息的类型。

对象可以根据需要处理消息,并且可以将消息传递给它们的每个组件,并且组件通常会对消息起重要作用。例如,如果要更改对象的位置并向其发送SetPosition消息,则对象在收到消息时可能会更新其位置变量,但是渲染组件可能需要消息以更新渲染网格的位置,并且物理组件可能需要消息来更新物理体的位置。

这是场景管理器,对象和组件以及消息流的非常简单的布局,我花了大约一个小时才用C ++编写。运行时,它将在对象上设置位置,然后消息通过渲染组件,然后从对象中检索位置。请享用!

另外,我为可能精通这些语言而不是C ++的任何人编写了以下代码的C#版本Scala版本

#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
这段代码看起来非常不错。让我想起了Unity。
提利2012年

我知道这是一个旧答案,但是我有几个问题。一款“真正的”游戏会不会有成百上千种的消息类型,使代码成为噩梦?另外,如果需要(例如)要正确显示主角朝向的方式,该怎么办。您是否不需要创建一个新的GetSpriteMessage并在每次渲染时发送它?这不是太贵了吗?就是想!谢谢。
you786

在我的上一个项目中,我们使用XML编写消息,并且在构建期间使用python脚本为我们创建了所有代码。您可以针对不同的消息类别将其分为多个XML。您可以创建用于消息发送的宏,使它们几乎与函数调用一样简洁,如果您需要字符面对的方式而没有消息传递,则仍然需要获取指向组件的指针,然后知道要调用的函数它(如果您不使用消息传递)。RenderComponent可以在渲染器中注册,因此您不必在每一帧都对其进行查询。
Nic Foster 2012年

2

我认为这是使用场景管理器和界面的最佳方法。已实现消息传递,但我会将其用作辅助方法。消息传递非常适合线程间通信。尽可能使用抽象(接口)。

我对Ogre不太了解,所以我一般来说。

核心是游戏主循环。它获取输入信号,计算AI(从简单动作到复杂AI和游戏逻辑),加载资源[等]并呈现当前状态。这是基本示例,因此您可以将引擎分为这些部分(InputManager,AIManager,ResourceManager,RenderManager)。并且您应该有SceneManager来保存游戏中存在的所有对象。

这些部分及其子部分中的每一个都有接口。因此,请尝试组织这些部分来完成其唯一的工作。他们应该使用子部件,这些子部件为了父部件而在内部进行交互。这样一来,您就不会陷入麻烦而没有完全重写的机会。

PS,如果您使用的是C ++,请考虑使用RAII模式


2
RAII不是一种模式,而是一种生活方式。
Shotgun Ninja
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.