The idea of the cake baker game is simple: make the cakes and get them out the door. It will be a fast-paced bakery simulator with fun physics and goofy mechanics. Don't like a customer? Throw a cake at them! Cover the walls using the frosting gun! Eat the cakes yourself! And best of all, it's all in VR. This series will highlight the progress we're making on this exciting game.
This week's challenge is making the frosting hose snap to the player's controller and then be able to shoot the frosting when the trigger is pulled.
Our summoning system is built using our custom event system (documentation pending). There is the
Summoner component which extends
EventListener and is put onto each player controller. It handles
PointAway events sent from the controller input trigger when a raycast from the controller is touching a
Pointable game object. It also handles the events when the trigger is pulled to summon the object the controller is pointing at. We put the physical summoning logic into the
update(), updating the velocity each frame to smoothly bring the object to the controller.
There are two problems with the current system:
- Summoned objects can't be smoothly sent home. Currently, once we let go of an object it immediately snaps back. We could add the logic to do the inverse motion to the main update() loop, but that does not scale. If you pointed at another object during that transition keeping track of both sets of motion would be very complicated and error prone.
- It doesn't work within the physics simulation. Summoned objects go through walls and obstacles rather than bump their way around them. This could be fixed regardless of the implementation, but if we're going to refactor we should do it right! :)
To fix these issues we moved our implementation of the summoning logic from the
Summoner to the
PointableObject component. This means each item you point at is now responsible for moving itself to the controller when summoned, and returning home once it is no longer needed. This lets us point at any number of objects without worrying about moving them all in one update method. We also managed to clean up the motion logic to use forces instead.
Summoner component now simply acts as a coordinator. It still handles the
PointAt enter and exit events, and accepts the controller inputs, but now instead of taking action to move the object, those events are passed off to the
PointableObject then handles two new events:
SummonStopEvent. On a
SummonStartEvent the object moves itself to the desired location, eventually "locking into" the target once it has fully arrived. Unsurprisingly, the
SummonStopEvent does the same thing, but back to the objects starting position.
With the summoning in place, it was time to actually make the summoned object shoot some frosting! This very quickly presented a problem: both the summon command and the start frosting command are tied to the trigger pull. We needed to change the summon button to be to the grip button. Rather than make this a one-off change we decided to build a more generic input mapping system. This system will allow us to change all the controls in one place.
We built this using the event system as well, so the new
InputMapper class is an
EventListener that accepts the events from the
ControllerTrigger and maps them to action start/stop events. For example, the
InputMapper listens for a
ControllerButtonDownEvent, sees it is for the
SteamVR_Controller.ButtonMask.Grip button, and fires a
TrySummonStartEvent to indicate to the
Summoner that it should start summoning. It's then up to the
Summoner to decide whether summoning makes sense and take the real action.
Building the mapper was relatively straightforward, just set up some switch statements to handle the different inputs and map them to the new action events. After porting a few systems to the input mapper we found a new issue - different buttons do different things at different times. When you have the frosting gun summoned, pulling the trigger should shoot out delicious metaball frosting! When you're hovering over a cake, pulling the trigger should pick it up. To reconcile this we added new logic to the input system to manage what state each controller is in. It tracks whether there is summoned object or not, and then uses that to route the actions.
The long term plan is to build a finite state machine to handle the different modes our controllers can be in, and how the player will be able to switch between them. This should scale better than a
Dictionary<SteamVR_TrackedObject, Summonable> for storing whether an input should go to the summoner or the grabber.