Articles

GetComponent i C #

i denna handledning

  • åtkomst till ett annat skript inifrån objektet
  • Hur fungerar GetComponent?
  • få många komponenter
  • interaktion mellan objekt.
  • interaktion mellan objekt i hierarkin
  • SendMessage och BroadcastMessage
  • interaktiva funktioner

introduktion

en återkommande fråga när man börjar med Unity är hur man kommer åt medlemmarna i ett skript från ett annat skript. Många skulle tro att dereferencing från manusnamnet skulle räcka, de inser snabbt att det inte är det.

vid utveckling av ett program lagras variabler i minnet på olika platser. Om ett objekt kunde se några andra objektmedlemmar skulle det finnas en risk att ändra dem även om det inte var avsett. Om två objekt för instanser har samma skript med samma variabelnamn, skulle kompilatorn inte kunna ta reda på vilken vi hänvisar till.

för att förhindra det här problemet kan varje objekt i minnet inte se andra objekt. Det är då nödvändigt att berätta ett objekt där variabeln den behöver finns i minnet. Hela pekarprincipen har tappats för länge sedan och nu använder vi referens istället (som faktiskt använder pekare internt).

det finns olika sätt att komma åt variabler, vissa är bättre än andra och vissa är helt enkelt att användas i vissa situationer.

  1. GetComponent, den vanligaste, också den som förvirrar mest först
  2. SendMessage, det kan se lättare att förstå men det är också mindre effektivt.
  3. statiska variabler, Det enklaste men också det mest komplicerade att förstå till en början.

ställa in en scen

skapa en ny scen, Lägg till ett tomt spelobjekt och namnge det ”StormTrooper”. Skapa två skript som heter” hälsa ”och”kraft”. Lägg båda skript till StormTrooper.

öppna båda skripten i redigeraren och Lägg till dessa rader:

hälsa.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; } } }}

kraft.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; }}

att prova den här koden kommer helt enkelt att returnera fel ”en objektreferens krävs för att komma åt icke-statiska medlemmar”.

Vi måste berätta för hälsokomponenten på StormTrooper var är Kraftkomponenten så att den kan komma åt sina medlemmar. Eftersom våra skript är båda på samma objekt behöver vi helt enkelt förklara en variabel av typen av skriptet vi vill komma åt och använda GetComponent för att hitta skriptet.

åtkomst till ett annat skript inuti objektet

ändra hälsa med:

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; } } }}

med ändringarna, kör spelet, tryck på Mellanslag och tryck sedan på P, din StormTrooper har nu 10 av hälsa (observera att en StormTrooper inte har kraften, det här är bara för exemplet). Du får tillgång till variabler på samma sätt som du använde metoder. Se bara till att medlemmarna du försöker nå förklaras offentliga eller så får du ett fel.

om medlemmarna var privata skulle det inte vara möjligt att komma åt dem. Allt som inte bör nås utanför klassen/skriptet bör vara privat för att klara inkapslingsprincipen.

Hur fungerar GetComponent?

först förklarar vi ett objekt av den typ av skript vi vill nå.

private Force forceScript = null;

Force är vår typ och forceScript är bara ett namn som vi tillhandahåller för att använda referensen i skriptet. Vi kan ge det vilket namn som helst.

GetComponent-funktionen kommer att se inuti objektet (StormTrooper – spelobjektet) för att hitta en komponent som motsvarar den typ vi passerade. Om ingen hittas returneras en null-referens och vår forcescript-variabel kommer inte att göra någonting. Om en komponent av typen Force hittas skickas adressen till den komponenten till variabeln forceScript, variabeln pekar nu på Kraftkomponenten som lagras någonstans i minnet. För att komma åt de offentliga medlemmarna i Force behöver vi bara avreferera skriptvariabeln med hjälp av punktoperatören. Det sägs att forceScript-variabeln är ett handtag till objektet. Det ger tillgång till alla offentliga medlemmar (och interna).

script.PublicMembers;

privata och skyddade medlemmar förblir otillgängliga.

med attributet SerializeField kan variablerna i inspector despute vara privata. Din forcescript-variabel börjar med värdet None (Force) och förvandlas till en Kraftreferens. Om du klickar på det kommer det att markera målobjektet.

reference_component

så jag kan cache komponent med GetComponent?

Ja, och det rekommenderas att göra det för vissa regelbundet använda komponenter.

vid åtkomst till inbyggda komponenter som Rigidbody, Transform, Renderer,…etc, användes Unity för att tillåta ”direkt” åtkomst så här:

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

de tog bort dem alla utom transformen eftersom det alltid finns en transformation på ett visst spelobjekt.

GetComponent bör undvikas inuti uppdatering så caching rekommenderas med några enkla rader kod som:

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);}

detta sparar lite beräkning.

få många komponenter

vad händer om vår StormTrooper har många krafter i sig själv (inte troligt). Duplicera kraften två gånger så StormTrooper har tre av dem.

hälsa blir:

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; } } } }}

kör spelet, tryck på Space, vår StormTrooper blir en Jedi. GetComponents letar efter alla komponenter av typen och returnerar en array av dem. Det här är bra om du inte riktar dig mot en viss komponent av typen men mer sannolikt alla.

se följande skulle slå på och stänga av lamporna:

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; } } }}

interaktion mellan objekt.

Vi kan också få två olika objekt att interagera på ett liknande sätt. Skapa ett nytt objekt som heter ”Sith”. Ta bort de duplicerade komponenterna från StormTrooper så att bara en är kvar.

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."); } }}

ändra kraftskriptet för StormTrooper så att han inte har kraften (Sith skulle hitta detta misstänkt…), jag ger dig en ledtråd det händer i startskriptet.

nedan är linjen som hittar StormTrooper och får sin kraftkomponent.

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

GameObject.Sök () hittar först objektet som passerat som parameter och letar sedan efter den nödvändiga komponenten inuti den. Det rekommenderas att utföra dessa åtgärder i början eller vid en tidpunkt i spelet där lags inte påverkar spelningen. GameObject.Hitta och GetComponent är dyra funktioner eftersom de behöver iterera genom hela hierarkin. Om din scen bara innehåller 10 objekt kommer det att bli bra men om det rymmer tusentals av dem (en klonhär) kommer du säkert att se en dålig effekt. Överväg att ringa dessa i startfunktionen. Observera att om din scen innehåller många StormTroopers, kan Uity inte veta vilken du menade att använda och kommer att returnera den första som den hittar. Troligtvis har du en referens till objektet du vill interagera med, till exempel en kollision återuppringning.

interaktion mellan objekt inom hierarkin

Tänk på ett fordon, när du kör det är tecknet kopplat till det som ett barnobjekt. Fordonet skulle ha speciell funktion om föraren har kraften (bättre hantering, snabbare och så vidare). När föraren är ansluten är han nedan i hierarkin.
fordonet kan hitta kraftkomponenten hos föraren med samma princip som vi använde tidigare (GameObject.Hitta) men vi kan få det att hända snabbare genom att indikera för fordonet att föraren befinner sig i sin egen hierarki.

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

GameObject byts mot transform. Faktum är att ett barnobjekt tillhör förälderns omvandling, så vi måste titta på omvandlingen för att hitta vårt objekt. I exemplet är objektet rätt under det anropande objektet, om din Sith är inne i cockpitobjektet under bilobjektet måste du ange sökvägen:

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

det anropande objektet är bilen och kallas detta i samtalet. Sedan söks Cockpit och Sith söks. Om sökvägen är fel returnerar Find null och du får ett Null-Referensundantag. Du kan undvika det så här:

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.Hitta returnerar en trans medan GameObject.Find returnerar ett GameObject.

ännu bättre kan vi använda:

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

Nu kan vårt fordon veta att vår Sith har kraften och tillämpa specialfunktionen.
ett problem att veta om, om det överordnade objektet innehåller ett skript av den typ du letar efter, kommer det att returnera det. Så tänker du att du har att göra med barnkomponenten, du har faktiskt att göra med föräldern.

i det här exemplet måste vi kontrollera för varje typ av förare om det är en StormTrooper eller en Sith. Ett bättre tillvägagångssätt skulle ha varit att använda arv så att den övre basklassen skulle hålla kraftvariabeln och en enda funktion skulle användas för alla typer av förare. Men arv och effektivitet var inte vår huvudfråga här.

SendMessage och BroadcastMessage

SendMessage och BroadcastMeassage är två andra möjligheter som kan vara enklare men också dyrare. Principen förblir densamma, vi har en hänvisning till ett objekt och vi vill ändra ett skript som det innehåller.

med SendMessage kan du anropa en funktion på ett objekt utan att behöva hitta en referens till skriptet. Nu säger du till mig: ”Vad är all denna förvirringsresa om du kan göra det enkelt?”

SendMessage använder reflektion och är extremt långsam jämfört med GetComponent och bör endast övervägas i fall där det inte påverkar spelet eller om du inte har någon annan lösning.

och det borde låta logiskt, med GetComponent anger vi exakt var skriptet finns i minnet. Med SendMessage känner kompilatorn objektet, men det kommer att gå igenom alla skript och funktioner tills den hittar den lyckliga vinnaren. Detta kan påverka ditt spel om du använder det ofta på flera objekt och om objektet har många komponenter och skript (som förresten är komponenter).

SendMessage söker bara det objekt som samtalet görs, BroadcastMessage startar en sökning på målobjektet men också på alla dess barn. Forskningen kommer att bli ännu längre om objektet innehåller en stor hierarki. En nykomling dök upp med Unity4 SendMessageUpwards som du borde ha gissat gör detsamma som BroadcastMessage men uppåt så alla föräldrar till objektet tills root.

Ok låt oss betrakta vår StormTrooper har en funktion för att ta emot 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 } }}

låt oss nu överväga vår StormTrooper möter en befälhavare från imperiet:

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

som du kan se är principen densamma, jag behöver en hänvisning till målobjektet och sedan skickar jag ett meddelande till det. Precis som GetComponent är det bättre om du har Cachat objektreferensen.

det är också möjligt att lägga till en parameter för att se till att meddelandet hittar en mottagare eller inte.

Observera att det inte går att använda en funktion som returnerar ett värde. Du måste då gå tillbaka och använda GetComponent.

interaktiva funktioner

Unity använder många funktioner som gör att två eller flera objekt interagerar. Fördelen med dessa funktioner är att de lagrar mycket information om det andra objektet.

  1. Kollisionsfunktioner OnTriggerXXXX / OnCollisionXXXX
  2. Fysikfunktioner XXXXcast

Där går vi med kollisionsdeklaration:

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

funktionens samtal skapar en variabel av typen kollision eller kolliderare. Funktionen kommer att fylla denna instans med information om kollisionen och kommer att innehålla en hänvisning till det andra objektet vi interagerar med.

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

Observera att jag inte behöver leta efter det andra objektet eftersom colReference är av typen kollision och innehåller en hänvisning till det objektet som medlem. Nu kan jag utföra min GetComponent som vanligt och få tillgång till min folkhälsovariabel.

funktioner från Fysikklassen har en liknande princip. Uppgifterna lagras inte i en kollision / Collider-instans utan i en RaycastHit-instans:

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"); } } }}

Jag förklarar en variabel av typen RaycastHit, jag skickar den till funktionen. RaycastHit är en struktur och sedan en värdetyp, jag behöver ut nyckelordet för att skapa en referens till den strukturen så att funktionen kan ändra strukturen inom. Om du undrar över den processen, kolla in artikeln om minneshantering och avsnittet om värde/referenstyp.
vad jag gör här är att kontrollera hur platt är ytan jag placerar markören ovanpå. Detta är användbart för strategiska spel där du vill placera en byggnad men inte på en klippa.
Jag använder punktprodukten av terrängen normal med Vector3.Upp och om resultatet är tillräckligt nära 1 vilket innebär att de är i linje, marken är platt nog för konstruktion.
självklart behövs några fler beräkningar eftersom det bara är för en punkt och du skulle behöva kontrollera det normala men du får tanken. Jag skapade interaktion mellan min musposition och terrängen med en raycast.

en raycast innehåller en startpunkt och en riktning. I vårt fall är utgångspunkten omvandlingen av muspositionen till världspositionen och sedan en riktning baserad på kamerans rotation och projektionsmatris. Denna linje går oändligt i den definierade riktningen (du kan också ställa in ett maxavstånd) och om den träffar en kolliderare lagras informationen om den kollideraren och kollisionen i Raycasthitstrukturen. Strukturen innehåller sedan en hänvisning till träffobjektet.