Actor Oriented Design in Unity

profile picture of AniAko

AniAko

Actor Oriented Design in Unity

Here at Delightful Games we believe in a wholesome experience. So in following that guidance we’ve begun a development blog! Delightful Games will host weekly(-ish) posts diving a bit deeper into the various pieces that makes our games tick. Keep reading for our first installment: Actor Oriented Design in Unity for Spaceschuter McGavin!

Note: This blog is meant for intermediate/advanced programmers. It will not cover common/basic software design patterns in great detail; There is plenty of free information on software design patterns already available online. Same goes for Unity specific functionality; it will likely go uncovered in this blog. Instead, this blog will feature time-saving / future-proofing techniques that we have employed in our game already or are building. I am not a professional Unity programmer, so I don’t guarantee I’ll have the best approach. However, I am a professional C# programmer and have several decades of experience in enterprise programming environments.

Unity & Actor Oriented Design

statistics
Game Statistics

So here in Spaceschuter McGavin, we’re essentially collecting flight recorder/ black-box data from 9 ships, across one kilometer. It makes for several thousand data points! However, when collected and applied correctly, the number of data points isn’t difficult to manage. The core of our Unity development is based on a concept called Actor Oriented Design (AOD). Through this design pattern, you will find it’s simpler to “bolt on” a new feature/behavior for the actors in your scene.

State Machines

Actors are mostly empty Unity MonoBehavior scripts . They contain a State Machine that governs which behaviors are active. State machines come in several flavors, most notable being the Finite-State-Machine. However, “finite” state machines don’t allow for new states and transitions to be easily added to them. So we’re opting for a simple state machine that will only be concerned with the current state. GameObjects can transition states with or without conditions.

actor oriented design
Actor Oriented Design

So above we have an example GameObject called PlayerObject. It contains some pseudo-code of what will be present in our MonoBehavior class. You’ll notice that Update(), LateUpdate(), and FixedUpdate() only have a single call to an identically named method in the stateMachine. Inside each “Behavior” there will exist EnterState(), ExitState(), Update(), FixedUpdate(), & LateUpdate(). This allows a specific Behavior to focus on a specific script of instructions without needing to worry about other states’ functionalities bleeding over into its state.

This unfortunately happens a lot when you have a single object that’s responsible for several behaviors. The behaviors then co-exist in a single Update() method! This can make leaking of states as well reducing readability of your code. Of course you could develop the multiple behaviors in multiple MonoBehavior scripts, but then you’ll need to turn off and on specific scripts based on your “current state”. It’s a situation that cries for a stateMachine.

Our tiny little stateMachine

Notice inside our SetState() methods we have:

stateMachine.ChangeState(new IdleState(stateMachine, this));

We pass a reference to the stateMachine so our Behavior can further change its Actor’s state if necessary, and a reference to the MonoBehavior itself. This allows our Behaviors to access other unity components as necessary; such as Rigid Body, Mesh Renderer, Sprite, etc. I sometimes think of Actors’ Behaviors as “MiniBehaviors” of the MonoBehavior **chuckle** because these MiniBehaviors exhibit the same commons traits that a regular MonoBehavior has. And if I needed new functionalities (e.g. stateMachine.Awake()) I can easily add it.

But Wait!

It’s important to know that not all our objects in our scenes are Actors. When GameObjects need to have more than two states they are upgraded to be an Actor. The only downside is AOD requires additional programming to be supported properly (At the time of this writing we had around ~400K lines of code). I find it to be fairly boilerplate, but you may not depending on your experience.

We’ve also purposely violated one of the tenants of Actor Oriented Design: Actors should be the only entity allowed to change their own states. This means other actors aren’t allowed to directly change an actor’s state. Instead, objects are expected to “message” an actor, and the actor may change its state. The reason for this is that we didn’t want to make a whole messaging subsystem to manage state changes. Instead we allow Actors to “cue” one another into their next behavior. This is where the public SetPlayState(), SetIdleState(), & SetShipSelectState() methods come into play: Actors can cue one another by calling each other’s Set*State() methods. This is a shortcut, but one that meets our needs perfectly so far.

This has enabled us to treat Unity GameObjects like Actors in a scene such as a movie. The GameManager directs the actors to do things, and they cue one another and react to one another until the director yells “cut!” and gives the actors new instructions.

Ok… that’s great, but what about the statistics?

Well, the Actor and stateMachine explainations are crucial since all our GhostSystem does is observe interesting Actors (yes in some cases using an actual Observer pattern) and record them when they’re in a specific state. Kind of like a camera man that only records when the actors are ready. Since the GhostSystem is our camera man, he has one job: record and observe. And that’s a model of software that fits perfectly into our story telling system. Now just think, a tutorial (think of them as an assistant director) that has the ability to tell actors to freeze their position for a moment, then continue gameplay the next. Did you think of it? Good, because that’s how we handle the Tutorial as well! This is the power Actor Oriented Design has offered us so far.

Now that’s not the only shortcut we’ve employed with statistics. The GhostSystem could not observe the thousands of Actors our game presents. At least, not in an efficient manner; It increased load times by over 200%. So now our thousands of Actors report behaviors to a central StatsSystem. These Actors report statistics such as “shots fired” and “obstacles destroyed”. The StatsSystem then combines that data with the GhostSystem data and shows you the Game Statistics screen seen at the beginning of the post.

This breaks the SOLID principle, but GameObjects are merely reporting about things they know, just not in a generic fashion. The truth is, it may in the future, but for now, as protype code goes, this works perfectly fine. I’m sure this won’t be the last time post about Actor Oriented Design in Unity, or Actor Oriented Design in Spaceschuter McGavin for that matter.

2 thoughts on “Actor Oriented Design in Unity

  1. Pingback: Delightful Games

Leave a Reply

Your email address will not be published. Required fields are marked *