Creating Combat Sequences(4)
cApp::frame
The cApp::frame function is a little crowded in its current incarnation. Here, Frame has
the job of collecting and processing player input. The only input used is that of the
mouse; the left mouse button selects a target character, a spell to cast, or an attack to
perform. The right mouse button closes the spell selection window if it is currently
open. Once a target and an action are picked, the appropriate action is performed.
Besides processing input, the frame function renders everything to the display,
including the arena, characters, spell animations, status window (which displays the
player’s health and mana points), charge bar, spell list, and action buttons:
{
static DWORD update_counter = timeGetTime();
if (timeGetTime() < update_counter + 33) // lock to 30 fps
return true ;
update_counter = timeGetTime();
m_keyboard.acquire();
m_keyboard.read();
m_mouse.acquire();
m_mouse.read();
// exit if ESC pressed
if (m_keyboard.get_key_state(KEY_ESC))
return false ;
static bool select_spell = false ;
static long target_id = -1;
static sCharacter* player = m_char_controller.get_char(0);
// get selected character if left button pressed
if (m_mouse.get_button_state(MOUSE_LBUTTON))
{
long mouse_x = m_mouse.get_x_pos();
long mouse_y = m_mouse.get_y_pos();
m_mouse.m_locks[MOUSE_LBUTTON] = true ;
m_mouse.set_button_state(MOUSE_LBUTTON, false );
if (select_spell) // see if selecting a spell
{
long spell_index = (mouse_y - 8) / PER_SPELL_HEIGHT * PER_ROW_SPELLS + (mouse_x - 8) / PER_SPELL_WIDTH;
if (spell_index >= 0 && spell_index < 64)
{
sSpell* spell = m_spell_controller.get_spell(spell_index);
// make sure player knows spell and has enough mana
if (char_can_spell(player, spell, spell_index))
{
player->spell_index = spell_index;
player->target_type = CHAR_MONSTER;
m_char_controller.set_char_action(player, CHAR_SPELL, 0);
select_spell = false ;
}
}
}
else
{
if (target_id != -1 && player->charge >= 100.0f)
{
// set victim and attacker information
sCharacter* victim = m_char_controller.get_char(target_id);
player->victim = victim;
victim->attacker = player;
player->target_x = victim->pos_x;
player->target_y = victim->pos_y;
player->target_z = victim->pos_z;
// Determine if attack selected by checking the coordinates of the mouse against the
// coordinates of the attack button on screen.
//
// button coordinates range from 572,328 to 636,360.
if (mouse_x >= 572 && mouse_x < 636 && mouse_y >= 328 && mouse_y < 360)
m_char_controller.set_char_action(player, CHAR_ATTACK, 0);
// Determine if spell selected by checking the coordinates of the mouse against the
// coordinates of the spell button on screen.
//
// button coordinates range from 572,364 to 636,396.
if (mouse_x >= 572 && mouse_x < 636 && mouse_y >= 364 && mouse_y < 396)
select_spell = true ;
}
// see if a character picked
target_id = get_char_at(mouse_x, mouse_y);
}
}
// clear spell state if right mouse button clicked
if (m_mouse.get_button_state(MOUSE_RBUTTON))
{
m_mouse.m_locks[MOUSE_RBUTTON] = true ;
m_mouse.set_button_state(MOUSE_RBUTTON, false );
select_spell = false ;
}
m_char_controller.update(33);
m_spell_controller.update(33);
m_camera.point(300.0f, 300.0f, -340.0f, 0.0f, 0.0f, 0.0f);
set_display_camera(&m_camera);
// render everything
clear_display(D3DCOLOR_RGBA(0, 32, 64, 255), 1.0f);
if (begin_display_scene())
{
enable_zbuffer();
m_terrain_obj.render();
m_char_controller.render(-1, NULL, 0.0f);
m_spell_controller.render(NULL, 0.0f);
if (target_id != -1) // check if target pointer needs rendering
{
cWorldPos target_pos;
// move target pointer to target character position
sCharacter* target = m_char_controller.get_char(target_id);
target_pos.m_use_billboard = true ;
target_pos.move(target->pos_x, target->pos_y, target->pos_z);
target_pos.rotate(0.0f, 0.0f, timeGetTime() / 100.0f);
float min_y, max_y;
// offset to half of character height
target-> object .get_bounds(NULL, &min_y, NULL, NULL, &max_y, NULL, NULL);
float y_off = min_y + (max_y - min_y) * 0.5f;
target_pos.move_rel(0.0f, y_off, 0.0f);
// render the target
set_texture(0, NULL);
disable_zbuffer();
set_display_world(&target_pos);
render_vertex_buffer(m_target_vb, 0, 2, D3DPT_TRIANGLELIST);
enable_zbuffer();
}
// display stats screen
char text[128];
sprintf(text, "HP: %ld / %ld\r\nMP: %ld / %ld",
player->health_points, player->char_def.health_points,
player->mana_points, player->char_def.mana_points);
m_char_stats.render(text, COLOR_WHITE);
begin_sprite();
// display charge
RECT rect;
calculate_texture_rect(m_button, 0, 64, 128, 16, &rect);
draw_texture(g_d3d_sprite, m_button, &rect, 508, 450, 1.0f, 1.0f, COLOR_WHITE);
calculate_texture_rect(m_button, 0, 80, 1.24f * player->charge, 12, &rect);
draw_texture(g_d3d_sprite, m_button, &rect, 510, 452, 1.0f, 1.0f, COLOR_WHITE);
// display attack and spell buttons
if (player->charge >= 100.0f)
{
calculate_texture_rect(m_button, 0, 0, 64, 32, &rect);
draw_texture(g_d3d_sprite, m_button, &rect, 572, 328, 1.0f, 1.0f, COLOR_WHITE);
calculate_texture_rect(m_button, 0, 32, 64, 32, &rect);
draw_texture(g_d3d_sprite, m_button, &rect, 572, 364, 1.0f, 1.0f, COLOR_WHITE);
}
end_sprite();
// display spell list
if (select_spell)
{
m_spell_options.render(NULL, COLOR_WHITE);
// display known spells
for ( long i = 0; i < NUM_SPELL_DEF; i++)
{
sSpell* spell = m_spell_controller.get_spell(i);
if (char_can_spell(player, spell, i))
{
long x = i % PER_ROW_SPELLS * PER_SPELL_WIDTH;
long y = i / PER_ROW_SPELLS * PER_SPELL_HEIGHT;
char text[128];
sprintf(text, "%s(cost %d)", spell->name, spell->cost);
draw_font(m_font, text, x+8, y+8, 0, 0, COLOR_WHITE, DT_LEFT);
}
}
}
end_display_scene();
}
present_display();
return true ;
}
A frame timer governs the speed in which the game plays. Currently, the battle project
is locked at 30 frames per second. If an update is allowed, execution continues
by reading in the player input.
Frame next determines what to process if the player presses the left mouse button.
Remember that this includes selecting a target, a spell, or an attack.
From here, frame determines what to process. If the player clicks the Spell button,
the spell list window opens displaying all known spells. When the spell
list window opens, the select_spell flag (which is set to false by default) is set to true,
and frame waits for a spell to be selected or for the window to be closed (by the
player clicking the right mouse button).
You select a spell by calculating the coordinates that were clicked onscreen and
comparing those coordinates to the location where each spell name was printed to
the window. Each spell name consumes a 200 x 20 pixel section of the window
(which is located at the screen coordinates 8,8), and there is room for 64 spell
names in the window.
Once the player selects a spell from the spell selection window, the appropriate
spell is cast, and the control classes process the effects. The spell selection window
is flagged as closed, and execution continues. If a spell is not being selected, the
frame function continues execution by determining whether the player clicked
either the attack or spell buttons.
After an action is selected (and if a target character has been chosen and the
player’s charge timer is fully charged), the attack information is set up. If the
player clicked the Attack button, the attack action initiates. If the player clicked the
Spell button, the spell window opens.
Regardless of what is clicked, the code continues to the next line that determines
whether a target character has been selected via a call to get_char_at:
If the player clicked the right mouse button, the spell selection window closes, and
execution continues with the frame function updating the characters and spell controllers
(by calling each controller’s update function). At this point, all input processing
is complete and rendering begins.
To begin, the camera is positioned, the scene cleared, and the scene rendering
begins by drawing the arena, characters, and spells.
After the arena, characters, and spells have been rendered, it comes time to render
the target vertex buffer (only if a target character is selected). You center the target
in the middle of the character (based on the character’s height) and draw the target
(using a billboard world transformation matrix so that the target always faces
the camera).
Next comes the player character’s status window, which you update to display the
player’s current health and mana points.
Next, you display the charge timer. The charge timer ranges anywhere from 0 to
100, and using the buttons image previously loaded into the m_button object, blit
only a small portion of the image that represents the current level of charge.
Now, you draw the action buttons (only if the charge timer is fully charged).
To wrap up the render, draw the spell selection window if needed, and display each
known spell for the player’s selection.