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

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

قام الإخوان ahmed ezz وعمار زاهدة بعمل رائع في شرح الكود☺
(بالمناسبة لم أقرأ الكود بعد, عدا عن الكود الخاص بتلقي الإدخال)


// 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;
}


لدي هنا ملاحظة صغيرة, رسالة WM_ACTIVATE تمرر حالة التفعيل (أي رسالة WA_INACTIVE هنا) عن طريق الجزء المنخفض low-word من بارميتر wParam, أم الجزء العالي high-word من الباراميتر فيتم تمرير قيمة تبين ما إذا كانت النافذة مصغرة minimized أم لا, إن كانت القيمة لا تساوي الصفر ذلك يعني إنها مصغرة, يمكن إستخدام الماكرو LOWORD والماكرو HIWORD لعزل الأجزاء عن بعض.
بالطبع قد لا تمثل مشكلة هنا وقد تكون قصدتها أنت أن تكون هكذا لكي تقوم بعدم تلقي الإدخال طالما النافذة مصغرة, وأعتقد بأن ذلك ما سيحصل.

وفي 10 نيسان 2008 03:52 م، أعرب وسام البهنسي عن رأيه بالموقف كالآتي:

أود أن أسأل من لديه الاستعداد لإضافة دعم عصا التحكم Joystick؟
 
لدي الإستعداد... وليس الوقت, إذا إنتهت إمتحاناتي (الإسبوع القادم إن شاء الله) ولم يقم أحد بعمل ذلك بعد سأقوم بعمله, سأتخلص تماماً من جزءGetAsyncKeyState والذي انا متأكد إنه سبب مشكلة تلقي اللإدخال بصورة Global, سأستخدم DirectInput8 إن أمكن وأقرأ joystick واحد مع لوحة المفاتيح طبعاً.
 ولكني حالياً أواجه مشاكل في تشغيل اللعبة... قبل إسبوع من الآن كان كل شئ على ما يرام, قمت يوم أمس بعمل hack سريع لمنع GetAsyncKeyState من إرجاع قيمة عندما لا تملك النافذة الـ keyboard focus, قمت ببناء المشروع وعندما حاولت التنفيذ ظهرت لي هذه الرسالة المشؤومة:




طبعاً من غير أن أنسى أن أذكر إني أستخدم Visual Studio 2005, ذلك قد يكون السبب ولكن الغريب في الموضوع إني جربتها وكانت تعمل بلا مشاكل في الإسبوع الماضي!
طبعاً أعدت التجربة على النسخة الأصلية الغير معدلة من الكود ولكن مرة أخرى نفس المشكلة, سأجد حلاً لذلك.


بالمناسبة... ما هو يا ترى "DSK|RenderSmith"؟... ;-)
إن كان هذا يدل إن هنالك على الأقل جزء من مكتبة DirectSkeleton مستخدم في داخل CoreLib, فلديكم الحق الكامل في تجنب نشر أجزاء من تقنياتكم التجارية, نحن هنا لنتعلم فقط...☺
لاحظت أيضاً أن اللعبة تستخدم أكثر من thread واحد, وذلك جميل.
 
من نظرتي السريعة على طريقة عمل المكتبة, لا أعتقد إن الإعتماد عليها كحزمة مغلقة سيصعب من القيام بالمهام المطلوبة.
 
وشكراً

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

تكلمت انا والأخ ahmed ezz بخصوص مشكلتي مع الملف التنفيذي للعبة وشجعني على البحث عن سببها, وبعد عدة محاولات تضمنت إرتكاب أخطاء وعمل أشياء غريبة... تضمنت البحث في داخل ملفات الـ binary عن معلومات الـ dependency في الـ manifest المدمج..
 
توصلنا أخيراً لسبب المشكلة, رحلة البحث بدأت من ملاحظة رسالة الخطأ التي يظهرها الـ Visual Studio 2005 وهذا هو نصها:


LDR: LdrpWalkImportDescriptor() failed to probe C:\Valley_of_Kings\Game\CoreLib.dll 
for its manifest, ntstatus 0xc0150002

وبعد عدة محاولات وصلنا إلى ملف الـ manifest المدمج في داخل CoreLib.dll والذي تضمن ما يلي:

      	
		type="win32" name="Microsoft.VC90.DebugCRT" version="9.0.21022.8" 
		processorArchitecture="x86" 
		publicKeyToken="1fc8b3b9a1e18e3b">

لاحظت مباشرة "Microsoft.VC90.DebugCRT", لقد قمت بتنصيب ملفات الـ runtime التابعة لـ VC90 قبل فترة, ولكن كما يعلم الجميع هذه الملفات توزعها مايكروسوفت مبنية بنمط الـ Release, في حين إن ملفات الـ Debug المقابلة لها محمية من التوزيع بحقوق طبع خاصة من مايكروسوفت.. أي لا يمكن الحصول عليها قانونياً إلا عن طريق تثبيت Visual Studio 2008, وبما إن المكتبة CoreLib مبنية كما يبدو بإستخدام نمط Debug فإنها ستحتاج ملفات التشغيل الـ Debug التي هي غير موجودة! وهنا تكمن المعضلة...إكتشفت أيضاً أحد الأخطاء التي إرتكتبها وهي تصوري بأني شغلت اللعبة من الفجوال ستوديو عن طريق بناء الكود... لا أعلم كيف كنت متأكداً ولكن يبدو لي الآن إني كنت على خطأ كبير (تحصل معي هكذا أشياء دائماَ)... آسف لذلك☺
 
والآن.. أمامي حلين كما أرى... الأول هو أن أطلب من فريق In|Framez أن يوفروا المكتبة CoreLib مبنية بنمط Release فقط إن أمكن ذلك, أو أن أقوم بتنزيل Visual Studio Express 2008.... انا لا أحبذ تثبيت نسخة جديدة لأني أعتمد على مشاريعي الأخرى على VS2005 ولدي أدوات معينة أستخدمها أحياناً (مثلاً Profiler) تدعم الإصدارة 2005.
إن لم أجد حلاً آخر سأقوم بتثبيت النسخة 2008 مجبراً.
 
أشكر الأخ ahmed ezz لأنه شجعني وشاركني البحث والتنقيب, كان وقتاً ممتعاً ;-)
 
(تعديل: طبعاً, إكتشفت فيما بعد إنه كان بإمكاني أن أستخدم Dependency Walker لأتلافى ذلك كله, وأعثر على المشكلة مباشرة... فقط لو كنت تذكرته, ولكن هذه التجربة بأكملها كانت مفيدة بشكل ما.)

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

في 10 ابريل 2008 03:13 م، عقد سلوان الهلالي حاجبيه بتفكير وقال:

لاحظت مباشرة "Microsoft.VC90.DebugCRT", لقد قمت بتنصيب ملفات الـ runtime التابعة لـ VC90 قبل فترة, ولكن كما يعلم الجميع هذه الملفات توزعها مايكروسوفت مبنية بنمط الـ Release, في حين إن ملفات الـ Debug المقابلة لها محمية من التوزيع بحقوق طبع خاصة من مايكروسوفت.. أي لا يمكن الحصول عليها قانونياً إلا عن طريق تثبيت Visual Studio 2008, وبما إن المكتبة CoreLib مبنية كما يبدو بإستخدام نمط Debug فإنها ستحتاج ملفات التشغيل الـ Debug التي هي غير موجودة! وهنا تكمن المعضلة...إكتشفت أيضاً أحد الأخطاء التي إرتكتبها وهي تصوري بأني شغلت اللعبة من الفجوال ستوديو عن طريق بناء الكود... لا أعلم كيف كنت متأكداً ولكن يبدو لي الآن إني كنت على خطأ كبير (تحصل معي هكذا أشياء دائماَ)... آسف لذلك☺
 
والآن.. أمامي حلين كما أرى... الأول هو أن أطلب من فريق In|Framez أن يوفروا المكتبة CoreLib مبنية بنمط Release فقط إن أمكن ذلك, أو أن أقوم بتنزيل Visual Studio Express 2008.... انا لا أحبذ تثبيت نسخة جديدة لأني أعتمد على مشاريعي الأخرى على VS2005 ولدي أدوات معينة أستخدمها أحياناً (مثلاً Profiler) تدعم الإصدارة 2005.
إن لم أجد حلاً آخر سأقوم بتثبيت النسخة 2008 مجبراً.
 

نعتذر إن كان هذا قد تسبب بإضاعة وقتك. كل ما ذكرته صحيح إلى درجة كبيرة. كما وضحتُ في مشاركة سابقة في حديثي عن مكتبة CoreLib، فإن هذه المكتبة تساعدنا في عدة أمور. كونها مبنية بنمط Debug (هو ليس نمط Debug في الحقيقة، لكن شيء قريب من ذلك) هو أحد العوامل التي تساعدنا في تحقيق النقاط السابقة الذكر. إلا أنني سأعود وأؤكد ثانية، هذه المكتبة لن تتسبب بتعطيلنا عن أداء مهامنا، إلا إن كنا نفكر بالاتجاه الخطأ.
 
كحل لمشكلتك، لن تضطر إلى تنصيب VS.NET 2008 (وإن كنا ندعوك لذلك بشدة). ستجد كمرفق مع هذه المشاركة ملف zip يحوي النسخة الخاصة من CoreLib التي تعمل مع VS.NET 2005. فقط استخرج الملفات التي فيها وضعهم بدلاً من نظائرهم في دليل اللعبة. أعد بناء المشروع كلية، ومن ثم قم بتشغيل اللعبة، ويجب أن تعمل الآن دون أي مشاكل...



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

(تعديل: طبعاً, إكتشفت فيما بعد إنه كان بإمكاني أن أستخدم Dependency Walker لأتلافى ذلك كله, وأعثر على المشكلة مباشرة... فقط لو كنت تذكرته, ولكن هذه التجربة بأكملها كانت مفيدة بشكل ما.)

أنا سعيد جداً أنك قمت بطرح خبرتك هنا، وبالمقابل أنك استفدت مما نحاول تعليمه في هذا المشروع. المزيد من الأمور المفيدة قادمة أيضاً في الطريق☺  ترقبوا! 


وفي 10 ابريل 2008 10:48 ص، ظهر شبح ابتسامة على وجه سلوان الهلالي وهو يقول:

بالمناسبة... ما هو يا ترى "DSK|RenderSmith"؟... ;-)
إن كان هذا يدل إن هنالك على الأقل جزء من مكتبة DirectSkeleton مستخدم في داخل CoreLib, فلديكم الحق الكامل في تجنب نشر أجزاء من تقنياتكم التجارية, نحن هنا لنتعلم فقط...☺ 

DSK|RenderSmith هو الوحدة التي تقوم بجميع المهام المملة التي تضطر إلى التعامل معها في أي مشروع لعبة واقعية.في حالة لعبة وادي الملوك فإن دور هذه الوحدة يقتصر على إدارة نافذة اللعبة ودورة حياة التنفيذ بشكل عام، إلا أن إمكانيات DSK|RS تمتد إلى أكثر من ذلك بكثير في الحقيقة. ويمكنك القراءة أكثر عن هذه الوحدة هنا:
 
http://www.inframez.com/dsk_rendersmith.htm
 
هناك العديد من الأمور الجميلة التي تقوم بها هذه الوحدة أيضاً، لكنها خارج نطاق هذا المشروع. في المشاريع القادمة إن شاء الله سنتعرض أكثر لهذه التفاصيل دون الاعتماد على DSK|RS وسنتعلم سوية كيف نبنيها باذن الله.
 
 

أما في 10 ابريل 2008 10:48 ص، فقد تنهد سلوان الهلالي بارتياح وهو يرد:

لاحظت أيضاً أن اللعبة تستخدم أكثر من thread واحد, وذلك جميل.

في الحقيقة اللعبة كما ترى من الكود فيها، لا تقوم بإنشاء أي threads إضافية. هذه الـ threads منشأها إما نظام التشغيل أو DirectSound (باعتبار أننا نستخدم Software Mixer للهروب من مشاكل التوافقية مع كروت الصوت الغير محترمة).  لكني بصراحة كنت أفكر يوم أمس بكيفية جعل كود اللعبة يعمل على أكثر من thread. طبعاً في حالة وادي الملوك نحن لسنا بحاجة لذلك أبداً لأن الأداء سريع جداً كما هو الآن. إلا أنني كنت أفكر بالموضوع من منظور عام أو تخيلي. وقد توصلت لبعض الأفكار بهذا الصدد، ويمكننا أن نناقشها سوية إذا أردتم (وإن كان الموضوع متقدم جداً ولن يفيدنا في هذا المشروع).

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

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

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

نعتذر إن كان هذا قد تسبب بإضاعة وقتك. كل ما ذكرته صحيح إلى درجة كبيرة. كما وضحتُ في مشاركة سابقة في حديثي عن مكتبة CoreLib، فإن هذه المكتبة تساعدنا في عدة أمور. كونها مبنية بنمط Debug (هو ليس نمط Debug في الحقيقة، لكن شيء قريب من ذلك) هو أحد العوامل التي تساعدنا في تحقيق النقاط السابقة الذكر. إلا أنني سأعود وأؤكد ثانية، هذه المكتبة لن تتسبب بتعطيلنا عن أداء مهامنا، إلا إن كنا نفكر بالاتجاه الخطأ.

لا حاجة للإعتذار, ولا تقلق بشأن الوقت فقد إستفدت في النهاية☺
بل أعتذر انا لإصراري على VS2005 (رغم تنبيهك لنا منذ البداية إن ذلك قد يسبب المشاكل)..



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

كحل لمشكلتك، لن تضطر إلى تنصيب VS.NET 2008 (وإن كنا ندعوك لذلك بشدة). ستجد كمرفق مع هذه المشاركة ملف zip يحوي النسخة الخاصة من CoreLib التي تعمل مع VS.NET 2005. فقط استخرج الملفات التي فيها وضعهم بدلاً من نظائرهم في دليل اللعبة. أعد بناء المشروع كلية، ومن ثم قم بتشغيل اللعبة، ويجب أن تعمل الآن دون أي مشاكل...

شكراً جزيلاً لكم لتوفير المكتبة مبنية لـ VS2005, لقد جربتها وكل شئ يعمل بشكل صحيح الآن.
 
 
--------------------------------------------------------------------
والآن.... إستعدوا للـ hack السريع الذي عملته لحل مشكلة الإدخال.....
(تحذير: هذه قد تكون أسوأ طريقة ممكنة لعمل المطلوب, ولكنها أسرع طريقة للعمل وبأقل تعديلات ممكنة)
المشكلة الأساسية هي إن دالة GetAsyncKeyState تقرأ من لوحة المفاتيح بشكل شبه مباشر من دون الإهتمام بأي النوافذ أو البرامج فعالة, طريقتي السيئة هي التلاعب مباشرة بالماكرو الذي يستخدم لقراءة الإدخال من قبل اللعبة, ذلك هو الماكرو KEYDOWN.
ما قمت به هو إضافة mask يتم عمل عملية and (&) بينه وبين ما تعيده الدالة GetAsyncKeyState, وهذا الـ mask هو... -إستعدوا للصدمة- Global Variable!
يتم تحديث ذلك الـ mask من قبل الـ Message Procedure بشكل يجعل الدالة تعيد صفراً دائماً في حالة عدم كون النافذة هي المنشطة حالياً.
 
التطبيق:
تعريف المتغير العام windowActiveInputMask وإعادة تعريف الماكرو KEYDOWN في أعلى ملف Game.h:


////////////////// Input masking hack //////////////////
extern unsigned short windowActiveInputMask;
#define KEYDOWN(vkCode) (GetAsyncKeyState(vkCode) &0x8000 &windowActiveInputMask)

تهيئة المتغير العام windowActiveInputMask للإستخدام في أعلى ملف Game.cpp:


unsigned short windowActiveInputMask = 0xffff;
 
أخيراً إضافة كود التعامل مع رسالة WM_ACTIVATE للـ GameApp::GameWndProc في ملف Game.cpp:
 



case WM_ACTIVATE:
  if(LOWORD(wParam) == WA_INACTIVE)
   	windowActiveInputMask = 0x0000;
  else
   	windowActiveInputMask = 0xffff;
  break;
 
إنتهى! لقد حذرتكم... لست مسؤولاً عن النتائج ;-)
لقد جربت اللعبة بعد هذه التعديلات ويبدو إن المشكلة قد حلت.
 
ما سأحاول فعله في المرة القادمة هو بناء نظام إدخال بمزايا مقبولة لكي أتخلص من دالة GetAsyncKeyState تماماً..

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

السلام عليكم

جزاكم الله خيرا على المعلومات المفيدة

في 11 ابريل 2008 03:20 ص، غمغم وسام البهنسي باستغراب قائلاً:

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

وجزاكم الله خيرا
والسلام عليكم

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

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

في 10 ابريل 2008 10:21 م، غمغم سلوان الهلالي باستغراب قائلاً:

والآن.... إستعدوا للـ hack السريع الذي عملته لحل مشكلة الإدخال.....
(تحذير: هذه قد تكون أسوأ طريقة ممكنة لعمل المطلوب, ولكنها أسرع طريقة للعمل وبأقل تعديلات ممكنة)
المشكلة الأساسية هي إن دالة GetAsyncKeyState تقرأ من لوحة المفاتيح بشكل شبه مباشر من دون الإهتمام بأي النوافذ أو البرامج فعالة, طريقتي السيئة هي التلاعب مباشرة بالماكرو الذي يستخدم لقراءة الإدخال من قبل اللعبة, ذلك هو الماكرو KEYDOWN.
ما قمت به هو إضافة mask يتم عمل عملية and (&) بينه وبين ما تعيده الدالة GetAsyncKeyState, وهذا الـ mask هو... -إستعدوا للصدمة- Global Variable!
يتم تحديث ذلك الـ mask من قبل الـ Message Procedure بشكل يجعل الدالة تعيد صفراً دائماً في حالة عدم كون النافذة هي المنشطة حالياً.

هاها. الحل فعال حقاً، لكن كما قلت أنت، ليس أمثلياً... على كل أعتقد أن الوضع سيتغير حالما تقوم بإضافة دعم أجهزة التحكم الأخرى، لذا فإن هذا الحل مقبول الآن ونحن في "شوط التحمية".
 
سأعلق على نقطة هنا أيضاً... في بنية اللعبة، هناك class مخصص يحمل كل المعلومات عن وضع اللعبة بشكل عام (GameApp)، ويمكنك وضع متغيرك الجديد به بدلاً من جعله معلقاً في الهواء. يمكنك الوصول إلى القيمة عن طريق شيء مثل: GameApp::Instance->IsAppActive

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

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

حسناً... حسناً, سأقوم بإستخدام الـ Instance☺
ولكن هذه المرة, التغييرات أكثر طبعاً, سأكمل ما بدأه الأخ ahmed ezz.
 
ملخص الطريقة: إستخدام method جزء من GameApp لقراءة الإدخال عندما تكون النافذة مفعلة بدل الماكرو KEYDOWN, حالة تفعيل النافذة سيتم الإحتفاظ بها في m_bAppActive الذي يمتلك ميزة static لكي يمكن الدخول إليه من الـ Message Procedure (الذي يجب أن يكون static).
 
خطوات العمل:
---------------
إضافة الدالة العضو KeyDown لـ class GameApp كدالة عامة public في ملف Game.h:



const short KeyDown(int vKey) const;

 
إضافة المتغير m_bAppActive لـ class GameApp كمتغير عام public وجعله static:
 


static bool m_bAppActive;

تهيئة m_bAppActive في ملف Game.cpp قبل الـ GameApp constructor (أقوم بذلك لأنه static):
 



bool GameApp::m_bAppActive = true;

إضافة  كود التعامل مع رسالة WM_ACTIVATE للـ Message Procedure في ملف Game.cpp:
 



case WM_ACTIVATE:

    if( LOWORD(wParam) == WA_INACTIVE )
        m_bAppActive = false;
    else
        m_bAppActive = true;
    break;

كتابة متن () GameApp::KeyDown في نهاية الملف Game.cpp:



/////////////////////////////////////////////////////////////////////////////////////////////
// Function Name : GameApp::KeyDown

//

// Purpose : Returns virtual key state using GetAsyncKeyState() taking into account the
// application's window focus state.

//
// Params : 

// int vKey: Specifies one of 256 possible virtual-key codes.

//

// Return Value : Returns 0 if the application does not have focus, vKey state otherwise.
/////////////////////////////////////////////////////////////////////////////////////////////
const short GameApp::KeyDown(int vKey) const
{
    return GameApp::Instance->m_bAppActive? GetAsyncKeyState(vKey) &0x8000 : 0;
}


والآن, تغيير جميع إستدعاءات الماكرو KEYDOWN في كود اللعبة بالطريقة الآتية:

KEYDOWN(VK_RIGHT)

تصبح:


GameApp::Instance->KeyDown(VK_RIGHT)

إنتهى!
لأن التغييرات كثيرة فسأقدم مجموعة الملفات المعدلة في المرفقات.
 
آخر إمتحان مشؤوم لدي سيكون بعد غد... سأحاول عمل نظام الإدخال الجديد في يومها أو في اليوم الذي يليه إن شاء الله.
 
وشكراً لكم☺

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

تعديلات رائعة سلوان!
سأقوم بتحميل التعديلات ودمجها مع المشروع هنا، ومن ثم نشر الملفات النهائية على عنوان ثابت في الموقع كي يستطيع أي واحد دائماً تحميل آخر إصدار من الكود والذي يحوي كل التعديلات التي يقوم بها الأعضاء (بمعنى آخر أنا سألعب دور Visual SourceSafe).
 
أعتقد أنك لست الوحيد الذي يواجه تحدي فترة الامتحانات، لذلك لم أقم بطرح مهام المرحلة الثانية كي لا أضع أي ضغط عليكم... سنعود إلى وتيرة العمل الأصلية خلال عدة أيام إن شاء الله، ونقوم بطرح مهام المرحلة الثانية لجميع الأقسام التي حققت المتطلبات للمرحلة الأولى، مع ملخص لما تعلمناه في هذه المرحلة.

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

خبير  سعيد بسيوني مشاركة 19

في 08/ربيع الثاني/1429 03:27 م، عقد سلوان الهلالي حاجبيه بتفكير وقال:

والآن, تغيير جميع إستدعاءات الماكرو KEYDOWN في كود اللعبة بالطريقة الآتية:

KEYDOWN(VK_RIGHT)

تصبح:


GameApp::Instance->KeyDown(VK_RIGHT)

إنتهى!

برأيي مفيش داعي تعمل كده. بكل بساطة غير تعريف الماكرو نفسو عشان يعمل نداء للإجراء الجديد بتاعك..
حاجة زي كدة يعني:
 

#define KEYDOWN(key) GameApp::Instance->KeyDown(key)
 
وبكده انت مش مضطر تغير حاجة في الكود الأصلي.

محترف مشرف عبد اللطيف حاجي علي مشاركة 20

وفي 14 نيسان 2008 11:12 م، ظهر شبح ابتسامة على وجه سعيد بسيوني وهو يقول:

برأيي مفيش داعي تعمل كده. بكل بساطة غير تعريف الماكرو نفسو عشان يعمل نداء للإجراء الجديد بتاعك..
حاجة زي كدة يعني:
 

#define KEYDOWN(key) GameApp::Instance->KeyDown(key)
 
وبكده انت مش مضطر تغير حاجة في الكود الأصلي.

بالعكس.
اللعبة حالياً فيها مشكلتين من ناحية الإدخال الأولى هي استخدام الماكرو دون داعي (دون ذكر مشاكل استخدام الماكرو بدلاً من الإجراءات)
والثانية هي عدم وجود وحدة مختصة بالإدخال في اللعبة لذلك تجد إجراءات طلب الإدخال مبعثرة في اللعبة.
طبعاً حل سلوان يحل المشكلة الأولى بشكل كامل و الثانية بشكل جزئي

عبد اللطيف حاجي علي
مبرمج
In|Framez