الحالات كأصناف
جزء من قسم هندسة الألعاب في Unity في رحلة Unity على Coddy. الدرس 28 من 50.
يضع التبديل باستخدام enum في القسم 3 عمل كل حالة داخل Update واحد. لكن هذا الأسلوب لا يتوسع جيدًا عندما تكون للحالات بياناتها الخاصة: فالمؤقّت الذي تستخدمه حالة واحدة فقط ينتهي به الأمر حقلًا في MonoBehaviour، إلى جانب حقول كل الحالات الأخرى، ويجب أن تتذكّر كل حالة إعادة ضبط ما تستخدمه. يمنح نمط الحالة في C# كل حالة صنفًا خاصًا بها، وتطبّق كل الأصناف واجهة واحدة. يومض هذا المنارة أو يبقى مضاءً، ويبدّل Space بين الحالتين:
public interface IState
{
void Enter();
void Tick();
void Exit();
}
public class BlinkState : IState
{
private Renderer lamp;
private float timer;
public BlinkState(Renderer lamp)
{
this.lamp = lamp;
}
public void Enter() { timer = 0f; }
public void Tick()
{
timer += Time.deltaTime;
if (timer >= 0.5f)
{
timer = 0f;
lamp.enabled = !lamp.enabled;
}
}
public void Exit() { lamp.enabled = true; }
}
public class SteadyState : IState
{
public void Enter() { }
public void Tick() { }
public void Exit() { }
}
public class Beacon : MonoBehaviour
{
public Renderer lamp;
private IState blink;
private IState steady;
private IState current;
void Start()
{
blink = new BlinkState(lamp);
steady = new SteadyState();
SetState(steady);
}
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame) SetState(current == steady ? blink : steady);
current.Tick();
}
void SetState(IState next)
{
if (current != null) current.Exit();
current = next;
current.Enter();
}
}الحالات أصناف C# عادية، وليست MonoBehaviours. تنشئها باستخدام new وتمرّر إليها ما تحتاج إليه عبر المُنشئ، وفي هذه الحالة Renderer الخاص بالمصباح، لأنها ليست مكوّنات ولا يمكنها استدعاء GetComponent أو عرض حقول في Inspector. كما أن Unity لا يستدعي أساليبها: فـ Beacon هو MonoBehaviour، ولذلك يستدعي Update الخاص به current.Tick() في كل إطار.
ينتمي timer إلى BlinkState وحدها، ويضبطه Enter على 0 في كل مرة تبدأ فيها المنارة بالوميض. ينظّف Exit الأمور عند انتهاء الحالة: قد يُضغط Space بينما يكون المصباح مطفأً، ويعيده Exit إلى الإضاءة، لذا لا تبدأ SteadyState بمصباح مطفأ. ينفّذ SetState أسلوب Exit للحالة القديمة، ثم أسلوب Enter للحالة الجديدة. وتتطلب إضافة حالة ثالثة، مثل نبض بطيء، صنفًا إضافيًا واحدًا، ولا يتغير Update في Beacon.
التحدي
متوسطيحرس برجٌ حارسٌ الفناء. حرّك اللاعب باستخدام الأسهم (جاهز). امنح البرج حالتين، واكتبهما على شكل صنفين:
- في
IState.cs، عرّفpublic interface IStateمعvoid Enter()وvoid Tick()وvoid Exit(). - في
TurretStates.cs، اكتب صنفين عاديين يطبّقان هذه الواجهة. يستقبلScanStateرأس البرج في الباني. يحرّك الرأس من جانب إلى آخر: تتغير زاوية y المحلية للرأس بمعدل 90 درجة في الثانية بين-45و45، بدءًا من 0 والاتجاه إلى الأعلى في كل مرة يبدأ فيها المسح. كما يلوّن Enter الرأس باستخدامColor.green. - يستقبل
AlertStateالرأس واللاعب. يلوّن Enter الرأس باستخدامColor.redويسجّلIntruder!؛ ويوجّه Tick الرأس لمواجهة اللاعب حول المحور y فقط، دون إمالة؛ ويسجّل ExitAll clear. - في
Turret.cs، أنشئ الحالتين في Start وابدأ المسح. في كل إطار، انتقل إلى حالة التنبيه عندما تكون المسافة بين اللاعب والبرج أقل منrange(4)، وارجع إلى المسح عندما لا يكون كذلك، ثم نفّذ Tick للحالة الحالية. عند تغيير الحالة، يُنفّذ Exit للحالة القديمة، ثم Enter للحالة الجديدة.
اضغط Play، وامشِ نحو البرج ثم ابتعد عنه، ثم اضغط Check.
جرّب بنفسك
using UnityEngine;
// ScanState وAlertState
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس هندسة الألعاب في Unity
1المكوّنات لا التسلسلات الهرمية
مكوّنات صغيرةسكربت أساسي للعدومكوّنات مجرّدةتعمل المكوّنات معًامراجعة: برج دفاعي معياري4البيانات المتسلسلة
الأصناف القابلة للتسلسلالقوائم في Inspectorالتعدادات في Inspectorمراجعة: جدول الغنائم10مشروع: الناجي في الساحة
البطلأنواع الأعداء