Developedia
Developedia
Objektumok létrejötte és megszűnte

Objektumok létrejötte és megszűnte

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 komponens OnEnable-je.
  • Minden OnEnable először közvetlenül ugyanazon komponens Awake-je előtt fut le.
  • Az összes MonoBehaviour komponens Awake-je és OnEnable-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 és Start-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:

icon
Ha saját komponens beállítására használjuk az 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ük

Ezá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-okPrefab-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:

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)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
	}
}
ApplicationUtility.cs

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: PoolingPooling

Ennek lényege, hogy az egyes objektumokat nem töröljük, hanem csak kikapcsoljuk, hogy később újra felhasználjuk.

Logo

Főoldal

Blog

Elmélet

3D Studio

Adatvédelmi nyilatkozat

GY.I.K.

Házirend

Szerző: Marosi Csaba / marosi.csaba@3d-studio.hu

DiscordGitHubLinkedIn
[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 
    } 
}