Putting Together a Full Game(10)
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.
{
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();
}
}