We have our data ready to save but there is one last thing we need to do
before we start saving our graph, and that's allowing the removal of
choices from our multiple choice nodes. Right now we can add as many choices as we'd want,
but there is no way for us to remove any of them. We'll also be updating the choices list data accordingly. Let's start this off by swapping our Node Choices Type with the choice save data we've created previously. So go to our "DSNode" script and inside swap the "string" type from the "Choices" list
with "DSChoiceSaveData" instead. Of course, don't forget to import the "Data.Save" namespace
for this class to be recognized. Swap it in our list initialization as well. This will allow us to save the connected nodes and
the text of the choice in our node choices list. Note that our "Single" and "Multiple"
choice nodes scripts also use this variable, so we'll need to update them
to use the new type as well. So go to our "DSSingleChoiceNode" script and
start by importing the "Data.Save" namespace. Then, in it's "Initialize" method, create a
new variable of type "DSChoiceSaveData", to which I'll name "choiceData" and call its constructor. I'll use the Object Initializer here to
pass in the "Text" of "Next Dialogue". When that's done, pass in "choiceData" to the "Choices.Add()" method instead
of the string it currently has. Then, swap the Draw method "string" type from the
"foreach" loop to be "DSChoiceSaveData" as well and pass in "choice.Text" to the "CreatePort" method. Now in our "DSMultipleChoiceNode" script, do the same in our "Initialize" method
after importing the "Data.Save" namespace. So simply create a new variable of type "DSChoiceSaveData", set it's text to be "New Choice" and pass the variable to the "Add" method. In the "Draw" method, in our "addChoiceButton" we'll have to create a new choice data too so simply copy the code above and paste it here. Remove the last "Add" method and then pass in "choiceData.Text"
to the "CreateChoicePort" method. In our output container "foreach" loop,
swap the "string" with "DSChoiceSaveData" and pass in "choice.Text" to the
"CreateChoicePort" method as well. We can now start deleting our choices. We'll be doing that in our "deleteChoiceButton" in the "CreateChoicePort" method by giving it a callback. So pass in an empty parameter callback and
inside we'll start by removing our choice. What we'll need to do is quite simple: If there is more than one choice, we can
press on this button to remove the choice. The "if" is there because I want to always
have a minimum of one choice per node, because if we didn't want custom choices, we
could simply make it a single choice node. If the choice port is connected, then we need to disconnect it, which will remove the edge from the Graph. To finalize it, we'll need to remove the port from the graph itself.
(and the choice from the Choices list) So in our callback start by typing
in "if (Choices.Count == 1)" and return if that's the case. With this, if there is more than one
choice, we can remove that choice. Next, let's remove our connections if
our choice port is connected so type in "if (choicePort.connected)", then
we can remove the connections. However, we'll need the "graphView" variable
here, which we only have in our "DSNode" script, as it's a private variable. So let's go back to our "DSNode" script and make the "graphView" variable
be "protected" instead of "private". With this, all classes that inherit from the
"DSNode" class have access to this variable. So back in our multiple choice node, pass in "graphView.DeleteElements(choicePort.connections)". This will remove any connections this port has. We can now remove our choice from the
Choices list and the port from the graphview. However, we don't really have anything here that allows us to know what Choices element we need to remove. What we'll do is quite simple: We'll simply pass in our choiceData
to the method and remove it. However, we will do that by using
the port "userData" variable. The "userData" variable is simply a variable in the "VisualElement" class that allows us to give it any data. This means that once we pass it in, we can get the choiceData from any script as long as we have access to the port. This will be useful for us in a bit
when setting the choice node ID. So instead of having a string with
the text of the choice as a parameter, simply pass in a parameter of type
"object" and name it "userData". Then, right under the "choicePort" declaration
type in "choicePort.userData = userData". Under that, create a new variable of type "DSChoiceSaveData", to which I'll name "choiceData" and assign
the "userData" parameter to it, casting it into "DSChoiceSaveData". With this, we now have access to our choiceData variable. Of course, make sure you now pass in the choiceData
when we call this method instead of the Text. When that's done, back in our method, update the
old string usage to be "choiceData.Text" instead. All that's left now is to remove the choiceData
from the Choices list and the port from the graph, so in our delete button callback type in
"Choices.Remove(choiceData)". Right under that type in
"graphView.RemoveElement(choicePort)". Go to the "DSSingleChoiceNode" script and pass in the foreach "choice" variable to the "choicePort.userData" variable as well. If we save and go back to Unity, we
should now be able to remove our choices. The edges, or port connections should
also be deleted when a choice is removed. However, there is one thing we still need to do. Whenever we create an edge, or a
connection between a choice and another, we should update the output choice data and set its Node ID to be the ID
of the node it got connected with. We should also remove that ID whenever we
remove that edge, or disconnect the choices. The way we'll be doing that is by
overriding the "graphViewChanged" callback. So let's go to our "DSGraphView" script
to the bottom of our "Callbacks" region. We can make it in two different ways: The first one is by creating a method
that accepts and returns a variable of type "GraphViewChange". This class holds certain variables that we'll need. Then, we simply set the callback to be that method,
which has whatever code we want in it. The second one is what we've done so far: we simply override the callback with the necessary parameters. This is what we have used in all of our other callbacks. We'll be going with the second one. So create a new private method
that I'll name "OnGraphViewChanged" and inside type in "graphViewChanged"
and override the callback. This callback accepts one parameter which are the
Graph View Changes, so I'll name it "changes". This callback requires the parameter variable to be returned, so make sure you type in "return changes;" at the bottom. We'll be making use of two variables here: The "edgesToCreate" variable, which will allow us to go through each edge that we've created, get its port and set it's Node ID, and then the "elementsToRemove" variable, which will allow us to go through every
element we've removed from the graph, get the ones that are of type "Edge",
get their port and remove their Node ID. So let's start by going through our "edgesToCreate" list. So type in "if (changes.edgesToCreate != null)", and this is simply so that we do not iterate
through a list if there are no elements on it. In here, we'll loop through our list by typing
in "foreach (Edge edge in changes.edgesToCreate)" and inside we'll need to get the next
node and the output port choice data. Thankfully, we can very easily do that with the edge variable. This is because the "Edge" class has an
"input" and "output" variable which gets the input and output port of that edge. Besides that, the "Port" class also has a "node" variable, which gets the node that the port belongs to. So type in "DSNode" and we'll be getting the "nextNode", so "= edge.input", to get the port, ".node". Of course, don't forget to cast it to our "DSNode". Remember that the "input" port is the
port that the choices will connect to. Then, we need to get the "choiceData" of the output port, as we don't really care about the output node. So type in "DSChoiceSaveData" and
make sure you import its namespace, and I'll name it "choiceData". Then, assign the "edge.output.userData" variable to it and cast it to "DSChoiceSaveData". This will get the choiceData we've passed
in when we created our choice port. When that's done we have all we
need, so type in "choiceData.NodeID" and assign the "nextNode.ID" value to it. Our node Choices list will also be updated because this choiceData is referencing to that list element. To remove the Node ID it will be quite similiar. However, because it is "elementsToRemove"
and not "edgesToRemove", we'll need to check if the type of the element is of "Edge" Type. So type in just under our if statement
"if (changes.elementsToRemove != null)", we'll start by creating a variable of type "Type", to which I'll name "edgeType"
and assign the value of "typeof(Edge)" to it. Then, iterate through the list by typing in "foreach (GraphElement element in changes.elementsToRemove)" and if the type is not edge type, we continue, so "if (element.GetType() != edgeType)",
we "continue;" to the next element. If it is of type edge, we need to get the
Edge through casting the element so type in "Edge edge = element" and cast it to "Edge". We can now get our choice data through
the edge output port variable so type in "DSChoiceSaveData" and I'll name it "choiceData", equals to "edge.output.userData". Don't forget to cast it. To remove the Node ID we simply type in
"choiceData.NodeID = """. Unless I've attempted it wrong, using
the "RemoveElement" in an edge in our "deleteSelection" callback didn't add
the edge to the "elementsToRemove" list. Because we're using the "DeleteElements" there,
removing an edge from the graph will also add it to the "elementsToRemove" list, which means
the Node ID will also reset. Of course, don't forget to call this method in
our constructor for the callback to get overrided. With this done, we should be able
to remove all of our choices, except the last one, and
our node ID's should be set. There is still one thing left to do though and that's updating the choice data text
whenever we update the text field. Thankfully, this is pretty straight forward so
go back to our "DSMultipleChoiceNode" script and in our "CreateChoicePort" method
we'll be adding a callback to our "choiceTextField" variable, so pass in "null" for the label and then pass in a callback to which I'll name "callback". Inside, simply type in "choiceData.Text = callback.newValue". Our choice data text should now be updating
every time we type something in our text field. With that done, we now have
everything we need for our choices.