Menu
CoddyTech

מצבים כמחלקות

חלק מהיחידה ארכיטקטורת משחקים ב-Unity במסלול ה-Unity של Coddy. שיעור 28 מתוך 50.

מתג על enum ביחידה 3 משאיר את העבודה של כל מצב בתוך Update אחד. הפתרון הזה מפסיק להיות מדרגי כשמצבים מכילים נתונים משלהם: טיימר שמשמש רק מצב אחד הופך לשדה של MonoBehaviour, לצד השדות של כל שאר המצבים, וכל case חייב לזכור לאפס את מה שהוא משתמש בו. תבנית המצבים מ-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 לא משתנה.

challenge icon

אתגר

בינוני

צריח זקיף שומר על החצר. החצים מזיזים את Player (מוכן). תנו לצריח שני מצבים, המוגדרים כמחלקות:

  • ב־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 בלבד, ללא הטיה; פעולת Exit שלו רושמת ביומן All clear.
  • ב־Turret.cs, צרו את שני המצבים ב־Start והתחילו בסריקה. בכל פריים, עברו למצב התראה כשהשחקן קרוב יותר לצריח מ־range (4), וחזרו לסריקה כשהוא אינו קרוב יותר, ואז הפעילו את Tick של המצב הנוכחי. בעת שינוי מצב, פעולת Exit של המצב הקודם מופעלת, ולאחריה פעולת Enter של המצב החדש.

לחצו על הפעלה, התקרבו לצריח והתרחקו ממנו, ואז לחצו על בדיקה.

נסו בעצמכם

using UnityEngine;

// ScanState ו-AlertState
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה ארכיטקטורת משחקים ב-Unity