Puzzle Garden
Role: Gameplay, UI, & Tools Programmer
Tool(s): Unity Engine, Git, Click Up, Figma
Genre(s): Puzzle, Mobile
A short, casual puzzle game where you utilize unique land layouts and crop abilities to maximize your profit. The game was released on Steam through DigiPen and the people who worked on the original reached out to me to work with them on making the mobile game version!
Original GameBy default, the editor in Unity does not display grids in a user friendly nor does it show a preview of what a level we were building would look like. The editor I've made on the right works like this:
Grid Size: determines the size of the Tile Type Grid, Crop Placement Grid, and the Visualization Of Grid
Tile Type Grid: will hold data that can be changed by user using drop down menu. The selections that the user can choose is reliant on what Tile Scriptable Objects exist. This can easily be expanded in the future.
Crop Placement Grid: will hold data that can be changed by the user using drop down menu. The selections that the user can choose is reliant on what Plant Scriptable Objects exist. This can easily be expanded in the future.
Visualization Of Grid: takes the data from the two previous grids and will show the sprites of the data given in an overlaid manner. This way the user can see how the level will be set up without having to play and test the game.
The next task implemented was intuitive plant interaction mechanics: clicking or dragging on tiles places, waters, or removes plants based on key press. Once the UI is implemented, the player should be able to select the type of plant they wish to plant, a watering can, or a shovel to decide what they want to do.
On Next Day pressed, the plants that need water and was watered will move onto the next growing stage resulting in a sprite change and the water effect on tiles will be removed. The functionality for harvesting the crops will be passed on to another programmer.
Building off of the previous implementation I talked about, I implemented the UI of the tool wheel to let users pick what tool they would like to use and visualize the button to show what current tool was being used. Upon harvesting or planting, the tool button will reset to no tool selection to prevent confusion.
To call the function to change the image of the button, this is called: UIManager.Instance.HighlightTool(currAction);
This function is called when the player holds on the tool button to make the tool wheel itself visible.
public void SetToolWheel(bool openWheel)
{
ToolButtonCollider.transform.parent.gameObject.SetActive(!openWheel);
ToolWheel.SetActive(openWheel);
}
For the behavior implementation, a programmer proposed rather than building different plant objects with their unique traits built in, that we create scripts for each unique behavior and add it as a component of the plant object to help with reusability which will help avoid repetitive code.
I agreed with this statement and proposed to add interfaces so that we could add unique behaviors when they were tied to an event that all plants share. For example, we wanted the pumpkin's behavior to have: On harvest, change regular dirt tile to exhausted.
Interface Script: IOnHarvest.cs
The written code below is what an interface looks like and we can create more based on what we'll need (OnPlant, On Water, etc.).
public interface IOnHarvest
{
// writing this means this is a
// required function to implement on inheriting
void OnHarvest();
}
Behavior Script: ChangeTileOnHarvest.cs
By inheriting from IOnHarvest interface, we are saying that implementing OnHarvest() is required for this behavior.
public class ChangeTileOnHarvest : MonoBehaviour, IOnHarvest
{
...
public void OnHarvest()
{
// change tile from regular tile to exhausted tile
GameObject currTile = plantRef.gameObject.transform.parent.gameObject;
currTile.GetComponent<BaseTile>().ChangeTileType(tile);
}
}
Script: BasePlant.cs
Now when we create a new plant using the BasePlant script, we can just check to see if the IOnHarvest interface component exists, if it does we will call the correlating function. If it doesn't exist, it will proceed as normal.
public void Harvest()
{
// add to score for end screen
LevelManager.instance.scoreManager.AddScore(Data.Reward);
// check if we have additional harvest behaviors to run
// that are unique to the plant
foreach (var component in GetComponents<IOnHarvest>())
{
component.OnHarvest();
}
// destroy plant
Destroy(gameObject);
}
To make the game interesting, the team wanted to be able to place plants of different shapes and sizes. The wheat which represents our most basic plant takes up one tile and the pumpkin is currently planned to take up 3 tiles as a 1x3. We will worry about rotating plants but we wanted to get the base implementation of this first.
We already know what the size of the plant is in code because designers will have this property set in the plant's scriptable object by adding in their coordinates in the shape array. An example of the pumpkin is shown below:
Then I programmed a check for when the player tries to plant something that is bigger than a 1x1:
public bool CanPlacePlant(PlantObject plantData, Transform originTile)
{
// already has a plant on this tile, can skip the check since we can't plant
if (originTile.childCount > 0) return false;
// if plant is bigger than 1x1 tile
if (plantData.shape.Length > 0)
{
// GetShapeCoordinates will use the shape array as an offset to know what the coordinates
// on the grid we're planting is
GameObject[] relativeTiles = TileManager.Instance.GetShapeCoordinates(plantData, originTile);
// if it's false, it means one or more coordinates are out of bounds of the grid
if (relativeTiles.Length != plantData.shape.Length) return false;
// check that all tiles are available
foreach (var tile in relativeTiles)
{
// tile doesn't exist, out of bounds or a plant exists here
if (tile == null || tile.GetComponent<BaseTile>().RootTile)
{
return false;
}
// check that it's okay for plant to be on this tiletype
if (plantData.tileToPlantOn != tile.GetComponent<BaseTile>().Data)
{
return false;
}
}
// passed all checks therefore it's placement is valid
return true;
}
return false;
}
Stars in the top right will reflect the current score earned as the plants are being harvested. Reaching 3 stars means we've reached the target score.
On clicking the plant button instead of dragging will cause a detailed version of the plant button to show up and describe it's behavior or size.