Articles

GetComponent in C#

In deze tutorial

  • toegang tot een ander script vanuit het object
  • Hoe werkt GetComponent?
  • veel componenten ophalen
  • interactie tussen objecten.
  • interactie tussen objecten binnen de hiërarchie
  • SendMessage en BroadcastMessage
  • interactieve functies

Inleiding

een terugkerend probleem bij het starten met Unity is hoe toegang te krijgen tot de leden in een script vanuit een ander script. Velen zouden denken dat dereferencing van de script naam zou genoeg zijn, ze snel beseffen dat het niet.

bij het ontwikkelen van een programma worden variabelen opgeslagen in het geheugen op verschillende locaties. Als een object andere objectleden zou kunnen zien, zou er een risico zijn om ze te wijzigen, ook al was het niet de bedoeling. Als voor gevallen twee objecten hetzelfde script met dezelfde variabele namen bevatten, zou de compiler niet in staat zijn om erachter te komen naar welke we verwijzen.

om dit probleem te voorkomen, kan elk object in het geheugen geen andere objecten zien. Het is dan noodzakelijk om een object te vertellen waar de variabele die het nodig heeft in het geheugen is. Het hele pointer principe is al lang geleden vervallen en nu gebruiken we in plaats daarvan referentie (die eigenlijk intern pointer gebruikt).

Er zijn verschillende manieren om toegang te krijgen tot variabelen, sommige zijn beter dan andere en sommige zijn gewoon te gebruiken in bepaalde situaties.

  1. GetComponent, de meest voorkomende, ook degene die het meest verwart op het eerste
  2. SendMessage, het lijkt misschien gemakkelijker te begrijpen, maar het is ook minder efficiënt.
  3. statische variabelen, de makkelijkste maar ook de meest ingewikkelde om volledig te begrijpen op het eerste.

een scène instellen

Maak een nieuwe scène, voeg een leeg spelobjecten toe en noem het “StormTrooper”. Maak twee scripts genaamd “gezondheid” en “kracht”. Voeg beide scripts toe aan StormTrooper.

Open beide scripts in de editor en voeg deze regels toe:

gezondheid.cs

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private int health = 5; private bool hasForce = false; private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { this.hasForce = Force.ReportForce(); if(hasForce == true) { this.health = 10; } } }}

Force.cs

using UnityEngine;using System.Collections;public class Force : MonoBehaviour{ private bool force = false; private void Start() { this.force = true; } private void Update() { if (Input.GetKeyDown(KeyCode.P)) { string str = (this.force == true) ? " the": " no"); Debug.Log("I have" + str + " force"); } } public bool ReportForce() { return force; }}

Als u deze code probeert, geeft u de foutmelding”een objectreferentie is vereist om toegang te krijgen tot niet-statische leden”.

We moeten de Gezondheidscomponent op de StormTrooper vertellen waar de Force-component is, zodat het zijn leden kan benaderen. Omdat onze scripts beide op hetzelfde object staan, moeten we gewoon een variabele van het type van het script declareren die we willen openen en gebruiken om het script te vinden.

toegang krijgen tot een ander script in het object

Wijzig gezondheid met:

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private Force forceScript= null; public int health = 5; private bool hasForce = false; private void Start() { this.forceScript= GetComponent<Force>(); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { this.hasForce = this.forceScript.ReportForce(); if(this.hasForce == true) { this.health = 10; } } }}

met de wijzigingen, voer het spel uit, druk op spatie en druk vervolgens op P, uw StormTrooper heeft nu 10 van gezondheid (merk op dat een StormTrooper niet de kracht heeft, dit is alleen voor het voorbeeld). Je hebt toegang tot variabelen op dezelfde manier als je methoden hebt benaderd. Zorg ervoor dat de leden die u probeert te bereiken openbaar worden verklaard of u krijgt een fout.

als de leden privé waren, zou het niet mogelijk zijn om ze te benaderen. Alles wat niet van buiten de class/script bereikt mag worden, moet privé zijn om met het encapsulation principe om te gaan.

Hoe werkt GetComponent?

eerst declareren we een object van het type script dat we willen bereiken.

private Force forceScript = null;

Force is ons type en forceScript is slechts een naam die we geven voor het gebruik van de referentie in het script. We kunnen het elke naam geven.

De GetComponent functie zal in het object (het StormTrooper game object) kijken om een component te vinden die overeenkomt met het type dat we hebben doorgegeven. Als er geen wordt gevonden, wordt een null referentie geretourneerd en zal onze forcescript variabele niets doen. Als een component van het type kracht wordt gevonden dan wordt het adres van die component doorgegeven aan de variabele forceScript, de variabele wijst nu naar de kracht component opgeslagen ergens in het geheugen. Om toegang te krijgen tot de publieke leden van Force, hoeven we alleen maar de scriptvariabele te derefereren met behulp van de dot operator. Er wordt gezegd dat de forcescript variabele een handvat is voor het object. Het geeft toegang tot alle publieke leden (en interne).

script.PublicMembers;

privé-en beschermde leden blijven ontoegankelijk.

met behulp van het kenmerk SerializeField kunt u zien dat de variabelen in inspector despute privé zijn. Je forcescript variabele begint met de waarde van None(Force) en verandert in een Force referentie. Als u erop klikt, wordt het doelobject gemarkeerd.

reference_component

zodat ik component kan cachen met GetComponent?

Ja, en het wordt aanbevolen dit te doen voor sommige regelmatig gebruikte componenten.

bij het benaderen van ingebouwde componenten zoals Rigidbody, Transform, Renderer,…etc, Unity gebruikt om “directe” toegang toe te staan als volgt:

private void Update(){ this.transform.position += this.transform.forward; this.rigidbody.AddForce(force);}

ze verwijderden ze allemaal, behalve de transformatie omdat er altijd een transformatie is op een bepaald spelobject.

GetComponent moet worden vermeden binnen Update dus caching wordt aanbevolen met een paar eenvoudige regels code zoals:

private Transform myTransform = null;private Rigidbody rigidbody = null;private void Start(){ this.myTransform = this.gameObject.GetComponent<Transform>(); this.rigidbody = this.gameObject.GetComponent<Rigidbody>();}private void Update(){ this.myTransform.position += this.myTransform.forward; this.rigidbody.AddForce(force);}

Dit bespaart enige berekening.

veel componenten krijgen

Wat als onze StormTrooper veel kracht in zichzelf heeft (niet waarschijnlijk). Dupliceer de kracht twee keer, zodat StormTrooper er drie heeft.

Gezondheid wordt:

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private Force scripts = null; private int health = 5; private void Start() { this.scripts = this.gameObject.GetComponents<Force>(); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { for (int i = 0; i < this.scripts.Length; i++) { if(this.scripts.ReportForce()) { this.health += 5; } } } }}

voer het spel uit, druk op de spatie, onze StormTrooper wordt een Jedi. GetComponents zoekt naar alle componenten van het type en geeft een array van hen terug. Dit is prima als je niet richten op een specifiek onderdeel van het type, maar meer waarschijnlijk allemaal.

zie het volgende zou de lichten aan en uit doen:

public class LightFlicker:MonoBehaviour{ private Light lights = null; private float timer = 0.0f private float clockTimer = 0.0f; private void Start(){ this.lights = this.gameObject.GetComponents<Light>(); this.clockTimer = Random.Range(0.5f, 2.0f); } private void Update(){ this.timer += Time.deltaTime; if(this.timer > this.clockTimer){ this.timer = 0.0f; this.clocktimer = Random.Range(0.5f, 2.0f); for(int i = 0; i < this.lights.Length; i++){ lights.enabled = !lights.enabled; } } }}

interactie tussen objecten.

we kunnen ook twee verschillende objecten op een vergelijkbare manier laten interageren. Maak een nieuw object met de naam “Sith”. Verwijder de gedupliceerde componenten van de StormTrooper zodat er nog maar één over is.

using UnityEngine;using System.Collections;public class DarkSide : MonoBehaviour { private const bool force = true; private Force script = null; private void Start () { this.script = GameObject.Find ("StormTrooper").GetComponent<Force>(); } private void Update () { if(Input.GetKeyDown (KeyCode.Space)) { UseForceToControl (); } } private void UseForceToControl() { if(this.script.ReportForce() == false) { Debug.Log ("Do as I say."); } }}

Wijzig het force script van de StormTrooper zodat hij de force niet heeft (de Sith zou dit verdacht vinden…), Ik geef je een aanwijzing dat het gebeurt in het Start script.

Hieronder is de regel die de StormTrooper vindt en zijn Force component krijgt.

private void Start () { this.script = GameObject.Find ("StormTrooper").GetComponent<Force>();}

GameObject.Find () vindt eerst het object dat als parameter is doorgegeven en zoekt vervolgens naar de vereiste component erin. Het wordt aanbevolen om deze acties uit te voeren in het begin of op een moment van het spel waar vertragingen de gameplay niet zal beïnvloeden. GameObject.Find en GetComponent zijn dure functies omdat ze moeten herhalen door de hele hiërarchie. Als je scène slechts 10 objecten bevat, zal het prima zijn, maar als het duizenden van hen bevat (een leger van kloon) zul je zeker een slecht effect zien. Overweeg om deze in de startfunctie aan te roepen. Merk op dat als je scene veel StormTroopers bevat, Uity niet kan weten welke je bedoelde te gebruiken en de eerste zal teruggeven die het vindt. Hoogstwaarschijnlijk heb je een verwijzing naar het object waarmee je wilt communiceren, bijvoorbeeld een collision callback.

interactie tussen objecten binnen de hiërarchie

beschouw een voertuig, wanneer het wordt bestuurd, wordt het teken eraan gekoppeld als een dochterobject. Het voertuig zou speciale functie hebben als de bestuurder de kracht heeft (betere handling, sneller enzovoort). Als de bestuurder is bevestigd, staat hij onder in de hiërarchie.
het voertuig kan de krachtcomponent van de bestuurder vinden met hetzelfde principe dat we eerder gebruikten (GameObject.Vinden) maar we kunnen het sneller laten gebeuren door het voertuig aan te geven dat de bestuurder zich in zijn eigen hiërarchie bevindt.

private Force script = null;private void Start () { this.script = this.transform.Find ("Sith").GetComponent<DarkSideForce>();}

GameObject wordt geruild voor transform. Inderdaad, een kind object behoort tot de transformatie van de ouder dus we moeten kijken naar de transformatie om ons object te vinden. In het voorbeeld, het object is recht onder het aanroepende object, als uw Sith is in de cockpit object onder de auto object, moet u het pad:

private void Start () { this.script = this.transform.Find ("Cockpit/Sith").GetComponent<DarkSideForce>();}

het aanroepende object is de auto en wordt aangeduid als De dit in de aanroep. Dan wordt de Cockpit doorzocht en wordt de Sith doorzocht. Als het pad verkeerd is, geeft Find null terug en krijg je een Null referentie uitzondering. U kunt dat als volgt vermijden:

private void Start () { Transform tr = this.transform.Find ("Cockpit/Sith"); if(tr != null){ this.script = tr.GetComponent<DarkSideForce>(); }else{Debug.LogWarning("No component found");}}

transform.Find geeft een transformatie terug tijdens het GameObject.Find geeft een GameObject terug.

nog beter, we kunnen gebruiken:

private void Start () { this.script = GetComponentInChildren<DarkSideForce>();}

nu kan ons voertuig weten dat onze Sith de kracht heeft en speciale functie toepassen.
Een probleem om te weten, als het bovenliggende object een script bevat van het type dat u zoekt, zal het het teruggeven. Dus denken dat je te maken hebt met het kind component, je hebt eigenlijk te maken met de ouder een.

in dit voorbeeld moeten we voor elk type driver controleren of het een StormTrooper of een Sith is. Een betere benadering zou geweest zijn om overerving te gebruiken zodat de hoogste basisklasse de krachtvariabele zou houden en één enkele functie voor al type van bestuurder zou worden gebruikt. Maar overerving en efficiëntie waren hier niet ons belangrijkste probleem.

SendMessage en BroadcastMessage

SendMessage en Broadcastmessage zijn twee andere mogelijkheden die eenvoudiger, maar ook duurder kunnen zijn. Het principe blijft hetzelfde, we hebben een verwijzing naar een object en we willen een script dat het bevat wijzigen.

met SendMessage kunt u een functie op een object aanroepen zonder dat u een verwijzing naar het script hoeft te vinden. Nu vertel je me: “Wat is al deze verwarring rit als je het eenvoudig kunt maken?”

SendMessage maakt gebruik van reflectie en is extreem traag in vergelijking met GetComponent en dient alleen overwogen te worden in gevallen waarin het geen invloed heeft op het spel of als je geen andere oplossing hebt.

en dat zou logisch moeten klinken, met GetComponent geven we precies aan waar het script zich in het geheugen bevindt. Met SendMessage, de compiler kent het object, maar het zal gaan door alle scripts en functies totdat het vindt de gelukkige winnaar. Dit kan van invloed zijn op je spel als je het vaak op meerdere objecten gebruikt en als het object veel componenten en scripts heeft (wat overigens componenten zijn).

SendMessage zoekt alleen het object waarnaar de oproep wordt gedaan, BroadcastMessage Start een zoekopdracht op het doelobject, maar ook op al zijn kinderen. Onderzoek zal nog langer duren als het object een grote hiërarchie bevat. Een nieuwkomer verscheen met Unity4 SendMessageUpwards die je had moeten raden doet hetzelfde als BroadcastMessage maar omhoog dus alle ouders van het object tot root.

Ok laten we eens kijken naar onze StormTrooper heeft een functie voor het ontvangen van order

//Order.csusing UnityEngine;using System.Collections;public class Order : MonoBehaviour{ private void ReceiveOrder(string order) { switch(order) { case "Fetch": //Action Debug.Log("Can you repeat Sir?"); break; // other cases } }}

laten we nu eens kijken naar onze StormTrooper meets a commander from the Empire:

// Commander.csprivate void Update(){ if(Input.GetKeyDown(KeyCode.Space)) { GameObject.Find("StormTrooper").SendMessage("ReceiveOrder","Fetch"); }}

zoals je kunt zien, blijft het principe hetzelfde, Ik heb een verwijzing naar het doelobject nodig en dan stuur ik er een bericht naar. Net als GetComponent is het beter als je de object referentie gecached hebt.

het is ook mogelijk om een parameter toe te voegen om er zeker van te zijn dat het bericht een ontvanger vindt of niet.

merk op dat het niet mogelijk is om een functie te gebruiken die een waarde retourneert. Je zou dan een back-up moeten maken en GetComponent moeten gebruiken.

interactieve functies

Unity gebruikt veel functies die twee of meer objecten interageren. Het voordeel van deze functies is dat ze veel informatie over het andere object opslaan.

  1. Botsingsfuncties OnTriggerXXXX/OnCollisionXXXX
  2. Physics functies XXXXcast

daar gaan we met collision declaration:

private void OnCollisionEnter(Collision colReference){}private void OnTriggerEnter(Collider colReference){}

de aanroep van de functie creëert een variabele van type Collision of Collider. De functie zal deze instantie vullen met informatie over de botsing en zal een verwijzing bevatten naar het andere object waarmee we interactie hebben.

prviate void OnCollisionEnter(Collision colReference){ GameObject goRef = colReference.gameObject; if(goRef.CompareTag("StormTrooper")) { Health script = goRef.GetComponent<Health>(); script.health -= 5; }}

merk op dat ik niet naar het andere object hoef te zoeken omdat colReference van het type Collision is en een verwijzing naar dat object als lid bevat. Nu kan ik mijn GetComponent zoals gewoonlijk uitvoeren en toegang krijgen tot mijn volksgezondheidsvariabele.

functies uit de natuurkundeles hebben een soortgelijk principe. De gegevens worden niet opgeslagen in een Collision / Collider-instantie, maar in een raycasthit-instantie:

using UnityEngine;using System.Collections;public class CheckFlat : MonoBehaviour { private void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { float result = Vector3.Dot(hit.normal,Vector3.up); if(Mathf.Abs(result) > 0.9) { Debug.Log("Ground is flat enough"); } else { Debug.Log("Not Flat Enough"); } } }}

Ik verklaar een variabele van het type RaycastHit, Ik geef het door aan de functie. RaycastHit is een structuur en dan een waarde type, Ik heb het uit trefwoord nodig om een verwijzing naar die structuur te maken, zodat de functie de structuur binnen kan wijzigen. Als u zich afvraagt over dat proces, check out de Memory Management artikel en de sectie over waarde/referentie type.
wat ik hier doe is controleren hoe vlak het oppervlak is waarop ik de cursor plaats. Dit is handig voor strategische games waar je een gebouw wilt plaatsen, maar niet op een klif.
Ik gebruik het dot product van het terrein normaal met de Vector3.Omhoog en als het resultaat dicht genoeg bij 1 is, wat betekent dat ze uitgelijnd zijn, is de grond vlak genoeg voor de bouw.
Uiteraard zijn er wat meer berekeningen nodig omdat dit slechts voor één punt is en je zou de normale rond moeten controleren maar je krijgt het idee. Ik creëerde interactie tussen mijn muispositie en het terrein met behulp van een raycast.

een raycast bevat een beginpunt en een richting. In ons geval is het uitgangspunt de conversie van de muispositie naar wereldpositie en vervolgens een richting gebaseerd op de camera rotatie en projectie matrix. Deze lijn gaat oneindig in de gedefinieerde richting (je kunt ook een maximale afstand instellen) en als hij een botser raakt, worden de informatie van die botser en botsing opgeslagen in de raycasthit-structuur. De structuur bevat dan een verwijzing naar het hit-object.