Around the end of the first week of July, I decided to rework the xml I made, because it was quite ugly code. The script that reads the xml file was built in a way that it depended on the xml-file, meaning that if the xml file had some errors in the order of elements, the dialog would not be displayed correctly.
Script-central instead of xml-central code
I took the current xml file and the reader script and started flipping things around. The first thing I did was to make the script read all of the elements of the dialog and save them to local variables, instead of executing the actions as soon as the element is read.
For each of the strings (which are an element in xml) a few things are done. First I check whether there is more than one node of the same name (eg <GainDialogItem />). In the original reader-method I already deal with this by adding a ‘+’ between multiple nodes of the same name, so in this method I can check for that. In most cases there should never be more than one node of the same name, but I like to put some kind of error handling in, to expect human (especially my) errors.
After that I do whatever that element is supposed to do. In the case of GainDialogItem and LoseDialogItem it’s simple, I save the item as “true” or “false” in the PlayerPrefs, therefore saving player progress, and for the GainDialogItem I also display a little icon, so the player knows they gained a new item.
SendAMessage is very straightforward as well. I normally just call the method specified in the innertext, which is something like “OpenMansionDoors” which allows the player to enter the mansion to talk to the boss, as well as saves that information to a PlayerPref, so that progress is saved.
Dealing with what the npc and possibly the detective say, and especially their audio, is a bit more complicated.
Unlike for the other strings I don’t have error handling here if there is more than one element for the npc’s text, I just remind myself not to do that.
The first step is to put the text in the speech bubble for the npc, commented out here, because the script is in a testing project. Then I do a quick check to see if the line has been filled properly, if it is “TEXTLINE” that means the dialog is missing somehow. After that I create strings for the audio files that shall be played, utilizing the naming conventions I decided on. I save the detectiveSays and detectiveAudio strings globally to use them later on.
Now the audio file for the npc shall be played. Most of the text is commented out here again, for reasons explained above.
First I try to Load the npc audio file from the Resources, this is the reason why the naming convention is so important.
Then the playing audio for this npc and the detective is stopped to avoid overlapping. Now I play the audio file for the npc, and -if the detective has a reply for this dialog option- I start a coroutine to let the detective talk, delayed by the length of the npc’s audio. Here I also have a Debug Warning for missing audio files, again, commented out.
After the specified delay, the method “DoDetectiveTalking” is called. This method does pretty much the same as the above, it writes the text on a speech bubble, stops what the detective is currently saying, and then plays the new audio file.
When I realised just how big the xml file would be (it’s >6500 lines, 185 combinations per character), I imagined that traversing through all of it could take up a lot of computing power, and might severely lower the framerate, which I absolutely want to avoid in a VR-game especially! I did a quick stress test, copy+pasting about 3000 options for one character, and -as expected- the game stuttered badly for 2 seconds. Of course we wouldn’t have nearly as many options in the game, but then again, there is a lot of other stuff happening too. I did some optimisation in the code, like stopping the search after the item is found, but It was obvious that that would not do too much.
I thought that I could at least make it better by doing the search parallely to the Update function, instead of attempting to do it all within one frame, so I had to look deeper into Coroutines, which had so far eluded my comprehension.
I tested around a bit and finally learned how exactly they work. I believed that they would actually work in parallel to the Update function, like multithreading (I believe that that is what multithreading does?). However I now understood that they work in the same thread, but a Coroutine can halt its execution through the “yield return null” statement, and let the Update finish, and then continue where it left off in the next Update.
At the end of my testing I decided to “yield return null” after every 50th Item, and that has been working just fine.
Checking for Human Error
As I mentioned before, I like to make my code check itself for errors, and I definitely wanted to check the huge xml file for any missing combinations, so I wrote a script for it. The script should go through all of the characters and all of the possible combinations, DO those combinations, and I’ll be left with a whole bunch of awesome DebugLog!
Then I do the combinations. I go through all the single Items, add them to each other single Item -alphabetically sorted- and add them to the final list, if it’s not already in there. I also exclude a few combinations here that are not possible due to the story in the game.
Finally I do all of the combinations for all of the characters.
Note that I wrote this as a Coroutine. Even when returning after every 20 items, the Game has a framerate of around 10 while doing this, and the method takes around a minute to finish checking the xml file.