What do I consider a code haven? TL;DR; It’s a place where your prized code can reside peacefully while you work your day job ๐ It’s more than a repository of code, it’s sometimes several repositories and it’s more like a fortress of solitude. Here your imagination, fortitude and perseverance pays off greatly. Here you can code with passion and not have to worry about various business interests getting in the way (mostly).
Coding bliss
I like code havens, not because they’re particularly good at things (though they are), they can actually encourage ivory tower like coding which is bad. Outside of the few dangers though, code havens provide me with a laboratory of code to lean off of, where past ideas and future ideas meet. At its core exists the essence of the software, the hope, love, hardship, and everything that comes with the dreams. It’s cool to hang out with the essence of creation ๐
Anyways, that’s why I like code havens, but what are they good for? Well, they’re good for your coding soul. A place to hone skills, learn, cry, and experience creating something from virtually nothing. No inheriting problems you didn’t create yourself, no deadlines to worry about, just coding freedom. You can almost think of it as a code “journal”. As such, code havens chronicle your adventure in great detail.
Where, When, & How to make a Code Haven
My current code haven includes a project that includes C# & NodeJS, my top two favorite languages. Dashed with some firebase design education, some help from postman, and I have a tidy little code haven deep within the recesses of our premiere title: Spaceschuter McGavin. The timing was serendipitous, but otherwise a Code Haven should be part of a developers daily or weekly regiment. Code Havens are a place to unwind or get wound up about. Often it’s a departure from the drudgery software development might produce. As far as how, that’s the easy part! Just think about something exciting you believe software can address and devote time to addressing it, that’s it! ๐
I’ve sometimes entered into my code haven and haven’t come out for days at times. That means little visible “progress” on outstanding issues. But knowing what I know and being in the particular position of being my own boss in this instance, what I already understand is that code havens shouldn’t be disrupted for things like “visible” progress. You don’t normally get all three: the where, when and how; you typically get 2 easily and need resources for the other to come into place.
My Code Haven Curse – Time dilation
Ultimately, my code havens ends up producing a better, more level-headed programmer, producer, and designer. The cost for me is time in the code haven is warped. Kind of like the effect a black hole has on a planet orbiting close to it like from the movie Interstellar. But that’s my story, your mileage my vary. Overall, over time, I think all things coming from a code haven pan out in the end. The ability to carve out little safe areas where someone can let a good idea stew and build up some legs to for the idea to serve as a center-piece to build upon are both special and sacred opportunities.
So we took a break from Spaceshooterโฆ but only for a week! ๐ During this break we partook in a Code Jam where we implemented Conway’s Game of Life on Unity’s DOTS ECS technology. It was exciting to test our newly learned ECS knowledge and contribute to the ever growing ECS community!
I said we’d most likely revisit Actor Oriented Design in Unity, and here we are! This time we’ll be focused on Actor Eventing/Messaging/Cues. To catch up on the first installment, please read more here
State Machines + Messaging โ Actor Oriented Design
State Machines plus Messaging doesn’t quite equal Actor Oriented Design, but it’s awfully close. Actors in our scene will enter different states of action such as telling the camera zoom in, fire a projectile, or display a new ship. When an actor in our scene enters a state, they can create events/messages/cues for other actors in the scene to take up a new states in the scene. Those other actors “listen” for those events/messages/cues, then enter the appropriate state.
So what’s different from our original approach?
Our first approach to Actor Oriented Design involved only the state machines; we explicitly called other actors in our scene to enter specific states. This works for very small, very simple systems, but as systems grow, this creates a typical software issue known as “tight coupling”. This means that our actors have to know about each other and their states. This causes several issues as your project begins the grow. Most namely, that actors will have to have references to each other in order to instruct one another to enter different states.
This example is how a tightly-coupled piece of code would act. You’ll see, all the asteroids have a reference to our Player Ship for the purpose of registering a score on the Player Ship. It’s easy for our Asteroid to register a score for our Player Ship, but it requires each asteroid to “know” about the Player Ship in order to increase the score. There is nothing functionally wrong with this approach, but it is a design flaw.
So when we applied this solution to Spaceschuter McGavin it became problematic over time. In Spaceschuter McGavin we have 1000+ asteroids needing to get a reference to our Player Ship. Each asteroid is responsible for instructing the Player Ship to register a new score if is shot down. We also have to update that reference for each of the 100’s of asteroids each time the player changes ships.
As I mentioned, this racks up a lot of time in the Update and FixedUpdate methods, constantly checking if a player blows an asteroid up. It’s better to apply a loosely-coupled solution based on a event/messaging/cue system to relay this information.
Loosely-Coupled vs. Tightly-Coupled
Here we’ll see the difference between tightly coupled and loosely coupled Actor Oriented Design code.
A seasoned programming mind might realize we’re not avoiding tightly coupled objects all together. We’re still introducing tight coupling, but through a third object (Actor Cues). Actor Cues only job is to couple objects together that need to communicate. The idea is to control the coupling through this third object when you need to facilitate communication, but otherwise the objects take care of themselves.
This loosely-coupled example is broken into 2 parts: Publishing an event/message/cue and Subscribing to a event/message/cue. An event/message/cue is nothing more than “something that has happened in the scene”. That’s it. The asteroids are simply screaming out to any actor that will listen that an Asteroid Actor has been shot down. If no actor is listening at the moment, the event/message/cue dies and nothing changes. However if some actor is listening for the “Shot Down” cue, they will react in whichever way they choose.
The benefits of loosely coupled code goes beyond code fragmentation. It supports the SOLID coding principle, namely the Dependency inversion principle: Depend upon abstractions, not concretions. That’s a lot of tech-talk for meaning “the code is in good form and should perform/change well over time”. That’s the crux of what we’re getting at; designing code in a manner that reduces further redesigns down the road.
I talk in abstracts a lot, so how about some code?
Dear reader, I agree, but be warned: I will glaze over my use of C# generics as this is meant to be demonstrative in nature and not a course on the C# language.
ActorCues
using System;
using System.Collections.Generic;
using System.Linq;
namespace Assets.Scripts.Utility
{
public interface IActor
{ }
public interface IActorCue<T> where T : IActor
{
T actor { get; }
}
public interface IActorCueCollection
{ }
public interface ICueSubscriber<T> where T : IActor
{
public void OnCue(IActorCue<T> cue);
}
public class ActorCues
{
private Dictionary<Type, IActorCueCollection> actorCueCollections = new Dictionary<Type, IActorCueCollection>();
public void Subscribe<TActor, TCue>(ICueSubscriber<TActor> cueSubscriber) where TActor : IActor
where TCue : IActorCue<TActor>
{
var actorType = typeof(TActor);
if (!actorCueCollections.ContainsKey(actorType))
actorCueCollections.Add(actorType, new ActorCues<TActor>());
var cues = actorCueCollections[actorType] as ActorCues<TActor>;
cues.Subscribe<TCue>(cueSubscriber);
}
public void Unsubscribe<TActor, TCue>(ICueSubscriber<TActor> cueSubscriber) where TActor : IActor
where TCue : IActorCue<TActor>
{
var actorType = typeof(TActor);
if (!actorCueCollections.ContainsKey(actorType))
actorCueCollections.Add(actorType, new ActorCues<TActor>());
var cues = actorCueCollections[actorType] as ActorCues<TActor>;
cues.Unsubscribe<TCue>(cueSubscriber);
}
public void Publish<TActor, TCue>(IActorCue<TActor> cue) where TActor : IActor
where TCue : IActorCue<TActor>
{
var actorType = typeof(TActor);
if (!actorCueCollections.ContainsKey(actorType))
actorCueCollections.Add(actorType, new ActorCues<TActor>());
var cues = actorCueCollections[actorType] as ActorCues<TActor>;
cues.Publish(cue);
}
}
public class ActorCues<TActor> : IActorCueCollection where TActor : IActor
{
private Dictionary<Type, HashSet<ICueSubscriber<TActor>>> subscribers = new Dictionary<Type, HashSet<ICueSubscriber<TActor>>>();
private Type ActorType { get => typeof(TActor); }
public void Subscribe<TCue>(ICueSubscriber<TActor> cueSubscriber) where TCue : IActorCue<TActor>
{
var type = typeof(TCue);
if (!subscribers.ContainsKey(type))
subscribers.Add(type, new HashSet<ICueSubscriber<TActor>>());
if (!subscribers[type].Contains(cueSubscriber))
subscribers[type].Add(cueSubscriber);
}
public void Unsubscribe<TCue>(ICueSubscriber<TActor> cueSubscriber) where TCue : IActorCue<TActor>
{
var type = typeof(TCue);
if (subscribers.ContainsKey(type))
if (subscribers[type].Contains(cueSubscriber))
subscribers[type].Remove(cueSubscriber);
}
public void Publish(IActorCue<TActor> cue)
{
var type = cue.GetType();
if (subscribers.ContainsKey(type))
foreach (var subscriber in subscribers[type].ToList())
{
subscriber.OnCue(cue);
}
}
}
}
It all starts with Actor Cues. This serves as our messaging bus to which events/messages/cues are published and subscribed to. It’s important to know that PubSub implementation, such as this one can be accomplished many ways, some solutions can even involve the Unity Messaging system. It’s a kind of “developer’s choice”. In our use case, my events/messages/cues include a reference to the object that published the event/mesasge/cue. Don’t worry if this code seems confusing, we won’t be modifying it beyond its current state, but instead consuming it by Publishing and Subscribing to it from various actors. We’ll host the ActorCues in our GameManager
public class GameManager : MonoBehaviour
{
public ActorCues actorCues = new ActorCues();
}
Publishing
using Assets.Scripts.Utility;
namespace Assets.Scripts.Asteroid.Cues
{
public class ShotDownCue : IActorCue<AsteroidObject>
{
public ShotDownCue(AsteroidObject actor)
{ this.actor = actor; }
public AsteroidObject actor { get; }
}
public class AsteroidObject : MonoBehaviour, IActor
{
public GameManager gameManager;
public int shotDownScore = 100;
public OnDestroyAsteroid()
{
gameManager.actorCues.Publish<AsteroidObject, ShotDownCue>(this);
}
}
So here we have a simple AsteroidObject and ShotDownCue object. Notice that the Asteroid object implements the IActor interface. When prompted to blow up, the asteroid will publish the ShotDownCue for our listeners to subscribe to. Easy peasy.
Subscribing
public class PlayerShipObject : MonoBehaviour, ICueSubscriber<ObstacleObject>
{
public GameManager gameManager;
private score = 0;
private OnEnable()
{
gameManager.actorCues.Subscribe<AsteroidObject, ShotDownCue>(this);
}
private OnDisable()
{
gameManager.actorCues.Unsubscribe<AsteroidObject, ShotDownCue>(this);
}
public void OnCue(IActorCue<ObstacleObject> cue)
{
if (cue is ShotDownCue)
AddScore(cue.actor.shotDownScore)
}
private void AddScore(int addedScore)
{
score = score + addedScore;
}
}
Here’s our Player Ship subscribing to the AsteroidObject‘s ShotDownCue. When a event/message/cue is received, it will adjust the score accordingly. Notice how we unsubscribe and subscribe to the event/message/cue on OnEnable and OnDisable? That’s so if this ship is disabled it will no longer receive updates for a score! Useful if the Player changes ships or dies before an asteroid is shot down. All those features in a tiny little bundle of code.
That’s a lot more code
Sadly, yes, with good design comes more code. As I stated in my previous article. The Messaging System was not authored because it wasn’t quite “needed” yet. That word: “Need” is a tough term to define in this instance. For me it meant: My actors aren’t chatty enough yet with each other (tightly coupled) to warrant a redesign.
Spaceschuter McGavin is a prototype that’s evolving into our final product. That means I purposely take shortcuts at times to get something quickly into prototyping. IF that prototype passes playtesting and ages, I refactor the code to take more permanent residence using good design measures. Add in the fact that I have several prototyped features all aging at different rates, and the result is I have to be selective in what I redesign. It’s a balancing act as we learn more and do more.
I’d be remiss if I didn’t say I wish I had fully implemented Actor Oriented Design with the messaging component in tact. Refactoring any amount of code is typically more bug prone than starting from scratch with the new code. However, I strategically picked a point in time where I knew I had to refactor my prototype code to adopt events/messages/cues before tight-coupling became an issue. For me, this became evident because I ran into a limitation with my tight-coupling I purposely put in place. A kind of bookmark0 to where I knew if I ran into that known limitation, it was time to refactor code before I pursued.
What’s the right choice for me?
If you’re reading this blog to learn about Actor Oriented Design in Unity, I recommend following the full pattern. Our experience with software architecture and the C# language is high so we’re able to plan phased roll-outs of designs. If you’re reading this blog to learn about HOW TO INTRODUCE Actor Oriented Design to your project, I’d recommend implementing the StateMachine first. THEN work on messaging after you feel you have a firm grasp on the StateMachine portion. However if you notice immediate coupling of actors in your scene, I wouldn’t hesitate to introduce the Event/Message/Cue system.
In Conclusion
Actor Oriented Design in Unity is a perfect match in our minds here at Delightful Games. Unity works well with any amount of Actor Oriented Design, so use as sparingly or aggressively as you will. Actor Oriented Design approach provides a powerful, organized programming platform you can grow into as your projects grow.
About 28 years ago, a classmate of mine was in the library running a QBasic program. I thought it was cool and asked where he got it. He said he MADE it. My brain promptly exploded. This teenage kid wrote a program on a school computer during free-study time. I found an instant hunger for wanting to make my own program, although I knew nothing about programming. That didn’t matter though; I knew how videogames operated and I wanted to make a videogame.
Early on in life, I realized that card games, videogames, board games, basically games in general, follow a simple recipe: Develop mechanics (rules) in an otherwise chaotic environment and witness the “fun” that cooks up from the drama your rules/restrictions dish out! I created many games for me and my friends to play as a child. If you ever meet me, ask me about “Croquet Race” from my childhood, it’s a hoot.
After my brain calmed down, I started asking my classmate questions and within a week was off to learn QBasic. I’d say I “learned how to program” but that would be a lie. These program lacked formatting, naming conventions, and violated so many software principles I give my old self an failing grade, lol. Nevertheless, 28 years ago my journey began to live in the world of computing.
Law & Orderly Medicine
Before I ended up in computer science, I was planning to get a career in medicine or law. (I laugh at that these days) Once I saw my creations coming to life, I never looked back. It was the CPU life for me! I began to learn and build, spending my afternoons after school at my family’s computer with a stack of floppy disks so I could take them back and forth to school. Most of my programming was exploratory; I wanted to see what QBasic could do.
Soon though, I was downloading other people’s QBasic games and learning how they worked. So it wasn’t long before I started thinking about my own games. In the end I would end up starting 3 games: Star Chaser, a Simon clone, and MicroGolf.
Fast forward 28 years and unearthing the code was much like an archeological dig; the projects died suddenly and were left in an unfinished state. So the first thing I did was dust off my old QBasic skills and began to fix the programs so they were at least operable.
Star Chaser, 28 years later
First up was Star Chaser. A simple 2D ASCII Art game. It was unfinished because the last level I wanted to perform in 2.5D (ray casting) to simulate a 3D “Save the planet” level. However, as I learned, QBasic is SLOW to process and ray casting wasn’t going to cut it. As a result, Star Chaser got abandoned with the intention to port it to another language that could handle ray casting. After all, I was also learning PASCAL at the time. Instead I turned towards making my Simon clone and MicroGolf game.
Unfortunately, I never did get the opportunity to port Star Chaser, mostly due to my spaghetti coding that almost made it better off to re-write from scratch. It happens when learning, and wasn’t the last time…
Simon Clone
Growing up, I always liked the game Simon. It’s a simple man vs. machine memory game. The goal being to get the highest score you can. Pretty straight forward and simple. However, much like Star Chaser, I abandoned this game as well. Not because of limitations, but because I was young and simply moved on as I learned more. Much like Star Chaser the codebase was deplorable and not portable. It never was all together; I had the title in one program, the game in another, no organization, but I managed to mimic the basic Simon mechanics and turned my QBasic skills from programming to programming art. Soon I was drawing partial circles and otherwise making clean programming art. Learning how to make art with the geometry calls was fun, but time consuming. Kinda like today’s shaders.
Ultimately Simon doesn’t come together and instead I began work on my QBasic Magnum Opus: MicroGolf.
MicroGolf!
A play on words, MicroGolf was simply a miniature golf simulator (ON A MICROCHIP, GET IT?!) in which you could create and share levels (You could fit several courses on a floppy disk, WITH THE GAME!). I fell in LOVE with this game. But it was at that time I didn’t understand certain geometry functions (I think it was Arc Tangent I needed) and couldn’t explain it properly to my teachers to get the answer. I grew very frustrated. I needed to better understand the math required before I could finish my game.
So I decide to shelve the game until I’m able to complete my PASCAL course and solve my issue, maybe even port the game. And in that was my mistake. By the time I returned to my spaghetti-code MicroGolf Game, I had actually learned how to program (a little) and grew so frustrated one night I deleted the entire game file in a fit of rage at the younger me that didn’t know how to program. And with that Microgolf, what could have been the first of many Delightful Games, was gone.
I regretted what I had done, but also learned a valuable lesson. Sometimes it’s better to have a fresh start with software. Something that stands true even today. Sometimes the old has to be torn down to make way for the new.
New Beginnings
Maybe this explains why I modeled Spaceschuter McGavin after golf, but all I know is that I don’t want to go out on a sad note. Do I wish I finished these games, or still had the original files to MicroGolf? Darn tootin’, but that mistake and those broken dreams never went to waste. I kept them with me and it has delivered me here, today, at Delightful Games. If were I given a choice to relive my life, I wouldn’t change a thing, because I would never forgive myself if I hadn’t landed just where I am today 28 years later. Peace my friends!
Today we’re going to take a look monetization options for Spaceschuter McGavin. As per usual, we’ll get there in a winding way.
TL;DR: Diversify!
~AniAko
Spaceschuter McGavin debuted as an alpha prototype focused primarily on game mechanics. At the time I’d say about 60-70% of the game mechanics were addressed. That was two months ago. Since then, we’ve brainstormed about another 20-40% game mechanics and implemented about 10% of that for playtesting. So soon we’re closing a chapter of our game development and move onto UI, FX, Sound, back-end, and monetization.
Why did we focus on Monetization Next?
To be truthful, monetization wasn’t next on our list. If you’re old enough, think back to arcades that would charge twenty-five cents to a few dollars to play a video game. In that market, slapping a price tag on your product involved some market research and a bit of math. In the mobile game market, you’ve got ads, consumables, subscriptions, etc. The days of a consumer buying a turn at a game at an arcade or purchasing a game at a store to play at home has evolved into a sea of mobile software that tries to win over users with features that rarely have much to do with gameplay.
Not knowing if our planned monetization was enough to cover our costs nagged at me for weeks. So I drew out the average cost per advertisement and essentially found how large of an audience I needed to reach to hit our financial targets. That was a start, but that nagging feeling didn’t go away, and it would continue to haunt me until I figured out what was bothering me about it. So I continued to work on fleshing out our monetization plan to see what it was that wasn’t sitting well with me.
Primary: Ads
First up is simple: advertising. Interstitial ads (full screen ads) offer some of the higher eCPM (effective cost per 1000 impressions/views) available. To have a greater understanding of the common advertising methods used, go here. Note: we have not chosen Unity Ads as our Ad platform, but other platforms offer similar functionality.
We dismissed banner ads immediately as our screen real estate is paramount to the gaming experience; allocating space for an ad would cheapen the immersion we believe our game can offer. This left us with interstitial ads and rewarded ads. Interstitial ads were a natural fit between scene changes in Spaceschuter McGavin. It was an easy decision.
Secondary: In-App Purchases – Item Shop
In-App purchases are another offering that can generate revenue. Typically, you’ll find in-app purchases hosted in some form of “Item Shop” in the game that can interact with your distribution platform (AppStore / Google Play). We’re entertaining the idea of offering an item shop in Spaceschuter McGavin. There you’ll be able to purchase ships and powerups you would otherwise unlock/earn during the game’s progress. This strikes a balance for our pay-to-win and play-to-win players; pay-to-win offers shortcuts, not exclusive content. Ultimately, even if Spaceschuter McGavin were to offer exclusive “paid” content, it wouldn’t offer content that misbalanced any of the established mechanics. This was not a choice made lightly, so just remember: Do what’s best for your game.
Before we move on we’ll cover an easy “In-App” purchase almost every ad-backed free-to-play game can offer: remove ads! Just keep in mind that those users are paying you to stop your ad revenue. If ads are the only manner in which your game generates money, you’re putting a hard limit on how much revenue you can expect. The only advise I can give is this: the price to remove ads should be equivalent to the price of the game were you to sell it outright. Essentially allowing the user to “purchase your game and take it home to play”.
To Tertiary… and Beyond!
The negative thoughts persisted. I knew there was something stinking up my cognition. So I continued to dive deeper, this time into the back-end of our game. Then, I found what was nagging at me.
Back-end systems are important structures in today’s games. Game backups, leaderboards, in-game chat, multi-player, etc. all require some form of “server” your user’s devices will have to communicate with. With that comes user accounts, authentication, databases, etc.; in other words, a lot we have to worry about. There are plenty of services that offer some form of “serverless” back-end, but none that will let you scale your game past about 100 users for free. This means there’s a cost to all those important game structures today’s user’s expect.
As I drew out the costs of the back-end features I knew before I finished that I had found what was nagging at me in the back of my head. Our monetization plans didn’t cover our estimated back-end costs. Whoops. This realization almost derailed the entire game, as I couldn’t in good consciousness release a game we couldn’t support. However, there’s other monetization methods I hadn’t yet touched on: Subscriptions
Subscriptions Monetization
I find balancing a game’s economy to be more artistic and less scientific. At the end of the day the numbers have to add up, but coming up with ideas to make those numbers add up isn’t spelled out in a theory somewhere. At first I didn’t like the idea of a subscription model, but it made the numbers add up, so I left the idea in place wondering what else could be offered to subscribers. It only took a few days for the ideas to start coming to me, and before I knew it, I had over-developed the back-end with too many ideas!
Summary
Monetization is an important part of today’s mobile game anatomy, so much so that it deserves the same amount of attention the rest of your game deserves. Don’t wait too long before you begin to crunch numbers to avoid your costs out-pacing your revenue. Delightful Games’ ride through the wild world of mobile monetization has only begun but it feels every bit of important as the core part of the game. As we here at Delightful Games learn more, we’ll share more.
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
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.
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.
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.