Updated: Aug 22, 2019
Welcome to another edition of this devlog. Today's topic is the Behavioral Model of coding, which is one of the most personally head-scratching things I've come upon in my years of game development. It's one of those things that sounds good in theory, but all of the examples only demonstrate really basic use cases, and anything more complicated seems to fall apart completely. So imagine my surprise when I found out that this is apparently the way you're supposed to code in Unity!
Let's start with the basics for all you non-coders out there. Unity as an engine makes use of the Object-Component model: anything you place into the game world is an "object", and anything that makes up that object (its model, its rigidbody for physics, its boundary collider, and any code scripts attached to it) are its "components". Now, don't get confused, Object-Component is how you would work with "physical" objects like the player, enemies, npcs, or collectables, while the Behavioral Model is strictly for code organization. Unity's infrastructure forces you to use Object-Component, but you can code however you want to. When I was first learning coding for games, I was taught the Controller model instead. In general, using the Controller model will result in scripts named things like PlayerController, EnemyController, ShotController… each of them one file that contains all of the functionality needed for the object they "control". This is both straightforward and intuitive, as every object's functionality is compartmentalized into the file that bears its name.
The Behavioral Model takes things one step further, however, and (theoretically) solves some of the problems with Controllers. One of these is duplicated code. In Alien Cow Farm, the player and COM UFOs all behave extremely similarly when it comes to moving, shooting, and abducting and dropping cows. Imagine if I had all the code for doing those things in two different places, the PlayerController and the AIController. First, the codebase would quickly get bloated when it doesn't necessarily need to be, and second, what if I decided to change something about one of those things? I'd have to hunt down every different Controller where the functionality existed and make the same change in all of them. That would be a workflow both tedious and error-prone. But what if, instead, the abduct functionality lived in its own script that both the player and COM could make use of? An AbductBehavior, if you will? This is the root of the Behavioral Model: break down all game functionality into its smallest-possible atomic components, and then build up your objects by combining them together...just like the Object-Component model does. The result is no longer having a single PlayerController script, but a player object with an AbductBehavior, an MovementBehavior, a ShootingBehavior, and whatever else is necessary to get back to everything that was originally in the PlayerController. Another big plus with this model is for quality assurance, because its much easier to debug problems and integrate unit tests when every little piece is so small and simple and split off.
But here's the main problem: it's really hard to break things down to be as atomic as they can be. Part of it is writing code so generic that it can be used by a whole bunch of different objects. A bigger part of it is that the pieces will still need to interact with each other, which means storing references upon references and creating code with high coupling, almost defeating the purpose in the first place. You may think that these problems could be solved with the right initial design, but that in itself is another problem: it's like the game needs to be completely and perfectly planned out at the very beginning to have any hope of the Behavioral Model working. Which is what I said at the very beginning, for anything other than the simplest of examples, it fails.
I tried the Behavioral Model for Undersea Odyssey because I wanted to give it a go, and because I felt that it was about as complex as a game could get before the model broke, and because my previous attempts with it meant the game was pretty much completely planned out already. And I've still had problems with objects that needed only slight variations of behaviors, which nevertheless required an entirely new class, or at least a subclass...and there's another interesting point to bring up. The "duplicate code" problem could just as easily be solved using a parent class (rather than an AbductBehavior, create an Abducter class that PlayerController and AIController both extend, so they get the common functionality. Or add shooting and other things to the parent, and now it's a complete UFO class).
My conclusion for the Behavioral Model is that I tried it and didn't like it. It's made me have to spend a lot of extra time thinking about how to do things that normally wouldn't have caused me any trouble to implement. We'll see if it makes writing unit tests any easier, but the truth is that partway through I just gave up on the model. I think the best way to use it is "when it works", but not to try and force it everywhere just for consistency. I have a DestroyableBehavior which goes on anything that the player can shoot and get rid of. It works pretty well, because, in the spirit of the model, the script is short, generic, and compartmentalized in that it doesn't need to interact with any other parts of whatever object is getting destroyed. It accomplishes its purpose, and doesn't give me a headache to use. But could it just as easily have been converted into a Destroyable parent class? Yes, and even I think that makes more sense to my object-oriented brain.
Okay, that's enough code talk for one day. Have a picture of a narwhal: