Procedural Generation of Stories for Video Games

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