Unity Dialogue System - Creating UI Utilities

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
We now have our nodes and groups functioning almost the way they do in the final result, but you might have noticed by now we're repeating a lot of elements constructors such as the TextField constructor or the Button constructor. It's a bit unfortunate because many times we need to use the Object Initializer as the constructor doesn't accept a parameter. Thankfully for us, we can fix that by creating our own methods that accept those parameters for us. By making those methods static we are able to call them wherever we want by, much like we did for the EditorGUIUtility.Load method. We're going to be making two Utilities for now, one for our Elements and another one for our Styles. Let's start with our Elements Utility by going into our Editor "DialogueSystem" folder and creating a new folder called "Utilities". Inside this folder, let's create a new C# Script and I'll name it "DSElementUtility". Opening it up, feel free to remove the default methods together with the MonoBehaviour inheritance. We'll be making this a static class so that whenever we create a method it's a requirement for it to be static. I'll make it part of the Utilities subnamespace. We'll start by creating a method for our Text Fields, so type in "public static TextField", and I'll name it "CreateTextField". As parameters, we're going to want the value variable, which is the value of the input, so type in "string value", and set its default value as null. And we'll also be adding an OnValueChanged, which we'll be user later on to do something when the user edits the TextField text. However, the TextField onValueChanged callback is actually of type EventCallback, which receives the type ChangeEvent, with the type string. I'll name it "onValueChanged" and give it a default value of null as well. Inside the method, we'll be creating a variable of type TextField, and I'll name it "textField" and call its constructor. In here, we'll be using the Object Initializer to update its value variable. For the OnValueChanged callback we're first going to have to check if the onValueChanged we've sent is not null and only add it as a callback if that's true. So type in "if (onValueChanged != null)" and in here type in "textField.RegisterValueChangedCallback(onValueChanged )". When that's done, we simply return our TextField. You might've noticed that none of our text fields act like text areas. However, we do want our dialogue text to act as one and not just as a one line text field, so we'll be create a new method for that. Type in "public static TextField" and call it "CreateTextArea". In here, we'll accept the same parameters as our CreateTextField method, so copy them in. Next, let's create a variable of type TextField and I'll name in "textArea", and use the CreateTextField method to create it, sending in our parameters. For a TextField to become a text area, we simply need to set its "multiline" property to true, so let's go ahead and type in "textArea.multiline = true". And of course, don't forget to return our "textArea" variable. We've made a new method for this instead of a parameter, because this way when we're reading it in our code we know right away we're creating a TextArea. Our text field methods are done so let's go ahead and start by swapping them out with the constructors. Head back to the DSNode class and here we'll be updating both the dialogue name and the dialogue text. Make sure you import the Utilities namespace and then in our dialogue name swap the constructor with "DSElementUtility.CreateTextField()" and pass inthe DialogueName as a value. For our dialogue text, we'll call "DSElementUtility.CreateTextArea()" instead and pass in our Text variable. The only TextField we have left is in our DSMultipleChoiceNode class so let's go there and start by importing the "Utilities" namespace and then in our "choiceTextField" we'll replace it with the "DSElementUtility.CreateTextField(choice)". That's all for our text fields so let's make our Foldout method now which we'll be using for our custom data container foldout. So type in "public static Foldout" and I'll name it "CreateFoldout" and receive the "title" as a parameter, and then the value to know whether to start collapsed or not, which I'll name "collapsed" and give it a default value of false. Inside the method we create a variable of type Foldout, I'll name it "foldout" and call its constructor, initializing it with the title and the value, in which the value will be the opposite of collapsed, as when the value is true it should be open, or not collapsed, and when it is false it should be closed, or collapsed. Don't forget to return the "foldout" variable at the end. We can now swap our Foldouts with this method, and of course we only need to swap one foldout in our DSNode class so let's head there and in our "textFoldout" we'll swap the constructor with "DSElementUtility.CreateFoldout("Dialogue Text")". Next, we'll be updating our Buttons so let's head back to our Utility class and create a new method called "CreateButton", so "public static Button CreateButton", and as parameters we'll pass in the Button "text" and an "onClick" action which we'll default as null. Inside, we create a new Button to which I'll name "button" and pass in the "onClick" action as a parameter. Then, we'll initialize our button text with the "text" variable. After that's done, simply return the button at the end. For our buttons we currently have them in one place which is our DSMultipleChoiceNode, so let's go there. And here, we'll be swapping the "addChoiceButton" and the "deleteChoiceButton" with our new method. Let's start by updating our "addChoiceButton" by swapping the constructor with "DSElementUtility.CreateButton("Add Choice")". We'll do the same for our "deleteChoiceButton" but we'll update the text to be "X" instead. We'll update the add button functionality in a bit and the delete button functionality later on in the series. But before that, let's first create a method for our Ports creation, so let's head back to our Utility class and create a new method by typing in "public static Port", and make sure you import the namespace here, and I'll name it "CreatePort". Because the InstantiatePort method is from the node class, we'll need to pass in our node here, to which we'll need to import our "Elements" namespace, and then, I'll name in "node". Next, pass in our "string portName" and default its value to an empty string (""). After that we'll make our Orientation parameter to which I'll just name "orientation" and default it to "Horizontal". After our Orientation, we have our Direction, so "Direction direction = Direction.Output". All that's left as a parameter is the Capacity of the port, so "Port.Capacity capacity = Port.Capacity.Single". Inside the method we create a new variable of type Port, which I'll name "port" and type in "node.InstantiatePort" and pass in our parameters, with a "typeof(bool)" at the end. Of course, don't forget to update the portName to our passed in portName, and then return the port at the end. So that we can do something like "node.CreatePort", we'll be making the node parameter have the "this" keyword. Our port creation method is done, so all that's left is to start using it. Let's start by using it in our DSNode class in our input port, and here we'll simply swap the constructor with "this.CreatePort()", and the "this" works here because it references to the node itself. As a parameter we pass in the port name of "Dialogue Connection", the Orientation as "Horizontal", as we're going to need the next parameters, the Direction as an "Input" Direction and the port capacity as "Multiple" (Multi). Once that's done, let's go to our DSSingleChoiceNode class and swap our output port, so don't forget to import the "Utilities" namespace, and then type in "this.CreatePort" and in here, we'll only need to change the portName to which we'll pass in choice. That's done for our single choice so let's now go to the DSMultipleChoiceNode class and then update our choices loop port to use the "CreatePort" method as well, and here, we won't be needing to passĀ  any port name as we want it to be empty. Now, we want our add choice button to do something, so we'll be adding a callback. Inside this callback we'll be passing in the exact same code that's in our foreach loop, this simply adds a new Choice whenever we click on this button. And because it is the same, it's best for us to create a method with this code, so let's select it and make a method out of it. I'll name it "CreateChoicePort". I'll be adding a new region named "Elements Creation" with this method inside. Because we're going to use this on the foreach loop as well, we need to make sure we pass in the text of our choice text field, so type in "string choice" as a parameter and then replace it with the "New Choice" text. Of course, we'll now need to type in that "New Choice" text on our "addChoiceButton". In the "addChoiceButton" make sure you are also adding the new choice to the Choices List, otherwise, it will never update and will keep on having one Choice. We're done with it in the "addChoiceButton" so all that's left to do is to swap it in our foreach loop so let's go to our loop, select the text and type in "CreateChoicePort(choice)". Our ports are now all updated so that's really it for our Elements. All that's left now are our styles, in specific, the "AddStyleSheet" and the "AddToClassList". The reason why we'll be making an Utility is so that we do not need to type in ourselves the Load method all the time, or type in "AddToClassList" per style class we want to add. So let's go back to our "Utilities" folder and create a new C# script named "DSStyleUtility". Inside the file we'll remove the default methods and the MonoBehaviour inheritance and make this class static. Let's then start with our Style Sheets method so type in "public static VisualElement" and I'll name it "AddStyleSheets". For our parameters, we'll want the element we're adding the style sheets to, so "this VisualElement element", don't forget the "this" keyword there so that we can type "element.AddStyleSheets()", and we'll also accept the names of the style sheets we want to add in, so type in "params string[] styleSheetNames". The "params" keyword here allows you to send multiple names separated by a comma, but do note that for that keyword to work, the parameter needs to be an array and be the last parameter as well. In here we'll iterate through the styleSheetNames array so type in "foreach (string styleSheetName in styleSheetNames)" we'll need to load the StyleSheet into a stylesheet variable through the Load method and add it in in our element stylesheets, so for that we can use the same code that our GraphView has, so copy it up and then paste it inside of our loop. Make sure you import the namespace and update the style sheet name to use the "styleSheetName" variable. Remember to add the element before the "styleSheets" variable as well. At the end, make sure to return the element we've passed in just in case we want to make chain method calls. Once that's done, we're going to need to swap the style sheet loads with this new method in both our Graph View and our Editor Window, so head back to the GraphView first and import the "Utilities" namespace and then swap both the style sheets with our new method by typing in "this.AddStyleSheets()" and passing in the file names. Remember that we use "this" to reference the current graphView, which is a visual element itself. You are free to organize the strings as you want, I'll make a new line for each style sheet. Then, let's head to our EditorWindow and make sure we import the "Utilities" namespace as well, and then do the same as in our GraphView, but make sure you're adding the style sheet to the "rootVisualElement" this time, as our EditorWindow isn't a VisualElement itself. Our style sheets are done, so let's create our method for our classes. Back to our Utilities class, type in "public static VisualElement" and I'll name it "AddClasses". As parameters, we'll have the same as above so "this VisualElement element" and "params string[] classNames". Inside, we'll loop through the "classNames" with a foreach loop and I'll name each string a "className". To add the class to the element we simply type in "element.AddToClassList(className)". At the end, we return our element. We now have our method, so it's time for us to swap it out with the "AddToClassList" methods. We'll however only be swapping them if the element adds more than one class. So let's go to our DSNode class and update our "dialogueNameTextField" first to use this method instead. Simply type in "dialogueNameTextField.AddClasses" and pass in all the classes separated by a comma. I'll again place a class name per line, but do it as you'd like. Then, do the same but for the "textTextField" variable. Once that's done, we only have one more TextField to update and that's our "choiceTextField" in the DSMultipleChoiceNode class, so let's go there and update our TextField to use this new method. If we now save all the files and go back to Unity, we should still be having the same exact elements, but with the extra functionality of adding a new choice in the MultipleChoiceNode.
Info
Channel: Indie Wafflus
Views: 601
Rating: undefined out of 5
Keywords: Unity Dialogue System, Unity Dialog System, Unity Graph View, Unity Node, Unity Utilities, Unity UI Toolkit, Unity Dialogues, Unity Node Based Dialogue System, Dialogue System, Node Based Dialogue System, Unity Dialogs, Unity Visual Elements, Node System, Dialogue System Tutorial, Unity 2D, Unity, Unity Tutorial, Unity Dialogue System Tutorial, Unity 3D, Unity3D, Unity2D
Id: gtZAN-vzuh4
Channel Id: undefined
Length: 17min 40sec (1060 seconds)
Published: Sun Jul 25 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.