Procedural Generation
Today, I decided to revisit an old engine I’ve used in the past, the Zero engine, in order to assess my memory of it. In my humble opinion as a computer programmer, it is only natural to know and understand multiple languages as well as to be able to learn new ones quickly. For this, I decided to revisit an older project of mine called Spell and Smite and make it more interesting.
Rather than modify the existing project, I decided to recreate it from scratch. I started first with a rotation script so that the player constantly rotates to face my mouse cursor. To accomplish this, upon every frame the script stores the mouse position and find the distance between the player’s translation and the mouse point. Then by using the signs of the X distance and the sign of the Y distance. Then we subtract 180 degrees (or since the function returns radians, pi / 2) in order to flip the player around. By repeating this every frame, the object this script is attached to will consistently look at the mouse cursor.
Movement remained quite similar to the old project so I won’t discuss it too much, we simply determine which button the player pressed and either add or subtract the x or y of a 3-dimensional vector that will be set to the owner’s velocity. The zero engine has a built-in gravity system that was not needed for this project, and without changing the gravity, the movement would be incredibly “floaty” so to remedy this I simply set the player’s mass to be a large number, creating the firm movement from the original project, when you could just simply remove gravity altogether.
One problem I had with the original was that when attacking, a gray box would appear to showcase the player swinging their sword, while this was intentional at the time, it looks rather unappealing. So, to remedy this instead of creating an archetype with a collider that damages the enemy, I instead just enable and disable an existing collider while rotating the hand/sword. This works, however, it ends up looking somewhat unnatural, even for a square character. But, in my opinion, it works and looks a lot better than previously.
Here’s the sword swinging, a considerable improvement from the original project.
Additionally, to actually challenge myself I wanted to see if I could create a randomized dungeon. Procedural generation, whether it be for a simple project, or for an entire game, such as that of many popular games of today, has always fascinated me. Utilizing the ‘random’ness of a computer to create parts of a project was always something I longed to do. This seemed the perfect playground for me to attempt this. After a few hours of brainstorming, I decided on this method:
First, the code would generate the size of a map, based on 4 predefined variables for minimum x, maximum x, minimum y, and maximum y, and X by Y grid of all 0s to begin. Then the code would generate a pathway through the use of many individual nodes, although in actuality the path nodes are the only actual rooms of the dungeon. Each path node would correspond eventually to one actual room, so a path with 7 nodes would have 7 rooms roughly the same size as the image above. To help keep track of what exits each room possesses I created an enumerator to keep track of 6 separate integers, 4 for the cardinal directions, 1 for if the room is a starting room, and 1 for if the room is an end room. I decided upon the numbers 1, 2, 4 and 8 for north, south, east, and west respectively, 16 for starting rooms, and 32 for an end room. The code first generates a random x and y coordinate for a starting node, the starting room value (16) gets added to it and the code generates a random number between 1 and 4 and a directional variable gets added to the room’s value, for a range between 17 and 24. The code then stores the opposite direction value in a separate variable called CameFrom. After a starting room with an exit has been determined, the code moves by 1 node in the direction of the current exit, and if the room node is equivalent to 0, the code will generate a new direction, if this new direction is not the same as the CameFrom variable, it gets added to the room’s node as well as the CameFrom direction. For example, if we came from the North and the code generates a west exit next, the total value for this specific node is 1 + 3 for a total of 4. This process repeats itself until the total path length is equivalent to a predefined path length variable, in which case the end room value gets added to the last created node. If the code is unsuccessful, as in it runs into the edge of the map, and has no available places to create rooms, it will move by one index in the CameFrom direction and try again. If the next attempt is successful the tries are reset, and if not the code will attempt to try 100 times, and in the event it is still unsuccessful. the last created room becomes the end room, no matter the path length. After a bit of testing, the code seemed to generate dungeons rather well in a random order.
Here is an example of a generated path. The red line translates the numerical nodes into an actual path.
Now that it generates a text display, I need it to generate actual levels. To do this, each different room variant would have its own level archetype, and these would simply be loaded upon request. However, this would require I make 14 different levels for each variant (luckily since each room had a max of 2 exits it was only 14, imagine the amount if a room could have 3 exits!) This turned out to be among the most challenging parts of today as I had to meticulously ensure each room had the correct amount of exits at the correct location. Additionally, each room’s name would be important as well, the codetermines the correct room to load by finding the level named DungeonX where X is the node number of the room, so the names of each room would have to all be similar in spelling, case, etc. The code first starts by displaying the starting room, and when the player enters a door, the code displays the next room. With enough bug testing and troubleshooting, the game properly generated a random dungeon and correctly displayed the levels. With this done, I feel as if I successfully created a working procedural generator. Mission accomplished!
However, this would not be the end of this project, as there was a task I wanted to accomplish for the next day: an inventory system.