This was the first game I made in Unity! Part of this process was figuring out how to use the editor and scripts to accomplish what I wanted to do. This was a fun difficulty and I am glad I took the time to learn it. Another issue I had when making this game was modeling. I have never made any 3D models before, so I was worried the game would not look good. Luckily, I found some blender tutorials on how to create a FPS level, and used a lot of assets from the Unity asset store. I am pleased with how it turned out. A huge difficulty I had when making this game was creating the dynamic round difficulty. I wanted to make sure that my implemented system would accurately scale to the player's skill, while also not easily taken advantage of. The first system I implemented was too trivial, so players could purposely do worse to live longer in the game. To fix this I changed my implementation to something slightly more complex, which is described in the section below.
AlgorithmsDynamic round difficulty
The dynamic round difficulty in this game is based on the paper by Robin Hunicke, The Case for Dynamic Difficulty Adjustment in Games. In this paper Hunicke described
a general algorithm for detecting when the player is in trouble, and suggested a few adjustment policies to aid the player. The paper was intended for FPS games,
so it fits perfectly for my game. I will describe her algorithm and the differences in
my implementation. First, Hunicke explained that the amount of damage a player takes over a series of measurements can be represented as a gaussian.
Calculating the gaussian error integral enables us to know F(d), the probability of recieving d or less damage at a given tick.
This is a continuous function, however it can be approximated using the C/C++ erf() function (I found an implementation in C#). This results in the formula:
to calculate the probability of recieving d or less damage at some time t. This formula is in terms of the player's health (h), mean damage rate (μ), and the standard deviation (σ) of damage rates. I implemented this formula to detect when the player is struggling after each round. This formula works well in the early rounds of my game, however it does not work well as the game goes on. This is because it takes 1 measurement of damage per round. So, as the game goes on the 1st round is weighted the same amount as the most recent round. This is an issue because the most recent round is supposed to be the most difficult. This causes the game to never scale down, no matter how hard the previous round was. To fix this, I changed the calculation of the mean. Instead of a normal mean calculation, my calculation weights the most recent round equal to all previous rounds:
μ = .5(prevRoundMean) + .5(lastRoundDamage)
This calculation weights the damage taken in the last round equal to the damage taken in all previous rounds. This ensures that the most recent round is the most influential in calculating F(d). I found calculating the mean this way works much better for my game. The adjustment policy I implemented is fairly simple. If F(d) > .4, then the game should scale down (get easier). The variables that change are the zombie speed and health. When F(d) > .4, both zombie speed and health are decremented. When F(d) < .4, the game scales up (gets harder). Either zombie speed or zombie health is incremented, each with equal probability. I found these numbers to work well through trial and error. The video below shows an example of dynamic difficulty in action. In round 4, the zombie's speed is noticably slower and they can be killed in 2 shots (instead of 3 in rounds 1-3).
The enemies in this game walk towards the player. Every enemy knows the player's location at all times. The enemy is defined as a Unity "Nav Mesh Agent". This means this entity can traverse a map known as a "Nav Mesh". The Nav Mesh is a data structure that stores the walkable surfaces of the level. The agent then uses this data structure to compute A* to find the shortest path to the player. This functionality is built-in to Unity. More information can be found here
Limitations and Future WorkThe one area I would like to improve on this game is to create more variablity. Right now the enemy spawn points, powerups, and weapon pickups are all fixed. I would like to randomize these so each play through is different. Also, as stated above, I was not able to procedurally generate a dungeon level. One difficulty in accomplishing this (as mentioned in class) is that way the zombies move. The zombies have a "Nav Mesh" which specifies the walkable areas on the map. This nav mesh would not be available when procedurally generating a level. Despite this, I still believe it is possible to add procedural generation and it is something I would like to add in the future.