Learning from the technical aspects of implementing puzzles and level events in CoBots, I was determined to come up with a solution for implementing the puzzles that generally would be easier to work with. The system had to fulfill the following requirements:
- Easy to visualize
Quickly being able to grasp how things are tied together saves a lot of time in the long run and makes it a lot easier for someone else other than the initial author to grasp the system
- Easily interchangeable prefabs
When working in a team on a project of this scope and on this timescale, rapid iteration is key. You should take this into consideration and design your tools to allow for it. Being able to easily switch out an entire object does just that.
- Self contained and environment-unaware prefabs
What does that even mean? It ties into the point above, for the different puzzle components/prefabs to be easily interchangeable the different instances can’t be very specialized as it would force you to do a whole bunch of setup whenever you want to switch something out. It also mean that the prefabs themselves should not concern themselves with anything outside their own hierarchy.
- Keep serialized references to a minimum
This further ties into the “interchangeable prefabs” point, to make that process as simple as possible, it’s easier if the references to a object is kept to a minimum. Also, Unity inspector object references is more or less an accident waiting to happen, something will happen which will magically break the reference connection for no apparent reason.
After giving it some thought and while taking all of the requirements above into consideration, the puzzle system that I came up consists of these three parts:
When activated, will send a predetermined event to the PuzzleManager. How it’s activated and what event is sent is determined by what sort of object it is.
Holds references to all of the PuzzleTriggers and PuzzleTargets. Will convey any events from PuzzleTrigger to all the registered PuzzleTargets.
Listens to and acts upon events set up in the editor.
The different events that can be sent across the ‘network’ is simply an enum, containing generic, common events (ON, MOVING, IDLE, etc).. One compromise had to be made when it comes to the PuzzleTarget, you choose per instance what event act as activation and/or deactivation. This is because you often would like several different things affecting the same object but do different things.
Here’s a simple example of using the system. The lever and the door does not know of each others existence, not even at run-time. The PuzzleManager holds all the references and makes sure the event triggered by pulling the lever reaches the door.
Here’s the most complex use-case we’ve got for the system so far. It centers around a rotating platform which can be moved by several different levers. Each lever corresponds to a different rotational state and depending on what state it’s in, different doors are open.
It’s actually somewhat more complex than that, the player needs to move a minecart across the platform so the PuzzleManagers also manages which pieces of rail are currently connected. It also disables all the levers while the platform is moving, re-enabling them once it stops moving.
With all the green and yellow lines, at a glance it looks rather confusing, but while working with it all makes sense and is still somewhat clear. Admittedly, this case is more complex than the system was designed for, so some improvements can certainly be made. For example, some objects could be grouped together under a common manager if they activate/deactivate under the same circumstances.
I’m quite happy with this system as it achieved all the goals we set and stayed well clear of problems we’ve had with setting up puzzles previously.