Using State-Based Processing
I developed the sample game to
use state-based processing in order to effectively
use the application class's processing structure. The game uses these four
states:
■ Main menu state. When
executed, the game displays a main menu giving the
player the option to start a new game, load a game, return to or save a game
in progress, or to quit the game.
■ In-game state. This state is
used most often because it takes care of updating
and rendering each frame of the game.
■ Character status window
state. Whenever the player right-clicks during gameplay,
he accesses the character status window. Here, the player can use, equip,
or unequip items just by clicking them, as well as check on the character’s
statistics and known spells.
■ Barter window state. When the
player talks to the villager, the barter window
opens in order to buy items. Click items to buy or press Esc or the right
mouse button to exit.
You use a state manager object
to control the processing of these four states.
menu_frame:
You use the menu_frame function to
display the main menu, which, in all its glory, has
a spinning texture-mapped polygon overlaid with the main menu options. The
purpose
of the menu_frame function is to track which option is being selected and
to handle
the appropriate functions.
void menu_frame(void* data, long purpose)
{
static const sMenuVertex verts[] =
{
{ -100.0f, 100.0f, 1.0f, 0.0f, 0.0f },
{ 100.0f, 100.0f, 1.0f, 1.0f, 0.0f },
{ -100.0f, -100.0f, 1.0f, 0.0f, 1.0f },
{ 100.0f, -100.0f, 1.0f, 1.0f, 1.0f }
};
static IDirect3DVertexBuffer9* menu_vb;
static IDirect3DTexture9* menu_texture;
static IDirect3DTexture9* menu_select;
static ID3DXFont* title_font;
static cCamera menu_cam;
static cWorldPos menu_pos;
cApp* app = (cApp*) data;
if(purpose == INIT_PURPOSE) // initialize menu related data
{
// create and set the menu vertices
create_vertex_buffer(&menu_vb, array_num(verts), sizeof(sMenuVertex), MENU_FVF);
fill_in_vertex_buffer(menu_vb, 0, array_num(verts), verts);
load_texture_from_file(&menu_texture, "..\\Data\\MenuBD.bmp", 0, D3DFMT_UNKNOWN);
load_texture_from_file(&menu_select, "..\\Data\\Select.bmp", 0, D3DFMT_UNKNOWN);
create_font(&title_font, "Consolas", 48, false, false);
menu_cam.point(0.0f, 0.0f, -150.0f, 0.0f, 0.0f, 0.0f);
}
else if(purpose == SHUTDOWN_PURPOSE) // shutdown resources used in menu
{
release_com(menu_vb);
release_com(menu_texture);
release_com(menu_select);
release_com(title_font);
}
else // process a frame of menu
{
// exit game or return to game if ESC pressed
if(app->m_keyboard.get_key_state(KEY_ESC))
{
app->m_keyboard.m_locks[KEY_ESC] = true;
app->m_keyboard.set_key_state(KEY_ESC, false);
app->m_state_manager.pop(app);
return;
}
// see which option was selected if mouse button pressed
if(app->m_mouse.get_button_state(MOUSE_LBUTTON))
{
// lock the mouse button and clear button state
app->m_mouse.m_locks[MOUSE_LBUTTON] = true;
app->m_mouse.set_button_state(MOUSE_LBUTTON, false);
// determine which, if any selection.
long mouse_start = app->m_mouse.get_y_pos() - MAIN_MENU_TOP;
if(mouse_start >= 0)
{
long hit_index = mouse_start / MAIN_MENU_HEIGHT;
app->m_state_manager.pop(app); // pop the menu state
// determine what to do based on selection
switch(hit_index)
{
case NEW_GAME:
app->m_state_manager.pop_all(app);
app->m_game_chars.free();
app->m_game_spells.free();
app->m_game_script.reset_data();
app->m_game_chars.add_char(ID_PLAYER, 0, CHAR_PC, CHAR_STAND, -100.0f, 0.0f, 50.0f, 3.14f);
g_player = app->m_game_chars.get_char(ID_PLAYER);
app->m_teleport_map = -1;
app->m_state_manager.push(game_frame, app);
// start new game and let script process as startup
app->load_level(1);
break;
case RETURN_TO_GAME:
app->m_state_manager.push(game_frame, app);
break;
case LOAD_GAME:
app->m_state_manager.pop_all(app);
app->m_game_chars.free();
app->m_game_spells.free();
app->m_game_chars.add_char(ID_PLAYER, 0, CHAR_PC, CHAR_STAND, -100.0f, 0.0f, 50.0f, 3.14f);
g_player = app->m_game_chars.get_char(ID_PLAYER);
// load character's stats and inventory
app->m_game_chars.load_char(ID_PLAYER, "..\\Data\\Char.cs");
g_player->char_ics->load("..\\Data\\Char.ci");
if(g_player->char_def.weapon != -1)
app->m_game_chars.equip(g_player, g_player->char_def.weapon, WEAPON, true);
g_player->health_points = g_player->char_def.health_points;
g_player->mana_points = g_player->char_def.mana_points;
app->m_game_script.load("..\\Data\\Script.sav");
app->m_teleport_map = -1;
app->m_state_manager.push(game_frame, app);
app->m_game_chars.move_char(ID_PLAYER, 100.0f, 0.0f, -100.0f);
app->load_level(1); // start in town
break;
case SAVE_GAME:
app->m_game_script.save("..\\Data\\Script.sav");
// save character's stats and inventory
app->m_game_chars.save_char(ID_PLAYER, "..\\Data\\Char.cs");
g_player->char_ics->save("..\\Data\\Char.ci");
break;
case QUIT_GAME:
app->m_state_manager.pop_all(app);
break;
}
return;
}
} // [end] if(app->m_mouse.get_button_state(MOUSE_LBUTTON))
set_display_camera(&menu_cam);
menu_pos.rotate(0.0f, 0.0f, timeGetTime() / 4000.0f); // rotate backdrop
// render menu backdrop and all menus
begin_display_scene();
disable_zbuffer();
set_display_world(&menu_pos);
g_d3d_device->SetTexture(0, menu_texture);
render_vertex_buffer(menu_vb, 0, 2, D3DPT_TRIANGLESTRIP);
// draw the game's title
draw_font(title_font, g_title_name, 0, 16, CLIENT_WIDTH, 0, COLOR_LIGHT_YELLOW, DT_CENTER);
// select option based on mouse position
long mouse_start = app->m_mouse.get_y_pos() - MAIN_MENU_TOP;
if(mouse_start >= 0)
{
long hit_index = mouse_start / MAIN_MENU_HEIGHT;
if( hit_index == NEW_GAME ||
(hit_index == RETURN_TO_GAME && (g_menu_options & MENU_BACK)) ||
(hit_index == LOAD_GAME && (g_menu_options & MENU_LOAD)) ||
(hit_index == SAVE_GAME && (g_menu_options & MENU_SAVE)) ||
(hit_index == QUIT_GAME))
{
begin_display_sprite();
RECT rect;
calculate_texture_rect(menu_select, 0, 0, 0, 0, &rect);
long dest_y = hit_index * MAIN_MENU_HEIGHT + MAIN_MENU_TOP;
draw_texture(g_d3d_sprite, menu_select, &rect, 192, dest_y, 1.0f, 1.0f, COLOR_WHITE);
end_display_sprite();
}
}
// draw enabled options
draw_font(app->m_font, "New Game", 0, 150, CLIENT_WIDTH, 0, COLOR_LIGHT_YELLOW, DT_CENTER);
if(g_menu_options & MENU_BACK)
draw_font(app->m_font, "Back to Game", 0, 214, CLIENT_WIDTH, 0, COLOR_LIGHT_YELLOW, DT_CENTER);
if(g_menu_options & MENU_LOAD)
draw_font(app->m_font, "Load Game", 0, 278, CLIENT_WIDTH, 0, COLOR_LIGHT_YELLOW, DT_CENTER);
if(g_menu_options & MENU_SAVE)
draw_font(app->m_font, "Save Game", 0, 342, CLIENT_WIDTH, 0, COLOR_LIGHT_YELLOW, DT_CENTER);
draw_font(app->m_font, "Quit", 0, 410, CLIENT_WIDTH, 0, COLOR_LIGHT_YELLOW, DT_CENTER);
end_display_scene();
present_display();
}
}