Quick Start Guide

From Hero of Allacrost Wiki
Jump to: navigation, search

This document is written to introduce the core concepts of the Allacrost code to new developers and interested parties. It is strongly recommended that you read this material before any other documentation. Here you will gain an overview of what the Allacrost code consists of, how it is organized, and what the most fundamental concepts to the Allacrost code are. The bottom of this document provides links to further reading material and documentation.


Code Base Structure[edit]

Code Structure.png

The Allacrost code is broken into several components. The figure on the right illustrates this structure, where each node includes the component name and the base directory where that code is located in parentheses. The dependency chain flows from top to bottom for these components. For example, the game engine only includes the utility code and never includes any of the other components. The game mode code includes all other nodes except for the game editor. Note that this structural diagram only covers the C++ portion of the Allacrost code base. There is also Lua code contained in the dat/ directory, but that code is much more loosely structured.

The utility code contains an assortment of classes and functions that are repeatedly re-used throughout various areas of the Allacrost code. This includes code for randomization, directory/file query, rounding, a base class for exceptions, a template for creating singleton classes, and a class for representing Unicode strings. src/utils.h declares those components. There is also another utility code header, src/defs.h that provides declarations for all Allacrost namespaces and classes. The purpose of this file is to prevent problems with recursive inclusion and also serves as a nice index for programmers to see what namespaces and classes are available.

Allacrost's engine is designed from scratch on top of several open source libraries including OpenGL, OpenAL, SDL, and many others. The game engine component implements the fundamental constructs for representing images and text, providing GUI elements, managing audio playback, user input, and much more. The engine structure is composed of several more component which are named: audio, video, input, script, system, and mode manager. Each engine component has a primary singleton class that serves as the main interface and manager of that system.

The global code represents objects that need to be shared by Allacrost's various modes of operation. For example, the global code contains classes that represent weapons, armor, characters, enemies, and battle skills. One very important role of the global code is something we call event management. We must keep a log of what events in the game have occurred so that we can figure out what areas the player is restricted to accessing, what conversations should be had with the townspeople, etc.

Common code and global code might sound like they have similar purposes, but this could not be farther from the truth. The purpose of common code is to allow game modes and/or the editor to share implementations composed of the engine and global code components. For example, if we wanted a dialogue box available both in maps and battle, it would serve us well to place the code for the dialogue box implementation in the common code base rather than have both map and battle code implement their own version of the dialogue box. You can view common code as a second sort of utility code layer that builds on top of the code available in the engine and the global code.

Game modes are the true meat of the game. This code determines how maps are drawn and interacted with by the player and how battles are managed and executed on screen, for instance. Essentially, each form of "operation" of Allacrost is its own game mode. So the boot screen operates in one game mode while map exploration operates in another. The list of game mode names include: map, battle, menu, shop, boot, and quit.

The editor code shares much of the same inheritance as the game mode code, but instead implements an entirely different application. The editor is bound tightly to the Allacrost engine and employs its constructs to construct the editor. Particularly the graphics engine, which is essentially embedded inside the editor and renders the map being edited.


Singleton Classes[edit]

Each component of the Allacrost engine is represented primarily by a single singleton class that serves as the primary interface and managing class to that engine component. The global code too has a single singleton class which represents an instance of the current game. It contains the current characters in the party, inventory, and other information. The singleton objects are referenced nearly everywhere throughout the code. Each singleton class has an associated object name that represents a pointer to that singleton object. Stick to using these object names to reference the singleton classes for the sake of keeping the code base consistent.


Class Name Singleton Object Name Header File Namespace Purpose
GameAudio AudioManager audio.h hoa_audio Manages all audio data and processing
GameInput InputManager input.h hoa_input Processes input events from the user
GameGlobal GlobalManager global.h hoa_global Retains game state information such as player's inventory, play time, etc.
GameModeManager ModeManager mode_manager.h hoa_mode_manager Manages the game mode stack
GameScript ScriptManager script.h hoa_script An interface between the C++ engine and Lua data and scripts
GameSystem SystemManager system.h hoa_system Handles timing, process threads, internationalization, and other miscellaneous tasks
GameVideo VideoManager video.h hoa_video Handles all visual data and processing
Table 1. Allacrost Singleton Classes


To access the singleton objects, all you need to do is include the header file in which it is defined. The class objects are located inside their respective namespaces in those files. Unless you are trying to make changes to the core engine or the main game loop, you do not have to worry about the creation or destruction of these singleton classes. All singleton classes are created and initialized in src/main.cpp before the main game loop, so you never need to worry about the class objects being NULL or otherwise invalid. So for example, to access the AudioManager in your code:

#include "audio.h" // contains the hoa_audio namespace where the AudioManager object pointer is defined

void foo() {
    hoa_audio::AudioManager->PauseAllAudio();
}
Note: Other Singleton Classes

There are more singleton classes than were defined here. For example, the video engine (which is extremely large and complex) contains some other singleton objects that serve as assistants to the video engine in managing specific aspects of the graphics code. These singletons are usually restricted in scope to the area of code in which they are defined and do not need to be referenced by other code areas.


Game Modes[edit]

A game mode in Allacrost can be thought of as a context for controlling the current state of the game. Map exploration, battle execution, and the shopping interface are all examples of individual game modes. The GameMode class defined in src/engine/mode_manager.h is an abstract class through which all game modes inherit from. To design and code your own game mode, your mode class must derive from this class. The table below lists some of the common game modes in the Allacrost engine and their purpose. Note that this is not a final list and more game modes may be created in the future.


Game Mode Name Purpose
BattleMode Battle operation and management
BootMode Boot screen menu and functions
MapMode Map exploration and interaction
MenuMode Used to organize the player's party
ShopMode Provides the player with an interface to purchase wares
QuitMode Active when the user has requested to quit or pause the game
Table 2. Common Allacrost Game Modes


The GameModeManager singleton class exists to manage and process all the current game modes. GameModeManager uses a stack of pointers to GameMode objects, where the top stack item is the active game mode, or AGM for short. There may be only one AGM at any instant in time. In each iteration through the main game loop, the AGM is the only game mode that has its state updated and its contents drawn to the screen. There may, be multiple instances of the same game mode type in the game stack. For example, we can have three pointers to different MapMode objects on the stack (each representing a different map), but only one of those may be the active game mode.

There are three purely virtual functions declared in the GameMode class, and each must be defined in the inheriting game mode class. These functions are called by the GameModeManager singleton.

virtual void Reset() = 0;
virtual void Update() = 0;
virtual void Draw() = 0;

The Reset() function is called whenever the game mode object is promoted to be the active game mode (i.e., when it is placed on the top of the game mode stack). Its purpose is to re-initialize any data that need to be set for this mode. This may include modifying the active properties of the singleton classes, such as the coordinate system for the video engine. The Update() function updates the state of the game mode by processing user input and other game events that have occurred since the last time this function was called. Finally, the Draw() function is called to place images, text, and other visuals onto the back buffer for the screen that this game mode instructs display when the next frame is drawn.


Time-Based Movement[edit]

The Allacrost engine uses time-based movement. Time-based movement (or TBM for short) is a method where you only update the game state depending upon the amount of time that has elapsed since the last update. Why do we bother with TBM instead of updating the game by the same amount on every pass through the game loop and keeping a constant framerate? We are designing Allacrost to run well on a wide variety of hardware and operating systems. Some systems will be much slower than others, and may not be able to keep up with the requirements we would set. This slow-down would be immediately noticeable to the player and destroy their gaming experience. Instead, with TBM, if the user is running on a slow machine, effectively what happens is that frames are dropped so that the game is still playable (technically we don't drop frames, but rather increase the amount that the game is updated between frames). A secondary reason for using TBM is if the user's system is too fast for our fixed frame rate, we would be forced to make system calls to put the game to sleep for small periods of time. However, the resolution of the sleep/wait timers is typically poor and not consistent across operating systems, which would again result in a detrimental gaming experience for the player.

Notice that when Allacrost is running, it attempts to consume as much of the CPU as possible (it does not put itself to sleep). This does not mean, however, that the user can not run other applications while playing Allacrost. Every modern operating system uses time-sharing between the different running processes, giving each process a certain amount of CPU run time. This is why you can run Allacrost at full-blast and still have your web browser, mail client, or whatever else running concurrently. However there are a few places where Allacrost makes a sleep call to yield itself to other processes. For example, this is done when the active game mode is QuitMode because it requires almost no CPU usage and we assume that the user may have paused the game to perform some other task with their PC.

Because Allacrost uses time-based movement, the GameMode Update() function needs to have access to the amount of time that has expired since the last time the function was called. This value can be grabbed with the uint32 GetUpdateTime() function of the GameSystem class. This function can be called from anywhere in the code and returns the amount of milliseconds that have expired since the last update.

uint32 elapsed_time = SystemManager->GetUpdateTime(); // returns number of milliseconds that have expired since last update


Main Loop Operation[edit]

Below is the main game loop that continuously runs so long as the user has not requested to quit the game.

	try {
		// This is the main loop for the game. The loop iterates once for every frame drawn to the screen.
		while (SystemManager->NotDone()) {
			// 1) Render the scene
			VideoManager->Clear();
			ModeManager->Draw();
			VideoManager->Display(SystemManager->GetUpdateTime());

			// 2) Process all new events
			InputManager->EventHandler();

			// 3) Update any streaming audio sources
			// AudioManager->Update();

			// 4) Update timers for correct time-based movement operation
			SystemManager->UpdateTimers();

			// 5) Update the game status
			ModeManager->Update();
		} // while (SystemManager->NotDone())
	catch (Exception& e) {
		#ifdef WIN32
			MessageBox(NULL, e.ToString().c_str(), "Unhandled exception", MB_OK | MB_ICONERROR);
		#else
			PRINT_ERROR << e.ToString() << endl;
		#endif
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;

The first step this loop takes is to draw the next frame and display it to the screen, which requires us to first clear the previous screen. The second step processes all the new user input events, which we will go into brief detail in the next section. The third step updates any streaming audio sources that are currently playing, such as music. The fourth step is to update the timers that the game relies on for time-based movement. And the fifth and final step is to update the state of the active game mode. The only way to safely exit from this loop is if a user submits a quit event, which can be done via the Ctrl+Q command on the keyboard, the close button on the game window, or selecting the Quit Game option while in BootMode. Depending on the current active game mode, we may require the user to enter a quit event more than once to confirm that they really wish to quit the game.

Note that the entire loop is wrapped inside a try/catch block for catching exceptions (there is an Exception class defined in utils.h, which all Allacrost specific exceptions derive from. If an exception is thrown from anywhere in the code and is not appropriately caught and handled, it will be caught here and the game will exit immediately with a failure status.


User Input Processing[edit]

Allacrost players can input commands to the game either through the keyboard or a gamepad/joystick. Mouse input is not supported due to the nature of the game. The GameInput singleton manages all of the keyboard and joystick key/button mappings so they are completely transparent to the user of the API. In other words you can only detect that the user requested a certain command, not whether that event came from the keyboard or a joystick. Every time the user registers an input event (whether it is a key press, key release, axis movement, etc.), that event gets pushed into an internal user event queue. All the events in this queue are processed every iteration through the main game loop and depending on what events occurred, different boolean values are set or cleared in the GameInput class. The table below lists the standard set of user input commands, their default keyboard mappings, and their general purpose.


Command Name Default Key Map General Purpose
Up 'up arrow' Move sprite or cursor upwards
Down 'down arrow' Move sprite or cursor downwards
Left 'left arrow' Move sprite or cursor to the left
Right 'right arrow' Move sprite or cursor to the right
Confirm 'f' Confirm an action or menu command
Cancel 'd' Cancel an action or menu command
Menu 's' Display the main menu
Swap 'a' Swap the character being displayed
Left Select 'w' Select multiple targets or page scroll up
Right Select 'e' Select multiple targets or page scroll down
Pause 'spacebar' Pause/unpause the game
Table 3. Allacrost User Input Commands

There are also several meta-key commands (Ctrl + key) for performing special functions like taking screenshots, but we will not discuss them here. Each command has three booleans registered to it: press, release, and state. Press is true when the user presses a command down and is cleared on the next iteration through the game loop. Release becomes true when the user releases a command and it becomes cleared on the next iteration through the game loop. State is true while the command is active (i.e., the key is being held down) and false otherwise. Using these three pieces of information, you can design a game mode to check and act on any combination of these commands and their states. The booleans can be accessed via member access functions in the GameInput singleton class. Table 4 below enumerates each of those function names, which all return boolean values.

State Functions Press Functions Release Functions
bool UpState() bool UpPress() bool UpRelease()
bool DownState() bool DownPress() bool DownRelease()
bool LeftState() bool LeftPress() bool LeftRelease()
bool RightState() bool RightPress() bool RightRelease()
bool ConfirmState() bool ConfirmPress() bool ConfirmRelease()
bool CancelState() bool CancelPress() bool CancelRelease()
bool MenuState() bool MenuPress() bool MenuRelease()
bool SwapState() bool SwapPress() bool SwapRelease()
bool LeftSelectState() bool LeftSelectPress() bool LeftSelectRelease()
bool RightSelectState() bool RightSelectPress() bool RightSelectRelease()
Table 4. GameInput Member Access Functions


Further Reading[edit]