Even at this early stage of development, I knew my project would need to include interactions between the following kinds of agents and objects: Actors, Areas, Items, Objectives and Actions. I also knew that – especially in terms of Objectives and Actions – I would likely have a lot of them, and that in order to facilitate emergent behaviour they would need to refer to each other in multiple dynamic ways.
One of my main concerns was having to write individual versions of these classes, so I decided to use Unity’s Scriptable Objects. I was quickly able to draft some Actor Scriptable Objects with the following variables:
- name (string)
- tension (int)
- status (int)
- holding (List<Item>)
- location (Area)
- want (Item)
- direction (Area)
- objectives (List<Objective>)
- currentObjective (Objective)
Creating new ones was as simple as adding a CreateAsset command to the script, and I could right-click my way through the dramatis personae with ease! However, Scriptable Objects can’t be instantiated in-game – they’re more like data containers, which can help a class object or game object decide how to behave. Also, without some tinkering, their values persist between editor and runtime – if an Actor picked up an Item in-game, the Actor would start the next run with the Item in their ‘holding’ list; if an Actor’s tension reached 7 in one run, it would start at 7 on the next. The Actor, Item (etc.) classes would therefore contain the methods for each class and the variables that the game would need to reference and change, but the Scriptable Objects would hold the initial variables.
I thus renamed my Scriptable Objects into ‘Datas’ – Actor became ActorData, Item became ItemData, etc. I wrote a number of Initialise functions, which would create new class instances for each ‘Data’ Scriptable Object then add them to a series of lists, which (through string comparison) I could then use to ‘point’ references (originally Datas) towards class instances. So, InitialiseActors() creates a new instance of the Actor class for each ActorData Scriptable Object, InitialiseItems() a new instance of the Item class for each ItemData Scriptable Object, and then the game loops through each list of class objects comparing the names of connected Scriptable Objects to the names of its class objects. Phew!
I had succeeded in setting up a smooth pipeline for adding agents and objects into the game – if I wanted to increase the amount of Actors in the game, it would only take a few clicks. However, I would still need to manually add individual ObjectiveData Scriptable Objects to each, so I created a LoadData class that, using the OnValidate method (called outside of runtime, basically whenever Unity compiles and refreshes through its assets), automatically assigned ObjectiveDatas to ActorDatas based on folder hierarchy. It also accessed the core scripts of the game and updated their initialisation lists – this automation even allowed me to declare those lists as static, which sped up the coding further.
Automating Scriptable Objects Using CSVs
Even with the Scriptable Object creation process all set up, there were some concepts that would prove too time-consuming to create individually: conversation topics. These objects needed to include a lot of quite variegated data, and also needed to be organised by Actor. In my Experimental Development project I had relied on JSON deserialisation to process large amounts of dynamic variables, but for this project I wanted to use an even more accessible format: the spreadsheet. I created an Editor script that would parse a .csv file (a spreadsheet rendered into lines of comma-separated values) into the values of a TopicData Scriptable Object, given the name of an ActorData file. This method could be called in the Utilities tab of Unity, and would search for a .csv file that shared the ActorData name, then create a series of TopicData Scriptable Objects by parsing each comma-separated string.
The final step in this was to automate the assignment of Topics – I didn’t want to be manually dragging and dropping all of them to each Actor – so I updated the LoadData class to populate each ActorData class with the TopicData Scriptable Objects in the folder that shared its name.
Conclusions
This was all a *lot* of work, and a big stretch of my confidence in C#. But knowing that I’ve created a smooth and modular pipeline for inserting both classes and content into Chekhov’s Gone! is not only a big step towards getting a quick-and-dirty prototype ready, but also a necessary foundational gesture towards the larger game, which will undoubtedly involve many more of these classes and certainly much more procedural content!