هل تساءلت يومًا عن كيفية تنفيذ وظيفة إعادة التشغيل في ألعاب مثل 
Super Meat Boy ؟ تتمثل إحدى طرق تنفيذها في تنفيذ الإدخال بنفس الطريقة التي اتبعها اللاعب ، وهذا بدوره يعني أن الإدخال يجب أن يتم تخزينه بطريقة ما. يمكنك استخدام 
نمط الأوامر لهذا وأكثر من ذلك بكثير.
يعد قالب الأوامر مفيدًا أيضًا في إنشاء وظائف "تراجع" و "إعادة" في لعبة استراتيجية.
في هذا البرنامج التعليمي ، ننفذ قالب الأوامر في C # ونستخدمه لتوجيه شخصية الروبوت خلال متاهة ثلاثية الأبعاد. من البرنامج التعليمي سوف تتعلم:
- أساسيات نمط القيادة.
- كيفية تنفيذ نمط القيادة
- كيفية إنشاء قائمة انتظار من أوامر الإدخال وتأخير تنفيذها.
ملاحظة : من المفترض أنك على دراية بالوحدة ولديك معرفة متوسطة بـ C #. في هذا البرنامج التعليمي ، سنعمل مع Unity 2019.1 و C # 7 .
الحصول على العمل
للبدء ، قم بتنزيل 
مواد المشروع . قم بفك ضغط الملف وافتح مشروع 
Starter في الوحدة.
انتقل إلى 
RW / Scenes وافتح المشهد 
الرئيسي . يتكون المشهد من روبوت ومتاهة ، وكذلك واجهة مستخدم طرفية تعرض التعليمات. يتم تصميم المستوى في شكل شبكة ، وهو أمر مفيد عندما ننقل الروبوت بصريا عبر المتاهة.
إذا قمت بالنقر فوق " 
تشغيل" ، فسنرى أن التعليمات لا تعمل. هذا أمر طبيعي لأننا سنضيف هذه الوظيفة إلى البرنامج التعليمي.
الجزء الأكثر إثارة للاهتمام من المشهد هو GameObject 
Bot . حدده في نافذة التسلسل الهرمي من خلال النقر عليه.
في المفتش ، يمكنك أن ترى أنه يحتوي على عنصر 
بوت . سنستخدم هذا المكون من خلال إصدار أوامر الإدخال.
ونحن نفهم منطق الروبوت
انتقل إلى 
RW / Scripts وافتح البرنامج النصي 
Bot في محرر الكود. لا تحتاج إلى معرفة ما يحدث في البرنامج النصي 
بوت . لكن ألقِ نظرة على طريقتين: 
Move and 
Shoot . مرة أخرى ، لا يتعين عليك معرفة ما يجري داخل هذه الطرق ، ولكن عليك أن تفهم كيفية استخدامها.
لاحظ أن الأسلوب 
Move يتلقى معلمة إدخال 
CardinalDirection . 
CardinalDirection هو تعداد. يمكن أن يكون عنصر التعداد من النوع 
CardinalDirection Up أو 
Down أو 
Left أو 
Left . اعتمادًا على 
CardinalDirection المحدد 
CardinalDirection يتحرك الروبوت مربعًا واحدًا على طول الشبكة في الاتجاه المقابل.
طريقة 
Shoot تجبر الروبوت على إطلاق قذائف تدمر 
الجدران الصفراء ، ولكنها غير مجدية ضد الجدران الأخرى.
أخيرًا ، ألقِ نظرة على طريقة 
ResetToLastCheckpoint ؛ لفهم ما يفعله ، انظر إلى المتاهة. هناك نقاط في المتاهة تسمى 
نقطة التفتيش . لتمرير المتاهة ، يحتاج الروبوت إلى الوصول إلى نقطة التحكم 
الخضراء .
عندما يخطو الروبوت في نقطة تحكم جديدة ، يصبح 
الأخير بالنسبة له. 
ResetToLastCheckpoint يعيد تعيين 
ResetToLastCheckpoint ، ونقله إلى نقطة التحكم الأخيرة.
على الرغم من أننا لا نستطيع استخدام هذه الطرق ، إلا أننا سنصلحها قريبًا. للبدء ، تحتاج إلى معرفة نمط تصميم 
الأوامر .
ما هو نمط تصميم القيادة؟
نمط القيادة هو واحد من 23 نمطًا من أشكال التصميم الموضحة في كتاب " 
أنماط التصميم: عناصر من البرمجيات الموجهة للكائنات القابلة لإعادة الاستخدام" ، والتي كتبها عصابة الأربعة من إريك جاما وريتشارد هيلم ورالف جونسون وجون فليسيديس (GoF ، عصابة الأربعة).
يذكر المؤلفون أن "نمط الأوامر يحتوي على الطلب ككائن ، مما يسمح لنا بمعلمة كائنات أخرى بطلبات مختلفة ، وطلبات قائمة الانتظار أو سجل ، ودعم عمليات عكسية."
نجاح باهر! كيف هذا؟
أفهم أن هذا التعريف ليس بسيطًا جدًا ، لذلك دعونا نحلله.
التغليف يعني أنه يمكن تغليف استدعاء الأسلوب ككائن.
يمكن أن تؤثر الطريقة المُغلفة على العديد من الكائنات وفقًا لمعلمة الإدخال. وهذا ما يسمى 
توحيد الكائنات الأخرى.
يمكن حفظ "الأمر" الناتج مع الفرق الأخرى حتى يتم تنفيذها. هذه هي 
قائمة انتظار الطلب.
قائمة انتظار الفريقأخيرًا ، تعني إمكانية 
الرجوع أن العمليات يمكن التراجع عنها باستخدام وظيفة التراجع.
حسنًا ، لكن كيف ينعكس هذا في الكود؟
سيكون لفئة 
الأمر طريقة 
تنفيذ ، والتي تستقبل كمعلمة إدخال الكائن (الذي يتم تنفيذ الأمر به) يسمى 
المتلقي . وهذا هو ، في الواقع ، يتم 
تغليف أسلوب التنفيذ بواسطة فئة الأوامر.
يمكن تمرير العديد من مثيلات فئة الأوامر ككائنات عادية ، أي ، يمكن تخزينها في هياكل البيانات مثل قائمة انتظار أو مكدس ، إلخ.
لتنفيذ أمر ، يجب عليك استدعاء أسلوب التنفيذ. تسمى الفئة التي تبدأ التنفيذ 
Invoker .
يحتوي المشروع حاليًا على فصل فارغ يسمى 
BotCommand . في القسم التالي ، سننفذ تنفيذ ما ورد أعلاه للسماح للروبوت بتنفيذ إجراءات باستخدام قالب الأوامر.
تحريك الروبوت
تنفيذ نمط القيادة
في هذا القسم ، ننفذ نمط القيادة. هناك العديد من الطرق لتنفيذه. في هذا البرنامج التعليمي سوف نغطي واحد منهم.
للبدء ، انتقل إلى 
RW / Scripts وافتح البرنامج النصي 
BotCommand في المحرر. فئة 
BotCommand لا تزال فارغة ، ولكن ليس لفترة طويلة.
أدخل الكود التالي في الفصل:
   
ما الذي يحدث هنا؟
- commandNameاستخدام متغير- commandNameببساطة لتخزين اسم الأمر- commandNameللقراءة من قبل الإنسان. ليس من الضروري استخدامه في القالب ، لكننا سنحتاجه لاحقًا في البرنامج التعليمي.
- يتلقى BotCommandوظيفة وسلسلة. سيساعدنا ذلك في إعداد طريقةExecuteلكائن الأوامرname.
- يعرّف المفوض ExecuteCallbackنوع الأسلوب المغلف. ستُرجع الطريقة المُغلفة فراغًا وتقبل كمعلمة إدخال كائنًا من النوعBot(المكون Bot ).
- تشير خاصية Executeإلى الطريقة المغلفة. سوف نستخدمها لاستدعاء الطريقة المغلفة.
- تم تجاوز أسلوب ToStringلإرجاعcommandNameالسلسلة. هذا مناسب ، على سبيل المثال ، للاستخدام في واجهة المستخدم.
حفظ التغييرات وهذا كل شيء! لقد نفذنا بنجاح نمط القيادة.
يبقى لاستخدامه.
بناء الفريق
افتح 
BotInputHandler في المجلد 
RW / Scripts .
هنا سنقوم بإنشاء خمس حالات من 
BotCommand . ستقوم هذه الحالات بتغليف طرق لتحريك GameObject Bot لأعلى ولأسفل ولليسار ولليمين ، وكذلك للتصوير.
لتنفيذ ذلك ، أدخل ما يلي في هذه الفئة:
   
في كل حالة من هذه الحالات ، 
يتم تمرير 
طريقة مجهولة المصدر إلى المُنشئ. سيتم تغليف هذه الطريقة المجهولة داخل كائن الأمر المقابل. كما ترون ، يفي توقيع كل من الأساليب المجهولة بالمتطلبات المحددة من قبل المفوض 
ExecuteCallback .
بالإضافة إلى ذلك ، المعلمة الثانية إلى المُنشئ عبارة عن سلسلة تشير إلى اسم الأمر. سيتم إرجاع هذا الاسم بواسطة أسلوب 
ToString لمثيل الأمر. في وقت لاحق سوف نطبقها على واجهة المستخدم.
في الحالات الأربع الأولى ، تستدعي الأساليب المجهولة الأسلوب 
Move على كائن 
bot . ومع ذلك ، معلمات الإدخال الخاصة بهم مختلفة.
تمر 
MoveRight و 
MoveLeft و 
MoveLeft و 
MoveRight Move المعلمات 
CardinalDirection.Up و 
CardinalDirection.Down و 
CardinalDirection.Left و 
CardinalDirection.Right . كما هو مذكور في قسم 
What is Pattern Design Pattern ، فإنهم يشيرون إلى اتجاهات مختلفة لتحرك GameObject Bot.
في المثيل الخامس ، يستدعي الأسلوب المجهول أسلوب 
Shoot للكائن 
bot . بفضل هذا ، سيقوم الروبوت بإطلاق قذيفة أثناء تنفيذ الأمر.
الآن وقد أنشأنا الأوامر ، نحتاج إلى الوصول إليها بطريقة أو بأخرى عندما يقوم المستخدم بإدخال مدخلات.
للقيام بذلك ، 
BotInputHandler التعليمات البرمجية التالية في 
BotInputHandler ، مباشرة بعد مثيلات الأوامر:
  public static BotCommand HandleInput() { if (Input.GetKeyDown(KeyCode.W)) { return MoveUp; } else if (Input.GetKeyDown(KeyCode.S)) { return MoveDown; } else if (Input.GetKeyDown(KeyCode.D)) { return MoveRight; } else if (Input.GetKeyDown(KeyCode.A)) { return MoveLeft; } else if (Input.GetKeyDown(KeyCode.F)) { return Shoot; } return null; } 
إرجاع الأسلوب 
HandleInput مثيل واحد من الأمر اعتماداً على المفتاح ضغط من قبل المستخدم. احفظ التغييرات قبل الانتقال.
تطبيق الأوامر
رائع ، الآن حان الوقت لاستخدام الفرق التي أنشأناها. انتقل إلى 
RW / Scripts مرة أخرى وافتح البرنامج النصي 
SceneManager في المحرر. في هذه الفئة ، ستلاحظ وجود ارتباط لمتغير 
uiManager من النوع 
UIManager .
توفر فئة 
UIManager طرق مساعدة مفيدة 
لواجهة المستخدم الطرفية التي نستخدمها في هذا المشهد. إذا تم استخدام الطريقة من 
UIManager ، فسوف يوضح البرنامج التعليمي ما الذي تفعله ، ولكن بشكل عام لأغراضنا ، ليس من الضروري معرفة بنيته الداخلية.
بالإضافة إلى ذلك ، يشير متغير 
bot إلى مكون bot المرفق بـ GameObject 
Bot .
أضف الآن الكود التالي إلى فئة 
SceneManager ، واستبدله بالتعليق 
//1 :
   
واو ، كم رمز! لكن لا تقلق ؛ نحن جاهزون أخيرًا للإطلاق الحقيقي الأول للمشروع في نافذة اللعبة.
ساوضح الشيفرة لاحقا. تذكر أن تحفظ التغييرات.
تشغيل اللعبة لاختبار قالب الأوامر
الآن حان الوقت للبناء. انقر فوق 
تشغيل في محرر الوحدة.
يجب أن تكون قادرًا على إدخال أوامر النقل باستخدام 
مفاتيح WASD . لإدخال أمر إطلاق النار ، اضغط على المفتاح 
F. لتنفيذ الأوامر ، اضغط على 
Enter .
ملاحظة : إلى أن تكتمل عملية التنفيذ ، لا يمكن إدخال أوامر جديدة.
لاحظ أنه يتم إضافة خطوط إلى واجهة المستخدم الطرفية. يشار إلى الفرق في واجهة المستخدم بأسمائها. تم إجراء ذلك بفضل متغير 
commandName .
لاحظ أيضًا كيف يتم تمرير واجهة المستخدم لأعلى قبل التنفيذ وكيف يتم حذف الخطوط أثناء التنفيذ.
نحن ندرس الفرق عن كثب
حان الوقت لتعلم الكود الذي أضفناه في قسم "تطبيق الأوامر":
- تقوم قائمة botCommandsبتخزين الروابط إلى مثيلاتBotCommand. تذكر أنه لحفظ الذاكرة ، يمكننا فقط إنشاء خمس حالات من الأوامر ، ولكن قد تكون هناك عدة إشارات إلى أمر واحد. بالإضافة إلى ذلك ، يشير متغيرexecuteCoroutineإلىExecuteCommandsRoutine، والذي يتحكم في تنفيذ الأمر.
- Updateيتحقق إذا كان المستخدم قد ضغط على مفتاح Enter ؛ إذا كان الأمر كذلك ، فإنه يستدعي- ExecuteCommands، وإلا- CheckForBotCommands.
- يستخدم HandleInputأسلوبHandleInputالثابت منBotInputHandlerللتحقق مما إذا كان المستخدم قد أكمل الإدخال ، وإذا كان الأمر كذلك ،BotInputHandlerإرجاع الأمر. يتم تمرير الأمر الذي تمAddToCommandsإلىAddToCommands. ومع ذلك ، إذا تم تنفيذ الأوامر ، أي إذا لم يكنexecuteRoutineخاليًا ، فسوف يعود بدون تمرير أي شيء إلىAddToCommands. وهذا هو ، يحتاج المستخدم إلى الانتظار حتى الانتهاء.
- يضيف AddToCommandsارتباطًا جديدًا إلى المثيل الذي تم إرجاعه من الأمر فيbotCommands.
- InsertNewTextالأسلوب- InsertNewTextللفئة- InsertNewTextجديدًا من النص إلى واجهة المستخدم الطرفية. السلسلة النصية عبارة عن سلسلة يتم تمريرها كمعلمة إدخال. في هذه الحالة ، نقوم بتمرير commandName- commandName.
- تقوم طريقة ExecuteCommandsRoutineبتشغيلExecuteCommandsRoutine.
- ResetScrollToTopمن- ResetScrollToTopيقوم- UIManagerواجهة المستخدم لأعلى. يتم ذلك قبل بدء التنفيذ مباشرة.
- يحتوي ExecuteCommandsRoutineعلى حلقة للتكرار عبر الأوامر الموجودة في قائمةbotCommandsوتنفيذها واحدة تلو الأخرى ، لتمرير كائنbotإلى الطريقة التي يتم إرجاعها بواسطة خاصيةExecute. بعد كل تنفيذ ، تتم إضافة توقف مؤقت في ثوانCommandPauseTime.
- حذف أسلوب RemoveFirstTextLineمنUIManagerالسطر الأول من النص في واجهة المستخدم الطرفية ، إذا كان موجودًا. وهذا هو ، عند تنفيذ أمر ، تتم إزالة اسمه من واجهة المستخدم.
- بعد الانتهاء من جميع الأوامر botCommandsيتم مسحbotCommandsوإعادة ضبط bot إلى آخر نقطة توقف باستخدامResetToLastCheckpoint. في النهاية ،executeRoutinenullويمكن للمستخدم متابعة إدخال الأوامر.
تطبيق ميزات التراجع والإعادة
قم بتشغيل المشهد مرة أخرى وحاول الوصول إلى نقطة التحكم الخضراء.
ستلاحظ أنه بينما لا يمكننا إلغاء الأمر الذي تم إدخاله. هذا يعني أنه إذا ارتكبت خطأ ، فلن تتمكن من العودة حتى تكمل جميع الأوامر التي تدخلها. يمكنك إصلاح ذلك عن طريق إضافة ميزات 
التراجع والإعادة .
ارجع إلى 
SceneManager.cs وأضف إعلان المتغير التالي مباشرة بعد إعلان 
List for 
botCommands :
  private Stack<BotCommand> undoStack = new Stack<BotCommand>(); 
متغير 
undoStack عبارة عن 
مكدس (من عائلة المجموعات) يقوم بتخزين جميع المراجع للأوامر التي يمكن التراجع عنها.
الآن نضيف طريقتين 
UndoCommandEntry و 
RedoCommandEntry التي ستنفذ التراجع 
RedoCommandEntry . في فئة 
SceneManager ، 
SceneManager التعليمات البرمجية التالية بعد 
ExecuteCommandsRoutine :
  private void UndoCommandEntry() {  
دعنا نحلل الكود:
- إذا تم تنفيذ الأوامر أو كانت قائمة botCommandsفارغة ، فإن طريقةUndoCommandEntryشيئًا. وإلا ، فإنه يكتب ارتباطًا للأمر الأخير الذي تم إدخاله على مكدسundoStack. يؤدي ذلك أيضًا إلى إزالة الرابط للأمر من قائمةbotCommands.
- تقوم طريقة RemoveLastTextLineمنUIManagerبإزالة السطر الأخير من النص من واجهة المستخدم الطرفية بحيث تطابق واجهة المستخدم محتوياتbotCommands.
- إذا كانت مكدس undoStackفارغة ، فإنRedoCommandEntryلا تفعل شيئًا. خلاف ذلك ، فإنه يستخرج الأمر الأخير من الجزء العلوي منundoStackمرة أخرى إلى قائمةAddToCommandsباستخدامAddToCommands.
سنضيف الآن إدخال لوحة المفاتيح لاستخدام هذه الوظائف. داخل فئة 
SceneManager نص أسلوب 
Update بالكود التالي:
  if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else if (Input.GetKeyDown(KeyCode.U))  
- عندما تضغط المفتاح U ، يتم UndoCommandEntryالأسلوبUndoCommandEntry.
- عندما تضغط المفتاح R ، يتم RedoCommandEntryالأسلوبRedoCommandEntry.
حافة التعامل مع القضية
عظيم ، لقد انتهينا تقريبا! لكن أولاً ، نحتاج إلى القيام بما يلي:
- عند إدخال أمر جديد ، يجب محو مكدس undoStack.
- قبل تنفيذ الأوامر ، يجب محو مكدس undoStack.
لتنفيذ ذلك ، نحتاج أولاً إلى إضافة طريقة جديدة إلى 
SceneManager . أدخل الطريقة التالية بعد 
CheckForBotCommands :
  private void AddNewCommand(BotCommand botCommand) { undoStack.Clear(); AddToCommands(botCommand); } 
هذا الأسلوب مسح 
undoStack ثم ثم استدعاء الأسلوب 
AddToCommands .
AddToCommands الآن المكالمة إلى 
AddToCommands داخل 
CheckForBotCommands التالي:
  AddNewCommand(botCommand); 
ثم أدخل السطر التالي بعد 
if داخل طريقة 
ExecuteCommands لمسحها قبل تنفيذ أوامر 
undoStack :
  undoStack.Clear(); 
وانتهينا في النهاية!
احفظ عملك قم ببناء المشروع وانقر فوق محرر 
Play . أدخل الأوامر كما كان من قبل. اضغط 
U لإلغاء الأوامر. اضغط 
R لتكرار الأوامر الملغاة.
حاول الوصول إلى نقطة التفتيش الخضراء.
إلى أين تذهب بعد ذلك؟
لمعرفة المزيد حول أنماط التصميم المستخدمة في برمجة الألعاب ، أوصي بأن تدرس 
أنماط برمجة ألعاب Robert Nystrom.
لمعرفة المزيد حول تقنيات C # المتقدمة ، خذ دورة 
C # Collections و Lambdas و LINQ .
مهمة
كمهمة ، حاول الوصول إلى نقطة التحكم الخضراء في نهاية المتاهة. اختبأت واحدة من الحلول تحت المفسد.
قرار- moveUp × 2
- moveRight × 3
- moveUp × 2
- moveLeft
- تبادل لاطلاق النار
- moveLeft × 2
- moveUp × 2
- moveLeft × 2
- moveDown × 5
- moveLeft
- تبادل لاطلاق النار
- moveLeft
- moveUp × 3
- تبادل لاطلاق النار × 2
- moveUp × 5
- moveRight × 3