Procedural Generation of Stories for Video Games Cameron Edwards Supervisor: Steve Pettifer School of Computer Science University of Manchester May 2, 2016 Abstract Many video games have found lasting success with fans thanks to their ability to algorithmically generate new content for players. I set out to explore whether procedural generation can be applied to one of the few aspects of video games that remains relatively untouched by algorithmic generation; the storyline. By applying methods used in existing programs to the world of video games, I have created a novel, interesting and fun game which presents the player with a new and randomly generated story every time they press play. 1 CONTENTS CONTENTS Contents 1 Introduction 1.1 Examples of Procedural Generation . . . . . . . . . . . . . . . . . 1.1.1 TaleSpin . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Project Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Implementation 2.1 Program structure . . . . . . . . . . . . . . 2.1.1 Actors . . . . . . . . . . . . . . . . . 2.1.2 Quests, Roles and Conditions . . . . 2.1.3 Actions . . . . . . . . . . . . . . . . 2.2 The Generation Process . . . . . . . . . . . 2.2.1 Assigning Quests to Actors . . . . . 2.2.2 Assigning Actors to Roles . . . . . . 2.2.3 Storing the Data . . . . . . . . . . . 2.3 Language and Frameworks . . . . . . . . . . 2.3.1 Choosing a Game Engine . . . . . . 2.3.2 Unity Game Engine . . . . . . . . . 2.3.3 Unreal Engine . . . . . . . . . . . . 2.3.4 XNA Game Studio . . . . . . . . . . 2.3.5 Why Unity . . . . . . . . . . . . . . 2.4 Creating the video Game . . . . . . . . . . 2.4.1 Core Mechanics . . . . . . . . . . . . 2.4.2 Integration with the story generator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 The Final Product 3 3 4 5 6 6 6 7 7 7 8 8 8 9 9 10 10 10 11 11 11 12 13 4 Evaluation and Testing 14 4.1 Testing the story generator . . . . . . . . . . . . . . . . . . . . . 17 4.2 Evaluating the stories . . . . . . . . . . . . . . . . . . . . . . . . 17 5 Reflection 5.1 Features Implemented 5.2 Differences from Initial 5.3 Future Development . 5.4 Conclusion . . . . . . . . . . Plans . . . . . . . . . . . . Appendices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 18 19 19 20 21 2 1 1 INTRODUCTION Introduction Procedural generation is a term used to describe the algorithmic creation of content. In computer graphics, procedural generation has often been used to generate textures at runtime. This was done to avoid storing them as images which would take up more storage space than the compiled algorithm itself would. It has also been used to introduce elements of randomness into programs, most notably video games. To keep players engaged in a video game, a suitably large amount of content is needed to provide variation. This can be in the form of multiple levels, playable characters, large numbers of puzzles or long lasting stories. Without using procedural generation all of this content must be written by people, a lengthy and difficult task. Algorithmically generating some aspects of a game reduces the amount of resources needed to provide players with this interesting content. Some games use procedural generation so heavily that it would likely be impossible to create them by hand. 1.1 Examples of Procedural Generation In 1980 a pair of university students began work on a game they called “Rogue.” They were inspired by text-based adventure games, but they wanted to make a game that they could enjoy playing themselves. To achieve this, it was decided that the dungeon explored by players of the game should be randomly generated, so that “Every time you played, you got a new adventure”[1]. Rogue was wildly successful with university students in particular after it was included in an early version of BSD Linux, the distribution used by the University of California, Berkeley. It was so popular and influential that Nethack, a game released in 1987 and based on a Rogue clone, is still played and receiving updates[2]. Since Rogue’s randomly built dungeons, video games have made significant use of procedural generation; almost every piece of content that could be hand authored has been generated by a computer. Gearbox Software’s 2009 game Borderlands featured randomly generated guns, allowing players to find and use more than three million weapons throughout the game[3]. Valve’s Left4Dead determines what enemies and pickups players should encounter leading to a small number of levels having a large amount of variation when played more than once[4]. A game that makes enormous use of procedural content generation is No Man’s Sky. It is a science fiction game centred around exploring space, and is set in a procedurally generated universe made up of more than 1.8×1019 planets. The universe is all generated deterministically from a single seed, meaning that although the universe is algorithmically generated, every player will experience the same universe. Planets vary in temperature and humidity, and may contain procedurally created plants, alien races with their own languages, food chains of animals and other details. A setting of this scale is only possible because of procedural generation; it could never have been created by hand. One element of video games that has not had procedural generation techniques applied to it often is the story. An example of something close to this 3 1.1 Examples of Procedural Generation 1 INTRODUCTION is Dwarf Fortress, a sandbox game with no real objective. The player controls a group of dwarves who set out into the wilderness to build a new home, managing things like farming, crafting and fending off wildlife. The game does not have a story that it tells to the player over time, instead it simulates the game world in incredible detail. Injuries sustained, items dropped, relations between creatures are all tracked for hundreds of years in game. This has lead to series of events that are so entangled with each other and so interesting to watch happen that it feels like watching a story deliberately written by a person. Websites have sprung up around the game for players to share their interpretation of such events, a prime example of this is “The Fable of Catten and the Eagle”[5]. This tells the story of a Dwarf named Catten, and an eagle that decides to follow him around for its entire life. Catten is annoyed at this fact, and ignores the eagle. When Catten is attacked by a dragon, the eagle enters the fight to protect him, and the dragon is killed by the pair. After the fight, Catten softens and befriends the eagle after all. This story has many elements you would expect to find in stories written by people. It revolves around two main characters and the relationship between them, it establishes a setting and builds towards a climax, an adversary is introduced and the pair overcome it. However, the game did not deliberately generate this string of events, nor did it present them to the player as a story. There were no mechanics in place to ensure the player was provided with such a story, it happened partly due to randomly generated events, and partly due to the player inventing details to connect them together. It is more common to play Dwarf Fortress and not encounter stories of this scale and complexity. 1.1.1 TaleSpin A program that took an approach similar to Dwarf Fortress, though much earlier, is TaleSpin. TaleSpin was written in 1976 by James Meehan[?], and generates short stories by simulating a world full of characters and objects. Each character has goals they want to achieve, like satisfying their hunger or helping a friend, and finds ways that they can complete these goals by building up a stack of actions to take. Such an approach is called a Goal Stack. The actions performed by each of the characters then tell a story, ending when there are no more goals to complete. Figure 1 shows an example story generated by TaleSpin. It tells the story of Henry the fox attempting to satisfy his hunger. The program defines a number of ways each goal can be accomplished, each depending on certain factors of the characters and the world. In this case, the only food is the piece of cheese owned by Joe Crow. Henry’s goal is now to take the cheese, and to achieve this, he decides to trick Joe Crow into dropping it. This decision is driven by the facts that Henry is dishonest, and Joe is trusting, making Henry able to trick Joe. Once Joe drops the cheese, Henry can pick it up, and eat it, completing his top-level goal of satisfying his hunger and ending the story. 4 1.2 Project Outline 1 INTRODUCTION Once upon a time, there was a dishonest fox named Henry who lived in a cave, and a vain and trusting crow named Joe who lived in an elm tree. Joe had gotten a piece of cheese and was holding it in his mouth. One day, Henry walked from his cave, across the meadow to the elm tree. He saw Joe Crow and the cheese and became hungry. He decided that he might get the cheese if Joe Crow spoke, so he told Joe that he liked his singing very much and wanted to hear him sing. Joe was very pleased with Henry and began to sing. The cheese fell out of his mouth, down to the ground. Henry picked up the cheese and told Joe Crow that he was stupid. Joe was angry, and didn’t trust Henry anymore. Henry returned to his cave. Figure 1: Sample output of the TaleSpin program 1.2 Project Outline This project aims to create a program capable of generating stories similar to TaleSpin, but with a focus on creating stories that can be integrated in to video games. This constraint was decided on for a number of reasons. Firstly, as previously mentioned, there are very few existing games which already do this. Additionally, focussing solely on stories for video games provides a more controlled set of stories to draw inspiration from, and evaluate my program against. Without this specificity, the domain of “stories” is too broad and vague to be of much use for evaluation of a project on this scale. As the stories generated are intended for use in video games, the program should take a player’s actions into account while generating the story, allowing players the freedom to take actions they as they see fit and have the story respond to those actions. An example of this could be allowing the player to kill characters who are central to the story, and have the program realise the story is impossible and generate a new one. As there is no player involved with TaleSpin, this is not a concern. It can take as much time as necessary to generate a story from start to finish, and simply present it to the reader. To cope with a player interacting with the world, my program must be able to generate a story fast enough to be useful in a video game and also be able to recognise stories which are valid or not. In the context of my project, a valid story is one that advances according to some supplied rules, such as “It is not possible to talk to dead characters,” or “A story ends when the player is dead.” An invalid story is one which includes actions that violate these rules, and should either never be generated or should be caught and amended. TaleSpin serves as the primary inspiration for my project. My implementation also takes a world full of characters and simulates them according to pre-defined rules, with each action taken forming a part of the story. Further details are provided in the following section. 5 2 IMPLEMENTATION Figure 2: A simplified block diagram showing the main components of the story generator 2 Implementation In this section I will discuss details of my implementation, starting with the major components and structure of the project, and moving on to the technologies used to build it. 2.1 Program structure The project is split into two large pieces of software, the story generator which exists as a standalone library, and a video game that makes use of the generator. I will begin by explaining the components, structure and inner workings of the story generator. Like the TaleSpin program discussed earlier, my project generates stories by simulating characters according to a set of predefined goals and actions. Figure 2 shows the most important components of the program which and will be explained in the following sections. 2.1.1 Actors The story generator represents characters as actors, objects with names and certain traits. Traits are a simple yet flexible way to model an actor’s personality, current state, belongings and much more. Traits are simply made up of a tag that is used to identify them, and some data associated with the trait. Examples could include a “Location” tag with a value of “Manchester,” a “Strength” tag with a value of 10, or a “Wounded” tag with a value of true. Actors also have relations, which are an extension of traits. Like traits, they have a tag and a value, but relations also have an actor that the relation is directed at. Relations can be used to model what actors think about other actors, and are single directional, meaning that if Actor A “Likes” Actor B, Actor B is not required to like Actor A back. This is particularly useful for storing information about past events. For example, if an actor is killed by 6 2.2 The Generation Process 2 IMPLEMENTATION another actor during the story, they may set a “Killed By” relation pointing to the killer. This would enable the story to know not only that the actor is dead, but also know the cause of their death. 2.1.2 Quests, Roles and Conditions Actors, traits and relations are how characters involved in the generation process are defined, while quests define the goals and interactions the actors can perform. Quests are made up of two sets of conditions, a set of roles and a set of actions. Conditions are a method of restricting what actors are capable of doing. Conditions are made up of the tag of a trait or relation to test, a value to test against, and a method of comparison. They can be used to express conditions like “Actor A’s Strength trait must be greater than 5,” or “Actor B must like Actor A.” A full list of condition types can be found in the class diagram in the appendix. Quests have a two sets of conditions, entry and exit conditions. Entry conditions define the traits and relations an actor needs to satisfy to be able to accept a quest, while exit conditions define the completion of a quest. Some quests can be defined involving only a single actor, while others model interactions between multiple actors. A quest’s roles define what sort of additional actors are involved, and the conditions needed to satisfy each one. When an actor is assigned a new quest, each role is filled by a suitable actor. 2.1.3 Actions Actions represent the changes an actor can make to the world in order to move closer to completing their current quest. Actions take the form of a list of changes made to traits and relations, such as incrementing numerical values, setting new values, or removing traits entirely. An action’s changes can be applied to the actor taking the quest, or any of the roles associated with it. 2.2 The Generation Process Now that all relevant components of the story generator have been introduced, the algorithms used to generate stories can be described. An important feature of the story generation is that all actors are simulated, not just one defined as the main character. The simulation will be described briefly, with complex or important concepts described in more detail afterwards. Initially, each actor in the simulation searches for a suitable quest to try to complete. This process includes finding other actors to fill roles as necessary. Actors then all take actions from their assigned quest until they have completed it, and are assigned a new one. As every action has the chance of invalidating previously valid quests by changing the traits of actors, each actor checks to see if their quest is still valid, or searches for a new quest each time an action is performed. 7 2.2 The Generation Process 2 IMPLEMENTATION As the story generator is intended to be used by other pieces of software such as a video game, an API exposes functions to perform each of these tasks. It is then up to the video game to decide which actions are chosen, or which of the available quests an actor chooses for example. Of these tasks, the most complex is the assigning of quests to actors. If roles are not involved, the process is relatively straight-forward, however assigning actors to roles greatly complicates the process. 2.2.1 Assigning Quests to Actors The story generator is composed of a set of actors, and a set of quests. While an actor has no quest to pursue, they will search for one. The set of quests an actor is capable of taking is obtained by iterating over each of the quests in the generator. For each quest, all of the entry conditions are tested against the actor’s traits. If the actor meets all of the conditions, he is eligible to take the quest. With no roles involved, this is the end of the process and the list of available quests is returned. 2.2.2 Assigning Actors to Roles The assigning of quests to actors becomes much more difficult if roles are involved. In addition to checking if an actor meets the Entry conditions of a quest, an actor must be found to play each role of the quest. Roles contain a list of conditions which can depend on other roles, meaning that to assign an actor to a particular role, another role must be assigned first. This problem can lead to circular dependencies, making role assignment a difficult problem. To solve this problem, a recursive algorithm was designed. Figure 3 shows the pseudo-code for the algorithm. Each iteration of the algorithm considers a single role, and iterates over all actors to find one who can satisfy it. Any conditions involving unassigned roles are assumed to be true. Once a role is filled, the function validates all roles that have already been assigned, checking to see if conditions that were previously assumed to be true are still valid. The algorithm then recurses on to the next unassigned role. If no actor can play the role, it returns false, which prompts the previous iteration to discard the actor it had chosen to play its role, and try the next. The first call of the function returns either true, indicating that all roles in the quest are filled, or false, indicating the quest’s roles are not satisfiable. 2.2.3 Storing the Data Once the algorithms were working on controlled datasets for testing purposes, a method for storing large amounts of actor and quest data had to be decided on. It needed to be a format that was easily machine readable to avoid having to write code to parse text, but also be reasonably friendly to edit by hand as I would have to create the content myself. I eventually settled on using XML to store all actor, quest and supporting data for the project. This turned out to be 8 2.3 Language and Frameworks 2 IMPLEMENTATION bool canFillRole ( r o l e ) : if ( all roles filled ) : // A l l r o l e s f i l l e d , c o m p l e t e return true f o r each a c t o r i n a c t o r s : i f ( canFillRole ( role , actor ) ) : assignActorToRole ( role , actor ) // Check a s s i g n e d r o l e s s t i l l v a l i d validateRoles () i f ( c a n F i l l R o l e ( nextRole ) ) : return true // I f r e c u r s i v e c a l l f a i l s , c h o o s e next a c t o r unassignActorFromRole ( r o l e , a c t o r ) // A l l a c t o r s t r i e d w i t h o u t s u c c e s s return f a l s e Figure 3: Pseudo Code for the role assigning algorithm a good decision as C#, the language my implementation is written in, supports automatic serialization of objects to and from XML. 2.3 Language and Frameworks In this section, I will discuss the technologies I used to implement my project, as well as compare them to the alternatives that I considered. 2.3.1 Choosing a Game Engine The goal of my project is to produce a piece of software capable of generating interesting and believable stories to be used in video games. To sensibly evaluate this, the stories it generates should be experienced in the context of a video game. I decided to build a simple game that would allow a player to experience and interact with the completed story generator in a sensible way for this purpose. To ensure that the game would not take up too much of my development time, I decided to use a video game framework that I was already familiar with. The chosen framework would provide me with much of the base code for creating a game, such as player input, importing and drawing graphics and playing audio. I considered three game engines, the Unity game engine, the Unreal engine, and Microsoft’s XNA Game Studio. 9 2.3 Language and Frameworks 2.3.2 2 IMPLEMENTATION Unity Game Engine Unity is a 3D game engine popular with smaller independent game developers. It allows for game play features and behaviours to be programmed as scripts which are run by the engine. Scripting in Unity is done using C#, an object oriented and memory managed language. Unity provides a large set of tools for building games, allowing developers to spent less time writing boilerplate code. These tools include cameras capable of translating between world and screen space coordinates, a two and three dimensional physics engine, and a powerful editor for creating environments and quickly testing scripts. Additionally, it supports a wide range of target platforms such as standalone programs for Windows, Mac OS and Linux, mobile applications for Android and iOS and WebGL applications which run in modern web browsers. 2.3.3 Unreal Engine Epic Games’ Unreal Engine is similar to Unity in many ways. It is a 3D game engine with a suite of tools for game development including a physics engine and graphical editor. Building a game with Unreal involves programming Components and Scripts much like Unity. Scripts for Unreal Engine are written in C++, an object oriented language using manual memory management. This can potentially make programming games with Unreal more difficult than with Unity, but there are advantages to not using a Garbage Collector when writing games. One particular issue is that Garbage Collection can be slow. In many applications, pausing for several milliseconds to free up memory would not be a noticeable problem. Video games however are very time sensitive applications, which often refresh what is drawn on screen around sixty times per second. In applications such as these, Garbage Collection can cause notable stuttering and negatively impact performance. Using a manually managed language allows a programmer to decide when memory is freed up. It is then possible to ensure any pauses or stutters happen at convenient times for players, like on designated loading screens. 2.3.4 XNA Game Studio Microsoft’s XNA Game Studio is a framework for creating games aimed at Windows PCs and the Xbox 360’s online game market, the Xbox Live Arcade. It is a collection of low level classes for game development written in C#, providing basic functionality such as taking player input, creating a window and efficiently drawing two-dimensional images to it. Unlike the Unity and Unreal engines, XNA does not provide any interface for building games, and does not offer many common components such as cameras or physics simulations. 10 2.4 Creating the video Game 2.3.5 2 IMPLEMENTATION Why Unity As the video game element is not the main focus of this project, I opted to use the software that would make the development of the game as straightforward as possible. Most of the time spent on the video game would be spent programming, and so I chose the language I was more experienced in, C#. This meant that the Unreal Engine was discarded. Development on XNA Game Studio halted in 2013. It was later revealed that while XNA based games would still run on Windows 8 and later operating systems, it would not be able to build apps published on the new Metro App Store. This, along with the powerful suite of editing tools Unity offered persuaded me to choose Unity as the game engine for my project. To aid in the integration of the story generator and the video game, I decided to write the generator in C# as well as this would allow me to re-use classes and logic from the generator in the game scripts. 2.4 Creating the video Game As the video game is not the main focus of the project, its design was kept simple. It is a played from a top-down perspective, and game play consists of moving to, interacting with, and attacking things. Unity made creation of the core game mechanics relatively straight-forward, and most development time on the game involved interfacing with the story generator. 2.4.1 Core Mechanics The first task when creating the game was to build an environment that the story could take place in. I decided to set the stories I would generate in a fantasy medieval world, and found a set of graphics that were free to use from the internet. The game’s environment was built using an external editor called Tiled. It allows graphical tiles to be placed in a regular grid to form large levels, and supports layering of tiles and the definition of polygon colliders for each tile. A script then converts the levels from XML data into a format compatible with Unity. Collisions between objects on the map and characters is handled using the collision data from Tiled and Unity’s built in physics engine. This makes the movement of characters as simple as setting the velocity of the character’s physics controller, dependent on keyboard input. Combat between characters is similarly detected by using colliders attached to each character that do not collide with level geometry, only with other characters. All moving and combat animation is handled by Unity’s animator, which uses a state machine to determine what animation to play. Keyboard keys assigned to moving and attacking control the transition between animation states. Another key aspect of the game is to allow messages to be shown to the player. These messages are used to inform the player of their current quest, and are also how dialogue in the game is represented as recording voices for 11 2.4 Creating the video Game 2 IMPLEMENTATION characters would have taken too much time. The message system performs useful functions such as looking up the names of actors playing roles in certain quests, and emphasising important information using coloured fonts. With only these features, the game was simple yet playable. 2.4.2 Integration with the story generator With a simple game completed, the next step was to integrate the game with the story generator. This proved to be much more difficult than expected, and took up most of the time spent on the game. Most of the difficulty of this step came from creating game play that represented the quests and actions from the story generator. The first aspect of the story generator I integrated with the video game was spawning appropriate characters for each of the actors. This involved giving them graphics that matched their character, as well as determining which actor the player of the game would be controlling. I used the flexible traits of actors to store information such as which graphic to use and the starting position of each actor. At the start of each game, this data is read from the story generator, and appropriate characters are spawned and placed accordingly. After a character select screen was introduced, a player was able to enter the game as any of the actors in the story generator, with the rest being controlled by simple AI players. In order to start driving the simulation needed to generate stories, the game engine then prompts the story generator to assign each actor a quest. The game has an auxiliary set of data that provides context on each quest and action, so that it can inform the player what they are supposed to be doing. Quest context comes in the form of a series of messages to be displayed when the quest is taken that provides some dialogue between the actors involved, and a series of messages to be played when the quest is completed. At this point the game assigns quests to each actor, and explains to the player what quest everyone is trying to complete. The problem of converting the quest actions available into game play, and realising when one has been completed remained. To introduce game play elements for each quest action, a further auxiliary dataset was introduced. Every action in the story generator was tagged as either a Kill action, or an Interact action, and a role from the quest was selected as the target. This means that killing or talking with the target in game registers as taking the corresponding action. To track this, the game implements a publish subscribe pattern. Every interaction or fight between two actors triggers an event to be published from the game, and each actor game object creates tracker objects which subscribe to these events. Trackers can then determine whether the interaction or kill is relevant to the owning actor’s quest, and if so, it informs the story generator which action has been taken. Figure 4 illustrates these interactions more clearly. With the game assigning quests when necessary and recognising actions being taken, the last element to implement was the AI controlling all the actors not chosen by the player. Choosing which quest to take and which action to 12 3 THE FINAL PRODUCT Figure 4: A block diagram illustrating the interactions between quest Trackers and other game objects perform was left to a simple random number generator, but navigating around the environment and reasoning about combat were more complex. To allow AI actors to find and move to actors elsewhere in the game world, a path finding algorithm was needed. Path finding algorithms generally operate on graphs, so the first task was to build a navigation graph representing passable and impassable spaces of the map. This was done by dividing the world into a regular grid and determining whether each individual cell was passable, and then linking cells to their passable neighbours to create a graph data structure. Once a method to generate a navigation graph was complete, I implemented the A* Path finding algorithm. This is an extension of Dijkstra’s algorithm used to find the shortest path between two nodes in a graph. It uses a heuristic function to prioritize nodes that seem likely to be in the shortest path, resulting in less nodes being considered and potentially leading to faster searches. Additionally, path finding calls execute in separate threads to the rest of the game logic, as searches across large amounts of distance took too long and caused a noticeable drop in performance when running in the main game thread. This allows AI controlled actors to find the location of the actor they need to interact with or kill, plot a path towards them, and follow it. A simple piece of logic checks to see if the distance from the target is small enough to allow the actor to attack or talk with their target. With AI Actors able to choose and complete quests, development on the game was completed. 3 The Final Product This section will contain sample output from two applications which are both driven by the completed story generator. One is a text-based application while the other is the completed video game. It will explain how the output has been generated in relation to the components discussed earlier. The text based version is an incredibly simple command line program which allows a player to interact with the story generator. It was built to allow quick 13 4 EVALUATION AND TESTING testing of datasets, as a story can be played from start to finish in a matter of seconds. Player interaction is handled by presenting a list of options to pick such as a choice of available characters to play as, or a list of actions to take. A number corresponding to the desired choice is then read from standard input. Figure 5 shows an example run of the program with the lines facilitating player interaction removed for clarity. It shows a simple story progressing, with the player controlling Griman the bandit. The player takes suitable quests for a bandit, such as destroying villages and attacking travellers. These quests increase the player’s “money” trait, but also decreases their “good” trait, which is used a measure of an actor’s lawfulness. The story ends with Griman being killed by Lentom, a member of the local militia. Griman’s “good” trait falls low enough that he can play the Bandit role in Lentom’s hunt down a bandit quest, leading to the player’s death. Figure 6 shows more examples of the text based application. Most of the output of the program has been omitted, leaving only the descriptions of actions taken. Both the output in this figure and figure 5 use the same dataset of seven quests and five actors. While it proved difficult to capture an unfolding story from the game in images, figure 7 shows a series of screenshots taken from the video game. The images show the story of a boy named Rin inheriting a quest to deliver a large sapphire to the king of the realm from a dying soldier, and illustrate how story details are presented to the player. In particular, the third screenshot shows a message giving the player a choice of actions to take. This corresponds to the quest the player is currently pursuing and the actions they have available to them. It is hard to capture some elements of the video games with single images. For example, the player receives the quest to transport the sapphire because the original owner has a fight with a bandit who tries to steal it. It is entirely possible for the bandit Griman to kill Sir Lancelot, resulting in an entirely different story taking place. This is not decided by the story generator. The generator presents Lancelot with a quest to take the sapphire to the king, and Griman with a quest to kill Lancelot. The game then handles the combat between them, and the story generator provides the quests that follow based on the changes it is informed of from the game. It is equally possible for Griman to kill Lancelot, or for the player to kill one or both of the actors involved in the fight, or even the king he is supposed to deliver the sapphire to. In all of these cases, the story generator determines which quests are still available to each actor and assigns new quests where necessary. 4 Evaluation and Testing In this section, I will discuss the methods used to verify the correctness of implemented algorithms in the story generator, and performance testing of the game. 14 4 EVALUATION AND TESTING You have chosen Griman You take a new quest, Raid a Village Griman breaks into a house in Wulfsere’s village. They rifle through the villager’s possessions, helping themselves to anything that takes their fancy. Oton takes a new quest, Enlist with the militia Oton helps the militia clear out a storehouse of giant rats. Their opinion of Oton improves. Wulfsere takes a new quest, Attack a Caravan Wulfsere attacks Lentom’s caravan. Lentom defends the caravan and drives Wulfsere away. Griman throws a torch into Wulfsere’s stables. Within minutes, the village is ablaze. Oton joins the militia on a night patrol. They are attacked by orks, but manage to repel them. The militia’s opinion of Oton improves. Wulfsere takes a new quest, Raid a Village Wulfsere and his bandit horde wander the streets of Griman’s village, killing anyone who stands in their way. You take a new quest, Attack a Caravan Griman ambushes Oton’s caravan, and kills them. They take all of their valuables. Lentom takes a new quest, Hunt down bandits Lentom kills Griman and claims the bounty on their head. You have died. Game Over Figure 5: Output from the story generator with player input and interaction omitted for brevity. Griman attacks Lentom’s caravan. Lentom defends the caravan and drives Griman away. Oton joins the militia on a night patrol. They are attacked by orks, but manage to repel them. The militia’s opinion of Oton improves. Wulfsere ambushes Lentom’s caravan. Lentom defends, and kills Wulfsere in the process. After Lentom killed Griman’s friend Wulfsere, Griman tracked them down for revenge. In a fight to the death, Griman kills Lentom. Oton harvests their crops and sells some. They sell for a meagre price at market. Wulfsere breaks into a house in Timthy’s village. They rifle through the villager’s possessions, helping themselves to anything that takes their fancy. Oton sells a sheep at market. It fetches a fair price. Wulfsere throws a torch into Timthy’s stables. Within minutes, the village is ablaze. Oton spars with the off duty militia members. They are impressed with Oton’s skills. Wulfsere ambushes Timthy’s caravan, and kills them. They take all of their valuables. Lentom kills Wulfsere and claims the bounty on their head. Figure 6: Additional examples from the story generator with input and verbose messages removed. 15 4 EVALUATION AND TESTING 16 Figure 7: A series of screenshots from the video game showing a story progress from the point of view of Rin. 4.1 4.1 Testing the story generator 4 EVALUATION AND TESTING Testing the story generator As the story generator is intended to work with another piece of software, initially it was difficult to develop, as there was nothing to interact with it and produce debugging output. To aid in this regard, I began using NUnit to write unit tests for the generator. This made testing new features much easier and faster than writing a separate program would have been, and over all made development much more pleasant. The unit test project consists of thirty seven tests, covering everything from role assignment to reading XML. The complete list of tests can be found in the appendix. Being able to run already written tests after implementing a new feature, and knowing if everything still functioned as expected was very useful. Unit tests were particularly reassuring to have when implementing the role assignment algorithm, as it was not based off an existing algorithm and had room for a number of confusing bugs. 4.2 Evaluating the stories An important aspect of the project is the evaluation of the stories generated by the program, and this proved to be an incredibly difficult problem. In other areas of procedural generation, it is possible to validate content algorithmically. Path finding algorithms can be used to check if the end of a level is reachable from the beginning and image processing techniques can determine the amount of similarity between a generated texture and a sample. Determining whether a story is “good” is incredibly subjective. Rather than attempt to classify individual stories as “good” or “bad,” I decided to lay out a set of story features the generator should be able to use, and evaluate the program as a whole based on how many of the features can be included in generated stories. The features selected were generic aspects of existing stories, both hand written and procedurally generated, that I felt were interesting and would like to see included in the story generator. The first feature I identified as a sign of a good story was a sense of connectivity between events. Due to TaleSpin’s goal stack based approach, each event in the story happens for a reason and is built upon by future events. To me, this is what differentiated a story from a series of randomly chosen events. This feature is seen best in my program in figure 7. The quest to take the sapphire to the king happens as a result of Lancelot being attacked by Griman, which in turn happens because Lancelot is carrying the sapphire in the first place. The story could have been sparked by Lancelot being killed by a wild animal to satisfy it’s hunger, or being taken ill. The important fact is that the quest to take over the delivery of the sapphire requires something to happen to the original owner but does not specify that they must be attacked by a bandit. This demonstrates the story generator’s ability to generate a story that progresses gradually, and one that builds from previous events in a way that still allows for a large amount of variation. Another feature that I wanted the generator to be able to include in the 17 5 REFLECTION stories it generated was relationships between actors. This is a common drive in many stories, such as the Dwarf Fortress story of Catten and the Eagle. Relationships are seen prominently in the first story in figure 6. In the story, Lentom is attacked by Wulfsere and kills him while defending himself. This fact is stored as a relation by the story generator. Additionally, Wulfsere was friends with Griman, another actor in the world. When Griman learns of his friend’s death at the hands of Lentom, he sets out to get revenge. The story generator’s ability to develop relations between actors and react to them like this is a fact I am particularly proud of, as it can allow for far more complex stories than I had thought I would be capable of generating. One last but very important feature I identified was that the stories must make sense. This was by far the most difficult feature to consider while writing this project and I am not entirely satisfied with how strictly it is adhered to. As discussed briefly in the overview of the project, the story generator has a set of rules it obeys whilst determining which quests and actions are possible for each actor. These rules restrict dead actors from taking actions or being involved in quests unless the quest specifies that they are dead. These rules generally result in generated stories not containing actions that should not be possible, however rules are not of much use beyond this. I would have liked rules to be broader and more customisable allowing for better control over what is defined as “making sense” to the generator. This could have resulted in far more focussed stories being generated. 5 Reflection In this section, I will reflect on the success of my project when compared to my initial set of features, and on how closely development followed my plan. 5.1 Features Implemented My initial aim was to create a piece of software capable of generating interesting stories for use in video games. After discovering TaleSpin, I decided that the best method of achieving this was to simulate a world full of characters. When compared against this loose description, I believe my project is a resounding success. I have created a program that generates a variety of stories, and the content of the generated stories is easily controllable by varying the dataset used. Based on my criteria for evaluating stories discussed in the previous section, I also think that the stories generated by my program are both interesting and sensible. I am incredibly pleased that in addition to the story generator, I was able to build a simple yet enjoyable video game that puts my software to use. I believe that this makes evaluation of the story generator far more worthwhile, as stories for video games should be evaluated in the context of a game. 18 5.2 5.2 Differences from Initial Plans 5 REFLECTION Differences from Initial Plans Overall development on my project followed my initial plan relatively closely. A particularly important milestone was the completion of the story generator by December. This was an optimistic milestone, and I was prepared to develop the Story Engine for the entire duration of development sacrificing the video game. Thanks to my meeting this milestone, I had ample time to build the game element of the project which was by far the most enjoyable part of development. Another important difference from initial plans was the inclusion of unit tests. Originally I had dedicated a week to test the story generator once it was complete, but I feel the use of unit testing throughout development time lead to both faster testing of my code, and more confidence while building the project. The use of the Unity game engine drastically decreased the time it took to build the video game. I had some experience with Unity before starting the project, but I did not realise how fast it would make prototyping a game. I was able to get the core mechanics of the game complete and working in around a week, while my initial plan predicted that that would have taken me three weeks. 5.3 Future Development While I believe that my project has been a success, there are elements I would have liked to develop more, had time allowed. The change that I think would have the largest impact on the quality of the project is to increase the size and complexity of the quest and actor datasets. There is currently enough variation to allow the player a large amount of freedom in the choices they have available, however it is evident after a few plays of the game that quests are being re-used. I believe that the algorithm behind the story generator can create much greater variation than it currently achieves, if it had a richer dataset to work from. Another element I would like to develop is the video game. It was designed to be simple so that it would not impact the development time of the story generator too heavily. If I were to spend more time on this project as a whole, the game is the other aspect I would devote more time to. With more variety in both combat abilities and map locations, I think it could turn into a much more enjoyable and interesting game thanks to the story generator behind it. One final aspect that I would like to explore is the method used to relay the story to readers. Currently both the text based and video game interactions with the story generator report every action that happens to the player, even if it ends up having no bearing on the story. Given more time, I would like to find a way to only inform the player of events that they are involved in, unless they are relevant to the story. As the story is generated action at a time, it can not be known if an action will be relevant in the future or not. To deal with this, some method of informing the player of previous events would have to be considered such as a flashback, or expository dialogue. 19 5.4 5.4 Conclusion 5 REFLECTION Conclusion Overall, I believe that I have created a novel and interesting piece of software, and accomplished almost everything I set out to do. I am confident that the code I have written works correctly and performs well, and I have built a game that I will continue to enjoy playing and developing in the future. 20 Appendices Figure 8: A complete class diagram for the story generator 21 REFERENCES REFERENCES Figure 9: A class diagram detailing the unit tests for the story generator References [1] A Brief History of “Rogue”, Glenn R. Wichman, 1997, http://www.wichman.org/roguehistory.html [2] Nethack v3.60 Release Notes, 7 December 2015, http://www.nethack.org/v360/release.html [3] BORDERLANDS HAS 3,166,880 DIFFERENT WEAPONS, Russ Frushtick, 28 July 2009, http://www.mtv.com/news/2459644/borderlands-has-3166880-differentweapons/ 22 REFERENCES REFERENCES [4] The AI Systems of Left 4 Dead, Michael Booth, 2009, http://www.valvesoftware.com/publications/2009/ai systems of l4d mike booth.pdf [5] The Fable of Catten and the Eagle, QuantumSawdust, 18 April 2013, http://dfstories.com/the-fable-of-catten-and-the-eagle/ 23
© Copyright 2026 Paperzz