내가 가장 좋아하는 게임 엔진 구조는 거의 모든 부분 간의 통신을 위해 메시징을 사용하는 인터페이스 및 객체 구성 요소 모델입니다.
장면 관리자, 리소스 로더, 오디오, 렌더러, 물리학 등 주요 엔진 부품에 대한 여러 인터페이스가 있습니다.
3D 장면 / 세계의 모든 객체를 담당하는 장면 관리자가 있습니다.
Object는 매우 원자적인 클래스이며 장면의 거의 모든 것에 공통되는 몇 가지 항목 만 포함합니다. 엔진에서 오브젝트 클래스는 위치, 회전, 구성 요소 목록 및 고유 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';
}