Hero of Allacrost - Developer's Quick Start Guide

Last Modified by Tyler Olsen (Roots): December 5th, 2006

Purpose

This document is written to introduce the core concepts of the Allacrost game engine to new developers. After reading this document, you should know just enough about the engine to extend or do something useful with it. It is called a Quick Start Guide for a reason: the constructs and explanation of them discussed here are very brief. In section VIII. Beyond the Basics, references to more verbose and complete documentation about specific engine components are given so that the user may continue learning about how the engine works behind the scenes. This guide is organized as follows.

I. Singleton Classes
A complete list of all the singleton classes that exist in the Allacrost engine and their purpose.

II. Game Modes
Explains what a game mode is, its purpose, and how to implement your own game mode.

III. Main Loop Operation
A very brief explanation of how the main game loop operates and the principles behind it.

IV. User Input Processing
An introduction into how the game engine collects user input and how a game mode can process that information.

V. Audio Interface
Covers the basics of how to setup audio loading and playing in a game mode.

VI. Video Interface
Outlines the most fundamental structures and function calls in the video engine.

VII. Data and Scripting Interface
An explanation of how the C++ engine can interface with Lua data and scripts.

VIII. Beyond the Basics
References to more complete parts of the Allacrost engine documentation.

I. Singleton Classes

The Allacrost engine is built via a series of singleton classes. These classes are referenced nearly everywhere throughout the code, and sometimes the singletons will even reference each other. Each singleton class has an associated object name that represents a pointer to that singleton object. Although you could technically create your own object names that point to these singletons, it is strongly advised that you stick to this naming standard to avoid confusion. The singleton class names and their corresponding object names are displayed in the table below.

Table 1. Allacrost Singleton Classes
Class Name Object Name Header File Namespace Purpose
GameAudio AudioManager audio.h hoa_audio Manages all audio data
GameData DataManager data.h hoa_data Interface between C++ engine and Lua data/scripts
GameInput InputManager engine.h hoa_engine Processes user input events
GameGlobal GlobalManager global.h hoa_global Retains information such as current party, inventory, etc.
GameModeManager ModeManager engine.h hoa_engine Manages the game mode stack
GameSettings SettingsManager engine.h hoa_engine Retains user settings and preferences
GameVideo VideoManager video.h hoa_video Handles all visual data and processing


To access the singleton class objects, all you need to do is include the header file in which it is defined, as specified in table 1. The class objects are located inside their respective namespaces in those files. Unless you are trying to make changes to the core engine itself, you usually don't have to worry about the creation or destruction of these singleton classes. All singleton classes are created and initialized in main.cpp before the main game loop, so you should never need to worry about the class objects being NULL or invalid otherwise.

II. Game Modes

A game mode in Allacrost can be thought of as a context for the current state of the game. For example, when the player is exploring a map they are in a mode and when they are in a battle they are in another mode. The GameMode class defined in engine.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 complete list and we are sure to have more game modes in the future.

Table 2. Common Allacrost Game Modes
Game Mode Name Purpose
BattleMode Battle operation and management
BootMode Boot screen and modification to game settings
MapMode Map exploration, dialogues, and shopping
MenuMode Main character menu
PauseMode Active when the game is paused
QuitMode Active when the user has requested to quit
SceneMode Displays a full-screen art scene


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. In each iteration through the main game loop, the active game mode is the only game mode that is processed, and there may be only one active game mode at any given instant. There may, however, be multiple instances of the same game mode type in the game stack (ie, we can have 3 pointers to MapMode on the stack, 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(uint32 time_elapsed) = 0;
virtual void Draw() = 0;


The Reset() function is called whenever the inherited game mode is made the active game mode (ie, when it is placed on the top of the game mode stack). Its purpose is to re-initialize any class members or other properties that need to be set for this mode. The Update(uint32 time_elapsed) function updates the state of the game mode by processing user input and other game events that have occured since the last time this function was called. The time_elapsed argument holds the amount of time (in milliseconds) that have expired since the last call to this function, which is used for the game's time-based movement engine (we will introduce time-based movement in the next section). Finally the Draw() function is called to place any images on the screen that this game mode wishes to display.

III. Main Loop Operation

The main game loop in Allacrost is small enough that its feasible for us to paste the entire code.

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

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

// 3) Update the timer
frame_time = SettingsManager->UpdateTime();

// 4) Update the game status
ModeManager->Update(frame_time);
} // while (SettingsManager->NotDone())

The first step this loop takes is to draw the next frame to the screen, which requires us to 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 is to update the main timer that the game relies on. And the fourth and final step is to update the state of the active game mode. The only way to exit from this loop (other than pre-maturely terminating the game) 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 MenuMode. 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.

Time-Based Movement


As mentioned in the previous section, the Allacrost engine uses time-based movement. Time-based movement (lets call it TBM for short) is a method in which you only update the game state by the amount of time that has elapsed since the last update. So why do we bother with TBM instead of updating the game by the same amount every pass through the game loop and keeping a constant framerate, like 60FPS (frames per second)? The answer is that 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 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, effective what happens is we 'drop' frames so that the game is still playable (technically we don't drop frames, but rather increase the amount of updating done 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 determental 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. The only place where Allacrost does use a wait call is when the active game mode is PauseMode or QuitMode, because those two modes require almost no CPU usage and it is assumed that if the player is in either one of these states, they may be temporarily using other programs and services on their machine.

IV. User Input Processing

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, or what specific key/button was used to generate the event. Everytime 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 occured, different boolean values are set or cleared in the GameInput singleton.

The table below lists the standard set of user input commands, their default keyboard mappings, and their general purpose.

Table 3. Allacrost User Input Commands
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


There are also several meta-key commands (Ctrl+key) for performing special functions like taking screenshots, but we will not discuss them here. The only one you should know about is Ctrl+Q for registering a quit game event. 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 clearend on the next iteration through the game loop. State is true while the command is active (ie, the key is being held down). Using these three pieces of information, you can design your game mode to check and act on any combination of commands. 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.

Table 4. GameInput Member Access Functions
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()


Note that there is no member access function to the pause or quit commands. This is because those commands are handled internally by the GameInput class (since they are always processed in the same manner). None of the meta-commands are available for access either.

V. Audio Interface

Allacrost uses the OpenAL and the Ogg Vorbis libraries to process audio. A basic understanding of OpenAL is useful to know for audio programming, but is not essential for the primitive tasks we will discuss here. To be able to access the audio interface in your code, you must have #include "audio.h" near the top of your file and use the hoa_audio namespace. You should know the fact that the engine does not treat sound and music the same, although there are many similar operations that can be performed on both music and sound. All sound files are contained in the snd/ direcotry and have a .wav file extension, while all music files are contained in the mus/ directory and have a .ogg file extension.

Playing Sounds


Sounds in Allacrost are represented by SoundDescriptor class objects, and several sounds can be played simulatenously. To load a sound from memory, all that required is the following:

SoundDescriptor my_sound;
bool success = my_sound.LoadSound("bash");


That's it! Notice that the string for the filename passed to the LoadSound() function was simply "bash". The pathname and file extension are not needed here. If you attempted to do the following: my_sound.LoadSound("snd/bash.wav"), the function would return false because it would attempt to open the file snd/snd/bash.wav.wav. Please be sure to remember that. Assuming that the sound is successfully loaded, we can now play it by issuing the following command:

my_sound.PlaySound();

Unlike the LoadSound() function, the PlaySound() function does not return a boolean indicating whether the operation was successful or not. In the audio engine, only loading functions directly return boolean success/fail codes. There is another error detection/handling mechanism used for non-loading functions, but we're not going to get into a discussion about that here (please read the full audio engine documentation for that). Along with loading and playing a sound, there are four other fundamental operations that can be performed on sounds: stop, pause, resume, and rewind. A piece of audio can be in one of four states at a time: playing, paused, stopped, or unloaded. These six fundamental functions only have an effect on a sound when it is in a certain state or states. The table below summarizes the six fundamental sound operations and what sound states they have an affect on.


Table 5. Fundamental Operations of the SoundDescriptor Class
Function Name Sound State(s) the Function Will Affect
bool LoadSound() Unloaded
void PlaySound() Stopped, Paused
void PauseSound() Playing
void ResumeSound() Paused
void StopSound() Playing, Paused
void RewindSound() Playing, Paused, Stopped


Playing Music


Music has the same fundamental operations and states that sounds do, but the six basic functions end in Music() instead of Sound(). MusicDescriptor objects are used instead of SoundDescriptor objects as well. Unlike sounds, only one piece of music may be playing at any given time, but multiple music data can be loaded simulatenously. As with loading sounds, when loading music you do not need to specify the pathname or file extension (ie, the argument to the LoadMusic function should be "Allacrost_Theme" and not "music/Allacrost_Theme.ogg"). The following table summarizes the fundamental operations available for the MusicDescriptor class.


Table 6. Fundamental Operations of the MusicDescriptor Class
Function Name Sound State(s) the Function Will Affect
bool LoadMusic() Unloaded
void PlayMusic() Stopped, Paused
void PauseMusic() Playing
void ResumeMusic() Paused
void StopMusic() Playing, Paused
void RewindMusic() Playing, Paused, Stopped


The Audio Manager


The audio engine is centrally managed by the GameAudio class, which is a singleton. This singleton class object, named AudioManager is available to any file which includes the audio.h header. The most important operations of the GameAudio class that you will need to use are listed in the table below.


Table 7. Fundamental Operations of the GameAudio Class
Function Name Notes
void SetSoundVolume(float volume) Valuable volume range is from 0.0f (silence) to 1.0f (maximum)
void SetMusicVolume(float volume) Valuable volume range is from 0.0f (silence) to 1.0f (maximum)
void PauseAllSounds() Pauses all sounds that are currently playing.
void ResumeAllSounds() Resumes all sounds that are currently paused
void StopAllSounds() Stops all sounds that are currently playing or paused
void RewindAllSounds() Rewinds all sounds that are currently playing, paused, or stopped
void PauseAllMusic() Pauses the music if it is currently playing
void ResumeAllMusic() Resumes the music if it is paused
void StopAllMusic() Stops the music if it is playing or paused
void RewindAllMusic() Rewinds the music if it is currently playing, paused, or stopped
void PauseAudio() Equivalent to calling PauseAllSounds() and PauseAllMusic()
void ResumeAudio() Equivalent to calling ResumeAllSounds() and ResumeAllMusic()
void StopAudio() Equivalent to calling StopAllSounds() and StopAllMusic()
void RewindAudio() Equivalent to calling RewindAllSounds() and RewindAllMusic()


What Was Not Covered


There are many more advanced features to the Allacrost audio engine that we didn't mention here, as the following list demonstrates. To learn more about what functionality is available in the audio engine, read the full audio engine documentation which can be found in section VIII.

Advanced Audio Engine Features

VI. Video Interface

The libraries used in the video engine include OpenGL for graphics, DevIL for images, and SDL_ttf for fonts. The video code is all located within its own directory in the source tree (src/video). To use the video engine, you should add #include "video.h" in your code files and use the hoa_video namespace. The video singleton class object is called VideoManager and you will find yourself using this singleton object often. In this section we'll outline some of the more basic things that you can do with the video engine, including cooordinate systems, loading and drawing of still images, creating GUI menu windows, and text rendering.


Coordinate Systems


A coordinate system is the method in how we divide up the viewable screen. By using the coordinate system, we don't need to worry about what screen resolution the game is running in (800x600, 1024x768, etc.). Each game mode operates in its own coordinate system, which is set using the following call:

VideoManager->SetCoordSys(0.0f, 32.0f, 24.0f, 0.0f);

In this example we have defined the left side of the the screen to be 0.0f, the right side to be 32.0f, the bottom to be 24.0f, and the top to be 0.0f. In fact, this is the same coordinate system that is used while the game is in map mode. But why use these numbers? Well in the case of map mode, we define the coordinate system so that each tile used has a width and height of 1.0f. This makes the drawing code much more simple and convenient. A coordinate system of (0.0f, 1024.0f, 0.0f, and 768.0f) is used in different game modes as well, as if the game was always running in a 1024x768 resolution.

The drawing of images, text, and other objects onto the screen are all affected by the position of the draw cursor. The draw cursor can be moved in both absolute and relative directions like so.

VideoManager->Move(1.0f, 5.0f); // Move to position X = 1.0f, Y = 5.0f
VideoManager->MoveRel(2.0f, 0.0f); // Move cursor 2.0f in the X direction

It is legal to move your cursor off the coordinate system (ie, -0.5f, 0.0f in the coordinate system example code). This way objects can be drawn so that only part of their image is on the screen. This may be obvious, but in your code you should not draw objects that are in a position where no part will be visible on the screen, because that is just a waste of time and effort for the CPU. The way in which objects are drawn relative to the cursor can be modified by several draw flags. For example if we draw an image when the cursor is at position 5.0f, 5.0f, is the image drawn left or right, top or bottom to that position? Or is that position the center of the image? Table 8 shows the cursor alignment flags. There are more flags in addition to these for flipping an image and for blending, but we'll not disclose them here for brevity.

Table 8. Video Drawing Alignment Flags
Flag Name Where Image Is Drawn Relative to Cursor
VIDEO_X_LEFT Image drawn right of cursor
VIDEO_X_RIGHT Image drawn left of cursor
VIDEO_X_CENTER Image centered horizontally to cursor
VIDEO_Y_BOTTOM Image drawn above cursor
VIDEO_Y_TOP Image drawn below cursor
VIDEO_Y_CENTER Image centered vertically to cursor

Here's some example code showing how to change these flags. Notice that the last argument must be zero. This is because the SetDrawFlags function takes an arbitrary number of arguments and needs a terminator value.

VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, 0); // Image is drawn to the right and above the cursor
VideoManager->SetDrawFlags(VIDEO_X_CENTER, 0); // Now image is drawn centered horizontally and above the cursor

One pre-caution: it is very dangerous to simply assume that the desired draw flags are set when you are about to begin drawing objects to the screen. Please be sure that your drawing code sets the drawing flags in the appropriate place, otherwise your rendered scene may come out looking disfigured.


Basic Image Drawing


Now you know enough basic information to draw images to the screen. In this example we're going to draw a StillImage, which is just an image with a single frame. The video engine also supports drawing animated images, but we're not going to get into that here. Unlike with sound and music data, for images we need to do a little work before we make the call to load an image.

StillImage tile;
tile.SetFilename("img/tiles/sand.png");
tile.SetDimensions(1.0f, 1.0f);
bool success = tile.LoadImage();


You may be wondering what the SetDimensions call is for. This specifies the width and height that the image will be in the coordinate system that it is used in. In this example let's assume that the tile is 32x32 pixels and is being used for map mode (whose coordinate system we already discussed). We want a tile to be 1.0f by 1.0f in the dimensions of that system, so we need to explicitly state that here. If we didn't make the call to SetDimensions, the pixel width and height of the image file would be used as its dimensions (ie, 32.0f, 32.0f). If we tried to draw this image to the screen (a {0,32},{0,24} coordiante system), it would be so big that the entire screen couldn't fit it, and the screen would look horrible pixelated.

Usually you will be loading several images all at once. If this is the case, you should load images in batches as the following code illustrates. This will increase performance, but won't impact anything in terms of how images are handled.

VideoManager->BeginImageLoadBatch();
// Perform several LoadImage() calls here
VideoManager->EndImageLoadBatch();


Assuming that the image loaded successfully, we now need to set the draw flags, move the image to the desired position, and finally draw the tile image.

VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_TOP, 0); // Draws the image to the right and bottom of the cursor
VideoManager->Move(0.0f, 0.0f); // Moves to the top left corner of the screen in this coordinate system
tile.DrawImage();


There, now that wasn't so hard was it? Like a good programmer should, when you are done with data you should delete it.

tile.DeleteImage();


Creating GUI Menu Windows


A GUI menu window is nothing more than an image that has borders and some colored texture that fills the inside. You've seen them countless times in other games. Allacrost has developed it's own in-house GUI system specifically for this game. It is easy to create a GUI menu window of any size, and the window can have "ownership" of various other GUI elements, such as text boxes or option boxes (we're not going to go into these components in this introduction). Due to it's nature, GUI elements must be used in a {0, 1024}, {768, 0} coordinate system. So when drawing GUI objects on the screen, you should change your coordinate system and then change it back once you are finished.

Now lets get onto the code. Here's how we can create a GUI menu window.

MenuWindow my_window; my_window.Create(320, 240); // Creates a 320x240 pixel GUI menu
my_window.SetPosition(512, 384); // Sets the position of the window to the center of the screen
my_window.SetAlignment(VIDEO_X_CENTER, VIDEO_Y_CENTER); // Specifies how the window should be drawn relative to its position

As you might have guessed, the GUI system does not use the draw cursor that we used previously for images. Instead, MenuWindows have their own position and alignment (the flags carry the same meaning). This example code will show the MenuWindow centered on the screen when it is drawn, which brings us to the next step.

my_window.Show();
my_window.Draw();

We need to make a call to Show() because by default, MenuWindow objects are hidden, and a Draw() call made on a hidden object would have no effect. The Show() function only needs to be called once, and then the object will remain in a non-hidden state unless you call a Hide() function on the object.

There is actually much more you can do with menu windows than what we have discussed here. You can create compound windows by combining their edges (ie, if you place two equally sized images next to each other vertically, you would only want a single border between them, not two borders). Windows can also be animated so that calling Show() or Hide() on them will gradually unravel the menu window in a number of different ways. Here we just used the default drawing method, which shows/hides the window instantly. Refer to the full video engine documentation to learn more about menu windows and other GUI objects.

my_window.Destroy(); // Don't forget to call me! Otherwise if the game quits, I'll cause a memory leak!


Text Rendering


Now we'll get into how to draw text to the screen. Only unicode text can be drawn to the screen, not standard strings or char buffers. This is because Allacrost aims to be a game which can be enjoyed in any language, not just English. Unicode text is represented via ustring objects, which are defined in the hoa_utils namespace. Almost all of the unicode text that will be drawn to the screen is stored in Lua data files, which we go into in the next section. However if you wish to test simple text rendering (which we do here) without going through all these hoops, you can use a special function called ustring MakeWideString(const string &text) located in utils.h header file and the hoa_utils namespace, which will convert normal strings to ustrings for you.

To render text though, first we'll need to load a font. Allacrost uses True-type fonts, which have a .ttf extension.

bool success = VideoManager->LoadFont("img/fonts/coolfont.ttf", "my_font", 18);

This loads the font coolfont.ttf at a size of 18pts. The second argument, "my_font", is an identifier that will be used to refer to the font. Any identifier name is fine, except for "debug_font", which is used by the video engine to print diagonstic messages. Assuming that the font loaded successfully, we can now use it to print messages to the screen. Text rendering is effected by the video draw flags and draw cursor, so we need to make sure that those are properly set.

VideoManager->SetFont("cool"); // All future font calls will now use this font
VideoManager->SetTextColor(Color(1.0f, 1.0f, 0.0f, 0.8f)); // 80% translucent yellow text
VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_TOP, 0); // Don't forget to set the draw flags!
VideoManager->Move(2.0f, 2.0f); // Moves the draw cursor
VideoManager->DrawText(MakeWideString("Hello world!"));

You can also give your rendered text various shadows, but we're not going to go into that. This is just basic, low-level text rendering that we have gone over. Higher-level rendering includes the use of text boxes (GUI objects), which are more fully-featured and can do things like animated text rendering (here we've only instantly drawn text to the screen).


What Was Not Covered


Now you know the basics of drawing images, menu windows, and text to the screen. There are dozens of amazing things you can do with the Allacrost video engine though, and if you intend to design your own game modes or other game content, you should definitely read the full documentation on the video engine. See section VIII. for information on where to find it. Below is a list of features of the video engine that we didn't get a chance to go into to wet your appetite.

Advanced Video Engine Features

VII. Data and Scripting Interface

Allacrost uses the Lua scripting language for both data storage and scripting. Allacrost uses two file types for data with extensions .lua for uncompiled, raw text data and .hoa for compiled binary data. It doesn't matter which file type you use, as nothing will be changed in the way that you need to manage the way you access data (we'll have higher performance reading from compiled binary files than raw text files though, so they are used more often). The singleton class that handles all of the communication between the C++ engine and the Lua data/scripts is the GameData class. The interface is simple enough that you should be able to access Lua files using the data and scripting engine without knowing a shred of knowledge about Lua (although it wouldn't hurt). Using this component of the game engine, you can both read and write Lua data files. In this documentation, we only cover the basics involved with reading Lua files and not writing. The code used in this introduction can be used by including the header file "data.h" and using the namespace hoa_data.


Reading Data Files


A data file that is to be opened for reading priveledges is represented and managed by the ReadDataDescriptor class. To open a data file, we need to do the following.

ReadDataDescriptor my_data;
bool success = my_data.OpenFile("dat/config/settings.hoa");

Assuming everything went okay, now we can read data from this file. The types of data that the file may contain are: booleans, integers, floating point numbers, strings, and unicode strings. To get any of these primitive data types, we need to know its key. You can think of a key as simply the name of a variable that holds the data, since that really what it is. The key is the argument to all of the primitive data access functions listed in table 9, which return the value.

Table 9. Primitive Data Read Operations
Function Data Type
bool ReadBool(const char *key); Booleans
int32 ReadInt(const char *key); Integers
float ReadFloat(const char *key); Floating Point Numbers
string ReadString(const char *key); Strings
ustring ReadUString(const char *key); Unicode Strings

Now we need to discuss what a table is. Tables are the data structure in Lua and can hold all of the primitive data types, as well as functions and other tables. We use tables in our data files to organize C++ data structures (for example, a two-dimensional vector of tiles). Here is how we can retrieve a string value that is a member of a table.

my_data.OpenTable("image_map");
string filename = ReadString("img_filename");
my_data.CloseTable();

That seemed harmless enough, didn't it? But it can be rather easy to write code that doesn't load file data effectively because data may be retrieved in the wrong "scope". The scope is what tells the data and scripting engine where it should look when it recieves a request to retrieve a piece of data. Initially a file is in a global scope, where all global variables and tables are found. When the table was opened in the last example, the new scope became of that table. So if there was an integer with a key my_int that was in the global scope and I attempted to retreive the value of that key with the ReadInt funciton after I opened the table and before I closed the table, then unless the "image_map" table had an element with the same key, the operation would have failed. That is why it is very important that for every table you open, you close it as soon as you are finished with it, so the proper scope is maintained.

Speaking of failures, how can you check if a read operation was sucessful or not? One (unreliable) way is to check the value of the datum recieved. If the read operation failed, the value returned will the equivalent of a zero for the data type (false, 0, 0.0f, or an empty string). A better way to check if a read operation was successful or not is to check the return value of a function that returns various error conditions during operation. To find out the name of that function and what types of error codes it detects, you should refer to the full data and scripting documentation, found in section VIII.


What Was Not Covered


With this brief introduction, you now know enough to read basic data from Lua files. There is much more to learn, however, and unless you are only going to be dealing with files that only need to do this much, you're going to need to read on in the full documentation. Below are a list of features that we didn't have time to cover in this introduction.

Advanced Data and Scripting Engine Features


VIII. Beyond the Basics

By now you should know enough that you can do something useful with the Allacrost engine, or maybe even extend it. But as was stated in the introduction, you only know the beginning of what this engine can do for you. It is strongly recommended that you use this starting point to learn about the more advanced features of this engine. Listed below are links to more complete pieces of documentation on particular components of the Allacrost engine. (The links to these pieces of documentation will be made at a later time).