تطبيق نمط تصميم القيادة في الوحدة

صورة

هل تساءلت يومًا عن كيفية تنفيذ وظيفة إعادة التشغيل في ألعاب مثل 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 لا تزال فارغة ، ولكن ليس لفترة طويلة.

أدخل الكود التالي في الفصل:

  //1 private readonly string commandName; //2 public BotCommand(ExecuteCallback executeMethod, string name) { Execute = executeMethod; commandName = name; } //3 public delegate void ExecuteCallback(Bot bot); //4 public ExecuteCallback Execute { get; private set; } //5 public override string ToString() { return commandName; } 

ما الذي يحدث هنا؟

  1. commandName استخدام متغير commandName ببساطة لتخزين اسم الأمر commandName للقراءة من قبل الإنسان. ليس من الضروري استخدامه في القالب ، لكننا سنحتاجه لاحقًا في البرنامج التعليمي.
  2. يتلقى BotCommand وظيفة وسلسلة. سيساعدنا ذلك في إعداد طريقة Execute لكائن الأوامر name .
  3. يعرّف المفوض ExecuteCallback نوع الأسلوب المغلف. ستُرجع الطريقة المُغلفة فراغًا وتقبل كمعلمة إدخال كائنًا من النوع Bot (المكون Bot ).
  4. تشير خاصية Execute إلى الطريقة المغلفة. سوف نستخدمها لاستدعاء الطريقة المغلفة.
  5. تم تجاوز أسلوب ToString لإرجاع commandName السلسلة. هذا مناسب ، على سبيل المثال ، للاستخدام في واجهة المستخدم.

حفظ التغييرات وهذا كل شيء! لقد نفذنا بنجاح نمط القيادة.

يبقى لاستخدامه.

بناء الفريق


افتح BotInputHandler في المجلد RW / Scripts .

هنا سنقوم بإنشاء خمس حالات من BotCommand . ستقوم هذه الحالات بتغليف طرق لتحريك GameObject Bot لأعلى ولأسفل ولليسار ولليمين ، وكذلك للتصوير.

لتنفيذ ذلك ، أدخل ما يلي في هذه الفئة:

  //1 private static readonly BotCommand MoveUp = new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Up); }, "moveUp"); //2 private static readonly BotCommand MoveDown = new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Down); }, "moveDown"); //3 private static readonly BotCommand MoveLeft = new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Left); }, "moveLeft"); //4 private static readonly BotCommand MoveRight = new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Right); }, "moveRight"); //5 private static readonly BotCommand Shoot = new BotCommand(delegate (Bot bot) { bot.Shoot(); }, "shoot"); 

في كل حالة من هذه الحالات ، يتم تمرير طريقة مجهولة المصدر إلى المُنشئ. سيتم تغليف هذه الطريقة المجهولة داخل كائن الأمر المقابل. كما ترون ، يفي توقيع كل من الأساليب المجهولة بالمتطلبات المحددة من قبل المفوض 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 :

  //1 private List<BotCommand> botCommands = new List<BotCommand>(); private Coroutine executeRoutine; //2 private void Update() { if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else { CheckForBotCommands(); } } //3 private void CheckForBotCommands() { var botCommand = BotInputHandler.HandleInput(); if (botCommand != null && executeRoutine == null) { AddToCommands(botCommand); } } //4 private void AddToCommands(BotCommand botCommand) { botCommands.Add(botCommand); //5 uiManager.InsertNewText(botCommand.ToString()); } //6 private void ExecuteCommands() { if (executeRoutine != null) { return; } executeRoutine = StartCoroutine(ExecuteCommandsRoutine()); } private IEnumerator ExecuteCommandsRoutine() { Debug.Log("Executing..."); //7 uiManager.ResetScrollToTop(); //8 for (int i = 0, count = botCommands.Count; i < count; i++) { var command = botCommands[i]; command.Execute(bot); //9 uiManager.RemoveFirstTextLine(); yield return new WaitForSeconds(CommandPauseTime); } //10 botCommands.Clear(); bot.ResetToLastCheckpoint(); executeRoutine = null; } 

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

ساوضح الشيفرة لاحقا. تذكر أن تحفظ التغييرات.

تشغيل اللعبة لاختبار قالب الأوامر


الآن حان الوقت للبناء. انقر فوق تشغيل في محرر الوحدة.

يجب أن تكون قادرًا على إدخال أوامر النقل باستخدام مفاتيح WASD . لإدخال أمر إطلاق النار ، اضغط على المفتاح F. لتنفيذ الأوامر ، اضغط على Enter .

ملاحظة : إلى أن تكتمل عملية التنفيذ ، لا يمكن إدخال أوامر جديدة.



لاحظ أنه يتم إضافة خطوط إلى واجهة المستخدم الطرفية. يشار إلى الفرق في واجهة المستخدم بأسمائها. تم إجراء ذلك بفضل متغير commandName .

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

نحن ندرس الفرق عن كثب


حان الوقت لتعلم الكود الذي أضفناه في قسم "تطبيق الأوامر":

  1. تقوم قائمة botCommands بتخزين الروابط إلى مثيلات BotCommand . تذكر أنه لحفظ الذاكرة ، يمكننا فقط إنشاء خمس حالات من الأوامر ، ولكن قد تكون هناك عدة إشارات إلى أمر واحد. بالإضافة إلى ذلك ، يشير متغير executeCoroutine إلى ExecuteCommandsRoutine ، والذي يتحكم في تنفيذ الأمر.
  2. Update يتحقق إذا كان المستخدم قد ضغط على مفتاح Enter ؛ إذا كان الأمر كذلك ، فإنه يستدعي ExecuteCommands ، وإلا CheckForBotCommands .
  3. يستخدم HandleInput أسلوب HandleInput الثابت من BotInputHandler للتحقق مما إذا كان المستخدم قد أكمل الإدخال ، وإذا كان الأمر كذلك ، BotInputHandler إرجاع الأمر. يتم تمرير الأمر الذي تم AddToCommands إلى AddToCommands . ومع ذلك ، إذا تم تنفيذ الأوامر ، أي إذا لم يكن executeRoutine خاليًا ، فسوف يعود بدون تمرير أي شيء إلى AddToCommands . وهذا هو ، يحتاج المستخدم إلى الانتظار حتى الانتهاء.
  4. يضيف AddToCommands ارتباطًا جديدًا إلى المثيل الذي تم إرجاعه من الأمر في botCommands .
  5. InsertNewText الأسلوب InsertNewText للفئة InsertNewText جديدًا من النص إلى واجهة المستخدم الطرفية. السلسلة النصية عبارة عن سلسلة يتم تمريرها كمعلمة إدخال. في هذه الحالة ، نقوم بتمرير commandName commandName .
  6. تقوم طريقة ExecuteCommandsRoutine بتشغيل ExecuteCommandsRoutine .
  7. ResetScrollToTop من ResetScrollToTop يقوم UIManager واجهة المستخدم لأعلى. يتم ذلك قبل بدء التنفيذ مباشرة.
  8. يحتوي ExecuteCommandsRoutine على حلقة للتكرار عبر الأوامر الموجودة في قائمة botCommands وتنفيذها واحدة تلو الأخرى ، لتمرير كائن bot إلى الطريقة التي يتم إرجاعها بواسطة خاصية Execute . بعد كل تنفيذ ، تتم إضافة توقف مؤقت في ثوان CommandPauseTime .
  9. حذف أسلوب RemoveFirstTextLine من UIManager السطر الأول من النص في واجهة المستخدم الطرفية ، إذا كان موجودًا. وهذا هو ، عند تنفيذ أمر ، تتم إزالة اسمه من واجهة المستخدم.
  10. بعد الانتهاء من جميع الأوامر botCommands يتم مسح botCommands وإعادة ضبط bot إلى آخر نقطة توقف باستخدام ResetToLastCheckpoint . في النهاية ، executeRoutine null ويمكن للمستخدم متابعة إدخال الأوامر.

تطبيق ميزات التراجع والإعادة


قم بتشغيل المشهد مرة أخرى وحاول الوصول إلى نقطة التحكم الخضراء.

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

ارجع إلى SceneManager.cs وأضف إعلان المتغير التالي مباشرة بعد إعلان List for botCommands :

  private Stack<BotCommand> undoStack = new Stack<BotCommand>(); 

متغير undoStack عبارة عن مكدس (من عائلة المجموعات) يقوم بتخزين جميع المراجع للأوامر التي يمكن التراجع عنها.

الآن نضيف طريقتين UndoCommandEntry و RedoCommandEntry التي ستنفذ التراجع RedoCommandEntry . في فئة SceneManager ، SceneManager التعليمات البرمجية التالية بعد ExecuteCommandsRoutine :

  private void UndoCommandEntry() { //1 if (executeRoutine != null || botCommands.Count == 0) { return; } undoStack.Push(botCommands[botCommands.Count - 1]); botCommands.RemoveAt(botCommands.Count - 1); //2 uiManager.RemoveLastTextLine(); } private void RedoCommandEntry() { //3 if (undoStack.Count == 0) { return; } var botCommand = undoStack.Pop(); AddToCommands(botCommand); } 

دعنا نحلل الكود:

  1. إذا تم تنفيذ الأوامر أو كانت قائمة botCommands فارغة ، فإن طريقة UndoCommandEntry شيئًا. وإلا ، فإنه يكتب ارتباطًا للأمر الأخير الذي تم إدخاله على مكدس undoStack . يؤدي ذلك أيضًا إلى إزالة الرابط للأمر من قائمة botCommands .
  2. تقوم طريقة RemoveLastTextLine من UIManager بإزالة السطر الأخير من النص من واجهة المستخدم الطرفية بحيث تطابق واجهة المستخدم محتويات botCommands .
  3. إذا كانت مكدس undoStack فارغة ، فإن RedoCommandEntry لا تفعل شيئًا. خلاف ذلك ، فإنه يستخرج الأمر الأخير من الجزء العلوي من undoStack مرة أخرى إلى قائمة AddToCommands باستخدام AddToCommands .

سنضيف الآن إدخال لوحة المفاتيح لاستخدام هذه الوظائف. داخل فئة SceneManager نص أسلوب Update بالكود التالي:

  if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else if (Input.GetKeyDown(KeyCode.U)) //1 { UndoCommandEntry(); } else if (Input.GetKeyDown(KeyCode.R)) //2 { RedoCommandEntry(); } else { CheckForBotCommands(); } 

  1. عندما تضغط المفتاح U ، يتم UndoCommandEntry الأسلوب UndoCommandEntry .
  2. عندما تضغط المفتاح R ، يتم RedoCommandEntry الأسلوب RedoCommandEntry .

حافة التعامل مع القضية


عظيم ، لقد انتهينا تقريبا! لكن أولاً ، نحتاج إلى القيام بما يلي:

  1. عند إدخال أمر جديد ، يجب محو مكدس undoStack .
  2. قبل تنفيذ الأوامر ، يجب محو مكدس 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

Source: https://habr.com/ru/post/ar463953/


All Articles