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 ensures 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
To visualize which tiles are selectable, each time a selection is made the creator checks each adjacent tile to determine if they should be highlighted. If the adjacent tile is the start, a corner, or another tile listed as un-selectable they are ignored by the creator. If the tile is determined to be empty they are immediately highlighted. If the creator finds that the tile is straight, it looks at the next tile in that direction. It continues to do this until it finds an empty tile, and un-selectable tile, or it runs out of tiles to check.
Assigning Track Pieces
The first two selections are important in that they help the creator to determine the starting position of the track and where the final track piece will be placed. The final track piece is indicated by the yellow tile.
If the start and final track piece have already been determined each newly selected tile helps to determine the permanent track piece for the previously selected tile. By calculating the direction the track moves into and out of each tile the creator is able to find and select a corresponding track piece to use for each tile.
When the final track piece is within range and the user selects it the track is automatically completed and any unused grid spaces are removed, leaving behind the finished track.
Intersections & Underpasses
Intersections and underpasses are automatically created when the user clicks on a highlighted tile that is more than one space away from the current position. When this occurs that creator calculates each tile located between the newly selected tile and the current position and changes their asset to an intersection or underpass.
Users can modify whether the creator uses underpasses, intersections, or both from within the pause menu. Any existing intersections or underpasses are unaffected by the change.
Saving & Loading Tracks
From within the pause menu, users are able to save their created tracks. When saving, the user is asked to give the track a name before they are able to save. After clicking save, the creator checks for any existing saves that have the same name. If one is found the user is warned that the existing save will be overwritten if they continue.
Upon saving a snapshot of the track is taken, this includes references for each track piece and the order they are in. When the user loads an existing save the existing track creation scene is loaded and the previously created track snapshot is used to populate the grid with the correct track tiles.
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.
To simplify the creation of matching elements, I chose to use two editable colors and one indirectly editable color. This allows for every UI element in the scene to pull their colors from the same source, resulting in matching elements.
As the user changes either color, the UI elements in the scene view will change to match the current settings. Allowing the user to see what the elements will look like without having to be in play mode.
Loading & Storing Assets
Within each element’s component, a reference to the current asset is stored. When the user makes alterations to the element’s settings such as shape or size, the new asset is found and it replaces the previously stored asset. Every asset that is needed to create each type of UI element is stored in a sprites folder.
In order to simplify the creation of new assets, an editor script is used to automatically format .pngs to the proper settings when they are imported into the correct 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.