Unity RTS Game Architecture

December 19, 2015
on
tutorialsrts

I have created many games over the years -- mostly small Flash games. And I feel that I have finally constructed a robust game architure which should be the basis of every game.
This is inspired by the MVC pattern but with some modifications -- a game isn't a user interface but instead revolves around a game loop.

I organize my games with an engine which can contain many controllers and communication between them is done through a global event manager. The engine contains the game loop and handles user input, while the controllers contain the logic.

If you are familiar with the MVC pattern you're probably wondering where the
models and views live. I haven't included that in this diagram and will
write a separate post about that. This focuses on running the core game logic.

Let look at the actors:

Engine

This will listen for player input and direct it to the appropriate controller.
This is essentially the main entry point into the game world.
Nothing should hold a reference to the engine, it is the highest level of the
game. Think of the engine as the glue which connects everything together but
doesn't contain any core logic itself.

Controller

This contains a slice of game logic and is always invoked by the engine. Each
controller has access to an instance of level which contains all the models
in the game. More on that in an other blog post.

Selection Controller this keeps
track of units selected and attempts to make new selections.

UI Controller this displays UI elements on the screen based on action in the
game. When the mouse is dragged it will display the UI element but contains none
of the selection logic. It's important to keep a separation between logic /
display.

Controllers allow for separation of game logic, which makes testing and
maintainability easier.

You can have as many controllers as you like, and as the game grows you can even
have nested controllers inside one an other.

Event Manager

The last piece is the Event Manager, I have already talked about this in an
other
blog post. It's used
for sending out events that happened in the game, this keeps the system
decoupled from other controllers.

The Engine isn't actually a MonoComponent. The
engine is initialized by an other component called Game. It initializes the
engine, and calls LoadLevel.

LoadLevel will initialize the level object, controllers, attach the event
listeners and then spawn all entities. Effectively starting the game.

You can see how input is listened to, and directed to appropriate controllers.
In the Unity docs, they like to create a component for each type of input, one
for mouse, key controls, etc.

However, I like making all input pass through a single method.

Easier to control the order of the input

Easier to handling pausing / game over screens

Much easier to debug

Example Controller

publicclassSelectionController{privateLevellevel;privateTeamplayerTeam;publicList<Entity>selection{get;privateset;}publicSelectionController(Levellevel){this.level=level;selection=newList<Entity>();this.playerTeam=level.playerTeam;}publicvoidClearSelection(){RemoveAllSelections();EventManager.Instance.TriggerEvent(newSelectionEvent(selection));}privatevoidAddToSelection(Entityentity){DebugUtil.Assert(entity!=null,"Selected object with no entity");selection.Add(entity);entity.transform.Find("Halo").gameObject.SetActive(true);}privatevoidAddAllWithinBounds(){Boundsbounds=SelectUtils.GetViewportBounds(Camera.main,anchor,outer);this.level.playerTeam.ForEach((Entityentity)=>{if(SelectUtils.IsWithinBounds(Camera.main,bounds,entity.transform.position)){AddToSelection(entity);}});}privatevoidAddSingleEntity(){Entityentity=SelectUtils.FindEntityAt(Camera.main,anchor);if(entity!=null){if(entity.team==level.playerTeam){AddToSelection(entity);}}}privatevoidRemoveAllSelections(){foreach(Entityentityinselection){entity.transform.Find("Halo").gameObject.SetActive(false);}selection.Clear();}publicvoidSelectEntities(){RemoveAllSelections();if(outer==anchor){AddSingleEntity();}else{AddAllWithinBounds();}hasActiveBox=false;EventManager.Instance.TriggerEvent(newSelectionEvent(selection));}publicvoidCreateBoxSelection(){hasActiveBox=true;anchor=Input.mousePosition;outer=Input.mousePosition;EventManager.Instance.TriggerEvent(newStartSelectBoxEvent(anchor));}publicvoidDragBoxSelection(){if(hasActiveBox){outer=Input.mousePosition;EventManager.Instance.TriggerEvent(newDragSelectBoxEvent(outer));}}}

The first thing to notice is the controller maintains its own state, by holding
a reference of selection. The controller will send out events when ever
something interesting happens. The Engine will then pick up those events and
relay them to other controllers, in this case the UiController.

This is beautiful, because controllers are now decoupled from
the owner --the engine-- and have no knowledge of other controllers but are still
able to fully collaborate through events.

Watch For Coupling

This is an ugly piece of code, I just haven't had the chance to refactor it.
What I would do is replace this with an event, DeselectEntity, with the entity
passed as a reference. The UiController can then pick the event and display
the halo.

Conclusion

In the end, this is a great way to architect games. I have used this in many of
my own games and it has always be sufficient. Please share your thoughts.