Cấu trúc công cụ trò chơi yêu thích của tôi là mô hình thành phần và giao diện <-> sử dụng tin nhắn để liên lạc giữa hầu hết các bộ phận.
Bạn có nhiều giao diện cho các bộ phận động cơ chính như trình quản lý cảnh, trình tải tài nguyên, âm thanh, trình kết xuất, vật lý, v.v.
Tôi có người quản lý cảnh phụ trách tất cả các đối tượng trong thế giới / thế giới 3D.
Đối tượng là một lớp rất nguyên tử, chỉ chứa một vài thứ phổ biến cho hầu hết mọi thứ trong cảnh của bạn, trong công cụ của tôi, lớp đối tượng chỉ giữ vị trí, xoay, danh sách các thành phần và ID duy nhất. Mọi ID của đối tượng được tạo bởi một int int, do đó sẽ không có hai đối tượng nào có cùng một ID, điều này cho phép bạn gửi tin nhắn đến một đối tượng bằng ID của nó, thay vì phải có một con trỏ tới đối tượng.
Danh sách các thành phần trên đối tượng là những gì mang lại cho các đối tượng đó là các thuộc tính chính. Ví dụ, đối với một cái gì đó mà bạn có thể nhìn thấy trong thế giới 3D, bạn sẽ cung cấp cho đối tượng của mình một thành phần kết xuất có chứa thông tin về lưới kết xuất. Nếu bạn muốn một đối tượng có vật lý, bạn sẽ cung cấp cho nó một thành phần vật lý. Nếu bạn muốn một cái gì đó hoạt động như một máy ảnh, hãy cung cấp cho nó một thành phần máy ảnh. Danh sách các thành phần có thể đi và về.
Giao tiếp giữa các giao diện, đối tượng và các thành phần là chìa khóa. Trong công cụ của tôi, tôi có một lớp thông báo chung chỉ chứa một ID duy nhất và ID loại thông báo. ID duy nhất là ID của đối tượng bạn muốn gửi tin nhắn và ID loại tin nhắn được sử dụng bởi đối tượng nhận tin nhắn để nó biết loại tin nhắn đó là gì.
Các đối tượng có thể xử lý tin nhắn nếu họ cần và họ có thể truyền tin nhắn cho từng thành phần của mình và các thành phần thường sẽ làm những việc quan trọng với tin nhắn. Ví dụ: nếu bạn muốn thay đổi và vị trí của đối tượng bạn gửi cho đối tượng một thông báo SetPocation, đối tượng có thể cập nhật biến vị trí của nó khi nhận được thông báo, nhưng thành phần kết xuất có thể cần thông báo để cập nhật vị trí của lưới kết xuất và thành phần vật lý có thể cần thông điệp để cập nhật vị trí của cơ thể vật lý.
Đây là một bố cục rất đơn giản của trình quản lý cảnh, đối tượng và thành phần và luồng thông báo mà tôi đã đánh dấu trong khoảng một giờ, được viết bằng C ++. Khi chạy, nó đặt vị trí trên một đối tượng và thông báo đi qua thành phần kết xuất, sau đó lấy vị trí từ đối tượng. Thưởng thức!
Ngoài ra, tôi đã viết một phiên bản C # và phiên bản Scala của mã dưới đây cho bất kỳ ai có thể thông thạo những thứ đó hơn là 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';
}