How do you maintain focus and not lose track of the forest for the trees as you develop an app?
It's very easy to lose track of where code "belongs" once you're deep into development and end up with duplicate code in multiple places or routines that become muddy doing work that belongs elsewhere.
There are many tools and methodologies that can help, such as entity relationship diagrams, business models, communication diagrams and data flow diagrams. All very useful, especially for an app of any complexity. User interface designs, screen flow models, and UX design can also be useful. However, one of the most helpful tools I've found when designing complex apps and when deep in the actual development process is this game app design pattern.
A game app design pattern provides a visual chart of where each function "lives" in the code and how, at a high level, the functional groups are connected. Here is an example of a game app design pattern for a single player versus a computer player, or "AI". You can download a full PDF of this diagram at the end of the article.
This is a high-level model that hides a lot of detail. For example, the connections between the functional groups, with a few exceptions, are from one macro group to another and not to the sub functions within the groups. Again, this is mainly for readability. Keep in mind this diagram is meant to help keep the forest in view, not the individual trees.
And looking at the forest, most of these functional groups make sense and feel obvious, which is the point. The intent is to create and internalize an easy-to-understand mental map of the code for developers.
There are a number of "Controllers," in red, a single "Animation Controller," a "Game State" used by most of the app, and routines to control the player's moves – "Manage Allowable Moves," the AI's brain, "AI", and so forth. But let's start where the app starts, with "Main."
There's always a "Main," responsible for launching, suspending and exiting an app. No surprises there. The main routine may also be used to check for app permissions, create an initial database and perform tasks based on the device on which it's running. In this case, these functions have been moved to a series of "Utilities." This keeps the "Main" cleaner and also allows these utility functions to be used elsewhere in the app.
In this single player vs AI game, there are "Permissions", which tracks the user's permission to access certain resources (ie. Database). "Initializations" create the app's database and initially populate it. When the developer releases an updated version of the app, this routine will also make any necessary modifications to the database data and structure.
"App Navigation" is an example of a function that is used throughout the app. It is responsible for navigating between the app's screens and dictates the navigation transitions: slide, fade, etc."Device" allows every screen in the app to present its graphic objects based on the particulars of the device on which the app is running.
"Create", "Show", and "Hide & Destroy" handle the phases of a screen's appearance and use. This includes laying out the user interface, retrieving and displaying information, and managing memory.
The "UI Updaters" are routines that are called by a user's interaction with the screen that demand a change in state. These routines are also called by other routines external to the screen that change the UI state. An example of this would be the "AI" making a move that needs to communicate via text or graphics with the human player.
"Pass Thrus" may be language-dependent or development environment-dependent. Like the "UI Updaters" they are called from external routines to access other routines that would be otherwise unreachable or that create a conflict.
"User Interaction" leads to...
As the player and the AI make their moves, the "Animation Controller" is called to handle the movement and transformation of the game's objects, including the timing (how fast), location (where objects move to), and sequence (one animation may need to complete before the next starts).
Manage Allowable Moves
The functionality in this group manages two important aspects of game play. First, what objects are "Playable", meaning what objects can a player interact with given the current state of the game ("Game State"). Second, given the current state of the game, what moves is the current player "Allowed" to make. These routines are responsible for storing and updating this information in a way that controls each player's ability to interact with the game.
This is the AI player's "brain." As the diagram shows, the AI determines its best or required move and then executes its move by calling the "UI Updaters" and the "Pass Thrus" in the given game screen. This allows the AI to both communicate with the human player and animate its moves (the animation is handled via "Pass Thru" calls to the "Animation Controller").
And finally, the "Post-Move Execution" handles any follow-up actions required after the AI makes its move. In other words, the AI will evaluate its moves, determine which is best, make the move, and if the move requires another turn, the AI either makes another specific move or loops through the process of evaluation, execution, and post-execution again.
Game Flow Controller & Game State
Apps are event-driven, meaning the device's OS is constantly testing for events such as a user's touch or swipe, or checking for notifications, messages, GPS location and so forth. The equivalent process in this model is the "Game Flow Controller". This routine in theory could be called right from "Main," but in my experience that would be overkill.
When the player starts or resumes a game and the game screen is presented, this controller is called. Like a device's OS, this routine loops through itself using its partner routine, "Game State" to determine what needs to happen based on the current state of the game. In this sense it acts like a traffic cop, directing and controlling the flow of the game, from setting up a new game, resuming a game, switching players, to controlling the user interface options available to the player.
The "Game State" is a simple but crucial routine that queries the app's database any time it's called and and retrieves data about the current state of the game. Most of the app's routines need this information, especially the "Game Flow Controller" which uses this information to also control whether it continues to loop through itself or if it needs to wait for a player's interaction before continuing.
Game, Round, and Turn Controllers
These controllers contain the routines needed at various stages in the game and respond to calls from the "Game Flow Controller." If a new game needs to be started, paused, resumed, recovered, or ended, the "Game Controller" handles these conditions. The "Round Controller" and "Turn Controller" operate similarly.
As the controllers update the state of the game, "Game State" is called to refresh the current state of the game for "Game Flow Controller". As rounds or turns are updated "Manage Allowable Moves" is called to ensure players can only perform allowable actions and can only interact with playable objects.
In summary, having a "design pattern" or an "architecture" is simply a way to understand the structure of an app. This model helps keep the development of the app organized, the code structure clean, and maintenance, enhancements, and bug fixes easier to handle.
Different coding languages and different environments may dictate different architectures, but with any tool, you may choose to use what's useful for your particular application and ignore what doesn't work. Don't let the model become a straight jacket. That being said, for me this model has proven to be very useful over multiple projects. Hopefully you'll also find it a convenient and effective tool that helps you maintain focus and not lose track of the forest for the trees as you develop your game apps.