Updating
the Local Player
Between updates from the
server, the clients need to update all players to keep the
game running smoothly. The client application limits updates to every 33ms (30
times
a second), which matches the server update rate. Between these player updates,
the
client is allowed to collect input from the player who is used to change their
actions.
The cApp::frame function is
generally used to update the local player. The players
use the keyboard and mouse to control their characters, so I included a few
Input
Core objects (m_keyboard and m_mouse):
bool cApp::frame()
{
// get local input every frame
m_keyboard.acquire();
m_mouse.acquire();
m_keyboard.read();
m_mouse.read();
// handle connection screen
if(!g_connected || m_players[0].player_id == 0)
{
// display connection message
clear_display(0, 1.0f);
if(begin_display_scene())
{
draw_font(m_font, "Connecting to server ", 0, 0, 0, 0, COLOR_WHITE, DT_LEFT);
end_display_scene();
}
present_display();
return true;
}
// store movements every frame
static long move_action = 0, last_move = 0;
if(m_keyboard.get_key_state(KEY_UP) || m_keyboard.get_key_state(KEY_W))
move_action |= ACTION_MOVE_UP;
if(m_keyboard.get_key_state(KEY_RIGHT) || m_keyboard.get_key_state(KEY_D))
move_action |= ACTION_MOVE_RIGHT;
if(m_keyboard.get_key_state(KEY_DOWN) || m_keyboard.get_key_state(KEY_S))
move_action |= ACTION_MOVE_DOWN;
if(m_keyboard.get_key_state(KEY_LEFT) || m_keyboard.get_key_state(KEY_A))
move_action |= ACTION_MOVE_LEFT;
// store attack action
if(m_keyboard.get_key_state(KEY_SPACE) || m_mouse.get_button_state(MOUSE_LBUTTON))
move_action |= ACTION_ATTACK;
// rotate camera
static bool cam_moved = false;
if(m_mouse.get_x_delta() > 0)
{
m_cam_angle -= 0.1f;
cam_moved = true;
}
if(m_mouse.get_x_delta() < 0)
{
m_cam_angle += 0.1f;
cam_moved = true;
}
static DWORD update_counter = timeGetTime();
// only update players every 33ms (30 times a second)
if(timeGetTime() < update_counter + 33)
return true;
// set flag to allow player movement
bool allow_move = true;
// do not allow movement if still swinging weapon or being hurt
if(m_players[0].last_state == STATE_SWING || m_players[0].last_state == STATE_HURT)
allow_move = false;
// handle movements if allowed
if(allow_move)
{
// process attack
if(move_action & ACTION_ATTACK)
{
move_action = 0; // clear movement
last_move = 0; // clear last movement
// send attack message - let server signal swing
sStateChangeMsg change_msg;
change_msg.header.type = MSG_STATE_CHANGE;
change_msg.header.size = sizeof(sStateChangeMsg);
change_msg.header.player_id = m_players[0].player_id;
change_msg.state = STATE_SWING;
change_msg.direction = m_players[0].direction;
send_network_msg(&change_msg, DPNSEND_NOLOOPBACK);
}
// process local player movements
if(move_action > 0 && move_action < 13)
{
// set new player state
EnterCriticalSection(&m_update_cs);
m_players[0].last_state = STATE_MOVE;
m_players[0].direction = g_angles[move_action] - m_cam_angle + 4.71f;
LeaveCriticalSection(&m_update_cs);
// reset last move if camera moved since last update
if(cam_moved)
{
cam_moved = false;
last_move = 0;
}
// send actions to server if changed from last move
if(move_action != last_move)
{
last_move = move_action; // store last action
m_players[0].last_update_time = timeGetTime();
sStateChangeMsg change_msg;
// construct message
change_msg.header.type = MSG_STATE_CHANGE;
change_msg.header.size = sizeof(sStateChangeMsg);
change_msg.header.player_id = m_players[0].player_id;
change_msg.state = STATE_MOVE;
change_msg.direction = m_players[0].direction;
send_network_msg(&change_msg, DPNSEND_NOLOOPBACK);
}
}
else
{
// change to idle state
EnterCriticalSection(&m_update_cs);
m_players[0].last_state = STATE_IDLE;
LeaveCriticalSection(&m_update_cs);
// send update only if player moved last update
if(last_move)
{
last_move = 0;
sStateChangeMsg change_msg;
change_msg.header.type = MSG_STATE_CHANGE;
change_msg.header.size = sizeof(sStateChangeMsg);
change_msg.header.player_id = m_players[0].player_id;
change_msg.state = STATE_IDLE;
change_msg.direction = m_players[0].direction;
send_network_msg(&change_msg, DPNSEND_NOLOOPBACK);
}
}
}
update_all_players();
render_scene();
move_action = 0; // clear action data for next frame
update_counter = timeGetTime(); // reset update counter
return true;
}
At every frame, the input devices
are restored (in case a device’s focus has been
lost), and input is read in. If the user presses Esc, the game-play quits by
returning
a value of false from the frame function.
From here, game-play may only
continue if the client is connected to the server.
If no such connection exists, a message displays to that effect. Also, if a
player is
still waiting for a DirectPlay identification number from the server, a message
displays,
and a request is periodically sent to the server for the correct identification
number.
From here on, player input is
parsed. A single variable tracks player actions (move_action),
and each bit in the variable represents a specific action (as shown in Figure
19.17). The
user’s actions are move up, move down, move left, move right, and attack. Also,
camera
angle changes are recorded (and flagged for later updating).
Normally, players are allowed
to move around the world, but if a player is currently
swinging his weapon or being hurt, that player is not allowed to move. You use
the
allow_move flag to signify when a player’s actions can be processed, as shown
here:
If a player chooses to attack,
you need to construct a state-change message and
send that message to the server. After you send the state-change message, clear
the
player’s movement actions. Notice that the client does not change its own state
at
this point; the server determines when to change the player’s state.
If the player did not attack,
his actions are checked to see whether the player is
moving.
After the player’s state and
movement direction is set, the Frame function continues
by resetting the camera’s movements (by setting the cam_move flag to false). The
player’s controls are relative to the camera-viewing angle (if the player is
pressing
the up arrow key, he is walking away from the camera). If you change the
camera’s
angle while the player is walking, you force the player’s direction to change as
well.
The client takes this change of the player’s direction into consideration when
the
camera is rotated.
Once a player has moved, the
client sends a state-change message to the server.
Notice that the state-change message is sent only if the player’s movement is
different
from the last move he performed (as recorded in the last_move variable).
If the player hasn’t moved, his
state is changed to standing still (STATE_IDLE), and a
state-change message is sent to the server.
At this point, the local
player’s actions have been recorded and sent to the server.
Next, all players are updated, the scene is rendered, and the movement actions
are
reset for the next frame.