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

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

مهمة البرمجة الأولى (++C): التعرف على كود اللعبة وإصلاح الأزرار
 
* تصفح كود اللعبة وبناؤه بشكل ناجح وتنفيذ اللعبة على جهازك.
* شرح مكونات الكود الأساسية.
* منع الأزرار من العمل عندما لا تكون اللعبة في الواجهة.
 
مدة المهمة: نرجو من كل عضو أن يوضح المدة التي يتوقع إنهاء المهمة فيها كي نستطيع تقييم المشروع بشكل سليم.
 
الأعضاء المشاركين:
عمار زاهدة
ib_doom
أحمد عز
سلوان الهلالي
أحمد برهام
تامر عادل
 
 
الكود يحتاج إلى تركيز لفهم ما يقوم به. على كل منا تصفح الكود ومحاولة فهم أحد مكوناته (أي class معين) وكتابة شرح عما يقوم به هنا، ليستفيد البقية من الشرح وتبنى لدينا الخبرة بكود اللعبة بشكل كامل.
الطلب الأخير هو أن يقوم كل من الأعضاء بإيجاد الكود المسؤول عن قراءة حالة الأزرار، وتعديله ليمتنع عن قراءتها في حال كون نافذة اللعبة ليست فعالة (not focused). لأن اللعبة الآن تستقبل الأزرار حتى لو كانت النافذة مخفية، وهذا غير لطيف.
 
أعتقد أن البعض سيواجه مفاهيماً جديدة في الكود، قد تمنعه من إتمام العمل. أرجو أن يقوم الجميع بطرح أية أسئلة تتولد لديهم في قسم البرمجة كي نجيب عنها ونتعلم سوية.
 
يمكنكم تحميل كود اللعبة الكامل من هنا:
http://www.agdn-online.com/source/kvalley_src.zip
 
ستحتاج إلى أي نسخة من Visual Studio .NET 2008 لتشغيل اللعبة. أما من لديه النسخة 2005 فإنه سيجد نسخة خاصة من ملف المشروع كي يستطيع فتح المشروع والاطلاع عليه داخل Visual Studio .NET 2005. وهذا سيتيح له المشاركة وإنجاز عمل بينما يجلب النسخة الجديدة (2008) أو يحملها من الإنترنت بشكل مجاني من مايكروسوفت:
http://www.microsoft.com/express/download/
 

ماذا تنتظرون؟ فلنبدأ العمل!

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

مبتدئ  Mr-X مشاركة 2

اين هم المبرمجين
يلا يا جماعة لنبدء بالعمل
ومن لديه مشكلة يخبرنا بها من الان

تعلمت فى حياتي : أن محادثة بسيطة أو حواراً قصيراً مع إنسان حكيم يساوي شهر دراسة

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

السلام عليكم

يسعدنى ان اكون اول من يفتح النقاش فى مهام البرمجة

اولا:

وفي 03 ابريل 2008 02:44 م، قال وسام البهنسي متحمساً:

* تصفح كود اللعبة وبناؤه بشكل ناجح وتنفيذ اللعبة على جهازك.
لقد تصفحت كود اللعبة سريعا وهو يعمل عندى بلا مشاكل


وفي 03 ابريل 2008 02:44 م، ظهر شبح ابتسامة على وجه وسام البهنسي وهو يقول:

* منع الأزرار من العمل عندما لا تكون اللعبة في الواجهة.
لقد حاولت الاتى بالرغم من ان الكود الذى سارفقه حاليا لا يقوم بمعالجة اسلوب المدخلات عندما تكون النافذة غير نشطة
بصورة صحيحة الا انه كود مبدئي
اتمنى من احد الاخوة الافاضل ان يوضح الفرق بين عملية ال تنشيط النافذة بأنواع التنشيط المختلفة (سواء عن طريق الماوس
 او عن طريق لوحة المفاتيح او حتى بطرق غير مباشرة مثل الدالة SetWindowActive )
ايضا ما العلاقة التى تربط اسلوب ال input focus وبين ال Window Activation
للتوضيح انا قرأت عن الاتى:
يمكننا استخدام رسائل الويندوز الاتيه للتحكم بموضوع ال window activation and input focus
الرسالة WM_ACTIVATEAPP والرسالة WM_ACTIVATE ما الفرق بينهما
هل استخدام الرسالة WM_SETFOCUS قد يعطى ال focus لنافذة ولو كانت غير نشطة ام يقوم بتنشيطها اولا
انا قرأت ان العكس صحيح يعنى اذا قمنا بتنشيط النافذة فانها ايضا تستلم خاصية ال focus
--------------
ايضا هل اذا تحرك الماوس فوق النافذة يعنى تم ارسال WM_MOUSEMOVE سيقوم ذلك باعطاء النافذة ال INPUT FOCUS
ولو كانت غير نشطة INACTIVE

if(uMsg == WM_ACTIVATE)
{
	if(wParam != WA_INACTIVE)
	{
		// the app should now active, but may not have input focus
		m_bAppActive = true;			
	}
	else  // app is being DeActivated
	{
		m_bAppActive = false;
		// if our windows has focus, then discard it
		/*if(GetFocus() == hWnd)
			SetFocus(NULL);*/
	}

	// we handled the message of app activation
	return 0;
}
//else   // other messeages coming like input msg(s)
{
	// check if app has focus and/or active
	if(!m_bAppActive)
	{
		WaitMessage();
	}
}
///////////////////////////////
اعتقد ان الموضوع بسيط لكنى بجد احترت - برجاء التوضيح

أما في 03 ابريل 2008 02:44 م، فقد تنهد وسام البهنسي بارتياح وهو يرد:

* شرح مكونات الكود الأساسية.


/////// this code inside Main fucntion /////////////
const bool bFullScreen = false;
GameApp gameApp(hInstance,bFullScreen);
HICON hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_GAME));
if (!CoreLib::LibInit(&gameApp,SCREEN_WIDTH,SCREEN_HEIGHT,bFullScreen,hIcon,GameApp::GameWndProc))
	return -1;
gameApp.Init();

اللعبة بتقدم خدمات منخفضة المستوى عن طريق مكتبة اسمها  CoreLib (خدمات زي تحميل الملفات وتهيئة النافذة الرئيسية
 وبدء ال game loop وغيرها - يمكننا معرفة هذه الوظائف ونبذة عنها بتصفح الملف CoreLib.h )
المكتبة بتعتمد على معلومات احنا بنعطيها للمكتبة في الدالة اللي اسمها CoreLib::LibInit في بداية تنفيذ الدالة Main
اهم المعلومات المبدئية اللي المكتبة محتاجها عنوان دالة ال WinProc والمسئولة عن معالجة رسائل الويندوز
اللي يهمنا ايضا اننا نرسل للمكتبة كأول معامل مؤشر لمتغير من نوع الكلاس BaseApp
ونحن لابد ان نعرف نسخة derived من هذه الكلاس اذا احتجنا لعمل دوال مفيدة لتتحكم باللعبة مثل OnIdeal
- المزيد عن لكلاس GameApp والتى عملتها لعبتنا بالاعتماد على الكلاس BaseApp لاحقا
الان بعد ان هيئنا المكتبة بالدالة LibInit نستخدم الدالة Init والمعرفة في الكلاس  GameApp وذلك للاتى:

bool GameApp::Init(void)
{
	if (!AudioInit())
		return false;

	if (!GameFont.Load("KonamiFont"))
		return false;

	if (!LoadConfig()) return false;

	m_StateFlow.StartGame();

	return true;
}

1- نقوم بتحميل ملفات الصوت المطلوبة فى اللعبة فى كاش مخزن داخل المكتبة وبالاعتماد على دوال المكتبة وبعض الدوال الاخرى
    التى عرفناها فى الملف  audio.h and audio.cpp وهي عبارة عن تغليف لتشغيل ملفات الاصوات وايقافها ومعرفة ايها يعمل حاليا
    وبالطبع الكلاسات المعرفة داخل ملفات الصوت المذكورة تعمل عن طريق ال CoreLib
ملاحظة : اذن المكتبة CoreLib كأنها Game Engine يوفر لنا وظائف هامة جدا مثل تشغيل الاصوات وتحميل ملفات المراحل والخطوط لكن
              على المستوى المنخفض جدا
2- نقوم فى الخطوة الثانية فى الدالة GameApp::Init بتحميل الخط Konami وسأقوم بشرح الملف text.h و text.cpp وهم المسئولين عن ذلك قريبا
3- الان نقوم بقراءة ملف التهيئة .MainConfig.cfg ما افهمه هو الاتى
    نستخدم الدالة loadConfig وفيها نقوم بعمل الاتى:

bool GameApp::LoadConfig(void)
{
	char szFileName[MAX_PATH];
	if (!GetGameFilePath(szFileName,"MainConfig.cfg",GameDir_Config))
		return false;

	CoreLib::ConfigDB db;
	if (!CoreLib::XFerConfig(szFileName,db))
		return false;

	Config.pExplorerWalkArt = SpriteArt::GetOrLoadSpriteArt(db.szArtFile[0]);
	Config.pExplorerAxeArt = SpriteArt::GetOrLoadSpriteArt(db.szArtFile[1]);
	Config.pExplorerSwordArt = SpriteArt::GetOrLoadSpriteArt(db.szArtFile[2]);
	Config.pExplorerDigArt = SpriteArt::GetOrLoadSpriteArt(db.szArtFile[3]);
	Config.pExplorerShootArt = SpriteArt::GetOrLoadSpriteArt(db.szArtFile[4]);

	if (!Config.pExplorerWalkArt || !Config.pExplorerAxeArt || !Config.pExplorerSwordArt ||
		!Config.pExplorerDigArt || !Config.pExplorerShootArt)
		return false;

	(GameData::Config&)Config = db.data;
	return true;
}

 نحمل البيانات من الملف بعد تهيئة مساره الفعلى ثم ننشأ متغير كأنه قاعدة بيانات لحفظ القيم المقروءة من الملف بالكلاس ConfigDB
  ثم قامت الدالة CoreLib::XFerConfig بتحميل متغير قاعدة بيانات من ملف التهيئة - لم نعرف كيف قامت الدالة بالتحميل وبالتالى
   لا نعرف كيف نعدل فى هيكلة ملف التهيئة بل كل ما عندنا طريقة لقراءة القيم منه ومن ثم اعادة حفظها واعتقد ان هذا كفاية
   كما انه بامكاننا تعديل ال ConfigDB struct  لتحتوى على قيمنا الاضافية التى نريد اضافتها ولا نشغل بالنا بكيفية كتابتها للملف
   نقوم بعد ذلك بتحميل ملفات الرسومات spirit art for exploring ونضيفها الى متغير التهيئة الخاص بنا والذي يحمل قيم اللعبة المبدئية
    (اعتقد ان الكود مفهوم)
4- في الخطوة الاخيرو من التهيئة الخاصة بـ gameEngine.Init نقوم بتجهيز قيم مبدئية لرقم المرحلة الحالية والى اى state سوف
    ننتقل وكل هذا عن طريق ال stateFlow حيث حددنا ايضا اننا سندخل اول مرحلة pyramid عن طريق ال intro door وظبطنا بعض المتغيرات
   التى سنتابع منها ال score الحالى وعدد المحاولات واشياء اخرى
void StateFlow::StartGame(void)
{
	m_eNextFlowAction = GameState::FlowTo_Intro1;
	m_iTransitionTime = 0;
	FlowData.eCurrentLevelStartMode = Pyramid::Starting_Intro;
	STATS& stats = GameApp::Instance->Stats;
	stats.iCurrentLevel = 0;
	stats.iLevelCleared = -1;
	stats.iScore = 0;
	stats.iAttempts = 1;
}


الشرح السابق كله لمجرد تهيئة اللعبة للعمل
 عذرا على الاطالة
كان هذا شرح طويل نوعا ما بدون استعانة بالكود هنا كمثال لكن ان شاء الله اتابع باقى الكود وقريبا نلقاكم   

والسلام عليكم

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

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

السلام عليكم رجعنا سريعا لمناقشة بعض الكود الخاص باللعبة

ما فهمته ان اللعبة ببساطة عبارة عن مجموعة من ال states مثل حالة اللعبة وهي في القائمة الرئيسية وحالة اللعبة
وهى فى شاشة كلمة المرور وهكذ ولقد تم تعريف حالات اللعبة كالاتى:

enum FlowAction
{
	FlowTo_Continue,		// Keep updating the current state
	FlowTo_Intro1,		// Transition to the Intro1 state
	FlowTo_Intro2,		// Transition to the Intro2 state
	FlowTo_Password,		// Transition to the Password state
	FlowTo_Play,		// Transition to the Play state
	FlowTo_Map,		// Transition to the Map state
	FlowTo_Outro,		// Transition to the Outro state
};

اللعبة كما نرى مجموعة حالات وبعض الحالات يتم الانتهاء منها والانتقال لغيرها بتسلسل طبعا اما بمرور مدة زمنية كما فى الحالة
الاولى للعبة وهى تعرض شاشة konami الزرقاء أو بالضغط على زر ما لانهاء الحالة كما هو الحال فى شاشة المرور مثلا او حتى يمكن
انهاء شاشة konami الزرقاء بالضغط على المسافة مثلا

ايضا ببساطة اللعبة عبارة عن عدة مراحل وعدة شخصيات او كائنات داخل المراحل
- نبدء بمفهوم المرحلة او ال pyramid وهى عبارة عن حجرة وا اكثر champer وكل حجرة مكونة من مجموعة BlockArts
   حيث كل BlockArt كأنه Tile غير متكرر لوصف مربع او جزء من الحجرة.
- الكائنات داخل اللعبة اما كائنات ساكنة أو متحركة:
   الكائنات الساكنة مثل الاحجاء والجواهر والبوابات وكلهم يتواجدوا داخل المرحلة ومن هذه الكائنات الساكنة
   ما يمكن تحطيمه مثل الحجارة بالفأس او ما يمكن حمله كالجواهر والسيف
    الكائنات المتحركة فى اللعبة عبارة عن الشخصيات مثلا اللاعب أو العدو وكل منهم يطلق عليه Creature وكذلك
    explorer ولكن كل منهم له اسلوب وسلوك حركة مختلف قليلا عن الاخر حيث أن اللاعب مثلا يتم التحكم فيه من
     قبل الشخص الذى يلعب اللعبة على عكس الـ monster والذى يتم التحكم فيه عن طريق AI
     - يتم تمثيل الشخصيات اللعبة باستخدام ال Sprite وهى ببساطة عبارة عن صورة قد تحتوى على أكثر من اطار متتالى لوصفها
       وهى تتحرك أو وهى فى أوضاع مختلف مثل اللاعب وهو يقفز مثلا أو يحفر
        لاحظ كمفهوم ال blockArt ايضا ال sprite تحتاج لـ spriteArt وهو كاش لمجموعة الصور والاطارات التى يعاد استخدامها
       لوصف ال sprite الرئيسى
    
--------------------------------------------------------------------------
بالنسبة لموضوع تحميل الاصوات اللعبة تقوم باستخدام كلاس اسمه AudioManager وهو عبارة عن تغليف لوظائف تشغيل الصوت ولكنها
تعتمد على تحميل ملفات الصوت في كاش داخل المكتبة وكل ملف صوت محمل داخل slot برقم id خاص به
وبالتالى اللعبة لا تحتاج لتحميل ملفات الصوت عدة مرات بل يكفى ان تقوم بفحص الكاش الموجود داخل مكتبة ال CoreLib
اولا والا تقوم ساتدعاء الدالة المناسبة CoreLib::AudioLoad لتحميل الصوت ان لم يكن موجودا
ايضا اللعبة تعمل update للصوت بطريقة ممتازة حيث تستدعى الدالة OnIdeal والتابعة للكلاس GameApp وذلك
وذلك لعمل update لكلا من ال stateFlow (سنتحدث عن ذلك لاحقا ) وكذلك بعمل update للصوت كالاتى:
تقوم اللعبة بالتاكد من صوت الموسيقى التى تعمل فى الخلفية قد تم تحميله والا لا تقوم الدالة بعمل update لانه فى
هذه الحالة قد نكون خارج المرحلة مثلا في الشاشة الرئيسية
تقوم دالة تحديث الصوت كل اطار ايضا باتالكد من عدم وجود اي تضارب بين بعض الاصوات التى لا يفترض ان تعمل مع صوت الخلفية
واذا وجدت احدى الاصوات التى تسبب التضارب سيتم تشغيله تقوم بايقاف صوت الخلفية مؤقتا وتعيد تشغيله بعد الانتهاء من الاصوات الفرعية


/////////////////////////////////////////////////////////////////////////////////////////////
// Function Name : AudioManager::Update
//
// Purpose : Per-frame heart-beat function for updating sounds. Currently this function
//			 guarentees that conflicting sounds don't get played together.
//			 This function must be called once every game simulation frame.
//
// Params : None.
//
// Return Value : None.
/////////////////////////////////////////////////////////////////////////////////////////////
void AudioManager::Update(void)
{
	// We only care about the background music. If it's already stopped then
	// there's not really anything to do
	if (!m_bBGMPlaying) return;

	// See if any of the conflicting sounds are playing. If so, then silence the background music
	static AudioSoundType aConflicts[] = { Sound_Door, Sound_RightDoor, Sound_CollectedAll };
	for (DWORD i=0;i
	{
		if (CoreLib::AudioIsPlaying(aConflicts[i]))
		{
			CoreLib::AudioStop(Sound_BGMusic);
			return;
		}
	}

	// If we are here then it means that none of conflicting sounds are playing,
	// so what we have to do is basically just re-activate the bg music if it was
	// stopped before...
	if (!CoreLib::AudioIsPlaying(Sound_BGMusic))
	{
		CoreLib::AudioRewind(Sound_BGMusic);
		CoreLib::AudioPlay(Sound_BGMusic);
	}
}


-----------------------------------------------------
الان مع تحميل الخط konami والذى يتم تحميله اثناء تهيئة اللعبة كما ذكرنا سابقا
الخط konami ببساطة عبارة عن map من الحروف ولكل حرف حجم معين وعندما نقوم بطلب رسم نص معين على الشاشة
فاننا نقوم بتمرير احداثيات النص الى دالة الرسم التابعة للمكتبة CoreLib والدلة اسمها CoreLib::FontPutChar
نعيد ترتيب الكلام للسهولة كالاتى:
اولا:  نقوم بتحميل الخط عن طريق المكتبة كالاتى FontLoad
ثانيا: نقوم بطلب رسم اي نص بالدالة GameApp::DrawStats مثلا والتى تقوم بالاتى:
        الدالة تقوم برسم نص باحداثياته الممررة كمعاملات للدالة GameFont.Draw
/////////////////////////////////////////////////////////////////////////////////////////////
// Function Name : FontMonospace::Draw
//
// Purpose : Draws the specified text at the required position, starting from the bottom-left
//	    corner, and increasing to the right (only supports English, and not all characters).
//
// Params : 
//    int iScreenX : Screen pixel coordinate of text left.
//    int iScreenY : Screen pixel coordinate of text bottom.
//    const char *pszText : String to draw. No line-breaks allowed.
//
// Return Value : None.
/////////////////////////////////////////////////////////////////////////////////////////////
void FontMonospace::Draw(int iScreenX, int iScreenY, const char *pszText) const
{
	RECT rcSource;
	while (*pszText)
	{
		GetCharacterRect(*(pszText++),rcSource);
		CoreLib::FontPutChar(iScreenX,iScreenY,rcSource);
		iScreenX += TEXT_CHARACTER_WIDTH;
	}
}

         والتى بدورها تقوم بالمرور على كل حرف في النص وتحسب حجمه بالدالة المساعدة والتى تستعين بال konami map
          والدالة اسمها GetCharacterRect وبعد حساب حجم الحرف تقوم برسمه على الشاشة بالمكتبة كالاتى CoreLib::FontPutChar
           وبالتالى فنكون قد رسمنا النص - لمزيد من التفصيل راجع الملف text.cpp
  
هذا الى الان وان شاء الله لنا لقاء قادم مع قلب اللعبة وهو كود الانتقال من state لاخري وطريقة تنظيم ذلك على حسب ما فهمت
منتظر ايضا اقتراحات الاعضاء ومشاركاتهم

والسلام عليكم

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

مبتدئ  Mr-X مشاركة 5

لصراحة احب اشكر الاخ احمد على مجهوده فى توضيح الكود
الكود فكرته جيدا ، وبأذن الله تصلوا يا مبرمجين السى بلس بلس الى ما تريدون باسرع وقت والى افضل نتيجة

تعلمت فى حياتي : أن محادثة بسيطة أو حواراً قصيراً مع إنسان حكيم يساوي شهر دراسة

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

ممتاز. شرح جيد. سأعلق على بعض النقاط التي ذكرتها، وأترك الأعضاء الآخرين ليردوا على أسئلتك أيضاً...
 



في 04 ابريل 2008 06:54 م، عقد ahmed ezz حاجبيه بتفكير وقال:

- نبدء بمفهوم المرحلة او ال pyramid وهى عبارة عن حجرة وا اكثر champer وكل حجرة مكونة من مجموعة BlockArts
   حيث كل BlockArt كأنه Tile غير متكرر لوصف مربع او جزء من الحجرة.

نعم. المرحلة (أو الهرم) مكون من حجرات أو غرف (Chambers وليس Champers). وهي كما رأينا بعض المراحل تتكون من غرفة واحدة، وبعضها الآخر يصل إلى ثلاث غرف (مثل مرحلة المقدمة والمرحلة التاسعة). كما نلاحظ، الهرم مبني من قطع متكررة من الأحجار يتم تشكيلها لتكوِّن المتاهة التي يتم اللعب فيها. القطعة الحجرية الواحدة هي ما يمثله الـ BlockArt. المراحل الصفراء تستخدم BlockArt يحمل قطعة حجرية صفراء. المراحل الزرقاء تستخدم BlockArt أزرق وهكذا...



وفي 04 ابريل 2008 06:03 م، قال ahmed ezz متحمساً:

ملاحظة : اذن المكتبة CoreLib كأنها Game Engine يوفر لنا وظائف هامة جدا مثل تشغيل الاصوات وتحميل ملفات المراحل والخطوط لكن
              على المستوى المنخفض جدا

هناك عدة أهداف من تقديم هذه المكتبة المغلفة. أولاً تفادي تشتيت الأعضاء بكود كبير وممل ومعروف لدى الجميع. ثانياً التشجيع على تبني مفهوم مقدم الخدمة والعميل. إذ يمكننا اعتبار هذه المكتبة تماماً مثل الـ Win32 API حيث تقدم لك خدمات مغلقة تساعدك في إتمام المهام دون القلق بمعرفة التفاصيل. كمثال، لا يهمني كمبرمج عالي المستوى معرفة كيف تعمل الإجراءات ()CreateFile أو ()MessageBox، إلا أنني أستخدمهم بشكل دائم بفرض أنهم يقومون بالمهمة كما يجب. هذه المكتبة تحوي كود إنشاء نافذة اللعبة، تشغيل D3D9، تشغيل DSound، وتحميل الملفات. وكل ذلك ليس هو محور اهتمامنا بهذا المشروع. (لمن يهمه الأمر: كود تحميل الملفات هو مجرد مجموعة كبيرة من نداءات ()fread والتي تقوم بقراءة المعلومات للعبة).
السبب الثالث هو أن يساعد إدارة الشبكة على إعطاءها القدرة على طرح النسخة النهائية دون القلق من وجود نسخ معدلة يتم نشرها دون علم بقية الأعضاء، وهذا لضمان عدم ضياع حق أي شخص عَمِل معنا في المشروع. فالنسخة النهائية ستحوي أسماء جميع من يعملون بالمشروع الآن، أما لو تم نشرها بشكل شخصي فإننا لا نستطيع ضمان هذا الحق للأعضاء.
 
لقد تم تصميم هذه المكتبة بناءً على المهام المخطط تنفيذها في هذا المشروع. أي أنكم فقط باستخدامها ستستطيعون إنجاز كل المهام المطلوبة. لو كنت تعتقد أن المهمة تحتاج إلى أمور لا تستطيع إنجازها بسبب هذه المكتبة فنرجو التوضيح كي نعالج الوضع فوراً.

 

وفي 04 ابريل 2008 06:03 م، ظهر شبح ابتسامة على وجه ahmed ezz وهو يقول:

نحمل البيانات من الملف بعد تهيئة مساره الفعلى ثم ننشأ متغير كأنه قاعدة بيانات لحفظ القيم المقروءة من الملف بالكلاس ConfigDB
  ثم قامت الدالة CoreLib::XFerConfig بتحميل متغير قاعدة بيانات من ملف التهيئة - لم نعرف كيف قامت الدالة بالتحميل وبالتالى
   لا نعرف كيف نعدل فى هيكلة ملف التهيئة بل كل ما عندنا طريقة لقراءة القيم منه ومن ثم اعادة حفظها واعتقد ان هذا كفاية
   كما انه بامكاننا تعديل ال ConfigDB struct  لتحتوى على قيمنا الاضافية التى نريد اضافتها ولا نشغل بالنا بكيفية كتابتها للملف

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

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

مبتدئ  عمار زاهده مشاركة 7

السلام عليكم
بصراحة الكود منظم بطريقة ممتازة ولاكنه معقد نوعا ما بلنسبة لي , أعتقد السبب هو ان خبرتي في ++C ما زالت جديدة ( أنا ما زلت اتعملها منذ ان فتحت موضوع نقاش عن كورسات معهد Game Institute بحوالي اسبوع )
المهم
تصفتح كود اللعبة الاساسي ولم أفهمه ولاكن بعد ان شرحه الأخ ahmed ezz وضح الأمور بشكل ممتاز
ولقد شرحت فئة الوحش Monster  ( أخذ معي وقت حتى فهمته و اليوم شرحته على حسب فهمي )
في التأكيد يوجد خطأ في الشرح و اليوم شرحت ثلاثة أجزاء منه و إن شاء الله أكمل غدا
وللننتقل على الشرح



////////////////////////////////////////////////////////////////////////////////////////////
// Function Name
: Monster::Tick
//
// Purpose :
Monster's simulation update function. Processes all state machines for it.
//
// Params :
None.
//
// Return Value
: Irrelevant.
/////////////////////////////////////////////////////////////////////////////////////////////
bool
Monster::Tick(void)
{
      // Try to follow explorer if visible
      AttackExplorer();
 
      // Tick him using base behavior...
      bool bCreatureTick = Creature::Tick();
 
      // If he is not in a base behavior,
then it is upon us to handle him
      if (!bCreatureTick)
      {
            switch (m_State)
            {
            case State_Mon_Spawn:   OnStateSpawn(); break;
            case State_Mon_Think:   OnStateThink(); break;
// Do AI
            case State_Mon_Die:           OnStateDie(); returntrue; // If dead, don't process anything else
            }
      }
 
      // Decay of deflection amount
      if (m_iDeflectionsAmount >
0)
            m_iDeflectionsAmount--;
 
      m_uInput &= ~Input_Action; //
Reset jump status
      CollideWithChamber(); // Either jumps
or reflects off the wall if collides
      CrossGap(); // Attempt to jump if
faced with a gap in front of him
 
      // AI state machine update
      switch (m_AIState)
      {
      case AIState_Chase:                             OnAIStateChase(); break;
      case AIState_Wander:                      OnAIStateWander(); break;
      case
AIState_ClimbNearestStairs:    OnAIStateClimbNearestStairs();
break;
      }
 
      if ((m_State == State_Stand) ||
(m_State == State_Run))
      {
            if (m_iNextThinkCounter
<= 0)
                  SwitchToStateThink();
            else
m_iNextThinkCounter--;
      }
 
      returntrue;
}
تستخدم هذه الوحدة لتحديث محاكاة الوحش و معالج
كل حالاته المبرمجة مثل الذكاء الاصطناعي و حالة الهجوم ... الخ .
في بداية الكود يتم الاستعلام إذا كانت اللاعب مرئيا
في الكود الثاني يتم الاعلان عن سلوك منطقي للوحش
في الكود الثالث الاستعلام إذا كان الوحش على غير
سلوكه المنطقي العادي مثلا إذا كان ميت
في الكود الرابع إذا وجد معدل انحراف ذا قيمة
أكبر من صفر يتم استدعاء امر القفز , و الرقم صفر هي معدل انحراف أرض الغرفة
المستقيمة و الأرقام الأعلى مثل الرقم واحد تكون عبارة عن حاجز , يجب القفز أو باب
يجب عكسه للعبور
الكود الخامس وهو عبارة عن تحديث حالة الذكاء
الاصطناعي للوحش وله عدة حالات وهي :
الحالة الأولى : إذا كان يطارد اللاعب فيبقى
يطارده مع إعطاء فترة إيقاف المطاردة
الحالة الثانية : إذا كان يتجول و يبحث عن اللاعب
يبقى يتجول و يبحث
الحالة الثالثة : إذا كان يصعد السلم و هو قريب
يتم استدعاء كود المطاردة
يوجد في النهاية كود استعلام عن حالة الوحش التي
في الأعلى بحيث إذا كانت واقفة يتم تشغيلها او استدعائها من جديد
========== نهاية الشرح الأول ==========
 

/////////////////////////////////////////////////////////////////////////////////////////////
// Function Name
: Monster::SpawnAtCreationPoint
//
// Purpose :
Every pyramid has default locations for spawning monsters. Calling this
function
//                 will make the monster get
spawned from that position instead of where he was last
//                 killed.
//
// Params :
None.
//
// Return Value
: None.
/////////////////////////////////////////////////////////////////////////////////////////////
void
Monster::SpawnAtCreationPoint(void)
{
      m_Pos.Chamber(m_Data.uSpawnChamber);
      m_Pos.Init(m_Data.wSpawnCellX*CHAMBER_CELL,m_Data.wSpawnCellY*CHAMBER_CELL);
      SwitchToStateSpawn(GameApp::Config.iMonsterSleepPeriod/2);
}
تستخدم هذه الوحدة لإنشاء وحش في نقطة معينة من
الهرم أو المكان الذي قتل فيه
في السطر الأول يتم تحديد موضع الغرفة من خلال
الموضع الذي يتم إنشاء الوحش فيه
في السطر الثاني يتم عملية حساب و تجهيز و
استدعاء الوحش
في السطر الثالث يتم تنشيط أو تشغيل الوحش من
خلال إعدادات اللعبة
========== نهًاية الشرح الثاني ==========


/////////////////////////////////////////////////////////////////////////////////////////////
// Function Name
: Monster::Kill
//
// Purpose :
Kill the monster. This can happen by the sword of the explorer, or because we
//                 are kind-of stuck in a
situation that keeps bumping us against walls.
//
// Params : 
//    bool bSpawnFromCreationPoint : 'true' if it
was suicide and the monster should spawn
//                                                    
pyramid default spot. 'false' if not.
/////////////////////////////////////////////////////////////////////////////////////////////
void
Monster::Kill(bool bSpawnFromCreationPoint)
{
      m_iDeflectionsAmount = 0;
      SwitchToStateDie(bSpawnFromCreationPoint);
      if (!bSpawnFromCreationPoint) // Increase score because he must have been killed by a
sword shot
            GameApp::Instance->Stats.AddScore(GameApp::Config.iKillMonsterScore);
}
 
bool
Monster::IsAlive(void) const
{
      switch (m_State)
      {
      case State_Hide:
      case State_Mon_Spawn:
      case State_Mon_Die:
            returnfalse;
      }
      returntrue;
}
 
SpriteArt* Monster::GetArt(void) const
{
      return m_pArtWalk;
}
 
fixed Monster::GetWalkSpeed(void) const
{
      return m_Data.fWalkSpeed;
}
 
fixed
Monster::GetClimbSpeed(void) const
{
      return m_Data.fClimbSpeed;
}
 
int
Monster::GetJumpFrame(void) const
{
      return
GameApp::Config.uMonsterJumpFrame;
}
 
bool
Monster::RespondToAction(void)
{
      if (!CanJumpAtPlace()) returnfalse;
      SwitchToStateJump();
      returntrue;
}
 
تستخدم هذه الوحدة لعملية معالجة قتل الوحش عن
طريق اللاعب أو عن طريق أمور آخرى مثل الوقوع في حفرة مغلقة أو حجرة مغلقة مع
المحاولة للخروج منها
في الكود الأول يتم تخزين قيمة وهي النقطة التي
مات فيها و إضافة نقطة للاعب
في الكود الثاني يتم تشغيل الاستعلام عن حالة
الوحش مثل حالة الاختفاء حالة الموت وهكذا ويتم استدعاء الكود الثالث التالي
في الكود الثالث يكون للوحش عدة خيارات أو فنون
أو خبرات لإتمام وضيفته وهي كما يلي
في القسم الأول يتم استدعاء قدراته أو فنونه
الشبحيه و التي سوف نشرحها في السطر التالي
القسم الثاني يتم تثبيت سرعة المشي لديه
القسم الثالث يتم تبيت سرعة صعود او نزول السلم
القسم الرابع يتم تثبيت سرعة القفز
وهكذا انتهت الفنون
في الكود الرابع يتم استدعاء الإجراء ( الإجابة
على الحدث ) وهي إذا كان يستطيع القفز من مكانه يتم استدعاء كود القفز
========== نهًاية الشرح الثالث ==========
 


و هذا شرح تبسيط و ليس شرح معمق
و أجرو أن أكون قد أفدتكم فقد بذلت جهد حتى فهمته و حللته و شرحته
و انتظر ردودكم و السلام عليكم

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

السلام عليكم

رجعنا بعد عودة أتمنى ألا تكون قد طالت
وكما وعدت سابقا ان شاء الله سنحاول اليوم شرح قلب كود اللعبة وهى عملية ال state flow

اولا: قبل البداية فى موضوع ال state flow سنحاول شرح مفهوم مهم فى برمجة هذه اللعبة والتى تعتمد على المكتبة CoreLib
       واعتقد ان هذا الكلام ينطبق أيضا على أي لعبة أخرى نريد عملها بالاعتماد على هذه المكتبة:-
     هناك مفهوم أساسى فى برمجة الالعاب وهو ال Game Loop وهو ضرورى ويتم تنفيذه تكراريا  وهو مثل ال MessegeLoop الذى يقوم ببساطة
     بالبحث عن اى رسالة جديدة قادمة من رسائل الويندوز وموضوعة فى طابور الرسائل Messege Queue فاذا وجدها فانه
     يقوم بارسالها الى ال Windows Proc وهى ايضا ببساطة عبارة عن User Defined Function لتعالج وتحدد كيف نريد الاستجابة والتعامل
      مع رسائل الويندوز.

 فكرة ال Game Loop تقريبا متشابهة وهى ايضا عملية تحديث ورسم اطار اللعبة بشكل متكرر فى حالة عدم وجود رسائل ويندوز فى طابور الرسائل
يعنى للتوضيح انظروا الكود التالى (مع ان الشرح غير دقيق الا اننى اتمنى ان تصل الفكرة)
int CGameApp::BeginGame()
{
    MSG msg; 
 
    // Start main loop 
    while (1)  
    { 
        // Did we receive a message, or are we idling ? 
        if ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )  
        { 
                 if (msg.message == WM_QUIT) break; 
  
                 TranslateMessage( &msg ); 
                 DispatchMessage ( &msg ); 
        }  
        else  
        { 
                 FrameAdvance(); 
        }  
  
    } // Until quit message is received 
 
    return 0; 
}
الشكل السابق هو احدى الاشكال البسيطة لل Game loop وفى الدالة FrameAdvance
نقوم بتحديث اللعبة ورسمها (اقصد بتحديث اماكن كائنات اللعبة وتطبيق الذاكاء الاصطناعى عليهم واخذ ال input من اللاعب وهكذا

كل ما تحدثت عنه الان من مفهوم ال Game Loop مطبق في اللعبة ولكن باسلوب مختلف - انظر الكود التالى
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
	const bool bFullScreen = false;
	GameApp gameApp(hInstance,bFullScreen);

	HICON hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_GAME));
	if (!CoreLib::LibInit(&gameApp,SCREEN_WIDTH,SCREEN_HEIGHT,bFullScreen,hIcon,GameApp::GameWndProc))
		return -1;

	gameApp.Init();

	CoreLib::LibRun(GAME_FPS);

	CoreLib::LibShutdown();
	return 0;
}
حيث بدأت اللعبة بعمل Initialize كما ذكرنا سابقا
 وفى السطر ;(CoreLib::LibRun(GAME_FPS قامت المكتبة CoreLib ببدء ال game loop ولكن اين الدالة التى تقوم المكتبة باستدعائها
 فى حالة عدم وجود رسالة فى طابور رسائل الويندوز
ان مرونة المكتبة CoreLib تسمح لنا بالتحكم في سير البرنامج وتسلسله بالطريقة التى نحتاجها عن طريق الوراثة من الكلاس BaseApp
كما تفعل اللعبة حيث قامت بانشاء الكلاس GameApp وذلك لتعرف المكتبة ال Definition للدوال الاتية وهى التى سنتحدث عنهم

GameApp::OnIdel
هذه الدالة معرفة pure virtual function فى ال BaseApp وهذا يجبرنا بعمل تعريف لها فى الكلاس GameApp
ونحن هنا لا نريد من هذه الدالة حاليا الا عمل Update للعبة وخصوصا ال State flow وهذا سنتحدث عنه بالتفصيل لاحقا

GameApp::OnDraw
هذه الدالة معرفة pure virtual function فى ال BaseApp وهذا يجبرنا بعمل تعريف لها فى الكلاس GameApp
نحن هنا لا نريد الا طلب رسم اللعبة الان وذلك حسب حالة اللعبة وسنشرح ذلك ايضا لاحقا


اذن بعد هذه المقدمة الطويله عرفنا كيف تعالج المكتبة موضوع توفير مرونة للعبة كان نريد مثلا عمل اي شيء
فقط نقوم بوضعه فى المكان المناسب داخل الدالة OnIdea لنحصل على ما نريد

عذرا مرة اخرى على التطويل وقريبا جدا سأضع حسب فهمى شرح لموضوع ال state flow
واتمنى من الاخوة الافاضل مراجعة ما كتب والتعليق عليه
وجزاكم الله خيرا
والسلام عليكم

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

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

السلام عليكم

عدنا من جديد ويسعدنى أن أقدم حل أجده مناسب (على الاقل من وجهة نظرى المبدئية) لمشكلة أن اللعبة تستقبل مدخلات المستخدم
حتى وهى فى الوضع غير النشط وهذا يمكن تفاديه كما يلى:

أولا: الفكرة:
 لاننا قمنا بعمل الكلاس GameApp كما سبق وذكرنا لنضيف للعبة مؤثرات معينة أو نتحكم فى اللعبة بمرونة ما
وقلنا ان لدينا الدالة GameApp::OnIdeal وهى المسئولة عن عمل تحديث للعبة فى حالة ما قبل الانتقال لرسم الاطار التالى
يمكننا استغلال هذه الدالة لتعديل اللعبة بحيث لا تعمل update يعنى أى لا تستقبل مدخلات المستخدم أو تعالجها أو حتى تعالج
 شخصيات اللعبة وحركتها وذلك بمنع هذه الدالة من العمل فى حالة كانت اللعبة فيه غير نشطة

ثانيا خطوات الحل:
ننشئ متغير جديد فى الكلاس GameApp كما يلى
class GameApp : public CoreLib::BaseApp
{
public:
   // normal code here
   // ...

  // used to monitor game app activation
  bool m_bAppActive;
 
  // ...
  // rest of the code here
}
الان نقوم بوضع قيمة ابتدائية للمتغير لتكون اللعبة نشطة وذلك فى ال GameApp Constructor وأيضا فى الدالة GameApp::Init كالتالى:
// this line in GameApp::Init before returning from the func
m_bAppActive = true;
الان يبقى لنا تعديل حالة المتغير عندما تكون اللعبة نشطة أو غير نشطة وذلك كالتالى


LRESULT CALLBACK GameApp::GameWndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
        // App is being Activated or Deactivated
	if(uMsg == WM_ACTIVATE)
	{
		if(wParam != WA_INACTIVE)
		{
			// the app should now active, but may not have input focus
			GameApp::Instance->m_bAppActive = true;			
		}
		else  // app is being DeActivated
		{
			GameApp::Instance->m_bAppActive = false;
			// if our windows has focus, then discard it
			/*if(GetFocus() == hWnd)
				SetFocus(NULL);*/
		}

		// we handled the message of app activation
		return 0;
	}

        // check if app has focus and/or active
	if(!GameApp::Instance->m_bAppActive)
	{
		//OutputDebugString(TEXT("app is inactive\n"));
		//WaitMessage();
		return -1;
	}

	// here normal switch case message handling
}
كما نرى قمنا باضافة كود لتعديل حالة المتغير واللعبة اذا كانت نشطة او لا

والان للخطوة الاخيرة وهى منع تعديل عمل Update للعبة اذا كانت غير نشطة وذلك كما يلى:
bool GameApp::OnIdle(void)
{
	// only update if App is Active
	if(m_bAppActive)
	{
		m_StateFlow.Update();
		AudioUpdate();
		return true;
	}

	return false;
}

والان وبتجربة اللعبة مع هذا التعديل كانت اللعبة تتجاهل مستقبلات المستخدم ان كانت غير نشطة

وانا مستعد لتوضيح أى شئ غير واضح
هذا وجزاكم الله خيرا
والسلام عليكم

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

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

بتاريخ 10 ابريل 2008 06:47 ص، قطب ahmed ezz حاجبيه بشدة وهو يقول:

والان للخطوة الاخيرة وهى منع تعديل عمل Update للعبة اذا كانت غير نشطة وذلك كما يلى:
bool GameApp::OnIdle(void)
{
	// only update if App is Active
	if(m_bAppActive)
	{
		m_StateFlow.Update();
		AudioUpdate();
		return true;
	}

	return false;
}

والان وبتجربة اللعبة مع هذا التعديل كانت اللعبة تتجاهل مستقبلات المستخدم ان كانت غير نشطة

الطريقة التي تستخدمها للكشف عن وضع اللعبة من حيث كونها مفعلة أم لا هي طريقة صحيحة. إلا أن الكود الذي وضعته في إجراء OnIdle يمنع اللعبة من التقدم كلياً وقد يتسبب بمشاكل في الصوت أيضاً. الهدف هو فقط عدم تلقي المدخلات من الكيبورد، وليس إيقاف اللعبة تماماً. عملية إيقاف اللعبة سنفعلها في المهمة القادمة (Pause) بشكل أفضل من هذا، حيث سنضع شاشة خاصة لها.
 
 
أود أن أسأل من لديه الاستعداد لإضافة دعم عصا التحكم Joystick؟

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