In Lumbermill, letters will arrive periodically from a range of sources – employees, trade companies, competitors, and various other people in the game that may have an interest in your business. I always found the letters feature from Theme Hospital pretty fun, and thought it would be cool if Lumbermill had something similar. Letters have two main parts – the body text, and the response options. This is how letters work in-game. (Some parts of this are Unity specific, but most aren’t)
Letter Files
All of the letter files are stored in a master Scriptable Object, which can then be used globally throughout the game to reference any of the letters. On startup, the game puts all of the letters into a dictionary based on ID for fast lookup.
Letters start as a template .txt file, with all of the info needed to localise the text and fill in any placeholders or gaps. A basic letter layout would look something like this:
[ID::=test_letter]
[EN:text:=Dear Reader, This is a test letter to check the Letter Generator and Basic Letter systems work as intended. There are a few placeholder values, which should be filled. For example, {logs} logs and {amber}A.
If working correctly, there should be numbers on the line above. That's all for now.
Kind Regards,
Ben]
[EN:option:=Close Letter]
[EN:option:=Do Nothing]
There are a few different “tags” used to help the game interpret the file:
[ID::=] – This is used to identify the letter file from within the game as a string. Every template is stored in a dictionary using the ID as the key.
[EN:text:=] – This is used to identify the english version of the letter’s main body text. “EN” can be swapped out with any supported locale abbreviation (based on the ISO-639-1 standard, e.g. DE for German).
[EN:option:=] – Similarly to the main body text, this identifies an english version of one of the response options.
Other tags used are from the Localisation system (which I plan on covering in a future blog post), in this example I use just one:
{logs} – Can be replaced with any string injected through code. In this case, a string representing the number of logs.
All of these tags can be extracted using regular expressions, passed through the localisation system to fill in any placeholders, and then inserted into the UI.
Coded Responses
I initially wanted to use Lambda functions for responses, as both the call to create a letter, and the code for the response itself could all be contained in a single method. The problem with lambda functions though, for good reason, is that they cannot be serialised. If they could, anyone could feasibly build malware into save files – which I definitely don’t want. So, in order to get around this I had to create a LetterActionManager class, which contains code for letter responses. Actions are defined as methods, and stored in a dictionary with an enum ID – similarly to letters. Every action method takes a single generic object parameter, which can then be cast when calling the method. A very simple example, which pays an integer amount to the player’s bank would be:
private void PayPlayer(object param) {
int amount = (int) param;
SaveManager.playerBank.AddAmber(amount);
}
This method can be referenced via an integer (enum) ID, “LetterActionType.PayPlayer”, and the “param” object can also be stored as an integer. Both of these are save-file friendly, and can be easily serialized as JSON. Not quite as elegant as inline Lambda functions would be, but still a solution nonetheless.
This looks like a great system. The only addition I would make is a description tag on the template. The description tag would be for the translator to understand what the context of the letter and options are. It’s not always obvious what the context of a piece of text is and although it might be obvious in English, other languages might have multiple options.
Keep up the great work!
Yep good idea – context is always helpful!