أنماط برمجة الألعاب › طريقة التحديث (update method) 15 / 26 السابق التالي

دالة التحديث

^title دالة التحديث ^section أنماط التسلسل

النية

حاكِ مجموعة من الكائنات المستقلة بإخبار كلٍّ منها أن يعالج إطاراً واحداً من سلوكه في كل مرّة.

الدافع

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

حسناً، هذا لا ينفع.

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

^code just-patrol

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

سنحتاج إلى إزالة تلك الحلقات والاعتماد على حلقة اللعبة (game loop) الخارجية في التكرار. وهذا يضمن أن تستمر اللعبة في الاستجابة لإدخال المستخدم والرسم بينما الحرّاس في جولته. كالتالي:

^code patrol-in-loop

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

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

متابعةً لأسلوبنا في ما أبسط طريقة لكتابة هذا، نصل إلى:

^code statues

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

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

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

مرة واحدة في كل إطار، تمرّ حلقة اللعبة على المجموعة وتستدعي update() على كل كائن. ومنح ذلك كل كائن فرصة تنفيذ ما قيمته إطار واحد من السلوك. وباستدعائها على جميع الكائنات في كل إطار، كلها تتصرّف في وقت واحد.

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

النمط

يحتفظ عالم اللعبة (game world) بـمجموعة كائنات. ويطبّق كل كائن دالة تحديث (update method) تحاكي إطاراً واحداً من سلوك ذلك الكائن. وفي كل إطار، تحدّث اللعبة كل كائن في المجموعة.

متى تستخدمه

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

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

تعمل دوال التحديث جيداً عندما:

  • لديك في لعبتك عدد من الكائنات أو الأنظمة التي يجب أن تعمل في وقت واحد.

  • يكون سلوك كل كائن مستقلاً إلى حدٍّ كبير عن سلوك البقية.

  • تحتاج الكائنات إلى أن تُحاكى عبر الزمن.

في بالك

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

تقسيم الشيفرة إلى شرائح إطار واحد يجعلها أكثر تعقيداً

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

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

عليك تخزين الحالة للاستئناف من حيث توقّفت في كل إطار

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

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

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

الكائنات جميعها تحاكى في كل إطار لكنها ليست متوازية حقاً

في هذا النمط، تمرّ حلقة اللعبة على مجموعة من الكائنات وتحدّث كل واحد منها. وداخل استدعاء update()، يستطيع معظم الكائنات أن تمدّ يدها وتلمس بقية عالم اللعبة، بما في ذلك كائنات أخرى يجري تحديثها. وهذا يعني أن ترتيب تحديث الكائنات له أهمية.

إذا كان A يسبق B في قائمة الكائنات، فعند تحديث A سيرى الحالة السابقة لـB. أما عند تحديث B فسي يرى حالة A الجديدة، لأن A قد جرى تحديثه في هذا الإطار بالفعل. ورغم أن كل شيء يبدو متحرّكاً في الوقت نفسه من منظور اللاعب، فإن جوهر اللعبة يظل قائماً على الأدوار. كل ما في الأمر أن دوراً كاملاً لا يستغرق سوى إطار واحد.

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

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

كن حذراً عند تعديل قائمة الكائنات أثناء التحديث

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

مثلاً، لنفترض أن حرّاساً هيكلياً يُسقط غرضاً عندما يُقتل. ومع وجود كائن جديد، يمكنك عادةً إضافته إلى نهاية القائمة من دون عناء كبير. فستستمر في المرور على تلك القائمة، وفي النهاية تصل إلى الجديد في نهايتها وتحدّثه أيضاً.

لكن هذا يعني أن الكائن الجديد يحصل على فرصة لتنفيذ فعله في الإطار الذي وُلد فيه، قبل أن تتاح للاعب حتى فرصة رؤيته. إن كنت لا تريد أن يحدث ذلك، فإصلاح بسيط هو تخزين عدد الكائنات في القائمة في بداية حلقة التحديث، وتحديث ذلك العدد فقط قبل التوقف:

^code skip-added

هنا objects_ مصفوفة للكائنات القابلة للتحديث في اللعبة، وnumObjects_ هو طولها. وعندما تُضاف كائنات جديدة، يُزاد هذا الطول. ونخزّن الطول في numObjectsThisTurn في بداية الحلقة بحيث يتوقف التكرار قبل أن نصل إلى أي كائنات جديدة أُضيفت أثناء الإطار الحالي.

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

^code skip-removed

هذه الحلقة البسيطة تزيد فهرس الكائن الجاري تحديثه في كل تكرار. ويبيّن الجانب الأيسر من الرسم أدناه كيف تبدو المصفوفة بينما نحدّث البطلة:

قائمة كائنات أثناء عملية إزالة. مؤشر يشير إلى الكائن الثاني Heroine. بعد إزالة Foul Beast الذي فوقها يتحرك المؤشر للأسفل بينما تتحرك Heroine للأعلى.

وبما أننا نحدّثها، فإن i يساوي 1. وهي تقتل الوحش الشرير فيُزال من المصفوفة. ترتفع البطلة إلى 0، ويرتفع الفلاح البائس إلى 1. وبعد تحديث البطلة، يُزاد i إلى 2. وكما ترى على اليمين، فإن الفلاح البائس يتم تجاوزه ولا يُحدَّث أبداً.

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

شيفرة نموذجية

هذا النمط مباشر إلى حدٍّ كبير لدرجة أن الشيفرة النموذجية تكاد تكرّر الفكرة. وهذا لا يعني أن النمط غير مفيد. فهو مفيد جزئياً لأنه بسيط: إنه حلّ نظيف لمشكلة من دون زخرفة كثيرة.

لكن، لنبقِ الأمور ملموسة، فلنمرّ على تنفيذ أساسي. سنبدأ بصنف Entity يمثّل الهياكل والتماثيل:

^code entity-class

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

تحتفظ اللعبة بمجموعة من هذه الكيانات. وفي مثالنا، سنضع ذلك في صنف يمثّل عالم اللعبة:

^code game-world

الآن، بعد أن أعدّ كل شيء، تطبّق اللعبة النمط بتحديث كل كيان في كل إطار:

^code game-loop

اشتقاق الكيانات؟!

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

حين ظهرت صناعة الألعاب من بحار شيفرة تجميع 6502 ودورات VBLANK البدائية إلى شواطئ اللغات المُوجَّهة بالكائنات، انقض المطوّرون في نوبة هوس بالهندسة المعمارية. وكان أحد أكبر تلك النوبات هو استخدام الوراثة. فبُنيت تسلسلات هرمية للصنفات شاهقة ومنحدرة (بيزنطية)، ضخمة إلى حدّ تحجب الشمس.

وتبيّن أن تلك كانت فكرة سيّئة تماماً، ولا يستطيع أحد صيانة تسلسل هرمي ضخم من الصنفات دون أن ينهار حوله. حتى الرباعي (Gang of Four) كان يعرف ذلك عام 1994 حين كتب:

فضّل تركيب الكائنات على وراثة الأصناف.

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

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

تعريف الكيانات

حسناً، لنعد إلى المهمة التي بين أيدينا. كان دافعنا الأصلي هو القدرة على تعريف حرّاس هيكلي يدور أمام الباب، وبعض التماثيل السحرية التي تنطلق منها صواعق البرق. لنبدأ بصديقنا العظمي. ولتعريف سلوك دورانه، نُنشئ كياناً جديداً يطبّق update() على النحو المناسب:

^code skeleton

وكما ترى، فقد نسخنا ذلك الجزء من الشيفرة إلى داخل دالة update() في Skeleton ولصقناه بها. الفرق الوحيد البسيط هو أن patrollingLeft_ أصبح حقلاً بدلاً من متغيّر محلي. وبذلك تبقى قيمته موجودة بين الاستدعاءات المتتالية لـ update().

لنفعل هذا مرة أخرى مع التمثال:

^code statue

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

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

يتيح لنا هذا النمط فصل تعبئة عالم اللعبة عن تنفيذه. وهذا بدوره يمنحنا المرونة لتهيئة العالم باستخدام شيء من هذا النوع: ملف بيانات منفصل أو محرّر مستويات.

مخطط UML. يحوي World مجموعة من Entities، لكل منها طريقة update(). يرث Skeleton وStatue معًا من Entity.

تمرير الزمن

هذا هو النمط الأساسي، لكنني سألمّح فقط إلى تحسين شائع. لقد افترضنا حتى الآن أن كل استدعاء لـupdate() يقدّم حالة عالم اللعبة بمقدار الوحدة الزمنية الثابتة نفسها.

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

وهذا يعني أن كل استدعاء لـupdate() يحتاج إلى معرفة ما مقدار تقدّم عقرب الساعة الافتراضية، لذا ستجد غالباً أن الزمن المنقضي مُمرَّر إليه. مثلاً، يمكننا أن نجعل هيكلنا الدوّار يتعامل مع خطوة زمنية متغيّرة على النحو التالي:

^code variable

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

قرارات التصميم

مع نمط بسيط من هذا النوع، لا تنوّع كبيراً، لكن ما تزال هناك بضعة مفاتيح يمكنك تدويرها.

على أي صنف تعيش دالة التحديث؟

أوضح قرار وأهم قرار ستتخذه هو وضع update() على أي صنف.

  • صنف الكيان:

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

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

  • صنف المكوّن:

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

  • صنف مفوَّض:

    هناك أنماط أخرى تتضمّن تفويض جزء من سلوك صنف ما إلى كائن آخر. فنمط الحالة يفعل ذلك كي تتمكّن من تغيير سلوك كائن بتغيير ما يفوّض إليه. ونمط كائن النوع يفعل ذلك كي تتمكّن من مشاركة السلوك عبر مجموعة من الكيانات من «النوع» نفسه.

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

    ^code forward

    يتيح لك هذا تعريف سلوك جديد بتبديل الكائن المفوَّض إليه. تماماً مثل استخدام المكوّنات، يمنحك مرونة تغيير السلوك دون الحاجة إلى تعريف صنف فرعي جديد كلياً.

كيف تُعالَج الكائنات الخاملة؟

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

أحد البدائل هو الاحتفاظ بمجموعة منفصلة تضم الكائنات «الحيّة» التي تحتاج إلى التحديث فقط. فحين يُعطَّل كائن ما، يُزال من المجموعة. وحين يُمكَّن من جديد، يُضاف إليها. وبهذه الطريقة، تمرّ فقط على العناصر التي لديها عمل حقيقي لتؤديه.

  • إن استخدمت مجموعة واحدة تضم الكائنات غير النشطة:

    • تهدر الوقت. فبالنسبة إلى الكائنات غير النشطة، ستنتهي عندك إما إلى فحص علم «هل أنا مُفعَّل؟» أو إلى استدعاء دالة لا تفعل شيئاً.

  • إن استخدمت مجموعة منفيلة من الكائنات النشطة:

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

      ومن الخيارات الأخرى للتخفيف من ذلك أن يكون لديك مجموعتان، مع أن تضم المجموعة الأخرى الكيانات غير النشطة فقط بدلاً من تضمها جميعاً.

    • عليك إبقاء المجموعتين متزامنتين. فحين تُنشأ الكائنات أو تُدمَّر تماماً (ولست مجرّداً معطّلة مؤقتاً)، عليك أن تتذكّر تعديل المجموعة الرئيسة ومجموعة الكائنات النشطة معاً.

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

انظر أيضاً

  • هذا النمط، مع حلقة اللعبة والمكوّن، يشكّل ثلاثياً كثيراً ما يشكّل نواة محرّك الألعاب.

  • حين تبدأ بالاهتمام بأداء الذاكرة المؤقتة (cache) عند تحديث كومة من الكيانات أو المكوّنات في حلقة في كل إطار، يمكن لنمط محلية البيانات أن يساعد في تسريع ذلك.

  • يستخدم إطار عمل Unity هذا النمط في عدة أصناف، منها MonoBehaviour.

  • تستخدم منصّة XNA من مايكروسوفت هذا النمط في صنفَي Game وGameComponent.

  • يستخدم محرّك الألعاب Quintus بلغة JavaScript هذا النمط في صنفه الرئيس Sprite.