My entire life I've been a night person. I think it formed a core part of my identity.
The late nights, the triple-ringed eyes, the lack of functionality before midday.
They were my work hours. Everybody is asleep. I crank out a bunch of work. Indulging in those second and third winds.
But it just doesn't work like that anymore. Maybe it's a function of age. Maybe it's a function of the energy I put into my office job. Maybe a mixture.
So I get up early. Two-and-a-half hours earlier than usual. And I work. And... it works.
I'm too tired to be easily distractable. I haven't suffered the thousand tiny failures that demotivate. The day is fresh and it's an easy choice:
"Launch Twitter and the first thing you'll have done is fail. Launch some Work and you will have a 100% success rate."
So this morning I received an email asking for some help with a tool I made many many years ago.
Tidy Text Adventures - a "Christmas 2012" project with these enormous aspirations of eventually making the point-and-click adventure tool for Unity.
It never happened, life and all. But the tool was completed and is OK, in a retro way.
You can play a demo of the text adventure in action - a little realistic electronic ditty I call:
Huh. Two-and-a-half years after the fact I realize the demo has "Development Build" in the corner. Embarrassing.
I actually made a pretty huge framework for this game. It had Google App Engine backend integration and loaded stories from the server - so I could publish small text adventures every week.
Like an interactive blog. Every week, a little slice of my life - creative non-fiction style.
To answer the query of "How does this work?" I wrote out a full overview of the demo - code line numbers, function calls - the whole business. It was... an hour of typing.
And I thought: Hell, maybe the wider internet would benefit from the summary. Who knows?
So... stop reading here, friends. Unless you're like... really into reading code summaries.
...or if you just can't get enough of my tone.
Tidy Text Adventures: The Email, The Walkthrough, The Code, The Demo, The Keyword Richness.
An overview of the tool logic can be found here - but I bet you're all over that already ;)
The scene logic:
- Open your TTA Project
- Open the scene: Tidy Text Adventures\Examples\Example_
- Click on the Main Camera object in your hierarchy
- There is a single script here: TTA_UI - this drives the entire demo game.
- This script has a field for the text asset generated by TTA (we will use this to load the game)
- Your game world files are saved to your Unity project under Tidy Text Adventures\Worlds
- Double-click on the TTA_UI script.
- Now it's time to talk through the logic!
I'm going to use approximate line numbers from the TTA_UI script file so we can walk through it together.
Initializing the game world:
- The demo UI uses a simple state machine to track what we should be drawing.
- The first thing we do is set the state to Title - the first screen of the game.
- The whole game relies on the class TTA_World - this holds the state of our world, all our objects - everything.
- Line 63: At this point we need to create the game world from our text file.
- Line 65: We check for a save file using: TTA_Utilities.
- Line 66: If we have an existing save, we can load a TTA_World from it using:TTA_Utilities.LoadSavedWorld(
- Line 69: If we have no existing save, we can create a new TTA_World usingTTA_Utilities.WorldFromString(
- Now we have a super important thing to do! We need to give the game engine three delegates - so it can call back to us with New Game, Quit Game and Game Over events.
- Line 73: We do this by passing three delegates to TTA_InputTranslation.
- In the TTA_UI demo, these functions are located at lines 27, 31 and 35 respectively.
- You can see the UI handling of these states at lines 123, 127 and 131 respectively.
- Okay! So now we have our game world loaded! But how do we do things with it?
- Next step: Output!
Your first text output:
- TTA provides you with TTA_OutputEntry classes to handle the text returned from the engine.
- This is driven by the TTA_InputTranslation
class. It takes natural language and returns game content based on your game world.
- Line 75: The first thing we will do is request the Title output from TTA_InputTranslation.
- This takes the world we created earlier as an argument.
- This returns a list of TTA_OutputEntry to you. For each user input, there will be many TTA_OutputEntry objects returned - one for each aspect of the input (e.g: A room description and also an item description).
- Line 231: I keep this list of output and draw then using GUI.
- TTA_OutputEntry is a very simple class containing the text string and a type - so you can style the text however you like. Open the file TTA_OutputType.cs to see the full list of output types.
- Great! We've got out first input for the Title screen. What now?
- We need to get into the game itself!
- Line 296: I do this by waiting for a keypress from the user. You can do this however you like.
- Based on this, I set the demo UI state to Game and request the very first game screen from the world.
- Line 79: This is achieved using TTA_InputTranslation.
Playing the game:
- So now we are in the game! We've loaded the world, we've shown the title and we've shown the welcome / beginning status message.
- It's time to process some user input.
- Line 256: I wait on user input with a Unity text field
- Line 260: And when the user presses "Go", I handle this input.
- Line 331: At this point, we take the string that the user has written and turn it into something we can use.
- Note: I have made this part unnecessarily complicated in order to give the developer total language control. Get ready!
- Line 344: We break the input into a list of TTA_Word using TTA_InputTranslation.
- A TTA_Word is a string that is processed to see if it contains an object or a command (e.g: "Fish" or "Use" etc).
- Line 347: We filter this list using TTA_InputTranslation.
FilterInput(sentence,world) to remove all words that are nothing / don't make sense. This function returns the filtered list to you.
- Now that we have a list of potential actions and objects, we want to turn this into something we can actually use.
- Line 350: We now translate this list of filtered words into a TTA_InputResultusing TTA_InputTranslation.
- A TTA_InputResult is an actionable thing - it reflects the use-case / action / objects that you created in your editor interface, or may be a system command like "Look".
- Great! We've now got the action you want to do. Let's turn it into text that we can print!
- Line 354: We call TTA_InputTranslation.
ProcessInputResult(result, output,world) - and give it the result we just came to, the previous list of output that we received from the engine, and the world itself.
- At this point - you may receive one of the events we subscribed to earlier: New Game, Quit or Game Over. We'll come back to that later.
- The function we just called (TTA_InputTranslation.
ProcessInputResult) is the function that will change your game world. Any actions that perform game logic (take, use) will be performed now.
- This occurs at line 654 of TTA_InputTranslation.cs
- The function responsible for changing the world is TTA_World.ProcessCommand(TTA_
- The function that saves the world is: TTA_Utilities.SaveWorld(
- Why do we pass the translator the previous list of output?
- Imagine you first type: "Look". You receive a description of the world as a TTA_OutputEntry.
Now you type "You are a fish-head". But this is invalid! So you will receive "I'm afraid I can't do that" as a TTA_OutputEntry.
But you may want to make sure you still have that original description on screen.
ssInputResult function is clever enough to know when to clear your history and when to add to it.
You can pass an empty list here if you don't want the engine to do this, and handle it however you like.
- Whoa! You've now taken a string typed by your player, turned it into words, turned those words into actions, executed those actions on your world and retrieved some text to show your player. Nice!
- That's it! Repeat this process until....
Events! New Game, Quit Game, Game Over.
- At the very beginning, we subscribed to receive a call when one of three events occur: New Game, Quit Game and Game Over.
- Line 109: Based on the game state, I call one of a few GUI functions. You can see the state / function calls here, and drill down further as you like.
- Line 151: When handling New Game - the demo presents a warning - retrieved fromTTA_Language through world.
- Line 170: If the user chooses to begin a new game, we delete their current save using TTA_Utilities.ClearSave(
textAsset.name) and start back at the beginning (showing the Title).
- Line 177: If the user chooses not to begin a new game, we just move back to the regular game.
- Line 190: When handling Quit Game - the demo presents a warning - retrieved from TTA_Language through worl
- Line 170: If the user chooses to quit, we just move back to the Title in the demo.
- Line 177: If the user chooses not to begin a new game, we just move back to the regular game.
- Line 270: Handling Game Over is easy. You will have already received the Game Over text content as your TTA_OutputEntry.
- The demo gives the user two choices at this stage. Quit or New Game.
- In either case, we reset the current world using TTA_Utilities.ClearSave(
- Done! You have successfully handled an entire cycle of a Tidy Text Adventure!
One last thing: Scoring
- As a super after-thought, I added a very primitive score system to TTA.
- Score is retrieved from your TTA_World via world.maximumScore andworld.currentScore.
- Score is calculated based on how many total UseCases the user has completed while playing the game.
- UseCases are marked with countTowardCompletion when creating them in the TTA Editor.
- UseCases that are not marked will not count toward the score.
And that's a full run-through of TTA!
Phew, my fingers are tired.