Navigation (edit) PITS (bugs, development)
|
Subject: Tips for random story generation: NovaCity
From: "Joe Hewitt" <pyrrho12@gmail.com>
Date: 23 Jan 2005 00:30:42 -0800
Newsgroup: rec.games.roguelike.development
As GearHead creeps closer to its v1.00 release, I've started work on another project. This one is far less roguelike than either GearHead or DeadCold, but will hopefully still feature such core RL traits as randomness, tactics, and replayability. Maybe I could describe it as a post-roguelike. NovaCity is to be a superheroes game. Ideally the player will start with his character's origin and thereafter be free to live out life in a four color world, whether as a hero or a villain... yadda yadda yadda, I have lots of plans for the future but haven't started on most of them yet. I'd rather not bore you with a ten page feature list for fear it will all turn out to be vaporware. For now I want to describe for you the part of the game that's already working: my second generation random adventure generator. I've been thinking for a while about writing an article on random story generation, but have been both too busy and too lazy to do so... and let me tell you, that's a horrible combination. Today I finished work on NovaCity's random storyline apparatus and decided that posting to r.g.r.dev about it would be far easier than working on content for it, or returning to the drudging labor of editing GearHead's 2.3Mb+ series directory, so here goes... The process of generating a random adventure in NovaCity is divided into two parts: The top level controls the overall story arc. The bottom level takes the events generated by the top level and builds encounters around them. The adventure is composed of pregenerated components just like in GearHead, but the size of these components is much smaller than it is in that game. Tip #1: It's better to increase the number of combinations than the number of components. If you have a random name generator that just picks a name from an external list of 200 names, then it can generate a maximum of 200 names. If, on the other hand, the generator composes a name by combining two syllables from an external list of 200 syllables, then immediately it can produce 40000 different names. The components in GearHead (the plot files) work like the first name generator. Once a specific plot is selected, it plays out in exactly the same way every time (ignoring internal randomization and player actions). For NovaCity I've decided to improve this by not just randomizing the story events, but by also randomizing the stuations surrounding those events. If an event in GearHead involves a locked door then the key will always be found in the same way. For NovaCity the top-level story creator will specify that a key must be found. Then the low-level encounter creator will decide how to add the key randomly. It may be in the posession of an NPC, or locked in a chest which may itself be guarded by thugs or by traps, or require a second key to open. Tip #2: Trust the player's sense of closure. The adventure is made of a series of components. These components get assembled into a meaningful story by the mind of the player. If you haven't yet done so, I highly reccomend reading Scott Macleod's "Understanding Comics". His theory of how comic books work is equally applicable to understanding how a random story generator could work. "Is There a Text in the Class?" by Stanley Fish is also relevant. The components don't nessecarily have to segue into each other, as long as the player can make logical sense of the jump from one to the other. First when I started work on Nova City's basic set of events, I wanted to add a lot of conditions that I thought would make them flow into one another better. During testing I removed most of these conditions, and found that it resulted in more interesting stories with less predictability. The logic of the sequence was unaffected. Tip #3: It's like cooking chilli. This brings me to my next point... For much of last week I was developing the master list of story events. I would edit the list, run the test program, then examine the resultant story and try to decide what went wrong. The programming behind the generator was finished very quickly but deciding how to describe events so that the computer can select them meaningfully was time-consuming work. It's like cooking chilli. You add some things, you taste it, you let it boil for a while... and then you add something else. The process can go on for quite a long time before you're satisfied with the results. Some people think that writing a random story generator is an easy way around writing the stories themselves. This is bunkum. If anything, a random story generator requires more writing than a single static story. You have to write lots of components to cover every possible event. You have to write dialogue multiple ways depending on who's going to be saying it. Tip #4: It's not great literature, it's genre fiction. I don't want the random story generator to give me great literature. I want it to give me an entertaining story, maybe with a few twists, in which my character will get to do interesting things and kick some butt. When dealing with genre fiction there are assumptions you can make that will make things easier. One assumption has to do with the nature of the story. In GearHead I patterned the main story after the stories from mecha anime. Mecha stories typically center on the main character's life; frequently there is some past wrong that he is either trying to avenge or live down. So in GearHead the main story is controlled by three descriptors: who the PC's enemy is, what they did to hurt the PC (or vice versa), and what mystery the PC has to solve. Comic book stories typically center around the hero's attempts to stop a specific villain. So, the descriptors for stories that I've developed for NovaCity are villain-centric: the villain's goal, the villain's modus operandi, the villain's schtick for this particular caper, and a task the PC has to perform. Obviously I'll have to develop a different set of descriptors for the villainous stories. Tip #5: Making something that works is better than dreaming about something that's perfect. In programming I'm not a perfectionist. I'm more of a good-enoughist. When I started on the random story generator for GearHead I knew that my system would have significant limitations, but that the ideas I had for it would probably work. Likewise the adventure generator for NovaCity isn't going to be perfect, though it will be a step above GearHead's generator. For a perfect random story generator we'll probably have to wait for GenRogue. How does the code work?Is there a variation of the flag/trigger/event type device? Yes, that's how the event scripting works. The random adventure generator builds the scripts in league with the random map generator. The top level selects seed events from a list based on the current requirements of the story. The story in NovaCity is described by four labels: The villain's goal (+G), the villain's modus operandi (+M), the villain's schtick for this adventure (+V), and the player's task (+T). At the start of the adventure all four labels have a value of unknown, which is described by the string "+G?? +M?? +V?? +T??". A villain is selected, then a starting event is chosen based on his traits. At the end of each episode the description string is altered; this new description plus the same old villain is sent to the event picker to choose the next seed event. Examples of seed events from my test file include "Villain tries to rob a bank", "Villain attacks TV station to broadcast blackmain demand", and "Aliens rampage through downtown". Generally the seed event describes what happens in the "goal room" of the scene to be created. When the time comes to play the episode, the seed event, villain, and location are passed to the encounter generator. The seed event requests additional random components to fill out the level. I'm afraid to say that the way I combine these components is through brute force: Each component has a list of script lines and a unique ID number which is used to identify its messages and variable space. During compilation all the components are squashed together into a single giant list of script events. If the trigger caught by the script line being processed is "empty", it gets added to the list without further processing. If the trigger already has a script associated with it, the old script is displaced to a new position and the new script is inserted there. Many script lines have pseudocode that looks like this: [trigger] [script]
Talk-to-fred <if [appropriate time for this] DoStuff else [goto pushed line]>
Reading this over I realize that what I've written is barely intelligible. I'll illustrate with an example. Suppose there were an event in which the PC could rescue a hostage. The pseudocode might look like this: Talk-Hostage <Say ThankYou then leave map>
Now suppose this component has a complication added to it, whereby the PC has to rescue the hostage's friend before the hostage will leave the building. This new script would displace the current script as follows: Talk-Hostage <ifFriendNotRescued Say "I can't leave without my friend!"
else Goto Talk-Hostage_2>
Talk-Hostage_2 <Say ThankYou then leave map>
As you see, the original script called by the trigger Talk-Hostage got pushed to a new non-trigger location. Also note that the original script didn't have a "if..else" block, presumably because it was the placed in the component that created the Hostage. Joseph Hewitt DeadCold: http://www.geocities.com/pyrrho12/programming/deadcold/index.html See also: Story, Quests |