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

صورة

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

ولكن إذا قررت استخدام القوة الغاشمة ، فسوف تتحول الشفرة بسرعة إلى فوضى متشابكة مع العديد من العبارات المتداخلة.

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

من البرنامج التعليمي لك:

  • تعلم أساسيات قالب الولاية في الوحدة.
  • سوف تتعلم ما هي آلة الدولة ومتى تستخدمها.
  • تعرف على كيفية استخدام هذه المفاهيم للتحكم في حركة شخصيتك.

ملاحظة : هذا البرنامج التعليمي مخصص للمستخدمين المتقدمين ؛ من المفترض أنك تعرف بالفعل كيفية العمل في الوحدة ولديك مستوى متوسط ​​من المعرفة بـ C #. بالإضافة إلى ذلك ، يستخدم هذا البرنامج التعليمي Unity 2019.2 و C # 7.

الحصول على العمل


تحميل مواد المشروع . قم بفك ضغط الملف المضغوط وفتح مشروع بدء التشغيل في الوحدة.

هناك عدة مجلدات في المشروع ستساعدك على البدء. يحتوي المجلد Assets / RW على مجلدات Animations و Materials و Models و Prefabs و Resources و Scenes و Scripts و Sounds ، المسماة وفقًا للموارد التي تحتوي عليها.

لإكمال البرنامج التعليمي ، سنعمل فقط مع المشاهد والبرامج النصية .

انتقل إلى RW / Scenes وافتح Main . في وضع اللعبة ، سترى شخصية في غطاء محرك السيارة داخل قلعة من القرون الوسطى.


انقر فوق " تشغيل" ولاحظ كيفية تحرك الكاميرا لتناسب إطار الحرف . في الوقت الحالي ، في لعبتنا الصغيرة لا توجد تفاعلات ، سنعمل عليها في البرنامج التعليمي.


استكشف الشخصية


في التسلسل الهرمي ، حدد الحرف . تحقق من المفتش . سترى مكونًا بنفس الاسم يحتوي على منطق التحكم في الأحرف .


افتح Character.cs الموجود في RW / Scripts .

ينفذ البرنامج العديد من الإجراءات ، لكن معظمها ليس مهمًا بالنسبة لنا. الآن ، دعنا ننتبه إلى الطرق التالية.

  • Move : يتحرك الحرف ، مستقبلاً قيم نوع تعويم speed مثل سرعة الحركة و rotationSpeed بسرعة السرعة الزاوية.
  • ResetMoveParams : ResetMoveParams هذه الطريقة تعيين المعلمات المستخدمة لتحريك الحركة والسرعة الزاوية للحرف. يتم استخدامه فقط للتنظيف.
  • SetAnimationBool : يقوم بتعيين param الحركة الأساسية من النوع Bool إلى القيمة.
  • CheckCollisionOverlap : يستقبل النوع Vector3 وإرجاع bool يحدد ما إذا كان هناك أي مصادمات داخل نصف القطر المحدد من .
  • TriggerAnimation : TriggerAnimation param الرسوم المتحركة TriggerAnimation .
  • ApplyImpulse : ApplyImpulse نبضة على الحرف تساوي force معلمة الإدخال force النوع Vector3 .

أدناه سترى هذه الأساليب. في البرنامج التعليمي لدينا ، محتوياتها والعمل الداخلي ليست مهمة.

ما هي آلات الدولة


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

آلات الدولة


آلات الحالة المحدودة أو FSM (آلة الحالة المحدودة) هي واحدة من أربع مجموعات رئيسية من الآلات . Automata هي نماذج مجردة من الآلات البسيطة. يتم دراستها في إطار نظرية الأتمتة - الفرع النظري لعلوم الكمبيوتر.

باختصار:

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


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

تعرّف الحالة الدائمة الضغط على زر كمدخل هام ، وكمخرج ، تنتقل إلى حالة القفز .

افترض أن هناك عددًا معينًا من حالات الحركة هذه وأن الشخصية لا يمكن أن تكون إلا في إحدى الولايات في وقت واحد. هذا مثال على FSM.

آلات الدولة الهرمية


النظر في منهاج باستخدام FSM ، حيث تشترك عدة ولايات في منطق الفيزياء المشترك. على سبيل المثال ، يمكنك التنقل والقفز في وضعي Crouching and Standing . في هذه الحالة ، تؤدي عدة متغيرات واردة إلى نفس السلوك وإخراج المعلومات لحالتين مختلفتين.

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

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

قالب الحالة


في كتابهم " أنماط التصميم: عناصر من البرامج الموجهة للكائنات القابلة لإعادة الاستخدام" ، حدد إريك جاما وريتشارد هيلم ورالف جونسون وجون فليسيديس ( عصابة الأربعة ) مهمة قالب الحالة على النحو التالي:

يجب أن يسمح للكائن بتغيير سلوكه عندما تتغير حالته الداخلية. في هذه الحالة ، يبدو أن الكائن قد غير فئته ".

لفهم هذا بشكل أفضل ، خذ بعين الاعتبار المثال التالي:

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

لذلك ، نظرًا لحقيقة أنه في أوقات مختلفة يشير متغير الحالة الحالي إلى حالات مختلفة ، يبدو أن فئة البرنامج النصي نفسها تتصرف بشكل مختلف. هذا هو جوهر قالب "الحالة".

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


في الحالة العامة ، هناك ثلاث نقاط أساسية لكل فئة ولاية تسمح بسلوك الدولة ككل:

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


تحديد آلة الدولة والدولة


انتقل إلى RW / البرامج النصية وفتح StateMachine.cs .

آلة الحالة ، كما قد تتخيل ، توفر تجريدًا لآلة الحالة. لاحظ أن CurrentState بشكل صحيح داخل هذه الفئة. سيتم تخزين ارتباط إلى حالة الجهاز الحالة النشطة الحالية.

الآن ، لتحديد مفهوم الحالة ، دعنا نذهب إلى RW / Scripts وفتح البرنامج النصي State.cs في IDE.

الحالة عبارة عن فئة تجريدية سنستخدمها كنموذج تستمد منه جميع فئات حالات المشروع. جزء من الكود الموجود في مواد المشروع جاهز بالفعل.

يعرض DisplayOnUI فقط اسم الحالة الحالية في واجهة المستخدم على الشاشة. لا تحتاج إلى معرفة أجهزتها الداخلية ، فقط أدرك أنها تستقبل UIManager.Alignment نوع UIManager.Alignment كمعلمة إدخال ، والتي يمكن أن تكون على Left أو Right . يعتمد عرض اسم الحالة في أسفل أو يسار الشاشة على ذلك.

بالإضافة إلى ذلك ، هناك نوعان من المتغيرات المحمية ، character و stateMachine . يشير متغير character إلى مثيل لفئة Character ، ويشير stateMachine إلى مثيل لجهاز الحالة المرتبط بالحالة.

عند إنشاء مثيل حالة ، فإن المُنشئ يربط بين character و stateMachine .

يمكن أن يكون لكل حالة من حالات Character في المشهد مجموعة خاصة بها من الحالات وأجهزة الحالة.

الآن أضف الطرق التالية إلى State.cs واحفظ الملف:

 public virtual void Enter() { DisplayOnUI(UIManager.Alignment.Left); } public virtual void HandleInput() { } public virtual void LogicUpdate() { } public virtual void PhysicsUpdate() { } public virtual void Exit() { } 

تحدد هذه الطرق الافتراضية نقاط الحالة الرئيسية الموضحة أعلاه. عندما تقوم آلة الحالة بالانتقال بين الحالات ، فإننا ندعو Exit للحالة السابقة Enter الحالة النشطة الجديدة.

HandleInput و LogicUpdate و LogicUpdate معًا حلقة تحديث . HandleInput يعالج إدخال اللاعب. LogicUpdate يعالج المنطق الأساسي ، بينما PhyiscsUpdate يعالج PhyiscsUpdate المنطق والفيزياء.

الآن افتح StateMachine.cs مرة أخرى ، أضف الطرق التالية وحفظ الملف:

 public void Initialize(State startingState) { CurrentState = startingState; startingState.Enter(); } public void ChangeState(State newState) { CurrentState.Exit(); CurrentState = newState; newState.Enter(); } 

Initialize تكوين جهاز الحالة عن طريق تعيين CurrentState startingState State واستدعاء Enter لذلك. هذا تهيئة جهاز الحالة ، لأول مرة تعيين الحالة النشطة.

ChangeState يعالج التحولات الدولة . وهو يستدعي Exit لـ CurrentState القديم قبل استبدال newState بـ newState . في النهاية ، فإنه يستدعي Enter for newState .

وبالتالي ، وضعنا آلة الدولة والدولة .

خلق الدول الحركة


ألقِ نظرة على مخطط الحالة التالي ، والذي يوضح حالات الحركة المختلفة لجوهر اللاعب داخل اللعبة. في هذا القسم ، نطبق قالب "الحالة" للحركة الموضحة في شكل FSM :


انتبه إلى حالات الحركة ، وهي الدائمة والالتفاف والقفز ، وكذلك كيف تسبب البيانات الواردة انتقالات بين الولايات. هذا هو هرمي FSM فيه Grounded هو حالة فرعية لـ Ducking و Stand- الولايات الفرعية .

العودة إلى الوحدة وانتقل إلى RW / البرامج النصية / الولايات . هناك ستجد العديد من ملفات C # مع أسماء تنتهي في الحالة .

يعرّف كل من هذه الملفات فئة واحدة ، كل منها موروث من State . لذلك ، تحدد هذه الفئات الحالات التي سنستخدمها في المشروع.

الآن افتح Character.cs من المجلد RW / Scripts .

قم بالتمرير أعلى ملف #region Variables وأضف الكود التالي:

 public StateMachine movementSM; public StandingState standing; public DuckingState ducking; public JumpingState jumping; 

تشير هذه movementSM إلى آلة الحالة التي تعالج منطق الحركة لمثيل Character . أضفنا أيضًا روابط إلى ثلاث ولايات نطبقها لكل نوع من أنواع الحركة.

انتقل إلى #region MonoBehaviour Callbacks في نفس الملف. أضف طرق MonoBehaviour التالية ثم احفظها

 private void Start() { movementSM = new StateMachine(); standing = new StandingState(this, movementSM); ducking = new DuckingState(this, movementSM); jumping = new JumpingState(this, movementSM); movementSM.Initialize(standing); } private void Update() { movementSM.CurrentState.HandleInput(); movementSM.CurrentState.LogicUpdate(); } private void FixedUpdate() { movementSM.CurrentState.PhysicsUpdate(); } 

  • في Start يقوم الكود بإنشاء مثيل لـ State Machine ويعينه إلى movementSM ، كما أنه ينشئ حالات حركة مختلفة. عند إنشاء كل حالة من حالات الحركة ، نقوم بتمرير الإشارات إلى مثيل Character باستخدام this ، وكذلك مثيل motionSM. في النهاية ، نحن ندعو Initialize movementSM SM وتمرير Standing كحالة أولية.
  • في طريقة Update ، ندعو HandleInput و LogicUpdate إلى CurrentState لجهاز LogicUpdate . وبالمثل ، في FixedUpdate ندعو FixedUpdate لـآلة movementSM . في جوهرها ، هذا تفويض المهام إلى حالة نشطة. هذا هو معنى قالب "الحالة".

الآن نحن بحاجة إلى ضبط السلوك داخل كل حالة من حالات الحركة. تستعد لنفسك ، سيكون هناك الكثير من التعليمات البرمجية!

شركة دائمة


العودة إلى RW / البرامج النصية / الدول في نافذة المشروع.

افتح Grounded.cs ولاحظ أن هذه الفئة بها مُنشئ يطابق مُنشئ State . هذا منطقي لأن هذه الفئة ترث منه. سترى نفس الشيء في جميع فئات الدولة الأخرى.

أضف الكود التالي:

 public override void Enter() { base.Enter(); horizontalInput = verticalInput = 0.0f; } public override void Exit() { base.Exit(); character.ResetMoveParams(); } public override void HandleInput() { base.HandleInput(); verticalInput = Input.GetAxis("Vertical"); horizontalInput = Input.GetAxis("Horizontal"); } public override void PhysicsUpdate() { base.PhysicsUpdate(); character.Move(verticalInput * speed, horizontalInput * rotationSpeed); } 

إليك ما يحدث هنا:

  • نعيد تعريف أحد الأساليب الافتراضية المحددة في الفئة الأصل. للحفاظ على جميع الوظائف التي قد تكون موجودة في الأصل ، ندعو الطريقة base بنفس الاسم من كل طريقة تجاوز. هذا قالب مهم سنستمر في استخدامه.
  • السطر التالي ، Enter يعين horizontalInput و verticalInput قيمهم الافتراضية.
  • داخل Exit كما ذكرنا أعلاه ، ندعو طريقة ResetMoveParams إلى إعادة ResetMoveParams عند التغيير إلى حالة أخرى.
  • في أسلوب HandleInput ، تقوم متغيرات horizontalInput و verticalInput HandleInput قيم محاور الإدخال الأفقية والعمودية. بفضل هذا ، يمكن للاعب التحكم في الشخصية باستخدام المفاتيح W و A و S و D.
  • في PhysicsUpdate نجري مكالمة Move ، لتمرير متغيرات المدخلات horizontalInput verticalInput مضروبة بالسرعات المقابلة. في speed المتغيرة speed يتم تخزين سرعة الحركة ، وفي rotationSpeed ، السرعة الزاوية.

افتح الآن Standing.cs وانتبه إلى حقيقة أنه يرث من Grounded . حدث ذلك لأنه ، كما قلنا أعلاه ، فإن Standing هو بديل لـ Grounded . هناك طرق مختلفة لتنفيذ هذه العلاقة ، لكن في هذا البرنامج التعليمي نستخدم الوراثة.

أضف طرق override التالية واحفظ البرنامج النصي:

 public override void Enter() { base.Enter(); speed = character.MovementSpeed; rotationSpeed = character.RotationSpeed; crouch = false; jump = false; } public override void HandleInput() { base.HandleInput(); crouch = Input.GetButtonDown("Fire3"); jump = Input.GetButtonDown("Jump"); } public override void LogicUpdate() { base.LogicUpdate(); if (crouch) { stateMachine.ChangeState(character.ducking); } else if (jump) { stateMachine.ChangeState(character.jumping); } } 

  • في Enter نقوم بتكوين المتغيرات الموروثة من Grounded . قم بتطبيق MovementSpeed و RotationSpeed speed و rotationSpeed . ثم يرتبطون ، على التوالي ، بالسرعة الطبيعية للحركة والسرعة الزاوية المخصصة لجوهر الشخصية.

    بالإضافة إلى ذلك ، يتم إعادة تعيين المتغيرات لتخزين crouch وإدخال jump إلى خطأ.
  • داخل HandleInput ، تخزن متغيرات crouch jump إدخال اللاعب HandleInput والقفزات. إذا ضغط المشغل في المشهد الرئيسي على مفتاح Shift ، فسيتم ضبط القرفصاء على true. وبالمثل ، يمكن للاعب استخدام مفتاح Space jump .
  • في LogicUpdate نتحقق من متغيرات jump لنوع bool . إذا كان crouch صحيحًا ، crouch إلى character.ducking . إذا كانت jump صحيحة ، فستتغير الحالة إلى character.jumping .

احفظ المشروع وقم بتجميعه ، ثم انقر فوق " تشغيل" . يمكنك التنقل في مكان الحادث باستخدام المفاتيح W و A و S و D. إذا حاولت الضغط على Shift أو Space ، فسيحدث سلوك غير متوقع ، لأن الحالات المقابلة لم يتم تنفيذها بعد.


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

نحن نتسلق تحت الطاولة


افتح البرنامج النصي Ducking.cs . لاحظ أن Ducking يرث أيضًا من فئة Grounded لنفس الأسباب التي Standing . أضف طرق override التالية واحفظ البرنامج النصي:

 public override void Enter() { base.Enter(); character.SetAnimationBool(character.crouchParam, true); speed = character.CrouchSpeed; rotationSpeed = character.CrouchRotationSpeed; character.ColliderSize = character.CrouchColliderHeight; belowCeiling = false; } public override void Exit() { base.Exit(); character.SetAnimationBool(character.crouchParam, false); character.ColliderSize = character.NormalColliderHeight; } public override void HandleInput() { base.HandleInput(); crouchHeld = Input.GetButton("Fire3"); } public override void LogicUpdate() { base.LogicUpdate(); if (!(crouchHeld || belowCeiling)) { stateMachine.ChangeState(character.standing); } } public override void PhysicsUpdate() { base.PhysicsUpdate(); belowCeiling = character.CheckCollisionOverlap(character.transform.position + Vector3.up * character.NormalColliderHeight); } 

  • داخل Enter المعلمة التي تسبب تبديل حركات القرفصاء على كراوتش ، والتي تمكن الرسوم المتحركة القرفصاء. character.CrouchRotationSpeed تخصيص خصائص character.CrouchSpeed و character.CrouchRotationSpeed لقيم speed rotation ، والتي تُرجع الحركة والسرعة الزاوية للحرف عند تحريك القرفصاء .

    character.CrouchColliderHeight التالي. يعيّن CrouchColliderHeight حجم مصادم الحرف ، والذي يُرجع ارتفاع مصادم الارتفاع المرغوب فيه عند القرفصاء. في النهاية ، belowCeiling إعادة تعيين belowCeiling إلى "خطأ".
  • داخل Exit يتم تعيين المعلمة الرسوم المتحركة القرفصاء على خطأ. هذا تعطيل الرسوم المتحركة القرفصاء. ثم يتم تعيين ارتفاع الاصطدام العادي ، يتم إرجاعها بواسطة character.NormalColliderHeight .
  • بداخل HandleInput يعين crouchHeld المتغيرة قيمة إدخال المشغل. في المشهد الرئيسي ، يؤدي عقد Shift إلى crouchHeld إلى حقيقة.
  • داخل belowCeiling تعيين قيمة المتغير belowCeiling عن طريق تمرير نقطة بتنسيق Vector3 برأس كائن لعبة الحرف إلى أسلوب CheckCollisionOverlap . إذا كان هناك تصادم بالقرب من هذه النقطة ، فهذا يعني أن الشخصية تحت سقف ما.
  • داخليًا ، يتحقق crouchHeld أو belowCeiling . إذا لم يكن أي منها صحيحًا ، فعندئذٍ يتغير moveSM.CurrentState إلى character.standing .

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

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


ترتفع!


افتح Jumping.cs . سترى طريقة تسمى Jump . لا تقلق بشأن كيفية عملها ؛ يكفي أن نفهم أنه يتم استخدامه بحيث يمكن للشخصية القفز مع مراعاة الفيزياء والرسوم المتحركة.

الآن إضافة أساليب override المعتاد وحفظ البرنامج النصي

 public override void Enter() { base.Enter(); SoundManager.Instance.PlaySound(SoundManager.Instance.jumpSounds); grounded = false; Jump(); } public override void LogicUpdate() { base.LogicUpdate(); if (grounded) { character.TriggerAnimation(landParam); SoundManager.Instance.PlaySound(SoundManager.Instance.landing); stateMachine.ChangeState(character.standing); } } public override void PhysicsUpdate() { base.PhysicsUpdate(); grounded = character.CheckCollisionOverlap(character.transform.position); } 

  • من الداخل Enter SoundManager بتشغيل صوت القفزة. ثم grounded إعادة تعيين grounded إلى قيمتها الافتراضية. في النهاية ، يسمى Jump .
  • داخل PhysicsUpdate نقطة PhysicsUpdate بجانب أرجل الشخصية إلى CheckCollisionOverlap ، مما يعني أنه عندما تكون الشخصية على الأرض ، سيتم ضبط grounded على صواب.
  • في LogicUpdate ، إذا كانت LogicUpdate صحيحة ، فإننا ندعو TriggerAnimation لتمكين الرسوم المتحركة للهبوط ، ويتم تشغيل صوت الهبوط ، TriggerAnimation إلى character.standing .

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


إلى أين تذهب بعد ذلك؟


مواد المشروع لديها مشروع المشروع والمشروع النهائي.

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

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

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


All Articles