Slik legger du til eget verktøy til enhetens redaktør

I denne opplæringen lærer du hvordan du utvider Unity3Ds redaktør, slik at du bedre kan bruke den i prosjektet. Du lærer å tegne din egen gizmo, lage og slette objekter i kode, lage redigeringsvinduer, bruk komponenter, og la brukeren å angre noen handlinger de tar med skriptet ditt.

Denne opplæringen forutsetter at du allerede vet grunnleggende om Unity-arbeidsflyt. Hvis du vet hvordan du lager objekter, prefabs, scener, beveger deg rundt i redigereren, legger til komponenter, så er det godt å gå!


Endelig resultatforhåndsvisning

La oss se på det endelige resultatet vi vil jobbe for:

Som du ser, skaper vi et redigeringsvindu, og en fargevalger som vi vil bruke til å tegne et rutenett. Vi vil også kunne lage og slette objekter, knipte til dette rutenettet og angre slike handlinger.


Trinn 1: Gizmos

Først lærer vi hvordan vi bruker gizmoer. Her er noen eksempler på innebygde gizmoer.

Dette er det du sannsynligvis vil se mest i Unity, siden det er tegnet for hvert objekt som har en Forvandle komponent festet til det - så i utgangspunktet vil hvert valgt objekt få denne gizmoen tegnet.

Her er en annen gizmo, som gjør at vi kan se størrelsen på BoxCollider festet til vårt spillobjekt.


Trinn 2: Opprett et grid-skript

Lag et C # -skript som vi kan bruke til å tegne vår egen gizmo for et objekt; Vi tegner et enkelt rutenett i redigeringen som et eksempel.

bruker UnityEngine; bruker System.Collections; offentlig klasse Grid: MonoBehaviour void Start ()  void Oppdatering () 

For et rutenett må vi legge til to variabler, bredden og høyden.

offentlig klasse Grid: MonoBehaviour public float width = 32.0f; offentlig flytehøyde = 32,0f; ugyldig start ()  ugyldig oppdatering () 

Å tegne i redaktøren må vi bruke OnDrawGizmos tilbakeringing, så la oss lage den.

offentlig klasse Grid: MonoBehaviour public float width = 32.0f; offentlig flytehøyde = 32,0f; ugyldig start ()  ugyldig oppdatering ()  ugyldig OnDrawGizmos () 

Trinn 3: Tegn rutenettet

For å tegne et rutenett trenger vi et sett med horisontale og vertikale linjer og plasseringen av redaktørens kamera, slik at vi vet på hvilket punkt vi skal tegne vårt rutenett. La oss først lagre kameraets posisjon til en egen variabel.

void OnDrawGizmos () Vector3 pos = Camera.current.transform.position; 

Som du kan se, kan vi få redaktørens kamera ved å bruke Camera.current henvisning.

Nå trenger vi to for looper som trekker de horisontale og vertikale linjene.

void OnDrawGizmos () Vector3 pos = Camera.current.transform.position; for (float y = pos.y - 800.0f; y < pos.y + 800.0f; y+= height)  Gizmos.DrawLine(new Vector3(-1000000.0f, Mathf.Floor(y/height) * height, 0.0f), new Vector3(1000000.0f, Mathf.Floor(y/height) * height, 0.0f));  for (float x = pos.x - 1200.0f; x < pos.x + 1200.0f; x+= width)  Gizmos.DrawLine(new Vector3(Mathf.Floor(x/width) * width, -1000000.0f, 0.0f), new Vector3(Mathf.Floor(x/width) * width, 1000000.0f, 0.0f));  

Å tegne linjer vi bruker Gizmos.DrawLine (). Legg merke til at dingser klassen har mange andre tegning API metoder, så det er mulig å tegne slike primitive som kube eller sfære eller til og med deres wireframes. Du kan også tegne et bilde hvis du trenger det.

Rutenettene skal være uendelig lange, men float.positiveInfinity og float.negativeInfinity syntes ikke å fungere bra med å tegne linjene, så vi kan ganske enkelt sette vilkårlige tall i stedet for dem. Også, antall linjer strenge avhenger av konstantene vi legger inn i til loops 'definisjoner; Teknisk bør vi ikke forlate disse konstantene, men det er bare en testkode.

For å se rutenettet, opprett en tom objekt og legg til vårt script til det:


Trinn 4: Lag en tilpasset inspektør

Den neste tingen å dekke er å tilpasse inspektøren. For å gjøre det må vi opprette et redigeringsskript. Opprett en ny C # -fil og gi den navnet GridEditor. Dette skriptet skal plasseres i Redaktør mappe; hvis du ikke har en, så opprett den nå.

bruker UnityEngine; bruker UnityEditor; bruker System.Collections; [CustomEditor (typeof (Grid)) offentlig klasse GridEditor: Editor 

Denne gangen må vi også bruke UnityEditor for å kunne benytte redaktørklassene og -funksjonene. Å overstyre standardinspektøren til vår Nett objekt må vi legge til et attributt før klassedeklarasjonen, [CustomEditor (typeof (Grid))] La Unity vite at vi vil tilpasse Nettinspektør. For å kunne bruke redigeringsanropene, må vi hente fra Redaktør klasse i stedet for MonoBehaviour.

For å endre gjeldende inspektør må vi overstyre den gamle.

offentlig klasse GridEditor: Editor public override void OnInspectorGUI () 

Hvis du kontrollerer gridobjektets inspektør i redigeringsprogrammet nå, vil det være tomt selv om objektet selv har noen offentlige medlemmer. Det er fordi ved å overstyre OnInspectorGUI () Vi kastet standard inspektøren for å lage en egendefinert en i stedet.


Trinn 5: Bruk GUILayout til å fylle den tilpassede inspektøren

Før vi lager noen felt, må vi få en referanse til objektet som inspektøren søker på. Vi har faktisk referansen allerede - den heter mål - men for enkelhets skyld vil vi opprette en referanse til Nett komponent av objektet. Først, la oss erklære det.

offentlig klasse GridEditor: Editor Grid grid;

Vi bør tildele det inn OnEnable () funksjon som kalles så snart inspektøren er aktivert.

offentlig klasse GridEditor: Editor Grid grid; Offentlig tomt OnEnable () grid = (Grid) mål; 

La oss lage noen inspektørfelter nå. Vi bruker GUILayout og EditorGUILayout klasser for det.

offentlig overstyring ugyldig OnInspectorGUI () GUILayout.Begin Horizontal (); GUILayout.Label ("Grid Width"); grid.width = EditorGUILayout.FloatField (grid.width, GUILayout.Width (50)); GUILayout.EndHorizontal (); 

Den første linjen, GUILayout.BeginHorizontal (); indikerer at vi vil plassere følgende inspektørelementer ved siden av hverandre, fra venstre til høyre. Som du kan tenke deg, den siste linjen, GUILayout.EndHorizontal (); indikerer at vi ikke lenger vil gjøre det. De faktiske elementene er mellom disse to linjene. Den første er en enkel etikett (i vårt tilfelle vil det bli vist Rutenettbredde tekst), og deretter ved siden av det oppretter vi en EditorGUILayout.FloatField som er som du kan forestille deg et flytefelt. Legg merke til at vi tilordner grid.width til verdien av det FloatField, og flytfeltet selv viser verdien av grid.width. Vi setter også bredden til 50 piksler.

La oss se om feltet er lagt til inspektøren:


Trinn 6: Fyll inspektøren og gjenta scenen

La oss nå legge til en ting til inspektøren; denne gangen vil det bli grid.height.

offentlig overstyring ugyldig OnInspectorGUI () GUILayout.Begin Horizontal (); GUILayout.Label ("Grid Width"); grid.width = EditorGUILayout.FloatField (grid.width, GUILayout.Width (50)); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Grid Height"); grid.height = EditorGUILayout.FloatField (grid.height, GUILayout.Width (50)); GUILayout.EndHorizontal (); 

Det ville være alt for våre gridobjektfelt, hvis du vil vite om andre felt og elementer som du kan bruke i inspektøren, kan du besøke Unity-referansessidene på EditorGUILayout og GUILayout.

Vær oppmerksom på at endringene vi lager i vår nye inspektør, bare er synlige etter at vi har valgt scenevinduet. For å gjøre dem synlige når de er laget, kan vi ringe SceneView.RepaintAll ().

offentlig overstyring ugyldig OnInspectorGUI () GUILayout.Begin Horizontal (); GUILayout.Label ("Grid Width"); grid.width = EditorGUILayout.FloatField (grid.width, GUILayout.Width (50)); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Grid Height"); grid.height = EditorGUILayout.FloatField (grid.height, GUILayout.Width (50)); GUILayout.EndHorizontal (); SceneView.RepaintAll (); 

Nå trenger vi ikke å klikke utenfor inspektøren for å se resultatene av endringene.


Trinn 7: Håndter Editor-inngangen

La oss nå prøve å håndtere redaktørens innspill, akkurat som vi ville gjøre det i spillet. Eventuelle nøkkel- eller musestater skal være tilgjengelige for oss. For å ha denne funksjonaliteten må vi legge til en onSceneGUIDelegate tilbakeringing til vår SceneView. La oss ringe vår oppdateringsfunksjon GridUpdate ().

Offentlig tomt OnEnable () grid = (Grid) mål; SceneView.onSceneGUIDelegate = GridUpdate;  ugyldig GridUpdate (SceneView sceneview) 

Nå trenger vi bare å få innspillet Begivenhet.

void GridUpdate (SceneView sceneview) Event e = Event.current; 

Trinn 8: Lag en prefab

For videre å spille med redigeringsskriptene trenger vi et spillobjekt som vi vil kunne bruke. La oss lage en enkel kube og lage en prefab ut av det.

Du kan matche størrelsen på rutenettet til kuben eller omvendt og justere den med et rutenett.

Som du ser, ser du i hierarkiet cube teksten er farget i blått; Dette betyr at den er koblet til en prefab. Du kan se det prefab i prosjektvinduet.


Trinn 9: Opprett et objekt fra Editor Script

Nå lager vi et objekt fra redigeringsskriptet. La oss gå tilbake til vår GridEditor.cs og utvide GridUpdate () funksjon.

La oss lage objektet når nøkkelen en er trykket.

void GridUpdate (SceneView sceneview) Event e = Event.current; hvis (e.isKey && e.character == 'a') GameObject obj; 

Som du kan se, kontrollerer vi bare om hendelsen er en nøkkeltallendring, og om tegnet som ble presset, er 'en'. Vi lager også en referanse for vårt nye objekt. La oss nå ordne det.

void GridUpdate (SceneView sceneview) Event e = Event.current; hvis (e.isKey && e.character == 'a') GameObject obj; hvis (Selection.activeObject) obj = (GameObject) Instantiate (Selection.activeObject); obj.transform.position = ny Vector3 (0,0f, 0,0f, 0,0f); 

Selection.activeObject er en referanse til det for øyeblikket valgte objektet i redigeringsprogrammet. Hvis et objekt er valgt, kloner vi bare klonen og endrer klonens posisjon til (0,0, 0,0, 0,0).

La oss teste om det virker. Du må være oppmerksom på en ting: vår GridUpdate () slutter å fungere når eiendelene reimporteres / oppdateres, og for å aktivere det, må du velge objektet (for eksempel fra hierarkisvisningen) som redigeringsskriptet refererer til - i vårt eksempel er det Nett gjenstand. Du må også huske at inngangshendelsene vil bli fanget bare hvis scenevisningen er valgt.


Trinn 10: Instantiate a Prefab fra Editor Script

Selv om vi klarte å klone objektet, er det klonte objektets link til prefaben ikke-eksisterende.

Som du kan se, er Terning (klon) Navnet vises med vanlig svart skrift og det betyr at den ikke er koblet til prefab som den originale kuben er. Hvis vi skulle kopiere den opprinnelige kuben manuelt i redigereren, ville den klonede kuben være knyttet til Cube ferdighus. For å få det til å fungere på denne måten må vi bruke InstantiatePrefab () funksjon fra EditorUtility klasse.

Før vi bruker denne funksjonen må vi få det valgte objektets prefab. For å gjøre det må vi bruke GetPrefabParent () som også tilhører EditorUtility klasse.

void GridUpdate (SceneView sceneview) Event e = Event.current; hvis (e.isKey && e.character == 'a') GameObject obj; Objekt prefab = EditorUtility.GetPrefabParent (Selection.activeObject); hvis (prefab) 

Vi kan også slutte å sjekke om Selection.activeObject eksisterer, fordi hvis det ikke gjør det da ferdighus vil være lik null, og derfor kan vi komme seg bort med bare å sjekke ferdighus henvisning.

La oss nå instansere vår prefab og sette sin posisjon.

void GridUpdate (SceneView sceneview) Event e = Event.current; hvis (e.isKey && e.character == 'a') GameObject obj; Objekt prefab = EditorUtility.GetPrefabParent (Selection.activeObject); hvis (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); obj.transform.position = ny Vector3 (0,0f, 0,0f, 0,0f); 

Og det er det - la oss sjekke om den klonede kuben er koblet til prefaben nå.


Trinn 11: Oversett Screen Mouse Coords til World Coords

De Begivenhet klassen gir oss ikke beskjed om hvor musen er i verdensrommet, det gir bare skjermplassens musekoordinater. Slik konverterer vi dem slik at vi kan få en tilnærmet verdensrommet-museposisjon.

void GridUpdate (SceneView sceneview) Event e = Event.current; Ray r = Camera.current.ScreenPointToRay (ny Vector3 (e.mousePosition.x, -e.mousePosition.y + Camera.current.pixelHeight)); Vector3 mousePos = r.origin;

Først bruker vi redaktørens kamera ScreenPointToRay for å få strålen fra skjermkoordinatene, men dessverre før det må vi oversette arrangementets skjermrom til et område som er akseptabelt for ScreenPointToRay ().

e.mousePosition holder museposisjonen i et koordinert mellomrom hvor øverste venstre hjørne er (0, 0) punkt og nederste høyre hjørne er lik (Camera.current.pixelWidth, -Camera.current.pixelHeight). Vi må oversette det til det rommet hvor bunn venstre hjørne er (0, 0) og øverst til høyre er (Camera.current.pixelWidth, Camera.current.pixelHeight), som er ganske enkelt.

Den neste tingen vi skal gjøre er å lagre strålingens opprinnelse til vår mousePos vektor, så det er lett tilgjengelig.

Nå kan vi tildele klonens posisjon til hvor musen er.

hvis (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); obj.transform.position = ny Vector3 (mousePos.x, mousePos.y, 0.0f); 

Vær oppmerksom på at når kameraet er satt veldig flatt, er tilnærmingen av museposisjonen på en av aksene virkelig veldig dårlig, det er derfor jeg satte z klonens posisjon manuelt. Nå skal kubene opprettes hvor musen er.


Trinn 12: Juster kubene til rutenettet

Siden vi har satt opp vårt nett, ville det være synd å ikke bruke det. la oss bruke museposisjonen til å justere de opprettede kubene til rutenettet.

hvis (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); Vector3 aligned = ny Vector3 (Mathf.Floor (mousePos.x / grid.width) * grid.width + grid.width / 2.0f, Mathf.Floor (mousePos.y / grid.height) * grid.height + grid.height / 2,0f, 0,0f); obj.transform.position = justert; 

Ta en titt på resultatet:


Trinn 13: Ødelegg et objekt fra Editor Script

I dette trinnet sletter vi objekter programmatisk i redigeringsprogrammet. Vi kan gjøre det ved å bruke DestroyImmediate (). I dette eksemplet la vi gjøre større bruk av utvalg klasse og slett alle de valgte objektene når 'd'-tasten er trykket.

hvis (e.isKey && e.character == 'a') GameObject obj; Objekt prefab = EditorUtility.GetPrefabParent (Selection.activeObject); hvis (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); Vector3 aligned = ny Vector3 (Mathf.Floor (mousePos.x / grid.width) * grid.width + grid.width / 2.0f, Mathf.Floor (mousePos.y / grid.height) * grid.height + grid.height / 2,0f, 0,0f); obj.transform.position = justert;  annet hvis (e.isKey && e.character == 'd') foreach (GameObject obj i Selection.gameObjects) DestroyImmediate (obj); 

Når "d'-tasten er trykket, løper vi gjennom alle de valgte objektene og sletter hver enkelt av dem. Selvfølgelig kunne vi også trykke Slett tast inn redigereren for å slette disse objektene, men da ville disse ikke bli slettet av vårt skript. Test det i redaktøren.


Trinn 14: Koble til Object Instantiation

I dette trinnet vil vi gjøre bruk av angre klassen, som i utgangspunktet lar oss angre hver handling som redigeringsskriptet vårt gjør. La oss begynne med å angre opprettelsen av objektet.

For å kunne ødelegge et objekt som vi opprettet i redaktør, må vi ringe Undo.RegisterCreatedObjectUndo (). Det tar to argumenter: Den første er objektet som er opprettet, og det andre er navnet på å angre. Navnet på handlingen som vil bli slettet, vises alltid under Rediger-> Angre Navn.

hvis (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); Vector3 aligned = ny Vector3 (Mathf.Floor (mousePos.x / grid.width) * grid.width + grid.width / 2.0f, Mathf.Floor (mousePos.y / grid.height) * grid.height + grid.height / 2,0f, 0,0f); obj.transform.position = justert; Angre.RegisterCreatedObjectUndo (obj, "Create" + obj.name); 

Hvis du lager noen kubinger ved hjelp av en nøkkel og prøv å angre nå, vil du legge merke til at alle de opprettede kubene er slettet. Det er fordi alle disse skapte terningene gikk inn i en enkelt angrekkehendelse.


Trinn 15: Løsne enkelttilordning

Hvis vi ønsker å plassere hvert opprettet objekt på en annen angrekkehendelse og gjøre det mulig å angre å lage dem en etter en, må vi bruke Undo.IncrementCurrentEventIndex ().

hvis (prefab) Undo.IncrementCurrentEventIndex (); obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); Vector3 aligned = ny Vector3 (Mathf.Floor (mousePos.x / grid.width) * grid.width + grid.width / 2.0f, Mathf.Floor (mousePos.y / grid.height) * grid.height + grid.height / 2,0f, 0,0f); obj.transform.position = justert; Angre.RegisterCreatedObjectUndo (obj, "Create" + obj.name); 

Hvis du tester skriptet nå, ser du at kubene slettes en etter en ved å angre skapelsen.


Trinn 16: Løs på objekt sletting

For å angre objektet sletting må vi bruke Undo.RegisterSceneUndo (). Det er en veldig treg funksjon som i hovedsak sparer scenen, slik at vi senere kan gå tilbake til den aktuelle tilstanden ved å utføre en angrepsaktion. Dessverre synes det å være den eneste måten for nå å få de slettede objektene tilbake på scenen.

ellers hvis (e.isKey && e.character == 'd') Undo.IncrementCurrentEventIndex (); Angre.RegisterSceneUndo ("Slett utvalgte objekter"); foreach (GameObject obj i Selection.gameObjects) DestroyImmediate (obj); 

Undo.RegisterSceneUndo () tar bare ett argument, og det er å angre navnet på. Etter å ha slettet et par kuber med d nøkkel du kan angre den slettingen.


Trinn 17: Opprett et redigeringsvindueskript

Lag et nytt skript, og la oss få dette til å strekke seg ut EditorWindow i stedet for Redaktør. La oss nevne det GridWindow.cs.

bruker UnityEngine; bruker UnityEditor; bruker System.Collections; offentlig klasse GridWindow: EditorWindow public void Init () 

La oss lage en referanse til vår Nett objekt slik at vi kan få tilgang til det fra vinduet.

offentlig klasse GridWindow: EditorWindow Grid grid; offentlig tomrom Init () grid = (Grid) FindObjectOfType (typeof (Grid)); 

Nå må vi lage vinduet, det kan vi gjøre fra vår GridEditor manus.


Trinn 18: Opprett GridWindow

I vår OnInspectorGUI () la oss legge til en knapp som vil opprette GridWindow.

offentlig overstyring ugyldig OnInspectorGUI () GUILayout.Begin Horizontal (); GUILayout.Label ("Grid Width"); grid.width = EditorGUILayout.FloatField (grid.width, GUILayout.Width (50)); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Grid Height"); grid.height = EditorGUILayout.FloatField (grid.height, GUILayout.Width (50)); GUILayout.EndHorizontal (); hvis (GUILayout.Button ("Open Grid Window", GUILayout.Width (255))) GridWindow window = (GridWindow) EditorWindow.GetWindow (typeof (GridWindow)); window.Init ();  SceneView.RepaintAll (); 

Vi bruker GUILayout For å opprette en knapp angir vi også knappens navn og bredde. De GUILayout.Button avkastning ekte når du trykker på knappen, hvis det er tilfelle, åpner vi vår GridWindow.

Du kan gå tilbake til redaktøren og trykke på knappen i vår Nett objekt inspektør.

Når du har gjort det, GridWindow skal dukke opp.


Trinn 19: Opprett et fargefelt i GridWindow

Før vi redigerer noe fra vinduet, la vi legge til et fargefelt i vår Nett klasse, så vi kan redigere det senere.

offentlig klasse Grid: MonoBehaviour public float width = 32.0f; offentlig flytehøyde = 32,0f; offentlig Fargefarge = Color.white;

Tilordne nå Gizmos.color i OnDrawGizmos () funksjon.

void OnDrawGizmos () Vector3 pos = Camera.current.transform.position; Gizmos.color = color;

Og nå, la oss gå tilbake til GridWindow skript og opprett et fargefelt der, slik at vi kan velge fargen i vinduet. Vi kan gjøre det i OnGUI () Ring tilbake.

offentlig klasse GridWindow: EditorWindow Grid grid; offentlig tomrom Init () grid = (Grid) FindObjectOfType (typeof (Grid));  ugyldig OnGUI () grid.color = EditorGUILayout.ColorField (grid.color, GUILayout.Width (200)); 

Ok, nå kan du sjekke om alt fungerer riktig i redaktøren:


Trinn 20: Legg til en delegat

Akkurat nå setter vi en delegat for å få innspillingshendelsene fra scenevisningen vi bruker = tegn, som ikke er en god metode for å gjøre det fordi det tilsidesetter alle de andre tilbakekallingene. Vi burde bruke += tegn i stedet. La oss gå til vår GridEditor.cs skript og gjør det forandring.

Offentlig tomt OnEnable () grid = (Grid) mål; SceneView.onSceneGUIDelegate + = GridUpdate; 

Vi må også lage en OnDisable () tilbakeringing for å fjerne vår GridUpdate (), hvis vi ikke gjør det da vil den stables opp og bli kalt flere ganger samtidig.

Offentlig tomt OnEnable () grid = (Grid) mål; SceneView.onSceneGUIDelegate + = GridUpdate;  Offentlig tomgang OnDisable () SceneView.onSceneGUIDelegate - = GridUpdate; 

Konklusjon

Det er det for introduksjonen til editor scripting. Hvis du ønsker å utvide din knowladge, er det mye å lese om emnet i Unity Script Reference - du vil kanskje sjekke ressurser, AssetDatabase eller FileUtil klasser avhengig av dine behov.

Dessverre er noen klasser fortsatt uokumenterte og på grunn av det, tilbøyelige til å endres uten å arbeide. For eksempel SceneView klasse og dets funksjoner eller Undo.IncrementCurrentEventIndex () funksjon fra angre klasse. Hvis dokumentasjonen ikke gir svarene du søker, kan du prøve å søke gjennom UnityAnswers eller Unity Forum.

Takk for din tid!