A cute 2D puzzle game about Hiro's journey back home to Amma!
A cute 2D puzzle game about Hiro's journey back home to Amma!
An internship experience with Picnic Game Labs where I was one of the primary programmers on the team, helping create a 2D puzzle adventure game with featuring rock-paper-scissors-like solutions.
🔵 Roles: Gameplay Programmer, Shader Programmer, Tools Programmer
🔵 Collaborators: Team of 12
🔵 Year: 2024
🔵 Timeline: 4 months
The Blob Background is what we call the outline around the elements that fit to the baord’s shape.
There's 3 main goals I outlined when programming this system:
Create a fully automatic system to make a blob fit into any puzzle of any shape (no manual work).
Use Shaders to create an outline for the blob.
Tune parameters to make it less or more blobby.
For this attempt I didn’t read the initial prompt clearly enough. It was mentioned that “we want them to take the shape of the puzzles but no negative spaces/holes within”. I understood this as ‘make a rectangle around all the elements’. This encapsulates all nodes but fails to take the shape of the puzzle board, which is why communication is so important and fully understanding the system before implementing it. To achieve this first result, I used a sliced rounded rectangle image to ensure that the corner’s aspect ratio stays the same when scaled. Then through code I scale the size Delta of the image based on how many elemental nodes there are in the X and Y axis. I’m proud of the methods created with this system as I’ve used the dependency injection pattern to completely isolate the functions from the rest of the script.
Before trying this concept in Unity, I decided to make a quick prototype in photoshop to see if it would actually work. I made a circle with a radial gradient, and increased the levels to remove grays. Then, in the curves I increased it to enlarge the circles. With the simple adjustments to the levels and curves, I was able to achieve meatballs in photoshop! I created many circles and tried to overlay them onto a puzzle background, but noticed that no matter how hard I tried, I could not get a clean replication of the blob background in the concept art. 2D meatballs were a no-go and I’m glad I did not first pursue them as it would have consumed much of my time for a result that did not satisfy the conditions.
I didn’t spend too much time on this, but I was playing around with open and closed sprite shapes in unity, which use splines to create shapes. I hoped to make a point for each element and then somehow increase the width, however with this technology it was just not possible.
After many approaches, I leaned into using tilesets to form the shape of the puzzle. The problem is, how do I know which tiles to place down, and where the nodes even are on the board? There's also the other issue of needing different types of tiles such as round vs sharp corners which cannot fit into the same default tileset. The solution is that I have different rule tilesets, 1 for the regular tiles (Just Vertical and Horizontal Paths), 1 for creating Curved Tiles (For puzzles with curved paths), and 1 for placing a Cap at the end of a node that has 2 paths. With these different rulesets I can place specific tiles from the specific rulesets.
Below is my process for achieving the tileset solution:
In a script I store the x and y dimmensions of the whole game board as if it were a rectangle from bottom corner to top corner represented by the coloured tiles in the image below. Then, I store all the elemental nodes in a 2D array and set the node's index to it's x and y position in the json file that's used to generate these puzzles. This way, there will be some indexes in the 2D array that are blank with no node, and some that have a node stored. Below is a visualization of the result.
2. I then first loop through all the indexes that HAVE a node (all the green cells) and place a tile there with the default tile ruleset. I also check if there is a "soft end" and place a tile if so too. Soft end tiles make the overall shape of a circle or arch more smooth instead of giving a flat line. I have many helper functions to get the neigbouring nodes or nodes down the line!
3. I then check for what I call "Special Tiles", where they are usually the connecting tiles between 2 nodes. For example, getting the curved edge, or placing a tile in between a 3X path that connects 2 nodes.
Great! I've managed to place the tileset down, but there's many sharp edges which didn't align with the concept art provided to me. To achieve this magical softening, I use post-process effects and a fair bit of overrides to achieve this effect, but the essence is adding Gaussian Blur to soften up the tileset, then playing around with the color curves and Shadows, Midtones, and Highlights to expand the white area and soften the corners. The “Color Adjustments” override allows us to amplify the effects of the blur component if we want it blobbier or not.
Click here to see an image of the post processing overrides used in Unity!
This concept of blurring and then adjusting the curves was inspired by 2D metaballs, where you apply those same modifiers to connect objects together in a blob fashion. Here's a video I created experimenting in Photoshop FIRST before trying to make a solution in Unity for softening sharp corners. That saved countless hours of going down a path only to realize it would not work.
I was tasked to later add a glow to the blob background I created, and immediately thought of using Bloom as a potential solution. I thought it would be as simple as adding the Bloom override to the post-process volume I used for the blob roundness. When applying the override however, no noticeable results were seen, and sometimes, all elements on the screen would be affected by bloom.
The solution was to use a Camera Stack and separate the puzzle scene into 3 main layers, with only the Camera 2 enabling post-processing for the bloom.
I learned how to properly stack cameras instead of using the base camera always. When trying this, the post process effect would always appear in the front-most layer. This is because for base cameras, post-process effects are always applied at the very end of the front-most camera's render, after everything else is rendererd. The benefit of using a camera stack is that we can apply post process effects in-between rendering the other camera's components and have it only affect that layer. Here are the final results of how different world's puzzles look with the glow effect!
The Abacus System is a UI element found in the top corners of the puzzle scene that keeps track of: the current move count, the goal amount of moves to complete the puzzle in, and the state of the game board through thte dots in a circle, where each dot represents an element and connected dots represent groups.
To achieve this result, I've thought of a few solutions:
1. Use the LineRenderer Component to create segments of the circle and that can extend to connect to the next segment.
2. Use 2D metaballs to add a nice visual effect when we're merging the blobs. The only issue I saw is that it might be difficult to connect 2 separate gameObjects with a shader.
3. Use shaders to draw circles and use the prior shader that draws the paths between nodes.
I started by using Sin and Cosine trigonometry to place dots around a circle with an even spread. This was achieved by getting the increment and increasing an angle by the increment every time we spawn in a new line dot.
With the correct circle, I wanted to then animate a line connecting to the next element. Currently, it is straight to the next element, but I want it to be in a circular shape and not straight to the end goal.
I thought of this system, where we can place points in between the elements and have the line segment animate through the points until the end goal. Below shows the end-product of adding sub-points!
When the line segment reaches one point, it adds a new position to the line renderer and then animates that new position to the next one. This image shows how adding more sub-steps between points can smooth out the circle.
Each dot in the Abacus Manager reflects the state of an element on the puzzle board. This connection took many hours of trouble-shooting to get right. Each Abacus Element is set to an ID and when a move is made, I re-check to see the state of the puzzle board if there are any new groups and if so, animate the Abacus Element that shares the ID of the puzzle element using Coroutines.
The final step of the Abacus System was to import the sprites our team's artist created and put them in engine. I anchored them to the top right and top left positions, and added outlines using Unity's Outline Component for UI elements which I didn't know existed until this addition. Throughout this implementation of the UI I kept an organized hierarchy that aligns with the visuals.
As a tools designer, I love User-Centered Design, and as a programmer, I always ensure whatever I do can be easily modified by designers, and easily accessed by other programmers. The colours of the Abacus UI change per world, and so I created a helpful array of colours visible in the inspector for designers to choose what colours are represented in each world. Along with this, I created a helper function to get the specific colour we need based on the current map we're in as shown in the code below. To know which map we're in, I created an enum with all the map names that designers can set in each scene for the code to know which map we're in.
Lastly, for the Abacus Manager I've made a few fields public for the designers to tune for closer alignment of their vision as seen in the images below. The parameters with red dots can be adjusted by the designer to change the way the UI looks/feels.
And here's a short video of the system fully working in-game! Enjoy :)
Had to implement new paths and arrow animations that communicates which actions are available to the player. Collaborated heavily with the other programmer to understand the previous system and build upon it. When they could not continue with this task, I took over and figured it out/ implemented it.
Scene Transition System Documentation
- UI, Tools -
Blob Background Documentation
- Tilesets, Shaders, Post-Processing -
Abacus System Documentation
- UI -