الشبكة العربية لمطوري الألعاب

خبير  أحمد عزالدين مشاركة 1

السلام عليكم جميعا

انا احمد عز من مصر
اتعلم برمجة العاب واحاول عمل لعبتي الاولي وهي لعبة تحاكي لعبة الدبابات الموجودة
بالاتاري قديما ولكني احاول تطبيقها باستخدام ال D3D

انا حاولت قدر الامكان تنظيم اللعبة كالاتي
ملف الكود الرئيسي main.cpp ويحتوي علي الدالة الرئيسية للبرنامج
وحاولت عمل محرك بسيط ينظم لي المهام التي احتاجها فكان شكل الكود في الدالة
الرئيسية كالاتي

int WINAPI WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance,
				   PSTR szCmdLine, int iCmdShow)
{
	// create a game engine instance and use it's functionality
	// to init game and objects and scene and everything
	Globals *m_globals;
	GameEngine* gameEngine = new GameEngine();	

	// first use gameEngine to init win/d3d/ and load/init game resources
	// and
	// call init game engine and set tex/mat and create
	// vertex buffers and set material/tex and create meshs
	if(!gameEngine->InitGame(hInstance))
	{
		::MessageBox(0, TEXT("GameInit - Failed"), TEXT("Error"), 0);
		return 0;
	}

	// set game state and app state
	gameEngine->SetAppState(Active);        // app is active
	gameEngine->SetGameState(Initialized);  // game resources are ready to use	

	//ShowCursor(false);

	// now all var should be valid, now enter message loop	
    EnterMsgLoop( RunGame );

	gameEngine->Clean();

	//ShowCursor(true);

	return 0;
}


لقد قمت بتعريف الكلاس GameEngine في ملفي h. and .cpp منفصلين
وكذلك عرفت ال Globals structure والذي يحتوي علي كافة
الوحدات البرمجية الاتية والتي من المفترض انها متغيرات عامة لكني جمعتها جميعا في
متغير واحد واسميته struct Globals m_globals

والان عند عمل build تحدث اخطاء unresolved build error
and objects already defined error
حيث انني قمت باستخدام المتغير gameEngine; والمتغير m_globals
في باقي ملفات الكود

برجاء من لديه تعليق حول افضل الطرق التي يمكنني بها تنظيم ملفات الكود بحيث لا تنتج اي اخطاء من تكرار الامر #include

يعني انا قمت بعمل #include "gameEngine.h" في كل ملفات الكود الاخري لاستخادم المتغير gameEngine فحدث الكثير من الاخطاء

ايضا لو في اي نصائح او تعليقات بالنسبة لطريقة عمل الزكاء الاصطناعي في اللعبة حيث اني لم ابرمجه بعد
من لديه اكواد او عناوين روابط لمواقع تشرح بابسط اسلوب ابسط الطرق لعمل
الزكاء الاصطناعي وكشف التصادمات في بيئة tiled based
ارجو ان يزودنا بها



هذا الي الان
وشكرا والسلام عليكم


Ahmed Ezz
Egypt
Al-Azhar Computer Engineering
3rd year Student

أحمد عزالدين
طالب دراسات عليا
جامعة كالجري

خبير  أحمد عبد الغني مشاركة 2

لكن إذا كنت تعلن عن المتغير gameEngine كمتغير عام، فكيف تعرفه في داخل الـ main؟ يعني المفروض تضع التعريف خارج الإجراء وليس داخله.

اللهم انصر أهلنا في فلسطين وآجرنا أن نكون عوناً لهم

خبير  أحمد عزالدين مشاركة 3

السلام عليكم

بالفعل انا معرف المتغير gameEngine كمتغير عام
ولكن عرفته داخل ال WinMain

ولكن لما حدثت مشاكل نصحني البعض بجعل المتغير في ملف header
وتعريفه extern كالتالي
in header file called main.h i write the expression
extern GameEngine* gameEngine;

واي ملف اريد استخدام المتغير العام gameEngine فيه اعمل الاتي
#include main.h'
بشرط وضع الجملة الاتية في الملف gameEngine.cpp
GameEngine *gameEngine = NULL;

وبالتالي يمكن بسهولة استخدام المتغير العام في اي ملف سورس كود
فقط بعمل استدعاء للملف main.h بجملة include

أحمد عزالدين
طالب دراسات عليا
جامعة كالجري

خبير  أحمد عزالدين مشاركة 4

السلام عليكم جميعا

اليكم التعديل النهائي بعد حل المشكلة مع بعض الملاحظات

المشكلة كان اساسها في البداية انني اريد تعريق متغير عام مرة واحدة
واستخدامه في اكثر من مكان بسهولة

اليكم اسهل الخطوات التي رايتها ومن يريد التعليق فليتفضل وسأقوم بتطبيق ما
سأقوله علي مثالي السابق

1- اذا اردت تعريف متغير عام وليكن من كلاس اسمها GameEngine فقم بالاتي
2- اذهب الي الملف gameEngine.cpp بفرض انه يحتوي كود الكلاس
وفي نهايته عرف المتغير واعطه قيم افتراضية كالاتي
GameEngine* gameEngine = NULL;
3- انشأ ملف جديد (طبعا يمكنك استخدام اي ملف هيدر موجود مسبقا)
وليكن اسمه game.h واكتب فيه الاتي
extern GameEngine gameEngine;

هنا تقوم الكلمة extern بجعل المتغير مرئيا لاي ملف يحتوي هذا السطر

للتطبيق انظر شكل الدالة WinMain لدي الان

/*-----------------------------------------------------
// Name: WinMain()
// Desc: main app entry point.
-----------------------------------------------------*/
int WINAPI WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance,
				   PSTR szCmdLine, int iCmdShow)
{
	// create a game engine instance and use it's functionality
	// to init game and objects and scene and everything		
	m_globals = new Globals();
	gameEngine = new GameEngine();
	m_globals->hInstance = hInstance;

	// set game state and app state
	gameEngine->SetAppState(Active);        // app is active
	gameEngine->SetGameState(PreLoading);   // game will start by loading it's resources	

	//ShowCursor(false);

	// append the file that we wanna use
	TCHAR* fileToUse = gameEngine->GetCorrectFilePath(TEXT("\\SOUNDS\\intro.wav"));		 

	// run intro music
	gameEngine->GetSoundManager()->PlayMidiOrWav(fileToUse);

	// now all var should be valid, now enter message loop
    EnterMsgLoop( RunGame );

	gameEngine->Clean();

	//ShowCursor(true);

	return 0;
}


ولدي الاتي ايضا
// here this is a created file called main.h
// any cpp file need to use the globals variables, he just
// include this file

#include "gameEngine.h"
#pragma once

extern GameEngine* gameEngine;
extern Globals *m_globals;


ولدي الاتي ايضا

// here in the class file gameEngine.cpp

// class code goes here
like the definitions of instructor and destructors and all class functions

// here i have defined 2 globals variables to use
GameEngine* gameEngine = NULL;
Globals *m_globals = NULL;


هذا الكود يعمل معي
وانا مستعد لاي استفسار في ما كتبت ان شاء الله

أحمد عزالدين
طالب دراسات عليا
جامعة كالجري

خبير مدير وسام البهنسي مشاركة 5

تماماً. هناك مفهومان أساسيان في لغة ++C ينطبقان على كل البنى في اللغة...
مفهوم الإعلان (declaration)، ومفهوم التعريف (definition).
الإعلان فقط مهمته تحديد النوع، وليس الوظيفة (مثلاً نوع متغير، أو رأس الإجراء).
التعريف مهمته تعبئة الوظيفة الفعلية للبنية، ويجب أن يطابق الإعلان في هيكله...

كمثال، فيما يلي إعلانات عن عدة بنى برمجية في اللغة...
int CalculateSomeStuff(float input1, float input2);
struct GameData;
class PathFinder;


من خصائص الإعلانات أنه يمكنك تكرارها عدداً مفتوحاً من المرات دون أن يعترض المترجم على ذلك. يعني لو أمسكنا مجموعة الأسطر السابقة ووضعناها في ملف واحد 10 مرات، فإن المترجم لن يعترض.
النقطة المهمة الثانية أن الإعلان _لا_ يحجز له أي ذاكرة.
النقطة المهمة الثالثة أنه لا يمكنك التعامل مع أي بنية غير معلن عنها مرة واحدة على الأقل. مثلاً تريد نداء إجراء، يجب أن يكون معلناً عنه قبل أن تستطيع ذلك، لكن لا يهم إن كان معرفاً أم لا...

فيما يلي التعريفات للمثال السابق:
int CalculateSomeStuff(float input1, float input2)
{
    return (int)(input1+input2);
}

struct GameData
{
    int iCurrentLevel;
    int iScore;
};

class PathFinder
{
public:
    PathFinder() { }
    ~PathFinder() { }
    bool FindPath() {  return false;  }
};


لا يجوز للتعريف أن يرد أكثر من مرة واحدة فقط لبنية ما. إن حدث ذلك فإن الرابط linker سيحتج ويخبرك أنه محتار بين أي من هذه التعريفات يجب أن يستخدمها في البرنامج النهائي...

عندما نفكر ضمن هذا النظام، نستنتج لم وضعت ملفات الـ header وملفات الـ CPP الاعتيادية. فملفات header هي تمثل مكتبة من الإعلانات عن مجموعة من البنى، وهذه المكتبة قد تريد استخدامها في عدة ملفات، بل وفي كثير من الأحيان يدخل الـ header نفسه أكثر من مرة في ملف CPP واحد. إذن هذا يعني أن الإعلانات في هذا الـ header قد يتم ذكرها عدة مرات متكررة، لكن لا مشكلة...

أما ملفات CPP فهي في أغلب الأحوال تحوي التعريفات الفعلية، ويتم طبعاً ترجمة كل ملف CPP إلى ملف OBJ واحد فقط يتم ربطه في النهاية مع بقية الـ OBJs الناتجة عن ملفات CPP الأخرى. باعتبار أن الرابط في هذه المرحلة لديه كامل التعريفات ضمن مجموعة ملفات الـ OBJ، فإنه يستطيع الآن معرفة إن كان هناك تعريفاً ناقصاً، أو إن وجد تعريفاً مكرراً لنفس البنية، وعندها طبعاً سيحتج...

في حديثي السابق أغفلت ذكر بنية هامة، وهي المتغيرات. وذلك لأن لها طريقة مختلفة في الإعلان والتعريف. إن أردنا تطبيق نفس المفاهيم السابقة على المتغيرات، فإن الإعلان عنها يكون عن طريق ذكرها مع إضافة كلمة extern قبلها، أما تعريفها فيجب أن يتم عن طريق وضع نفس الإعلان، لكن دون كلمة extern.

مثال عن إعلان...
extern int g_iPlayerID;

هنا يمكننا تكرار هذا السطر عدداً مفتوحاً من المرات دون مشاكل، وكل من يقرأ هذا السطر يستطيع التعامل مع هذا المتغير.
أما تعريف المتغير فسيكون ببساطة:
int g_iPlayerID = 0;

هناك نقطتان... أولاً، تعريف المتغير يحجز ذاكرة له حيث ستحفظ قيمته. إن عرفت المتغير أكثر من مرة فكأنك تقول أريد أن أحجز له عدة أماكن في الذاكرة، وهذا غير منطقي. لذلك شرط التعريف مرة واحدة فقط ينطبق هنا أيضاً...
ثانياً، فقط في التعريف يفترض أن تقوم بإسناد قيمة ابتدائية للمتغير (كما في المثال أعلاه)، أما في الإعلان فلا تفعل ذلك...
لو كان المتغير هو نسخة من class، فإن التعريف أيضاً هو الذي يقوم بتنفيذ الـ constructor للـ class... أي:
class CGameEngine gameEngine("myGameName");

فإن هذا السطر هو المسؤول عن نداء الـ constructor الذي يأخذ بارامتر من نوع نصي...

النقطة الأخيرة هي أن كلاً من الإعلان والتعريف يجب أن يكونا في نفس المجال scope كي يتطابقا. فإن كان الإعلان على مستوى global، فيجب أن يكون التعريف أيضاً global، وهذا هو الخطأ الذي ارتكبته في رسالتك الأولى، حيث أنك تعلن عن متغير global، لكنك تعرفه ضمن مجال إجراء WinMain.

أرجو ألا أكون قد أطلت الحديث وأمللتكم بهذه النظريات...

تحياتي...

وسام البهنسي
مبرمج في إنفيديا وإنفريمز