Start és Awake
Láttuk, hogy egy MonoBeaviour
osztálynak a Start()
metódusa automatikusan meghívódik mielőtt létrejön a komponens a játékban.
Azt is tanultuk, hogy az OnEnable()
metódus is lefut az objektum létrejöttekor, ha bekapcsolt állapotban van.
Az utolsónak tanult metódus, ami a komponens létrejöttekor automatikusan lefut az Awake()
. Ez fog a három közül legelőször meghívódni.
Nézzük a következő példát:
Tételezzük fel, hogy az itt látható szkriptet 2 különböző GameObject-hez társítjuk komponensként:
A-hoz és B-hez.
Ebben az esetben induláskor a következő két sorrendben lehetséges a sorok kiíratása:
A fut le először:
Awake: A
OnEnable: A
Awake: B
OnEnable: B
Start: A
Start: B
B fut le először:
Awake: B
OnEnable: B
Awake: A
OnEnable: A
Start: B
Start: A
using UnityEngine;
class Test : MonoBehaviour
{
void Awake()
{
Debug.Log("Awake: " + name);
}
void OnEnable()
{
Debug.Log("OnEnable: " + name);
}
void Start()
{
Debug.Log("Start: " + name);
}
}
Abban nem lehetünk biztosak, hogy a Unity végrehajtási sorában melyik GameObject lesz előrébb.
Mikor egyszerre jön létre több komponens akár több GameObject-en (pl.: játék indításakor), akkor a következők biztosak:
- Minden komponens
Awake
-ja előbb fog lefutni, mint ugyanazon komponensOnEnable
-je. - Minden
OnEnable
először közvetlenül ugyanazon komponensAwake
-je előtt fut le. - Az összes MonoBehaviour komponens
Awake
-je ésOnEnable
-ej le fog futni az előtt, hogy az elsőStart
lefutna. - A GameObject-ek végrehajtási sorrendje mindig ugyanaz lesz
Awake
-re,OnEnable
-re ésStart
-ra nézve is. - Az egy GameObject-en lévő komponensek metódusai egymás után fognak lefutni.
De mi szükség az Awake-re, ha már van egy Start metódus? (Ezen kérdés megválaszolása és megértése némi gyakorlatot igényel. A kezdők nyugodtan átugorhatják ezt a részt.)
A legjobb a következő szabály használni ezeket a metódusokat:
Awake
-ot.
Ha bármi olyasmit csinálunk, aminek hatása van más komponensre, objektumra (például hívjuk egy eljárását), akkor azt Start
-ban tegyükEzáltal elérhető, hogy minden komponens elvégezhesse a szükséges előzetes beállításokat saját magán, mielőtt bárki “használná” őt.
Objektumok létrehozása
A C#-ban alapesetben a new kulcsszóval hozunk létre új objektumot. Ezt korábban láthattuk a Vector2
és a Vector3
esetében. Ekkor egy osztály egy speciális függvényét hívjuk, az úgynevezett konstruktort. Egy típusnak több konstruktora is létezhet különböző paraméterezéssel. Pl. a Vektor3 konstruktorának 3 különböző verzióját is láttuk.
Vector3 v1 = new Vector3(); // 0 paraméter (x, y, z = 0)
Vector3 v2 = new Vector3(1, 2); // 2 paraméter (x-t és y-t megadjuk. z = 0)
Vector3 v3 = new Vector3(1, 2, 3); // 3 paraméter (Mindhárom értéket megadjuk)
// Újabb C# verziókban a változó neve másodjára elhagyható:
Vector3 v4 = new (1, 2, 3);
Új GameObject-et hasonlóképpen, a new
használatával készíthetünk.
A GameObject konstruktorának is több paraméterezése létezik:
GameObject myNewGameObj1 = new GameObject();
GameObject myNewGameObj2 = new GameObject("Name");
Az ekkor létrehozott objektumoknak nincsen más komponense, mint a Transform. Minden egyebet manuálisan kell hozzáadni az AddComponent
metódussal a következőképp:
SpriteRenderer sr = myNewGameObj1.AddComponent<SpriteRenderer>();
BoxCollider bc= myNewGameObj2.AddComponent<BoxCollider>();
A legtöbb esetben azonban nem így javasolt objektumot és hozzá komponenseket létrehozni.
Sokkal célszerűbb az Editorban elkészíteni egy objektumot és azt másolni. Ehhez az Instantiate
metódust fogjuk használni a következőképp:
GameObject newGameObject1 = Instantiate(originalGameObject);
var newGameObject2 = Instantiate(originalGO, position, rotation);
var newGameObject2 = Instantiate(originalGO, parentTransform);
var newGameObject3 = Instantiate(originalGO, position, rotation, parentTransform);
Az Instantiate
metódussal gyakran egy kikapcsolt objektumból hozunk létre egy másolatot és kapcsoljuk be egyből.
Máskor Prefab-okat példányosítunk vele: Lásd később: Prefab-ok
Az is gyakori, hogy a létrehozó GameObject a saját maga gyerekeként hozza létre az újat.
Például íme egy lövedéket létrehozó kódrészlet:
[SerializeField] GameObject bulletPrototype; // Lövedék prototípus GameObject
[SerializeField] Transform bulletsParent // Ez lesz minden lövedék szülője
void Update()
{
if(Input.GetKeyDown(KeyCode.Space)) // La lenyomjuk a Space-t
{
GameObject newBullet = Instantiate( // Elkészítünk egy új lövedéket
bulletPrototype,
transform.position,
transform.rotation,
bulletsParent);
newBullet.SetActive(true); // Új objektum bekapcsolása
}
}
Az Instantiate
egy publikus satikus metódusa a UnityEngine.Object
osztálynak. Ez azt jelenti, hogy bárhonnan meghívható, ám MonoBehaviour
-on kívülről kissé másként:
// Így hívható MonoBehaviour-on belülről
var item2 = Instantiate(prefab);
// Így hívható bárhonnan, ha using-oltuk a névteret a file elején:
var item2 = Object.Instantiate(prefab);
// Így hívható bárhonnan:
var item3 = UnityEngine.Object.Instantiate(prefab);
(Igazából nem csak MonoBehaviour
-on belül működik a hívás, hanem bármely olyan osztályból, ami leszármszik a UnityEngine.Object
-ből. Erről később: Objektum orientáltság és öröklés (Félkész).
Objektumok törlése
GameObject-et vagy komponenst törölni a Destroy
paranccsal tudunk, ahol a törlendő objektumot paraméterben kell átadni.
Destroy(someOtherGameObject); // Másik objektum törlése
Destroy(someOtherComponent); // Komponens törlése
Destroy(gameObject); // Saját GameObject törlése
Destroy(this); // Saját MonoBehaviour komponens törlése
A törölt MonoBehaviour
komponensnek automatikusan lefut az OnDisable
és azután az OnDestroy metódusa:
void OnDestroy()
{
Debug.Log("I'm dead!");
}
Kiegészítő anyag: OnDestroy a szoftver leállításakor
Az OnDestroy
és az OnDisable
metódusokat, a Unity akkor is meghívja, amikor a játékot kikapcsoljuk (Editorban leállítjuk a Play módot), hisz ekkor az összes GameObject törlődik.
Ebben az esetben lehet, hogy nem akarjuk végrehajtani azon műveleteket, amiket normális esetben az OnDestroy
/OnDisable
-ben végeznénk el, mert így az hibás működéshez, error-okhoz vezetne.
Erre példa lehet, hogy ha a törlődő lövedék, hívja a lövedék menedzsert, hogy értesítse azt a saját megszűntéről. (Ez egy gyakori, egyszerű és elegáns tervezési minta.) Viszont ha mindez leállításkor történik meg, akkor a lövedék-menedzser lehet, hogy nem is létezik már, ezért a lövedék nem tudja majd hívni azt.
Másik lehetséges hiba, hogy ha egy objektum törlése (OnDestroy
-ból indítva) eredményezi új objektumok létrejöttét. A probléma megint csak akkor lép fel, ha mindez leállításkor történik meg. A futás közben, de már a leállítás alatt újonnan létrehozott elemek ekkor “szemétként” bent maradhatnak a Scene-ben a leállítás után is.
Sajnos nincs triviális mód arra, hogy lekérdezzük, hogy épp kikapcsolás miatt hívodott-e meg az OnDestroy
/OnDisable
vagy sem. Feliratkozhatunk azonban egy eseményre, ami a kikapcsoláskor hajt végre a Unity, de még a GameObject-ek törlése előtt: Application.quitting
Ezt kihasználva írhatunk egy osztályt, amit ha hozzáadunk a projekthez, akkor már lekérdezhető lesz, hogy éppen leáll-e a szoftver.
(Az alábbi kód technikai részleteivel itt most nem foglalkozunk bővebben, ugyanis tartalmaz olyan megoldásokat, amiket nem tárgyaltunk. Lényeg, hogy az egész kód egyben bemásolható bármely Unity projektbe.)
using UnityEngine;
public static class ApplicationUtility // Statikus osztály
{
public static bool IsQuitting { get; private set; } // Épp leáll e a szoftver?
static ApplicationUtility() // Statikus konstruktor
{
Application.quitting += () => IsQuitting = true; // Feliratkozás az eseményre
}
}
Ezen osztály használata kifejezetten OnDestroy
és OnDisable
metódusokban hasznos. Példa:
void OnDestroy()
{
if(ApplicationUtility.IsQuitting) // Ha épp leáll a szoftver,
return; // akkor nincs semmi egyéb dolgunk: Kilépünk.
// Egyébként tesszünk, amit tennénk az objektum törlésekor
// ...
}
A gyakran létrehozott és törölt objektumok esetén, mint például lövedékek célszerű lehet optimalizációs technikákat használni: Pooling
Ennek lényege, hogy az egyes objektumokat nem töröljük, hanem csak kikapcsoljuk, hogy később újra felhasználjuk.