I hate building tutorials. They require all sorts of unrelated systems to talk to each other. Things must talk to each other that have no business even knowing each other exist. You have to build functionality not used elsewhere in the game. They are the quintessential special case. There is nothing I hate more than a big messy spider web in my otherwise elegant codebase.
Tutorials are essential from a UX perspective. The tutorial is the first thing that most players will see of your game and if it is crap it will be the last. You have only one chance to introduce players to your game in a way that won’t send them running for the hills.
Games must introduce concepts in a way that’s not overwhelming, but they can’t be boring either. They must draw the player in without drowning them. Tutorials can use text, but many players won’t stop to read for long (or at all!) so every word must count.
Compounding the problem, I had almost no time. One week before the feature lock at the alpha milestone I found out we required a new tutorial. One week or no tutorial! Talk about pressure.
Lance, our lead programmer warned me against overengineering, but I’ve never been a great listener. I ended up using inheritance and interfaces to make a flexible tutorial system that is slightly less disgusting than code written on a hard one-week deadline usually is.
My first idea was to make a manager class with many different functions that were moved through sequentially to fill in a premade UI card. This would be a very traditional Unity approach. The problem though is that this approach severely limits flexibility. There would be one card of one size at one position on the screen for every single prompt! The manager would become a monolith, subscribing to dozens of events and touching many many systems.
Instead, I chose a more OOP approach. I think most developers forget about OOP principles as soon as they hop in a game engine. Scripting doesn’t feel like programming so we let our guard down. Like Mad-Eye Moody, when coding, constant vigilance must be your motto.
I started by making a base class TutorialCard which has one virtual function IsComplete and inherits from monobehaviour. I did not know you could make subclasses of a class that inherited from monobehaviour and attach them to game objects! I had to google it! An interface would usually make more sense, but Unity does not allow interfaces to be dragged in from the inspector so abstract class it was.
I made each card a separate panel with a child of TutorialCard attached. Each script controlled all its functionality and each panel was separate. This was more work, but it allowed the controls prompt to have a list of checkmarks and the shoot prompt to be in a better location.
Then the classic code bloat of inheritance raised its ugly head! Some cards needed to know when the player collided with certain objects, others didn’t. I made the terrible decision to add a NotifyCollision function to the base class and provide the player with a list of cards to notify. I hated this solution right from the start. Firstly, it tightly coupled the player and the tutorial, setting me up for more problems down the road. Secondly, it meant that all other cards besides two were now stuck with an empty useless function for notifications they didn’t care about. This is why a core principle of OOP architecture is to favor composition over inheritance. Inevitably inheritance heirarchies result in unneeded functionality getting shoved in somewhere The tutorial was held together with equal portions of duct tape and hope but it got us through Alpha.
Interfaces are the answer. Interfaces allow a program to specify functionality without providing an implementation. This allows for multiple interfaces to be implemented without the nightmare fuel of multiple inheritance. You can implement as many interfaces as you like, each containing only one or two functions to give each object all the functionality it needs, and only what it needs.
As an aside, Swift, Apple’s programming language offers what they call “Protocol Oriented Programming”. Protocols allow objects to inherit actual functionality from multiple sources. You can read more about protocols here.
I refactored the code so that the cards that cared about player collisions were IEventListeners and made the player fire an event. This decoupled the two systems and allowed me to remove the unnecessary code from all the cards that didn’t care what the player stumbled into.
The tutorial was looking pretty snazzy. It still has a few major bugs and a lot of refactoring and I still hate tutorials. It still couples way too many things that it shouldn’t. There is too much text and more than a few spelling errors. Yet, overall, testers say it is understandable so, in the end, that’s all that matters.
However, I now have a bug! Cards may be moved through in sequence. If someone does not turn on the flashlight first then the tutorial will get stuck and never give them their gun. I don’t have a good fix for this. I’d love to wrap up this article with all the answers, but I can’t. There is always another bug, another problem. Yet, our games still get made