Let's say that we are creating a videogame using a procedural content generation algorithm. What shall we do to let the game's underlying software auto-generate a virtual world that is populated by meaningful contents?
It is a pretty well known observation that, using a variety of stochastic and hash-based mechanisms, one can randomly generate all sorts of patterns in space fairly easily. For instance, anyone who is well-versed in computer science would have no trouble writing an algorithm which configures a random maze based on a simple graph-oriented technique (i.e. Just make a grid of vertices and keep connecting random pairs of them until all of them are connected).
It is also fairly easy to introduce more advanced, abstract patterns to the cookbook of procedural generation. Lock-and-key mechanism, by which the maze is filled with not only corridors of obscure shapes but also with locked doors which can only be unlocked by certain types of keys, is also an option that is straightforward to implement because we can utilize graph-based logic for it (e.g. A "key node" inside the maze graph must precede its corresponding "door node" within at least 1 out of N paths that can be traversed by the player, and so on).
There are countless mathematical models from which we can derive appropriate procedural algorithms for the purpose of auto-generating any type of content we can think of, including procedural textures, procedural meshes, procedural architecture, procedural enemy waves, procedural ecosystems, and so forth.
But, can we procedurally generate narratives as well?
By "narratives", what I mean is a set of stories to tell, loaded with emotions which the player can grasp by heart and utilize as fountains of motivation. In order for the player to be motivated to play the game, one's freedom must be restrained instead of being infinitely granted under the promise: "You can do anything you want, and be anyone you like!".
Under a limited degree of freedom, the player will experience frustration due to one's inability to get what he/she wants to get and be what he/she wants to be. This sense of limitation is what fuels the person to fight against the system, for the purpose of achieving things that he/she wasn't allowed to achieve before. This is what gives emotional meaning to the gameplay. Generating a narrative means generating an emotional experience, which is something that emerges out of one's desperate struggle to pull the world out of its pit of painful imperfection and into a state of everlasting equilibrium.
In order to generate such an experience procedurally, the algorithm must first come up with a list of phenomena that the player does like, as opposed to a list of phenomena that the player does not like. One of the most obvious examples would be that the player likes to survive and does not like to die. So in this example, the player possesses two fundamental goals: (1) To survive, and (2) To avoid death. They are listed below as logical relations. They can be interpreted as: "In order to play the game, you must survive and avoid death".
PlayGame :- Survive, AvoidDeath
Survive :-
AvoidDeath :-
Now, if the game world in which the player is living is so generous in nature that the player doesn't have to do anything to survive and avoid death, the game will be meaningless. In a world in which there is infinite supply of food, shelter, and security, what is the player's purpose in life? Just to spend as much time as possible, whilst "enjoying" the continuum of this eternal peace? In this carefree state of beings, these two goals ("survive" and "avoid-death") will both be achieved instantly as soon as the game begins and stay so forever. This is a direct gateway to boredom because the player's sense of engagement originates from the process of achieving these goals, not the fact that these goals have already been achieved.
In order to motivate the player to play the game, these two goals must have their own obstacles which deter him/her from reaching them too easily. The existence of such obstacles forces the player to take a series of detailed actions, which in turn require him/her to subdivide the goals into more specific, smaller goals like the ones shown below.
PlayGame :- Survive, AvoidDeath
Survive :- Eat, Sleep
AvoidDeath :- DodgeHazard, FightEnemy
In order to survive, one must eat and sleep. And in order to avoid death, one must dodge nearby hazards and fight off enemies in sight. So we could say that "eat" and "sleep" are subgoals of "survive", while "dodge-hazard" and "fight-enemy" are subgoals of "avoid-death". This tree-like relationship among goals can potentially expand itself indefinitely in a recursive manner. If we add a new layer of subgoals, it will look like:
PlayGame :- Survive, AvoidDeath
Survive :- Eat, Sleep
Eat :- FindFood, SwallowFood
Sleep :- GoToShelter, LieDown
AvoidDeath :- DodgeHazard, FightEnemy
DodgeHazard :- FindHazard, MoveAwayFromHazard
FightEnemy :- FindEnemy, KillEnemy
Here is the interpretation of the list of statements above. In order to survive, one must eat and sleep. In order to eat, one must find a piece of food and then swallow it. And in order to sleep, one must go to the nearest shelter and then lie down. In order to avoid death, one must dodge hazards and fight off enemies. In order to dodge hazards, one must find the nearest hazard and move away from it. And in order to fight off enemies, one must find the nearest enemy and kill it.
It is pretty trivial to design a tree of goals like this manually, and so it is not an absolute requirement for us to write an algorithm that auto-generates it. All we need to do is just come up with a bunch of individual goals and relate them with one another. The question is, what to do with these goals?
Procedural realization of goals requires them to impose a certain set of rules upon the way in which the player interacts with the game world. For example, the "eat" goal should introduce a hunger-stat to the player's list of stats, so as to ensure that the player will fail to survive when this hunger-stat hits zero, and the "find-food" goal should introduce a sufficient number of random foods scattered all over the world because the act of finding food would be pointless otherwise. The "dodge-hazard" goal should introduce a health-stat to the player's list of stats, as well as imposing a rule that any object that is identified as a hazard should decrement this health-stat when touched by the player. Such rules then implicitly encourage the player to follow their corresponding goals, since the rules themselves are inclined to create a context in which the player has no choice but try to achieve these goals.
if (goalTree.Contains("Eat"))
{
player.stats.Add("Hunger");
player.stats.OnZero("Hunger", _ => player.Die());
}
if (goalTree.Contains("FindFood"))
{
world.DistributeRandomly("Food");
}
if (goalTree.Contains("DodgeHazard"))
{
player.stats.Add("Health");
player.stats.OnZero("Health", _ => player.Die());
collision.Between("Hazard", "Player").OnStart(_ => player.DecreaseStat("Health"));
}
This one-to-one correspondence between goals and their respective rules, however, is still not enough for enriching the player's goal-chasing experience. What we saw earlier was a tree of goals, rather than a set of independent goals that are completely separate from one another. If the goals are distributed as a horde of individual targets that the player is supposed to aim for but are scattered all over the place, the player is likely to be confused as to which goal to approach first. As an example, let's suppose that we simply distribute a bunch of foods, shelters, hazards, and enemies uniformly on the ground, according to the presence of the individual goals mentioned above. This setting will technically encourage the player to search for foods and eat them, occasionally find shelters and sleep in one of them, dodge hazards, and fight against enemies. But, that's it! The gameplay, as a whole, will be so blend that it will practically replace all the goals in this game with a single goal called: "Don't get bored".
Rich gameplay experience originates from a set of motivating goals which do not emerge all at once, but rather in a variety of alternative arrangements. The player may be compelled to "survive" first and then "avoid death" later, or "avoid death" first and then "survive" later. The player may be compelled to "eat" first and then "sleep" later, or "sleep" first and then "eat" later. In other words, there should be a variety of scenarios each of which assigns a unique permutation of priority levels to the tree's component goals. For example, the game should be able to introduce one of the following goal-trees in one occasion, and the other in yet another occasion.
(1)
PlayGame :- Survive, AvoidDeath
Survive :- Eat, Sleep
Eat :- FindFood, SwallowFood
Sleep :- GoToShelter, LieDown
AvoidDeath :- DodgeHazard, FightEnemy
DodgeHazard :- FindHazard, MoveAwayFromHazard
FightEnemy :- FindEnemy, KillEnemy
(2)
PlayGame :- AvoidDeath, Survive
AvoidDeath :- FightEnemy, DodgeHazard
FightEnemy :- FindEnemy, KillEnemy
DodgeHazard :- FindHazard, MoveAwayFromHazard
Survive :- Eat, Sleep
Eat :- FindFood, SwallowFood
Sleep :- GoToShelter, LieDown
These two trees have the exact same set of goals, yet they differ in orders. In the first scenario, the player is supposed to first survive and then avoid death by dodging hazards and fighting enemies. In the second scenario, the player is supposed to first avoid death by fighting enemies and dodging hazards, and then survive. In other words, they demand different orders of execution.
Order of execution among goals can be enforced through ways in which they are separated in space and time. The problem with the aforementioned "uniform distribution" implementation was that the game world was just a vast open ground upon which the player could access any location and confront any type of objects at any moment in time. This easily leads to boredom due to lack of variations in gameplay experience. As we start enforcing the structure of the goal-tree itself to the world (not just the presence of individual goals), the game becomes far more dynamic. This can be done via a step-by-step approach, where the procedural algorithm walks through the tree of goals in a recursive manner and constructs pieces of the world one by one as it moves along its way.
Let's begin with the root goal, which is "play game". This goal will be achieved when its two subgoals, "survive" and "avoid death", are achieved. What this means is that we can simply divide the game world into two spatial regions, each of which is responsible for forcing the player to approach one of these two subgoals.
Top-Down view of the game world:
+-------------------------------+
|O | |
| | |
| | |
| | |
| | |
| Survive | Avoid |
| | Death |
| | |
| | |
| > |
| | |
+-------------------------------+
(O = Player's Initial Position)
(> = Door)
The result of spatial partition shown above creates a physical barrier which prevents the player from reaching the "avoid death" region before passing through the "survive" region. Now let's subdivide these two regions into yet another layer of subgoals, which are "eat -> sleep" for the "survive" goal and "dodge hazard -> fight enemy" for the "avoid death" goal. Such a partitioning process is easily achievable via recursive function calls.
Top-Down view of the game world:
+-------------------------------+
|O | |
| | |
| Eat | Fight |
| | Enemy |
| | |
|-------v-------| |
| | |
| |-------------^-|
| Sleep | Dodge |
| > Hazard |
| | |
+-------------------------------+
(O = Player's Initial Position)
(> = Door)
Here, the physical barriers are clearly guiding the player to first enter the "eat" region, then enter the "sleep" region, then enter the "dodge hazard" region, and then finally enter the "fight enemy" region. The strict partitioning of space allows this natural emergence of order between goals, without even requiring the procedural algorithm to interpret the meaning of each of them.
Now we could theoretically subdivide these four regions into yet another layer of subgoals (which would correspond to "find food", "swallow food", "go to shelter", "lie down", "find hazard", "move away from hazard", "find enemy", and "kill enemy", respectively), but the truth is that this new set of goals are no longer abstract; they are primitive instructions which are supposed to dictate the player to take specific actions (i.e. they are leaf nodes in the goal-tree). Therefore, we have finally reached the end of this recursive space-partitioning process, and are responsible for populating each partitioned region with objects which encourage the player to carry out the corresponding goal's intended list of actions (e.g. "find food" and "swallow food" for the case of the "eat" region, etc).
Top-Down view of the game world:
+-------------------------------+
|O f | |
| | e |
| f | e |
| f | |
| f | e |
|-------v-------| |
| | e |
| s |-------------^-|
| | |
| > h |
| s | h |
+-------------------------------+
(O = Player's Initial Position)
(> = Door)
(f = food, s = shelter, h = hazard, e = enemy)
As you can see from the figure above, a straightforward implementation would be to randomly distribute "food" objects inside the "eat" region (in order to let the player "find food" and "swallow food"), randomly distribute "shelter" objects inside the "sleep" region (in order to let the player "go to shelter" and "lie down"), randomly distribute "hazard" objects inside the "dodge hazard" region (in order to let the player "find hazard" and "move away from hazard"), and randomly distribute "enemy" objects inside the "fight enemy" region (in order to let the player "find enemy" and "kill enemy"). However, the algorithm could be more sophisticated than the one presented here, for the purpose of making the gameplay a bit more interesting. One may consider placing such objects in the form of clusters rather than uniformly scattered points, for instance. Or the partitioning process itself could be converted into a grayscale (rather than black-and-white) model, in which spatial locations blend from one region to the other in the form of gradation.
The spatial configuration presented here can be considered one of many possible instances of the player's set of goals. we can append to this game world yet another chunk of space which instantiates a variant of the same goal-tree, in which the order of goals is different from the previous one. For instance, we can divide this new chunk of space into two regions called "avoid death" and "survive" just like before, but this time in a different order ("avoid death" -> "survive" instead of "survive" -> "avoid death").
Top-Down view of the game world:
+-------------------------------+-------------------------------+
|O f | | |
| | e | Avoid Death |
| f | e > |
| f | | |
| f | e |--------------------------v----|
|-------v-------| | |
| | e | |
| s |-------------^-| |
| | | Survive |
| > h | |
| s | h | |
+-------------------------------+-------------------------------+
We keep recursively subdividing this new chunk of space, where the general procedure is exactly the same but the order between subgoals can potentially differ.
Top-Down view of the game world:
+-------------------------------+-------------------------------+
|O f | | | |
| | e | Fight | Dodge |
| f | e > Enemy > Hazard |
| f | | | |
| f | e |--------------------------v----|
|-------v-------| | | |
| | e | | |
| s |-------------^-| < |
| | | Sleep | Eat |
| > h | | |
| s | h | | |
+-------------------------------+-------------------------------+
In the case above, for instance, the "fight enemy" subgoal comes before the "dodge hazard" subgoal instead of after.
Top-Down view of the game world:
+-------------------------------+-------------------------------+
|O f | | e | h h|
| | e | | h |
| f | e > e > h |
| f | | e | |
| f | e |--------------------------v----|
|-------v-------| | s | |
| | e | s | f |
| s |-------------^-| < f |
| | | | f |
| > h | s | f |
| s | h | | f |
+-------------------------------+-------------------------------+
The end result presents the player with quite a variety of goal sequences. Sometimes the player will face enemies after hazards, as opposed to hazards after enemies, and sometimes the player will be forced to survive after being forced to avoid death, as opposed to being forced to avoid death after being forced to survive. And so on.
Even this partition-based approach, however, quickly gets old as the player traverses the game world. The original set of narratives, which supposedly involve emotionally appealing elements of volition such as survival and death, quickly degrades into a repetitive maze of random noise, and it is rash to assume that the player won't notice it after hours of play.
A brute-force solution to this is to design a gigantic goal-tree that is so large, that it will take an unreachable duration of time for the player to experience sufficiently many variations of it and recognize a sense of repetition. This, however, will pull us further and further away from a purely procedural approach because the goal-tree itself is manually designed.
A subsequent conclusion, therefore, would be that the procedural algorithm somehow needs to be able to automatically construct the goal-tree itself in large scale (possibly infinite), in order to let us scale up the game's narrative space without putting massive amounts of design work. Achieving such level of automation will require the algorithm to be intelligent enough to be able to come up with brand new types of goals under minimal human intervention.
This is an interdisciplinary area of research which needs collaboration of individuals who have ideas on how to quantitatively measure and reproduce narrative elements. And this, I think, can only be realized through deep understanding of human nature.