DonerComponents هو إطار عمل كائن اللعبة القائم على مكون C ++ 14 لتطوير ألعاب الفيديو.
إذا لم تكن على دراية بهذا المفهوم ، فإنني أوصيك بإلقاء نظرة على مشروع المثال أو البرنامج التعليمي وتجربته!
يمكنك الحصول على إصدارات مستقرة هنا.
بدلاً من ذلك ، يمكنك التحقق من إصدار التطوير الحالي بـ:
git clone https://github.com/Donerkebap13/DonerComponents.git
تذكر تشغيل git submodule update --init --recursive -بعد ذلك.
يمكنك الاتصال بي مباشرة عبر البريد الإلكتروني أيضًا ، إذا كان لديك أي اقتراح أو تجد أي خطأ ، يرجى عدم التردد في إنشاء مشكلة جديدة.
DonerComponents_asteroids_example هو مثال مشروع أنشأته لإظهار كيفية استخدام DonerComponents . إنه استنساخ كويكبات بسيط حقًا. نيتي في هذا المشروع هو إظهار جميع الميزات التي يدعمها الإطار الآن ، مثل:
هنا سأحاول توضيح الاستخدام الأساسي للأنظمة الرئيسية للعاملين. بعد قراءة هذا ، سيكون لديك المعرفة الأساسية حول كيفية تنظيم الأشياء وكيف يمكن استخدامها. لفهم أعمق أوصيك بإلقاء نظرة على مشروع المثال.
CDonerComponentsSystems هي singleton تهيئة وتتيح الوصول إلى جميع الأنظمة المختلفة donercomponents . يجب تهيئته:
# include < DonerComponents/CDonerComponentsSystems.h >
DonerComponents::CDonerComponentsSystems::CreateInstance ();
DonerComponents::CDonerComponentsSystems::Get ()->Init();تحديث:
float elapsed = ...;
DonerComponents::CDonerComponentsSystems::Get ()->Update(elapsed);وتدمير:
DonerComponents::CDonerComponentsSystems::DestroyInstance (); DonerComponents::CGameObject هو الممثل الرئيسي لـ DonerComponents. يمكن أن تحتوي هذه الفئة على DonerComponents::CComponent التي تحدد سلوكها. كما أن لديها معلومات عن والدها وأطفالها. يمكن أن تتلقى أيضًا رسائل POD وإعادة توجيهها إلى مكوناته وأطفالها. أخيرًا وليس آخرًا ، يمكن أيضًا وضع علامة عليها.
يعد إنشاء GameObject بسيطًا مثل:
# include < DonerComponents/gameObject/CGameObject.h >
DonerComponents::CGameObjectManager* gameObjectManager = DonerComponents::CDonerComponentsSystems::Get()-> GetGameObjectManager ();
DonerComponents::CGameObject *gameObject = gameObjectManager-> GetNewElement (); GetNewElement(); سيعود DonerComponents::CGameObject طالما لم ينفد من GameObjects لتوليده. بشكل افتراضي ، يمكن أن يكون لدى DonerComponents 4096 GameObjects على قيد الحياة في نفس الوقت. يمكن تعديل هذه القيمة من خلال علامة التحويل البرمجي -DMAX_GAME_OBJECTS=4096 بحد أقصى 8.192 GameObjects.
يدعم DonerComponents تعريف Prefabs ، بحيث يمكن للمستخدم تحديد التسلسل الهرمي لـ GameObject لإعادة استخدامه أينما كان ذلك:
# include < DonerComponents/gameObject/CPrefabManager.h >
DonerComponents::CGameObjectManager* prefabManager = DonerComponents::CDonerComponentsSystems::Get()-> GetPrefabManager ();
prefabManager-> RegisterPrefab ( " prefabName " , anyGameObjectCreatedPreviously);يمكن أيضًا تحميل prefabs من ملف JSON.
DonerComponents::CComponent هي الفئة الأساسية لأي مكون في DonerComponents. تحدد المكونات سلوك GameObject عن طريق التجميع. يمكنهم الاستماع إلى رسائل محددة وإجراء الإجراءات وفقًا لذلك. يجب أن يرث أي مكون جديد من هذه الفئة ويمكنه تنفيذ بعض الأساليب الأساسية ، إذا لزم الأمر. يمكن للمستخدم تسجيل ما يصل إلى 512 مكونات مختلفة.
إليك مثال على إنشاء مكون جديد. تنفيذ جميع أساليبها اختياري :
# include < DonerComponents/component/CComponent.h >
class CCompFoo : public DonerComponents ::CComponent
{
public:
void DoInit () override { m_foo = 0 . 0f ; }
void DoUpdate ( float dt) override { m_foo += dt; }
void DoDestroy () override { m_foo = 0 . 0f ; }
void DoActivate () override { }
void DoDeactivate () override { }
private:
float m_foo;
};لتسجيل هذا المكون في النظام ، بحيث يمكن لأي GameObject استخدامه ، نحتاج إلى القيام بما يلي:
# include < DonerComponents/component/CComponentFactoryManager.h >
static constexpr int amountOfFooComponentsAvailable = 512 ;
ADD_COMPONENT_FACTORY ( " foo " , CCompFoo, amountOfFooComponentsAvailable); بعد تهيئة DonerComponents::CDonerComponentsSystems يمكننا البدء في تسجيل مكوناتنا في النظام باستخدام macro ADD_COMPONENT_FACTORY . السلسلة التي يتلقاها هي تحديد المكون مع تحليل GameObjects من ملف JSON . آخر المعلمات هي عدد المكونات المتاحة. كما هو الحال مع GameObjects ، هناك 8.192 مكونات كحد أقصى من نفس النوع على قيد الحياة في نفس الوقت.
بمجرد تسجيل المكونات في النظام ، يمكن إضافته إلى GameObject بطريقتين مختلفتين:
DonerComponents::CComponent* component = gameObject->AddComponent<CCompFoo>();
// same as
CCompFoo* component = gameObject->AddComponent<CCompFoo>();
// or
DonerComponents::CComponent* component = gameObject-> AddComponent ( " foo " );
// same as
CCompFoo* component = gameObject-> AddComponent ( " foo " );في DonerComponents ، يتم تحديث المكونات حسب النوع ، نوع واحد في وقت واحد ، بالترتيب تم تسجيلهم في النظام. لذلك في المثال:
ADD_COMPONENT_FACTORY ( " foo " , CCompFoo, 128 );
ADD_COMPONENT_FACTORY ( " bar " , CCompBar, 128 ); سيتم تحديث جميع CCompFoo الحالي بالتتابع قبل تحديث جميع مكونات CCompBar الموجودة.
يمكنك تحديد البيانات التي سيتم تعريضها لتعديلها في JSON باستخدام Donerserializer . يمكنك التحقق هنا من كيفية استخدامه. هنا سأعرض مثالاً.
class CCompFoo : public DonerComponents ::CComponent
{
DONER_DECLARE_COMPONENT_AS_SERIALIZABLE (CCompFoo)
public:
CCompFoo ();
private:
float m_dummy1;
std::vector<std::string> m_dummy2;
};
DONER_DEFINE_REFLECTION_DATA (CCompFoo,
DONER_ADD_NAMED_VAR_INFO (m_dummy1, " dummy1 " ),
DONER_ADD_NAMED_VAR_INFO(m_dummy2, " dummy2 " )
) بعد تعريض m_dummy1 و m_dummy2 ، يمكننا تحديد قيمهما في JSON مثل هذا:
{
"root" : {
"name" : " test1 " ,
"components" : [
{
"name" : " foo " ,
"dummy1" : 1.0 ,
"dummy2" : [ " Test1 " , " Test2 " , " Test3 " ]
}
]
}
}لإلقاء نظرة أكثر تعمقا على كيفية القراءة من JSON تحقق من هذا .
يدعم DonerComponents نظام الرسائل للتفاعل بين مختلف GameObjects والمكونات. يمكن أن تكون الرسالة أي بنية/فئة محددة من قبل المستخدم. عادةً ما تحتوي على بيانات فقط ، لا يوجد منطق ، ولكن لا يوجد قيود على ذلك. هذه هي الطريقة التي يمكن أن تستمع بها DonerComponents::CComponent إلى رسالة محددة:
// Somewhere in your code
struct SDummyMessage {
SDummyMessage ( int foo, int bar)
: m_foo(foo), m_bar(bar) {}
int m_foo = 0 ;
int m_bar = 0 ;
}
// Inside your component
CCompFoo::RegisterMessages () {
RegisterMessage (&CCompFoo::OnDummyMessage);
}
void CCompFoo::OnDummyMessage ( const SDummyMessage& message) {
// ...
}بعد تسجيل الرسائل التي تريدها ، يمكنك البدء في إرسال رسائل مثل هذا:
SDummyMessage message ( 2 , 3 );
// This will propagate the message to all gameObject's components.
gameObject-> SendMessage (message);
// This will propagate the message to all gameObject's components and its children's components.
gameObject-> SendMessage (message, DonerComponents::ESendMessageType::Recursive);
// This won't send the message to the current gameObject but it's children.
gameObject-> SendMessageToChildren (message);
// Same as before but recursively through all gameObject's children and children's children.
gameObject-> SendMessageToChildren (message, DonerComponents::ESendMessageType::Recursive); يرسل SendMessage الرسالة على الفور ، في نفس الإطار. إذا كنت ترغب في تأخير إرسال الرسالة حتى نهاية الإطار ، فاستخدم PostMessage بدلاً من ذلك.
أخيرًا وليس آخرًا ، إذا كنت ترغب في إرسال رسالة إلى جميع الكائنات الحية ، فيمكنك استخدام BroadcastMessage :
SDummyMessage message ( 2 , 3 );
// This will propagate the message to all GameObjects alive.
gameObjectManager-> BroadcastMessage (message); DonerComponents::CHandle هي نوع من المؤشرات الذكية ذات الخيوط الفردية . يشيرون إلى DonerComponents::CGameObject أو DonerComponents::CComponent ، مع العلم في جميع اللحظات إذا كانت لا تزال صالحة أم لا ، وبعبارة أخرى ، إذا تم تدميرها في مكان آخر في الكود. حجم DonerComponents::CHandle هو 32 بت. إن طريقة العمل في DonerComponents هي أننا لا نتخزن أبدًا مؤشرات RAW من DonerComponents::CGameObject أو DonerComponents::CComponent ، نحن دائمًا نخزن DonerComponents::CHandle ، حتى نتمكن من التحقق مما إذا كان العنصر الذي يشيرون إليه لا يزال صالحًا ، لذلك لا نصل إلى مؤشرات متدلية. يمكن إلقاء أي DonerComponents::CHandle إلى DonerComponents::CGameObject أو DonerComponents::CComponent . إذا كان فريق العمل صالحًا ولا يزال العنصر موجودًا ، فسوف يعيد مؤشرًا صالحًا إلى العنصر. وإلا فإنه سيعود nullptr . هذا مثال:
# include < DonerComponents/handle/CHandle.h >
# include < DonerComponents/gameObject/CGameObjectManager.h >
using namespace DonerComponents ;
CHandle gameObjectHandle = m_gameObjectManager-> GetNewElement ();
if (gameObjectHandle) {
// CGameObjectManager has return a valid CGameObject
} else {
// CGameObjectManager has run out of CGameObjects
}
CGameObject* gameObject = gameObjectHandle;
// gameObject will be valid as gameObjectHandle points to an alive gameObject
m_gameObjectManager-> DestroyGameObject (&gameObject);
// gameObject is nullptr at this point
// gameObjectHandle == false as it points to a destroyed gameObject.أيضا ، يمكنك إرسال الرسائل من خلال المقابض. إذا كان المقبض صالحًا ، فسيتم نشر الرسالة بشكل صحيح. خلاف ذلك ، سيتم تجاهل الرسالة:
DonerComponents::CHandle handle = gameObject;
SDummyMessage message ( 2 , 3 );
handle.SendMessage(message);
العلامات هي وسيلة لإضافة مزيد من المعلومات إلى GameObjects ، لذلك يمكنك بعد ذلك تصفيةها ، وإرسال الرسائل فقط إلى GameObjects مع علامات محددة وما إلى ذلك. هناك طريقتان لإضافة علامات إلى النظام ، بحيث يمكنك استخدامها لاحقًا. أول واحد ، أعلنهم مباشرة في الكود:
# include < DonerComponents/tags/CTagsManager.h >
DonerComponents::CTagsManager* tagsManager = DonerComponents::CDonerComponentsSystems::Get()-> GetTagsManager ();
tagsManager-> RegisterTag ( " Tag1 " );
tagsManager-> RegisterTag ( " TagN " );الثاني ، تحليلهم من ملف JSON:
tagsManager-> ParseTagsFromFile ( " path/to/your/tags.json " );إن تنسيق ملف tags.json شيء مشابه لهذا:
{ "tags" : [ " Tag1 " , " tag2 " , " tagN " ] }يدعم DonerComponents التحميل من القرص باستخدام JSON بفضل Donerserializer ، لذلك هناك طريقة لإنشاء مسبقات أو مشاهد يمكن تخزينها كأصول بدلاً من بنائها من الصفر في الكود في كل مرة نقوم فيها بتشغيل تطبيقنا. الاستخدام الأساسي هو كما يلي:
# include < DonerComponents/GameObjects/CGameObjectParser.h >
DonerComponents::CGameObjectParser parser;
CGameObject* gameObject = parser.ParseSceneFromFile( " path/to/your/scene.json " );إن تنسيق ملف المشهد. json هو شيء مشابه لهذا:
{
"root" : {
"name" : " test1 " ,
"tags" : [ " tag1 " , " tag2 " , " tag3 " ],
"components" : [
{
"name" : " comp_location " ,
"x" : 1 ,
"y" : -3 ,
"z" : 9
},
{
"name" : " comp_rotation " ,
"radians" : 0.2
}
],
"children" : [
{
"name" : " test11 " ,
"tags" : [ " tag1 " , " tag3 " ]
},
{
"name" : " test12 " ,
"initiallyActive" : false
}
]
}
} إذا ، بدلاً من تحليل المشهد ، نريد تحليل ما قبل التسجيل تلقائيًا في DonerComponents::CPrefabManager ، نحتاج فقط إلى استدعاء ParsePrefabFromFile :
# include < DonerComponents/GameObjects/CGameObjectParser.h >
DonerComponents::CGameObjectParser parser;
CGameObject* gameObject = parser.ParsePrefabFromFile( " path/to/your/prefab.json " );بعد القيام بذلك ، يتوفر الجرف لأي مشهد محسّن جديد لاستخدامه.
تدعم DonerComponents prefabs التي تتضمن مسبقات أخرى ، والقدرة على تجاوز معلومات مكونها:
prefab1.json
{
"root" : {
"name" : " Prefab1 " ,
"components" : [
{
"name" : " comp_location " ,
"x" : 1 ,
"y" : -3 ,
"z" : 9
},
{
"name" : " comp_rotation " ,
"radians" : 0.2
},
{
"name" : " sprite " ,
"texture" : " res/common/textures/asteroid_med.png "
}
]
}
}prefab2.json
{
"root" : {
"name" : " Prefab2 " ,
"prefab" : " Prefab1 " ,
"components" : [
{
"name" : " sprite " ,
"texture" : " res/common/textures/flower_big.png "
}
]
}
}المشهد. json
{
"root" : {
"name" : " root " ,
"prefab" : " Prefab2 " ,
"components" : [
{
"name" : " comp_location " ,
"x" : 0 ,
"y" : 0 ,
"z" : 0
}
]
}
}