Architecture: The Importance of Planning Your Code


Level 2 whitebox by Jacob Tyner

Architecture is the art of designing the spaces we inhabit. A poorly designed building is unpleasant and confusing for the people who must work in it. Architecture is also a part of code, and poorly designed code is much the same as a poorly designed building. Without planning your code when you start, it will become messy and difficult to expand or change.

For the first feature, we only need to worry about the player and the tablet. However, I decided to design the architecture for the full game to start with. That way, we know what scripts we need and don't have to scramble to plan it out while we make it.


Character concept art by Samantha Burke

I started by planning one script per function we would need. I split the player controller into three pieces: stats, actions, and inputs. The stats would handle things like health and stamina, while the actions would define all the things the player could do, like running and jumping. Seperating these from the inputs meant we could reuse these scripts for the AIs, swapping out inputs for the algorithm that controlled the AI. The inputs, of course, simply meant checking if keys like W and Spacebar were pressed.

The main feature of the game is the gradual unlocking of movement abilities like running and dashing by destroying tablets - to start, the player can only walk with WASD. Inputs is also the place where it is checked to see if an action is avaliable. A seperate RuleContainer script would be used to store which actions are allowed, and a TabletContoller script would be attached to the tablet to enable its corresponding action when destroyed.

First version of the tablet model by Samantha Burke

In addition, some other miscellaneous scripts were needed. A UIController would display health and stamina on the UI, while a SceneController loaded new scenes and quit the game when needed. To save the game, a dedicated script would be needed. Some tablets were planned that affected physics rather than just player actions, and these would be best done seperately. We needed a SoundController to play music and sounds. Lastly, we would need something to play particle effects and non-character animations such as opening doors.

This produced a great many scripts, many of which only did one thing. While every script is meant to be very specific in what it does, these were too simple. Several groups of these simple, single-use scripts were closely related, so I combined them together. The TabletController was meant to be attached to each tablet, while the RuleContainer was meant to have a single instance that everything referenced, so these needed to stay seperate. However, the save file, UI, and SceneController scripts were combined into one given their close relation in managing the game. The ActionContainer and StatContainer were also combined into a single, EntityController script.

In the end, our architecture ended up looking like this:

Scripts:

EntityContainer 

  • Defines the actions an entity can take (walk, run, jump, crouch, attack, etc.). Communicates with StatContainer to know how fast an enemy should move (speed), if an enemy should be able to run (stamina), if an enemy should be able to move (health > 0), how high an enemy should jump (jump height). Also responsible for playing animations, and includes an action for “idle. 
  • Stores stats for an entity. Stats include health, max health, stamina, max stamina, walk speed, run speed, and jump height. 
  • Walk(Vector3 direction), Jump(), Run(Vector3 direction), Crouch(), Attack(), Dash(Vector3 direction, Float force) 
  • Int maxStamina, Int stamina, Int health, Int maxHealth, Int speed, Int runSpeed, Int jumpHeight, Int attackDamage 

PlayerController 

  • Takes player inputs and communicates with EntityContainer to turn inputs into actions. 

RuleContainer 

  • One instance of this is responsible for storing which actions are allowed. Player and all AIs bound by tablets refer to this to see which actions they can perform. 
  • Bool canJump, Bool canRun, Bool canDash, Bool canCrouch 

TabletController 

  • Responsible for signaling when a tablet is destroyed. 

AIController 

  • Communicates with ActionContainer to make an AI perform actions. Has booleans for if an AI should attack the player, and if they are affected by tablets (figure or not). Has integers for how far the enemy can see and how much damage it should do. 

UIController 

  • Loads scenes and plays scene transition. Also responsible for closing the game when Quit is pressed. Responsible for saving progress, loading file, and clearing save if “new game” is selected. Also responsible for displaying health and stamina on the UI. 

PhysicsController 

  • Changes direction of gravity and toggles collision of transient objects 

FXController 

  • Plays particles and plays animations for anything that has animations but is not a living entity. 

SoundController 

  • Plays music and sound effects. 

The most important thing when architecting the code of a game is to know what your code needs to do. After that, it's best to start by planning every function as its own script, and if that produces too many small scripts. combine similar ones. Only an incredibly simple project can be done reasonably without some attention to code architecture.

Get Iconoclast

Leave a comment

Log in with itch.io to leave a comment.