Exploration Projects are personal projects that were created with the intent of exploring the uses and learning about the implementation of different systems, mechanics and editor tools. Each of the projects that I have chosen to showcase focused on a specific topic that I found interesting and wanted to explore more in depth.
Procedural Track Generator
My goal was to create a program that was able to generate a mesh track that looped. Using the Gamasutra article, “Generating Procedural Racetracks", by Gustavo Maciel, as guide to the steps that should be used. I wanted the variables to be adjustable. My other goal was for the program to run through the steps automatically or be able to manually go through each step to easily visualize what each step does.
Users are able to make adjustments to certain variables in order to modify the track that will be created. By adjusting the width slider, the user can change the area in which random points are created. Users are also able to adjust the number of random points that are placed. With fewer points, tracks tend to be simpler, with fewer curves. The minimum distance between points variable is used when placing the random points to prevent any two points from being too close to one another. The next variable is the midpoint chance. This variable is responsible for deciding whether a segment of the track that is long enough also receives an offset midpoint, which adds an extra curve to the track. Lastly, is the show steps toggle. This setting determines whether the process will automatically run through till completion or stop at each step allowing the user to see each step of the process.
A point is placed by randomly generating an x and y coordinate within the bounds specified by the user. I then compare the coordinate to any other previously placed points to determine if the distance between the points is above the minimum specified. If they are below the minimum, the coordinates are regenerated and checked again. If the point I above the minimum, a point is placed and the process is repeated until the number of placed points is equal to the number of points specified by the user.
The convex hull is a list of points that when connected to one another will encompass every other point. To calculate this list I begin by identifying the point with the lowest x coordinate and setting it as my current point. I then calculate a non-existent point with the same x coordinate as my current point. The next step is to find the next point in the hull. I do this by calculating the angle between the non-existent point, the current point, and the potential next point. At the end the point that creates the largest angle is determined to be the next point. I then move on to the next with the current point becoming the previous point that was originally the non-existent point, the next point becoming the current point and searching for the next point. This process repeats until the next point is equal to the original current point, thus a loop has been made.
Using the list of points that make up the convex hull, I calculate the distance between each point and the next point. If the distance is greater than the minimum set for a midpoint. I use the midpoint chance variable set by the user to determine if a midpoint should be placed. If it should, a midpoint point is placed half way between the two points and offset to create a random distance to create a curve in the path.
Cubic Bézier Curves
Using the list of edge points that now include the original convex hull points and the newly created midpoints. I create a curve for each segment of the track by using the handles attached to each of the points. By calculating the arc-length of the segment I am able to dynamically adjust how many points are created along the segment, which insures that my points are close to being equally distributed along each curve.
Once each point on the curve has been placed and rotated towards the next point, I calculate two additional sub-points, to the left and right of the point. These subpoenas become the vertices for the tris and uv map. I then generate the mesh and place a texture onto it adding the lines to the edge of the track.
This was easily one of the most educational projects that I've worked on. It gave me a chance to learn more about procedural generation, working with algorithms, and mesh generation within Unity. When it came to creating the curves of the track, I originally implemented a Catmull-Rom Spline, similar to what Gustavo Maciel used in his article, but I didn't like the results that I was getting, as the curves were too pointed and adjusting the variables couldn't be adjusted too much without resulting in loops in the track. This lead me to research other methods where I eventually found and decided to implement the Cubic Bézier Curve instead.
My goal for this project was to create a user friendly track creator that would allow the user to quickly build a race track. In order to make the creation process as quick as possible I chose to forgo use of track selection, instead focusing on a way to calculate what track piece the user intended when they clicked the tile. To reduce the user's chances of creating dead-end paths, I decided to implement the ability for the user to create intersections, which allow the user to cross over existing parts of the track. The second thing that I wanted to implement in order to reduce dead-ends, was the ability for the user to undo their track. In order to really challenge myself as well as give me an opportunity to explore saving and loading within games, I also wanted the user to be able to save and load their tracks.
Information regarding each track piece is stored within the track piece class. This information includes the sprite asset of the track piece, what type of track piece it is, and most importantly, the primary and alternate in and out direction of each piece. The in and out direction of each track piece is used when determining which track piece should be assigned to the grid space. By having both a primary set of directions and an alternate set, I was able to reduce the number of track pieces by about half since I can use the same track piece for both an up/right corner and a left/down.
Tile Selection & Calculations
Before the user is able to create a track, they need to know what tiles can be used. At the start of track creation, a bool is true that instructs the creator to allow the user to select any tile that isn’t on the edge of the grid. Once a tile has been selected, the bool becomes false and the user is now limited to highlighted tiles. To calculate which tiles should be highlighted, the creator checks each of the adjacent tiles around the previously selected tile. If the tile is empty, the space is highlighted. If the tile is a corner, start, or another un-selectable tile, it ignores that tile. If the tile detects a straight track piece, it looks at the next tile in that direction. It continues to do this until it hits an un-selectable tile or an empty tile. If it hits an empty tile, it is highlighted.
On the user’s first selection, the creator places a placeholder starting tile, since it doesn’t currently know what direction the user’s next click will be. On the user’s second click the final start tile is assigned, the finish tile is assigned to the opposite side of the start from the clicked tile, and a placeholder tile is placed in the clicked tile’s position. Following this every other click calculates the previously clicked tile’s final track piece using the direction into that tile and the direction out, as well as placing a placeholder tile into the clicked tile’s position. If the user clicks on the finish tile when they are within range, the track is automatically completed, and any unused grid spaces are removed, leaving the finished track.
Intersections & Underpasses
When the user clicks on a highlight square that is more than one grid position away from the last clicked tile, the creator knows that the user wants to create an intersection. At this point the creator changes each of the tiles between the last clicked tile and the new tile to be an intersection. Users have to ability to modify whether an intersection or an underpass is chosen from within the pause menu. Users are able to select intersections only, underpasses only, or use both. Any existing intersections of underpasses will not be altered if the user changes this setting, but any created after changing the setting will be effected.
Saving & Loading Tracks
When the user enters the pause menu, they have the option to save their track. From within the save menu, the user is required to give the track a name. Once they do this the button to save become active. After clicking save, the creator checks to see if an existing save has the same name. If it does a warning appears to let the user know that the previously saved track will be overwritten if they continue. If the user doesn’t want their track to be overwritten they are returned to the save screen where they can enter a new name. If the save is accepted, a quick snapshot of the track is taken. The snapshot includes a reference for each of the track pieces in the grid as well as a list of every track piece that is used in the order that they were used. When the user loads a previously created track they are brought to the standard track creator scene, except rather than giving the user a blank grid, each tile is restored to its previous state. Because the save stores a list of used track pieces in the order they were used, the user is also able to undo those tracks.
By pressing z or scrolling down, the previously placed track piece is removed and they are able to select a tile. This can be done all the way back so that the grid is empty and the user is selecting the start position. If the user uses undo in a loaded track the save is not altered unless the user overwrites the existing save.
I am extremely happy with the results of this project, some of the areas that that were the most challenging included saving and loading tracks, undoing, as well as creating intersections. But, those were also the areas in which I learned the most. Overall, I am very proud of what I was able to accomplish. As I’ve moved onto other projects, I often find myself loading the creator up and creating simple tracks or trying to make the tracks with as many intersections as possible.
UI Creation Tool
After having worked with Unity’s built in UI on some of my past projects, I came to the realization that, though building a basic UI was fairly simple, the amount of time it took to set up each individual element was time that I could have been spending focusing on other parts of the project. This lead me to the idea of creating a set of tools that I could use to quickly build out menus, in just a couple of minutes. As I set out building these tools, I knew that I wanted to avoid unnecessary clutter by only including the most important pieces of data. In order to make the creation of menus as quick as possible I also wanted to find ways in which I could build and edit elements with as little mouse movements as possible.
The UI Creator window is responsible for the creation of new UI elements. Within this window the user can give the element a name, size, shape, position, as well as selecting which type of UI element it is. Once the user is satisfied with their settings, they can click the create button and a new element is created with the settings that the user has entered.
The UI Editor window is responsible for the editing of all elements on the canvas. At the top of the window are two editable colors that determine the color of each button as well as the background of the menu. A third indirectly editable color is calculated based on these two colors. Below this second are options for the shape and alignment of each element. By pressing the make change button every element in the scene will be edited to match the settings. This allows the user to quickly make adjustments to all buttons so that they match. The last section is a list showing each element sorted in the order in which they appear from the top of the screen. Beside each element’s name are two buttons. One selects the element in the scene allowing the user to quickly make changes to that element’s components without impacting the other elements. The other deletes the element from the scene.
Types of UI
trigger an action when they are clicked. The Action variable within the Button Editor allows the user to quickly change between; change scene, call function, or quit application. When change scene is selected a second dropdown with all scenes in the build settings are listed allowing the user to quickly select which scene will be loaded. The quit application requires no additional steps. The call function creates an EventTrigger component that the user can quickly connect to a function.
allow the the user to toggle between two options. Within the Toggle Editor, the user can assign labels to each side of the toggle, set the starting value of the toggle, as well as toggling the label for the toggle itself. When the label is visible the user can change the orientation of the label and toggle between horizontal and vertical.
allow the user to change the value of a variable between a min and max value by moving the slider’s handle. Within the Slider Editor the user can adjust the min and max values as well as the starting value. Similar to toggles, users are also able to toggle the label as well as a box that displays the current value.
allow the user to enter text that are held in a string. Within the Text Input Editor the user can adjust the text alignment within the input field. I chose to use unity’s built in inputField component for the base of this element and overlay a custom editor on top that simplifies the editor to the main areas that I use.
In order to simplify the creation of matching elements, I chose to use two editable colors and one indirectly editable color. This way every element in the scene will pull their colors from the same place insuring that every element’s colors match, resulting in matching elements. Within the editor as the user changes either of the colors, the UI elements in the scene will change to match the current settings. This way the user can see what the elements will look like without having to hit play.
Loading & Storing Assets
Within each element’s component, I find and store the necessary assets based on the element's current settings. When the user alters settings such as the shape or size of an element. The new asset is located and replaces the original asset. Every asset that is needed to create each type of element is found within a sprites folder. In order to simplify the creation of new sprite assets, I have created an editor script that automatically formats .pngs to the proper settings when they are imported into this folder.
Overall, I am pretty happy with the outcome. It’s easy to create new elements as well as edit previously created elements. By simplifying the creation process I am now able to create a menu with each of the different types of elements with a couple of minutes. By importing the UI creator package into an existing project I can quickly create menus.