Baguette is a vertical slice of an action/management game with unique controls where you can move the player's hands individually while trying to complete your tasks in a bakery. The game features fast-paced gameplay with awkward controls, inspired by games like Surgeon Simulator, structured into days where you'll need to manage money to upgrade your bakery and pay bills. It was made in three months at the end of my university placement year.
Baguette was made to cap off my university placement year in 2020. Knowing that I would be working on it essentially full-time for three months, I wanted to make something small, focused, and polished, with an effort to regularly playtest too. I'd been toying with the idea of a game with awkward controls and frantic gameplay, and decided to make a game about baking bread while making money to try pay off your debts. I started the project with a very quick and dirty prototype, and then moved into filling it with content, refining mechanics, and polishing the art. I did playtests every two weeks with friends which gave me valuable feedback.
The gameplay for Baguette focuses on two individual hands that the player can move around the screen. The left hand moves while the left mouse button is held down, and the right hand with the right button. While a hand is active, pressing the spacebar will interact with items the hand is touching. Through these controls the player must roll, score, and bake dough into bread to be put in the shop front. The idea was that this unconventional control scheme would make the basic act of making bread more challenging and engaging, and coupled with the necessity to make money each day, this would create a fast paced, frantic gameplay loop. The hands are individual objects with a PlayerHand script attached, and a HandController object that actually handles input for the hands. To interact with objects, a Touchable class is used to define behaviour for when a hand touches an object. Touchables have a hand enter, exit, and interact event, which other behaviours can subscribe to for responses, as well as corresponding virtual functions for each event. There's also two subclasses, TouchableDrag and TouchableSpawn that define their own behaviours. For example, the TouchableDrag script is a component that allows an item with a collider to attach itself to a hand as long as the hand is active, and uses the virtual functions to attach and detach itself accordingly. The base Touchable class's events are useful for behaviours like the oven door, which opens and closes when it's touched, where the oven script listens for the Interact event on its handle. Facilitating these interactions, a few techniques are used to filter collisions for touchables. First, bitmasks are used with 2D collision functions on the hand's collider to find any relevant overlapping colliders. Secondly, touchable objects are sorted so interaction with them looks visually consistent. When a hand finds multiple colliders under it, a custom comparer is used to sort objects by which are visibly on top of each other, so that the hand will pick up objects that are "closest" to it. Each touchable always has a sprite attached to it, which is used to get its sort order value from the layer and sprite order. To easily check values from all these components when only having the colliders to work with, we use the CacheBehaviour class. Instances that inherit from CacheBehaviour, like Touchable, automatically add themselves to a static dictionary for that class that can be addressed with a GameObject's instance ID to retrieve the associated component. This is used across the game and is useful here with the comparer and for getting the desired touchable itself, as the component is automatically cached and bypasses the need for using the potentially expensive GetComponent call multiple times.
The game takes gameplay is sectioned into days, and at the end of each day you're able to upgrade your tools, ingredients, and equipment with the money you've made. With the upgrade system I took the approach of making the upgradable values actual objects which automatically register themselves with an upgrade manager, instead of just being a primitive value. For example, to upgrade the number of trays that can be in the oven, instead of an int that would be adjusted by its owning script through an event or direct call, it's an UpgradeValue object using the subclass for an int. When a script loads, it initialises these upgrade values, which register themselves with the manager. Then in the upgrade screen, when an upgrade is selected, the value updates itself automatically. This encapsulates a lot of the upgrading code to the upgrades themselves, and makes the scripts that use these values cleaner as they don't have to handle values changing, subscribing to upgrade events, data validation etc. UpgradeValue is an abstract class with a raw value string, and a backend name string. There are several subclasses of UpgradeValue that deal with ints, floats, booleans etc. with overridden functions for parsing and setting their internal value. For convenience, these subclasses also have implicit operators for their type and don't require explicit conversions to use the value. When Init is called on UpgradeValue, it attempts to parse the raw value string into a valid value for its type, and then registers itself with UpgradeManager using its backend name. This backend name is the identifier used to match with the UpgradeData instance that will affect it. UpgradeData instances are editor resources that define the different levels and values of an upgrade, and are the mechanism by which upgrade values are set. When a value is registered it's given to any data instances that are interested in it, and when the upgrade level is changed, it sets the value accordingly at runtime.
Baguette was a very valuable project for expanding my skillset and getting the chance to work with dedicated time on a game to achieve something more substantial than the average university project. I was able to use approaches and systems I'd learnt about in my placement year further, like making heavy use of C# events across the codebase to make my architecture more modular and less fragile. I was also able to experiment with ideas like the upgrade system, which gave me a lot of insight into the difficulties of writing a system like it, and the value of simplicity when designing architecture. I learnt about the importance of playtesting as I got to have regular playtests of the game and gain useful feedback on the state of the project, which was invaluable when I was the sole developer, and reinforced to me the utility of playtesting in game design and production.
While mostly satisfied with the quality of what I produced, Baguette never quite hit my expectations for a truly frantic, fun gameplay loop, and the short development timeframe meant that a lot of plans were left on the cutting room floor. I think the hands and how they interact is fun and novel, but the novelty does wear off, and there's not enough systems in place to keep the game's pace up. I think the quality of the codebase is up to par as well, but definitely suffers from some of my more experimental decisions like the upgrade system, and the specific implementation of the input manager I created for the game. Baguette is an idea I'd like to re-visit again in the future one day, but I'm happy with the state of this project as a vertical slice of the core idea.