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å!
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.
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.
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 ()
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:
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 Nett
inspektø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.
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:
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.
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;
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.
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.
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å.
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.
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:
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.
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.
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.
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.
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.
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.
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:
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;
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!