โครงสร้างเอ็นจิ้นเกมโปรดของฉันคืออินเตอร์เฟสและโมเดลวัตถุ <-> โดยใช้การส่งข้อความเพื่อการสื่อสารระหว่างเกือบทุกส่วน
คุณมีส่วนต่อประสานหลายอันสำหรับชิ้นส่วนเครื่องยนต์หลักเช่นตัวจัดการฉาก, ตัวโหลดทรัพยากร, เสียง, ตัวเรนเดอร์, ฟิสิกส์ ฯลฯ
ฉันมีผู้จัดการฉากที่รับผิดชอบทุกอย่างในฉาก 3 มิติ / โลก
Object เป็นคลาสอะตอมมิคที่บรรจุเพียงบางสิ่งที่พบได้ทั่วไปในฉากของคุณในเอ็นจินของฉันคลาสวัตถุจะมีเฉพาะตำแหน่งการหมุนรายการส่วนประกอบและ ID เฉพาะ ID ของวัตถุทุกชิ้นถูกสร้างขึ้นโดย int แบบสแตติกเพื่อให้ไม่มีวัตถุสองชิ้นที่จะมี ID เดียวกันทั้งหมดนี้ช่วยให้คุณสามารถส่งข้อความไปยังวัตถุด้วย ID ของมันแทนที่จะต้องมีตัวชี้ไปยังวัตถุ
รายการส่วนประกอบบนวัตถุคือสิ่งที่ทำให้วัตถุนั้นเป็นคุณสมบัติหลัก ตัวอย่างเช่นสำหรับบางสิ่งที่คุณสามารถเห็นได้ในโลก 3 มิติคุณจะต้องให้องค์ประกอบการเรนเดอร์วัตถุของคุณที่มีข้อมูลเกี่ยวกับตาข่ายการเรนเดอร์ ถ้าคุณต้องการให้วัตถุมีฟิสิกส์คุณจะต้องให้มันเป็นองค์ประกอบของฟิสิกส์ หากคุณต้องการบางสิ่งบางอย่างที่จะทำหน้าที่เป็นกล้องให้องค์ประกอบกล้อง รายการส่วนประกอบสามารถไปเรื่อย ๆ
การสื่อสารระหว่างอินเตอร์เฟสวัตถุและส่วนประกอบเป็นกุญแจสำคัญ ในเอ็นจิ้นของฉันฉันมีคลาสข้อความทั่วไปที่มีเฉพาะ ID ที่ไม่ซ้ำกันและ ID ประเภทข้อความ ID ที่ไม่ซ้ำกันคือ ID ของวัตถุที่คุณต้องการให้ข้อความไปถึงและ ID ประเภทข้อความจะถูกใช้โดยวัตถุที่ได้รับข้อความเพื่อให้ทราบว่าเป็นข้อความประเภทใด
วัตถุสามารถจัดการกับข้อความได้หากต้องการและพวกเขาสามารถส่งข้อความไปยังแต่ละส่วนประกอบได้และส่วนประกอบมักจะทำสิ่งสำคัญกับข้อความ ตัวอย่างเช่นหากคุณต้องการเปลี่ยนและตำแหน่งของวัตถุที่คุณส่งวัตถุเป็นข้อความ SetPosition วัตถุอาจอัปเดตตัวแปรตำแหน่งเมื่อได้รับข้อความ แต่องค์ประกอบการเรนเดอร์อาจต้องส่งข้อความเพื่ออัพเดทตำแหน่งของตาข่ายการแสดงผลและ องค์ประกอบของฟิสิกส์อาจต้องการข้อความเพื่ออัพเดทตำแหน่งของร่างกายฟิสิกส์
นี่คือเลย์เอาต์อย่างง่าย ๆ ของตัวจัดการฉากวัตถุและองค์ประกอบและการไหลของข้อความที่ฉันใช้เวลาประมาณหนึ่งชั่วโมงเขียนด้วย C ++ เมื่อเรียกใช้จะตั้งตำแหน่งบนวัตถุและข้อความผ่านองค์ประกอบการแสดงผลแล้วดึงตำแหน่งจากวัตถุ สนุก!
นอกจากนี้ฉันยังได้เขียนโค้ดC #และเวอร์ชั่นScalaด้านล่างสำหรับทุกคนที่อาจใช้ภาษา 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';
}