Articles

GetComponent in C#

I denne tutorial

  • adgang til et andet script inde fra objektet
  • Hvordan fungerer GetComponent?
  • få mange komponenter
  • interaktion mellem objekter.
  • interaktion mellem objekter i hierarkiet
  • SendMessage og BroadcastMessage
  • interaktive funktioner

introduktion

et tilbagevendende problem, når du starter med Unity, er, hvordan du får adgang til medlemmerne i et script fra et andet script. Mange ville tro, at dereferencing fra scriptnavnet ville være nok, de indser hurtigt, at det ikke er det.

Når du udvikler et program, gemmes variabler i hukommelsen på forskellige steder. Hvis et objekt kunne se andre objektmedlemmer, ville der være en risiko for at ændre dem, selvom det ikke var beregnet. Hvis to objekter i tilfælde har det samme script med de samme variabelnavne, ville kompilatoren ikke være i stand til at finde ud af, hvilken vi henviser til.

for at forhindre dette problem kan hvert objekt i hukommelsen ikke se andre objekter. Det er derefter nødvendigt at fortælle et objekt, hvor den variabel, det har brug for, er i hukommelsen. Hele pointer-princippet er blevet droppet for længe siden, og nu bruger vi reference i stedet (som faktisk bruger pointer internt).

der er forskellige måder at få adgang til variabler på, nogle er bedre end andre, og nogle skal simpelthen bruges i bestemte situationer.

  1. GetComponent, den mest almindelige, også den der forvirrer mest i starten
  2. SendMessage, det kan se lettere ud at forstå, men det er også mindre effektivt.
  3. statiske variabler, den nemmeste, men også den mest komplicerede at forstå fuldt ud i starten.

opsætning af en scene

Opret en ny scene, tilføj et tomt spilobjekter og navngiv det “StormTrooper”. Opret To scripts med navnet “sundhed”og ” kraft”. Tilføj begge scripts til StormTrooper.

Åbn begge scripts i editoren, og tilføj disse linjer:

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

forsøg på denne kode vil simpelthen returnere fejl “en objektreference er påkrævet for at få adgang til ikke-statiske medlemmer”.

Vi skal fortælle Sundhedskomponenten på StormTrooper, hvor er Kraftkomponenten, så den kan få adgang til sine medlemmer. Da vores scripts begge er på det samme objekt, skal vi simpelthen erklære en variabel af typen af scriptet, vi ønsker at få adgang til og bruge GetComponent til at finde scriptet.

adgang til et andet script inde i objektet

Rediger sundhed 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 ændringerne skal du køre spillet, trykke på Mellemrum og derefter trykke på P, din StormTrooper har nu 10 af sundhed (Bemærk, at en StormTrooper ikke har kraften, dette er kun for eksemplet). Du får adgang til variabler på samme måde, som du fik adgang til metoder. Bare sørg for, at de medlemmer, du prøver at nå, erklæres offentlige, ellers får du en fejl.

hvis medlemmerne var private, ville det ikke være muligt at få adgang til dem. Alt, hvad der ikke skal nås uden for klassen/scriptet, skal være privat for at klare indkapslingsprincippet.

Hvordan fungerer GetComponent?

først erklærer vi et objekt af den type script, vi vil nå.

private Force forceScript = null;

Force er vores type, og forceScript er bare et navn, vi giver til brug af referencen i scriptet. Vi kunne give det noget navn.

GetComponent-funktionen vil se inde i objektet (StormTrooper-spilobjektet) for at finde en komponent svarende til den type, vi passerede. Hvis ingen findes, returneres en null-reference, og vores forcescript-variabel vil ikke gøre noget. Hvis der findes en komponent af type Force, sendes adressen til den pågældende komponent til variablen forceScript, variablen peger nu på Kraftkomponenten, der er gemt et eller andet sted i hukommelsen. For at få adgang til de offentlige medlemmer af Force, skal vi bare dereference scriptvariablen ved hjælp af dot-operatøren. Det siges, at forceScript-variablen er et håndtag til objektet. Det giver adgang til alle offentlige medlemmer (og interne).

script.PublicMembers;

Private og beskyttede medlemmer forbliver utilgængelige.

brug af attributten Seriefelt gør det muligt at se variablerne i inspektør despute være private. Din forcescript variabel starter med værdi af ingen (Kraft) og bliver til en Kraftreference. Hvis du klikker på det, vil det fremhæve målobjektet.

reference_component

så jeg kan cache komponent med GetComponent?

Ja, og det anbefales at gøre det for nogle regelmæssigt anvendte komponenter.

Når du får adgang til indbyggede komponenter som Rigidbody, Transform, Renderer,…osv., bruges Unity til at tillade “direkte” adgang som denne:

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

de fjernede dem alle undtagen transformationen, da der altid er en transformation på et givet spilobjekt.

GetComponent bør undgås inde opdatering, så caching anbefales med et par enkle linjer kode 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);}

dette vil gemme nogle beregninger.

få mange komponenter

Hvad hvis vores StormTrooper har mange kræfter i sig selv (ikke sandsynligt dog). Dupliker styrken to gange, så StormTrooper har tre af dem.

sundhed bliver:

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 spillet, Tryk på Mellemrum, vores StormTrooper bliver en Jedi. GetComponents søger efter alle komponenter af typen og returnerer en række af dem. Dette er fint, hvis du ikke målretter mod en bestemt komponent af typen, men mere sandsynligt dem alle.

se følgende ville tænde og slukke lysene:

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 mellem objekter.

Vi kan også få to forskellige objekter til at interagere på en lignende måde. Opret et nyt objekt med navnet”Sith”. Fjern de duplikerede komponenter fra StormTrooper, så kun en er tilbage.

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

Rediger stormtrooperens kraftskript, så han ikke har kraften (Sith ville finde dette mistænkeligt…), jeg giver dig en anelse om, at det sker i startskriptet.

nedenfor er den linje, der finder StormTrooper og får sin kraftkomponent.

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

GameObject.Find () finder først objektet bestået som parameter og ser derefter efter den nødvendige komponent inde i det. Det anbefales at udføre disse handlinger i starten eller på et tidspunkt af spillet, hvor lags ikke vil påvirke gameplayet. GameObject.Find og GetComponent er dyre funktioner, da de har brug for at gentage gennem hele hierarkiet. Hvis din scene kun indeholder 10 objekter, vil det være fint, men hvis det rummer tusindvis af dem (en hær af klon), vil du helt sikkert se en dårlig effekt. Overvej at kalde disse i Startfunktionen. Bemærk, at hvis din scene indeholder mange stormtroopere, kan Uity ikke vide, hvilken du mente at bruge, og vil returnere den første, den finder. Mest sandsynligt har du en henvisning til det objekt, du vil interagere med, for eksempel en tilbagekald af kollision.

interaktion mellem objekter i hierarkiet

overvej et køretøj,, når du kører det, er tegnet knyttet til det som et barnobjekt. Køretøjet ville have særlig funktion, hvis føreren har den kraft (bedre håndtering, hurtigere og så videre). Da føreren er vedhæftet, er han under i hierarkiet.
køretøjet kan finde Kraftkomponenten i føreren med det samme princip, som vi tidligere brugte (GameObject.Finde) men vi kan få det til at ske hurtigere ved at indikere for køretøjet, at føreren er i sit eget hierarki.

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

GameObject byttes til transformation. Faktisk hører et barnobjekt til forældrenes transformation, så vi er nødt til at undersøge transformationen for at finde vores objekt. I eksemplet er objektet lige under det kaldende objekt, hvis din Sith er inde i cockpitobjektet under bilobjektet, skal du angive stien:

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

det kaldende objekt er bilen og omtales som dette i opkaldet. Derefter søges Cockpit, og Sith søges. Hvis stien er forkert, Find returnerer null og du får en Null Reference undtagelse. Du kan undgå det sådan:

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 returnerer en transformation, mens GameObject.Find returnerer et GameObject.

endnu bedre kan vi bruge:

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

nu kan vores køretøj vide, at vores Sith har kraften og anvende speciel funktion.
et problem at vide om, hvis det overordnede objekt indeholder et script af den type, du leder efter, vil det returnere det. Så tænker du har at gøre med barnekomponenten, du har faktisk at gøre med den forælder.

i dette eksempel skal vi kontrollere for hver slags driver, hvis det er en StormTrooper eller en Sith. En bedre tilgang ville have været at bruge arv, så den øverste basisklasse ville holde kraftvariablen, og en enkelt funktion ville blive brugt til alle typer drivere. Men arv og effektivitet var ikke vores vigtigste problem her.

SendMessage og BroadcastMessage

SendMessage og BroadcastMeassage er to andre muligheder, der kan være enklere, men også dyrere. Princippet forbliver det samme, vi har en henvisning til et objekt, og vi vil ændre et script, det indeholder.

Med SendMessage kan du ringe til en funktion på et objekt uden at skulle finde en henvisning til scriptet. Nu fortæller du mig: “Hvad er al denne forvirringstur, hvis du kan gøre det enkelt?”

SendMessage bruger refleksion og er ekstremt langsom sammenlignet med GetComponent og bør kun overvejes i tilfælde, hvor det ikke vil påvirke spillet, eller hvis du ikke har nogen anden løsning.

og det skal lyde logisk, med GetComponent angiver vi præcis, hvor scriptet er placeret i hukommelsen. Med SendMessage kender kompilatoren objektet, men det vil gennemgå alle scripts og funktioner, indtil det finder den heldige vinder. Dette kan påvirke dit spil, hvis du bruger det ofte på flere objekter, og hvis Objektet har mange komponenter og scripts (som forresten er komponenter). SendMessage søger kun det objekt, som opkaldet foretages til, BroadcastMessage lancerer en søgning på målobjektet, men også på alle dets børn. Forskningen vil være endnu længere, hvis objektet indeholder et stort hierarki. En nykommer dukkede op med Unity4 Sendmessageupad som du burde have gættet gør det samme som BroadcastMessage men opad så alle forældrene til objektet indtil root.

Ok lad os overveje, at vores StormTrooper har en funktion til modtagelse af ordre

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

lad os nu overveje, at vores StormTrooper møder en kommandør fra Imperiet:

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

som du kan se, forbliver princippet det samme, jeg har brug for en henvisning til målobjektet, og så sender jeg en besked til den. Ligesom GetComponent er det bedre, hvis du har cachelagret objektreferencen.

det er også muligt at tilføje en parameter for at sikre, at meddelelsen finder en modtager eller ej.

Bemærk, at det ikke er muligt at bruge en funktion, der returnerer en værdi. Du bliver derefter nødt til at gå op igen og bruge GetComponent.

interaktive funktioner

Unity bruger mange funktioner, der får to eller flere objekter til at interagere. Fordelen ved disse funktioner er, at de gemmer en masse information om det andet objekt.

  1. Kollisionsfunktioner Ontrigger/Oncollision
  2. Fysikfunktioner Cast

der går vi med kollisionserklæring:

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

funktionens opkald skaber en variabel af typen kollision eller Collider. Funktionen udfylder denne forekomst med oplysninger om kollisionen og indeholder en henvisning til det andet objekt, vi interagerer med.

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

Bemærk, at jeg ikke behøver at kigge efter det andet objekt, da colReference er af typen kollision og indeholder en henvisning til det objekt som medlem. Nu Kan jeg udføre min GetComponent som sædvanlig og få adgang til min folkesundhedsvariabel.

funktioner fra Fysikklassen har et lignende princip. Dataene gemmes ikke i en kollision / Collider-forekomst, men i en RaycastHit-forekomst:

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

Jeg erklærer en variabel af typen RaycastHit, jeg sender den til funktionen. RaycastHit er en struktur og derefter en værditype, jeg har brug for out-Nøgleordet for at oprette en henvisning til den struktur, så funktionen kan ændre strukturen indeni. Hvis du undrer dig over denne proces, skal du tjekke Hukommelsesstyringsartiklen og afsnittet om værdi/referencetype.
hvad jeg laver her er at kontrollere, hvor flad er overfladen Jeg placerer markøren oven på. Dette er nyttigt til strategiske spil, hvor du vil placere en bygning, men ikke på en klippe.
Jeg bruger prikproduktet af terrænet normalt med Vektoren3.Op, og hvis resultatet er tæt nok på 1, hvilket betyder, at de er justeret, jorden er flad nok til konstruktion.
det er klart, at der er behov for nogle flere beregninger, da dette kun er for et punkt, og du bliver nødt til at kontrollere det normale, men du får ideen. Jeg skabte interaktion mellem min museposition og terrænet ved hjælp af en raycast.

en raycast indeholder et udgangspunkt og en retning. I vores tilfælde er udgangspunktet konverteringen af musepositionen til verdensposition og derefter en retning baseret på kamerarotation og projektionsmatrice. Denne linje går uendeligt i den definerede retning (du kan også indstille en maksimal afstand), og hvis den rammer en collider, gemmes oplysningerne om den collider og kollision i RaycastHit-strukturen. Strukturen indeholder derefter en henvisning til hitobjektet.