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