ObjectPool של Unity
חלק מהיחידה ארכיטקטורת משחקים ב-Unity במסלול ה-Unity של Coddy. שיעור 39 מתוך 50.
Unity כוללת מאגר, ObjectPool<T> בתוך UnityEngine.Pool. מגדירים לה מה לעשות בכל שלב באמצעות ארבע פונקציות lambda, והיא מנהלת את העודפים ואת המונים. הארובה הזו פולטת עשן כל 0.2 שניות, וכל פליטה מוחזרת אחרי שנייה:
using UnityEngine;
using UnityEngine.Pool;
public class Chimney : MonoBehaviour
{
public Puff puffPrefab;
private ObjectPool<Puff> pool;
private float timer;
void Awake()
{
pool = new ObjectPool<Puff>(
createFunc: () => Instantiate(puffPrefab),
actionOnGet: puff => puff.gameObject.SetActive(true),
actionOnRelease: puff => puff.gameObject.SetActive(false),
actionOnDestroy: puff => Destroy(puff.gameObject),
collectionCheck: true,
defaultCapacity: 10,
maxSize: 20);
}
void Update()
{
timer += Time.deltaTime;
if (timer >= 0.2f)
{
timer = 0f;
Puff puff = pool.Get();
puff.transform.position = transform.position + Vector3.up;
puff.Launch(pool);
}
}
}// Puff.cs
private IObjectPool<Puff> pool;
private float age;
public void Launch(IObjectPool<Puff> owner)
{
pool = owner;
age = 0f;
}
void Update()
{
transform.Translate(0f, Time.deltaTime, 0f);
age += Time.deltaTime;
if (age >= 1f) pool.Release(this);
}createFunc מופעלת כש-Get מוצא את המאגר ריק; actionOnGet מופעלת בכל Get ו-actionOnRelease בכל Release. כשמחזירים פליטה בזמן שכבר ממתינים במאגר עודפים במספר maxSize, המאגר לא שומר אותה: actionOnDestroy משמידה אותה. defaultCapacity הוא רק גודל הרשימה ההתחלתי של המאגר, ולכן שום דבר לא נוצר מראש. CountAll סופר את הפליטות שהמאגר יצר ועדיין מחזיק, CountActive סופר את אלה שנמצאות במשחק, ו-CountInactive את העודפים. Get הפעיל את הפליטה לפני שהארובה ממקמת אותה, וזה בסדר כאן כי ל-Puff אין OnEnable שקורא את המיקום שלה. הפליטה שומרת את המאגר בתור IObjectPool<Puff>, הממשק שכל מאגר Unity מממש, ו-Launch מאפסת את הגיל שלה בכל שימוש.
כשהאפשרות collectionCheck מופעלת, החזרת פליטה שכבר נמצאת במאגר גורמת ל-InvalidOperationException: Trying to release an object that has already been released to the pool. הבדיקה פועלת רק בעורך, כי היא מחפשת בין העודפים בכל Release; בבנייה הפליטה תישאר במאגר פעמיים, ושתי קריאות Get יחזירו את אותו אובייקט. סיבה נפוצה לכך היא אובייקט שממשיך לפעול אחרי שהוחזר ומשחרר את עצמו שוב, ולכן actionOnRelease מכבה אותו.
אתגר
בינוניזיקוקים. כל לחיצה על Space משגרת 8 ניצוצות מהמשגר, וכל Spark קוראת ל־Return(this) של המשגר כששנייה אחת של חייה מסתיימת (הניצוץ מוכן). כיום המשגר יוצר ומשמיד כל ניצוץ. השתמשו במאגר של Unity:
- ב־
Launcher.cs, צרוObjectPool<Spark>ב־Awake. הוא יוצר ניצוץ באמצעות יצירת מופע שלsparkPrefab, מפעיל ניצוץ כשהוא נשלף ומכבה אותו כשהוא מוחזר, ומשמיד את אובייקט הניצוץ כשהוא משחרר אותו. - המאגר שומר לכל היותר 5 ניצוצות פנויים, כך שאחרי מטח של 8, 5 ממתינים ו־3 מושמדים.
- לחיצה על Space שולפת את 8 הניצוצות שלה מהמאגר במקום ליצור אותם, ו־
Returnמשחררת את הניצוץ בחזרה למאגר במקום להשמיד אותו.
לחצו על Play, שגרו כמה מטחים ואז לחצו על Check.
נסו בעצמכם
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;
public class Launcher : MonoBehaviour
{
public Spark sparkPrefab;
public int sparks = 8;
void Awake()
{
// צור את המאגר: צור, קבל, שחרר והשמד, עם לכל היותר 5 ניצוצות רזרביים
}
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
for (int i = 0; i < sparks; i++)
{
Spark spark = Instantiate(sparkPrefab);
spark.transform.position = transform.position + Vector3.up;
spark.Launch(Direction(i), this);
}
}
}
// ניצוץ שתוחלת חייו הסתיימה
public void Return(Spark spark)
{
Destroy(spark.gameObject);
}
// החוצה ולמעלה, בפיזור אחיד סביב המשגר
Vector3 Direction(int i)
{
return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.