Programim dhe zhvillim, javascript, python, php, html

Si të krijoni një menu rënëse të inspektorit Unity të ngjashme me enum nga një grup vargjesh me C#?

Unë jam duke bërë një skript Unity C# që është menduar të përdoret nga njerëz të tjerë si një mjet dialogu i karaktereve për të shkruar bisedat midis shumë personazheve të lojës.

Unë kam një klasë DialogueElement dhe më pas krijoj një listë me objekte DialogueElement. Çdo objekt përfaqëson një linjë dialogu.

[System.Serializable] //needed to make ScriptableObject out of this class
public class DialogueElement
{
    public enum Characters {CharacterA, CharacterB};
    public Characters Character; //Which characters is saying the line of dialog
    public string DialogueText; //What the character is saying
}
public class Dialogue : ScriptableObject
{
    public string[] CharactersList; //defined by the user in the Unity inspector
    public List<DialogueElement> DialogueItems; //each element represents a line of dialogue
}

Unë dua që përdoruesi të jetë në gjendje të përdorë mjetin e dialogut vetëm duke ndërvepruar me inspektorin Unity (pra pa kod redaktimi). Problemi me këtë konfigurim është se përdoruesi i mjetit të dialogut nuk mund të specifikojë emrat e tij të personalizuar (si Felix ose Wendy) për personazhet në enumin Characters pasi ato janë të koduara si "CharacterA" dhe "CharacterB" në klasën DialogueElement .

Për ata që nuk e njohin Unity, është një program për krijimin e lojërave. Unity i lejon përdoruesit të krijojnë skedarë fizikë (të njohur si objekte të skriptueshme) që veprojnë si kontejnerë për objektet e klasës. Variablat publike të objektit të skriptueshëm mund të përcaktohen përmes një ndërfaqeje vizuale të quajtur "inspektor" siç mund ta shihni më poshtë:
Inspektori i unitetit

Unë dua të përdor një enum për të specifikuar se cilët karaktere janë ata që thonë vijën e dialogut, sepse përdorimi i një enum krijon një menu të këndshme rënëse në inspektor ku përdoruesi mund të zgjedhë me lehtësi karakterin pa pasur nevojë të shkruajë manualisht emrin e karakterit. për çdo rresht të dialogut.

Si mund ta lejoj përdoruesin të përcaktojë elementet e Characters enum? Në këtë rast po përpiqesha të përdorja një variabël të vargut ku lojtari mund të shkruajë emrin e të gjithë karaktereve të mundshme dhe më pas ta përdorë atë grup për të përcaktuar numrin.

Nuk e di nëse është e mundur zgjidhja e problemit në këtë mënyrë. Unë jam i hapur për ÇDO ide që do t'i lejojë përdoruesit të specifikojë një listë emrash që më pas mund të përdoren për të krijuar një menu drop-down në inspektor ku përdoruesi zgjedh një nga emrat siç shihet në imazhin e mësipërm.

Zgjidhja nuk ka nevojë të deklarojë në mënyrë specifike një numër të ri nga një grup vargjesh. Unë thjesht dua të gjej një mënyrë për ta bërë këtë të funksionojë. Një zgjidhje që mendova është të shkruaj një skript të veçantë që do të modifikonte tekstin e skriptit C# që përmban numërimin e karaktereve. Unë mendoj se kjo teknikisht do të funksiononte pasi Unity ripërpilon automatikisht skriptet sa herë që zbulon se ato janë ndryshuar dhe përditëson objektet e skriptueshme në inspektor, por unë shpresoja të gjeja një mënyrë më të pastër.

Lidhja me depon për referencë:
https://github.com/guitarjorge24/DialogueTool


  • Ju nuk mund ta ndryshoni vetë enum pasi duhet të kompilohet (epo nuk është plotësisht e pamundur, por nuk do të rekomandoja të shkoni në mënyra si ndryshimi aktiv i një skripti dhe detyrimi për një ripërpilim) ;) Çfarë mund të bëni megjithatë bëhu një redaktues i personalizuar dhe përdor diçka si EditorGUILayout.Popup të cilën mund ta ushqeni me një string një sërë opsionesh të disponueshme (përfshirë ato të përcaktuara nga përdoruesi), ose meqenëse tashmë keni një listë karakteresh thjesht përdorni indekset e tyre 26.03.2020
  • A mund të shtoni llojin Characters? 26.03.2020

Përgjigjet:


1

Ju nuk mund ta ndryshoni vetë enumin pasi duhet të përpilohet (epo nuk është plotësisht e pamundur, por nuk do të rekomandoja të shkoni në mënyra si të ndryshoni në mënyrë aktive një skript dhe të detyroni një ri-përpilim)


Pa parë llojet e tjera që ju nevojiten është pak e vështirë, por atë që dëshironi, do ta bëni më mirë në një skript të personalizuar redaktues duke përdorur EditorGUILayout.Popup. Siç u tha, nuk i di nevojat tuaja të sakta dhe llojin Characters ose se si saktësisht i referoni ato, kështu që tani për tani unë do të supozoj se ju referoni DialogueElement tuaj në një karakter të caktuar nëpërmjet indeksit të tij në listën Dialogue.CharactersList. Kjo në thelb funksionon si një enum atëherë!

Meqenëse këto skripte redaktuese mund të bëhen mjaft komplekse, përpiqem të komentoj çdo hap:

    using System;
    using System.Collections.Generic;
    using System.Linq;
#if UNITY_EDITOR
    using UnityEditor;
    using UnityEditorInternal;
#endif
    using UnityEngine;

    [CreateAssetMenu]
    public class Dialogue : ScriptableObject
    {
        public string[] CharactersList;
        public List<DialogueElement> DialogueItems;
    }

    [Serializable] //needed to make ScriptableObject out of this class
    public class DialogueElement
    {
        // You would only store an index to the according character
        // Since I don't have your Characters type for now lets reference them via the Dialogue.CharactersList
        public int CharacterID;

        //public Characters Character; 

        // By using the attribute [TextArea] this creates a nice multi-line text are field
        // You could further configure it with a min and max line size if you want: [TextArea(minLines, maxLines)]
        [TextArea] public string DialogueText;
    }

    // This needs to be either wrapped by #if UNITY_EDITOR
    // or placed in a folder called "Editor"
#if UNITY_EDITOR
    [CustomEditor(typeof(Dialogue))]
    public class DialogueEditor : Editor
    {
        // This will be the serialized clone property of Dialogue.CharacterList
        private SerializedProperty CharactersList;

        // This will be the serialized clone property of Dialogue.DialogueItems
        private SerializedProperty DialogueItems;

        // This is a little bonus from my side!
        // These Lists are extremely more powerful then the default presentation of lists!
        // you can/have to implement completely custom behavior of how to display and edit 
        // the list elements
        private ReorderableList charactersList;
        private ReorderableList dialogItemsList;

        // Reference to the actual Dialogue instance this Inspector belongs to
        private Dialogue dialogue;

        // class field for storing available options
        private GuiContent[] availableOptions;

        // Called when the Inspector is opened / ScriptableObject is selected
        private void OnEnable()
        {
            // Get the target as the type you are actually using
            dialogue = (Dialogue) target;

            // Link in serialized fields to their according SerializedProperties
            CharactersList = serializedObject.FindProperty(nameof(Dialogue.CharactersList));
            DialogueItems = serializedObject.FindProperty(nameof(Dialogue.DialogueItems));

            // Setup and configure the charactersList we will use to display the content of the CharactersList 
            // in a nicer way
            charactersList = new ReorderableList(serializedObject, CharactersList)
            {
                displayAdd = true,
                displayRemove = true,
                draggable = false, // for now disable reorder feature since we later go by index!

                // As the header we simply want to see the usual display name of the CharactersList
                drawHeaderCallback = rect => EditorGUI.LabelField(rect, CharactersList.displayName),

                // How shall elements be displayed
                drawElementCallback = (rect, index, focused, active) =>
                {
                    // get the current element's SerializedProperty
                    var element = CharactersList.GetArrayElementAtIndex(index);

                    // Get all characters as string[]
                    var availableIDs = dialogue.CharactersList;

                    // store the original GUI.color
                    var color = GUI.color;
                    // Tint the field in red for invalid values
                    // either because it is empty or a duplicate
                    if(string.IsNullOrWhiteSpace(element.stringValue) || availableIDs.Count(item => string.Equals(item, element.stringValue)) > 1)
                    {
                        GUI.color = Color.red;
                    }
                    // Draw the property which automatically will select the correct drawer -> a single line text field
                    EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUI.GetPropertyHeight(element)), element);

                    // reset to the default color
                    GUI.color = color;

                    // If the value is invalid draw a HelpBox to explain why it is invalid
                    if (string.IsNullOrWhiteSpace(element.stringValue))
                    {
                        rect.y += EditorGUI.GetPropertyHeight(element);
                        EditorGUI.HelpBox(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), "ID may not be empty!", MessageType.Error );
                    }else if (availableIDs.Count(item => string.Equals(item, element.stringValue)) > 1)
                    {
                        rect.y += EditorGUI.GetPropertyHeight(element);
                        EditorGUI.HelpBox(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), "Duplicate! ID has to be unique!", MessageType.Error );
                    }
                },

                // Get the correct display height of elements in the list
                // according to their values
                // in this case e.g. dependent whether a HelpBox is displayed or not
                elementHeightCallback = index =>
                {
                    var element = CharactersList.GetArrayElementAtIndex(index);
                    var availableIDs = dialogue.CharactersList;

                    var height = EditorGUI.GetPropertyHeight(element);

                    if (string.IsNullOrWhiteSpace(element.stringValue) || availableIDs.Count(item => string.Equals(item, element.stringValue)) > 1)
                    {
                        height += EditorGUIUtility.singleLineHeight;
                    }

                    return height;
                },

                // Overwrite what shall be done when an element is added via the +
                // Reset all values to the defaults for new added elements
                // By default Unity would clone the values from the last or selected element otherwise
                onAddCallback = list =>
                {
                    // This adds the new element but copies all values of the select or last element in the list
                    list.serializedProperty.arraySize++;

                    var newElement = list.serializedProperty.GetArrayElementAtIndex(list.serializedProperty.arraySize - 1);
                    newElement.stringValue = "";
                }

            };

            // Setup and configure the dialogItemsList we will use to display the content of the DialogueItems 
            // in a nicer way
            dialogItemsList = new ReorderableList(serializedObject, DialogueItems)
            {
                displayAdd = true,
                displayRemove = true,
                draggable = true, // for the dialogue items we can allow re-ordering

                // As the header we simply want to see the usual display name of the DialogueItems
                drawHeaderCallback = rect => EditorGUI.LabelField(rect, DialogueItems.displayName),

                // How shall elements be displayed
                drawElementCallback = (rect, index, focused, active) =>
                {
                    // get the current element's SerializedProperty
                    var element = DialogueItems.GetArrayElementAtIndex(index);

                    // Get the nested property fields of the DialogueElement class
                    var character = element.FindPropertyRelative(nameof(DialogueElement.CharacterID));
                    var text = element.FindPropertyRelative(nameof(DialogueElement.DialogueText));

                    var popUpHeight = EditorGUI.GetPropertyHeight(character);

                    // store the original GUI.color
                    var color = GUI.color;

                    // if the value is invalid tint the next field red
                    if(character.intValue < 0) GUI.color = Color.red;

                    // Draw the Popup so you can select from the existing character names
                    character.intValue =  EditorGUI.Popup(new Rect(rect.x, rect.y, rect.width, popUpHeight), new GUIContent(character.displayName), character.intValue,  availableOptions);

                    // reset the GUI.color
                    GUI.color = color;
                    rect.y += popUpHeight;

                    // Draw the text field
                    // since we use a PropertyField it will automatically recognize that this field is tagged [TextArea]
                    // and will choose the correct drawer accordingly
                    var textHeight = EditorGUI.GetPropertyHeight(text);
                    EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, textHeight), text);
                },

                // Get the correct display height of elements in the list
                // according to their values
                // in this case e.g. we add an additional line as a little spacing between elements
                elementHeightCallback = index =>
                {
                    var element = DialogueItems.GetArrayElementAtIndex(index);

                    var character = element.FindPropertyRelative(nameof(DialogueElement.CharacterID));
                    var text = element.FindPropertyRelative(nameof(DialogueElement.DialogueText));

                    return EditorGUI.GetPropertyHeight(character) + EditorGUI.GetPropertyHeight(text) + EditorGUIUtility.singleLineHeight;
                },

                // Overwrite what shall be done when an element is added via the +
                // Reset all values to the defaults for new added elements
                // By default Unity would clone the values from the last or selected element otherwise
                onAddCallback = list =>
                {
                    // This adds the new element but copies all values of the select or last element in the list
                    list.serializedProperty.arraySize++;

                    var newElement = list.serializedProperty.GetArrayElementAtIndex(list.serializedProperty.arraySize - 1);
                    var character = newElement.FindPropertyRelative(nameof(DialogueElement.CharacterID));
                    var text = newElement.FindPropertyRelative(nameof(DialogueElement.DialogueText));

                    character.intValue = -1;
                    text.stringValue = "";
                }
            };

            // Get the existing character names ONCE as GuiContent[]
            // Later only update this if the charcterList was changed
            availableOptions = dialogue.CharactersList.Select(item => new GUIContent(item)).ToArray();
        }

        public override void OnInspectorGUI()
        {
            DrawScriptField();

            // load real target values into SerializedProperties
            serializedObject.Update();

            EditorGUI.BeginChangeCheck();
            charactersList.DoLayoutList();
            if(EditorGUI.EndChangeCheck())
            {
                // Write back changed values into the real target
                serializedObject.ApplyModifiedProperties();

                // Update the existing character names as GuiContent[]
                availableOptions = dialogue.CharactersList.Select(item => new GUIContent(item)).ToArray();
            }

            dialogItemsList.DoLayoutList();

            // Write back changed values into the real target
            serializedObject.ApplyModifiedProperties();
        }

        private void DrawScriptField()
        {
            EditorGUI.BeginDisabledGroup(true);
            EditorGUILayout.ObjectField("Script", MonoScript.FromScriptableObject((Dialogue)target), typeof(Dialogue), false);
            EditorGUI.EndDisabledGroup();

            EditorGUILayout.Space();
        }
    }
#endif

Dhe kështu do të dukej tani

fut përshkrimin e imazhit këtu

26.03.2020
  • Faleminderit, kjo është e mrekullueshme! Megjithatë, ka një problem ku unë nuk mund të heq karaktere nga Lista e Karaktereve duke klikuar simbolin minus -. A e dini pse ndodh kjo? 08.04.2020
  • A është zgjedhur artikulli që dëshironi të hiqni? 08.04.2020
  • Oh, e kuptoj, mendova se klikimi në kutinë e tekstit për të redaktuar emrin e personazhit llogaritet si zgjedhje e artikullit, por duhej të klikoja në elementin 1 dhe të tillë. Faleminderit perseri! A ka ndonjë burim që rekomandoni për të mësuar rreth skripteve të Unity Editor? Dua të shtoj më shumë veti nga klasa DialogueElement si një figurë karakteresh, një veti GUIStyle për të zgjedhur ngjyrën e tekstit dhe më shumë. 09.04.2020
  • jam i lumtur të ndihmoj :) Sinqerisht e mësova me provë dhe gabim gjatë 3 muajve dhe kërkova shumë në API dhe google .. oh dhe StackOverflow ;) ... për fat të keq nuk di një burim specifik që mund të rekomandoj këtu përveç atë :) 09.04.2020
  • oh wow, kjo është shumë mbresëlënëse. Ka pasur një problem vonesë me kodin e redaktimit të personalizuar pasi kam rreth 17 ose më shumë elementë dialogu në një objekt të vetëm të skriptueshëm të Dialogut. Sikur do të mbaroja së shkruari një fjali, por teksti nuk do të përfundojë duke u shfaqur në kutinë e tekstit në inspektor deri në 4 sekonda më vonë. U ktheva te një version më i vjetër i projektit përpara se të modifikoja skriptin e redaktuesit që ndave këtu dhe ai ende mbetet shumë. Më pas u ktheva te para se të përdorja skriptin e redaktuesit të personalizuar dhe nuk ka asnjë vonesë edhe me më shumë se 20 artikuj dialogu. A keni ndonjë ide se çfarë mund të shkaktojë këtë? 16.04.2020
  • Kam lexuar në forume se është më mirë të shmangni kryerjen e ndonjë përpunimi në OnInspectorGUI() por nuk jam i sigurt se ku ose kur tjetër do ta bëja përpunimin për listat e riorderueshme. 16.04.2020
  • Gjëja më e shtrenjtë aktualisht është ndoshta var availableOptions = dialogue.CharactersList.Select(item => new GUIContent(item)).ToArray(); Ajo që mund të bëni në vend të kësaj është ta përpunoni këtë vetëm një herë për OnInspectorGUI dhe më mirë ta ruani rezultatin në një fushë si private GUIContent[] availableOptions; .. e përditësoi atë në kodin e mësipërm. Kjo duhet të shpresojmë të përmirësojë performancën 16.04.2020
  • Përshëndetje derHugo, faleminderit përsëri për ndihmën tuaj. Provova kodin e ri që përditësove dhe gjithashtu u përpoqa ta rregulloja vetë kodin, por nuk munda ta rregulloja problemin e vonesës. Ndoshta nuk është CharactersList por dialogItemsList që e bën atë të vonojë? E di që tashmë më keni ndihmuar shumë dhe ju jam shumë mirënjohës. A do ta kishit mendjen t'i hidhnit sërish një sy mjetit tim nëse ju paguaja? github.com/guitarjorge24/DialogueTool Unë jam një student i zhvillimit të lojërave dhe po përpiqem ta rregulloj këtë para përfundimit të semestrit, kështu që unë do të vlerësoja vërtet çdo tregues se çfarë tjetër mund të shkaktojë vonesën ekstreme. 23.04.2020
  • Materiale të reja

    Masterclass Coroutines: Kapitulli-3: Anulimi i korutinave dhe trajtimi i përjashtimeve.
    Mirë se vini në udhëzuesin gjithëpërfshirës mbi Kotlin Coroutines! Në këtë seri artikujsh, unë do t'ju çoj në një udhëtim magjepsës, duke filluar nga bazat dhe gradualisht duke u thelluar në..

    Faketojeni derisa ta arrini me të dhënat false
    A e gjeni ndonjëherë veten duke ndërtuar një aplikacion të ri dhe keni nevojë për të dhëna testimi që duken dhe duken më realiste ose një grup i madh të dhënash për performancën e ngarkesës...

    Si të përdorni kërkesën API në Python
    Kërkesë API në GitHub për të marrë depot e përdoruesve duke përdorur Python. Në këtë artikull, unë shpjegoj procesin hap pas hapi për të trajtuar një kërkesë API për të marrë të dhëna nga..

    Një udhëzues hap pas hapi për të zotëruar React
    Në këtë artikull, do të mësoni se si të krijoni aplikacionin React, do të mësoni se si funksionon React dhe konceptet thelbësore që duhet të dini për të ndërtuar aplikacione React. Learning..

    AI dhe Psikologjia — Pjesa 2
    Në pjesën 2 të serisë sonë të AI dhe Psikologji ne diskutojmë se si makineritë mbledhin dhe përpunojnë të dhëna për të mësuar emocione dhe ndjenja të ndryshme në mendjen e njeriut, duke ndihmuar..

    Esencialet e punës ditore të kodit tim VS
    Shtesat e mia të preferuara - Git Graph 💹 Kjo shtesë është vërtet e mahnitshme, e përdor përpara se të filloj të punoj për të kontrolluar dy herë ndryshimet dhe degët më të fundit, mund të..

    Pse Python? Zbulimi i fuqisë së gjithanshme të një gjiganti programues
    Në peizazhin gjithnjë në zhvillim të gjuhëve të programimit, Python është shfaqur si një forcë dominuese. Rritja e tij meteorike nuk është rastësi. Joshja e Python qëndron në thjeshtësinë,..