天行健 君子当自强而不息

Controlling Players and Characters(36)

Notice that I keep talking about removing characters from the list. What about all
the hard work you’ve put into your PCs—how are you ever going to store their
achievements for later loading? With the following set of saving and loading functions,
of course!

///////////////////////////////////////////////////////////////////////////////////////////////////

bool cCharController::save_char(long id, const char* filename)
{
    sCharacter* character = get_char(id);
    
if(character == NULL)
        
return false;

    FILE* fp = fopen(filename, "wb");
    
if(fp == NULL)
        
return false;

    
// output character data
    fwrite(&character->char_def,      1, sizeof(character->char_def),      fp);
    fwrite(&character->health_points, 1, 
sizeof(character->health_points), fp);
    fwrite(&character->mana_points,   1, 
sizeof(character->mana_points),   fp);
    fwrite(&character->ailments,      1, 
sizeof(character->ailments),      fp);
    fwrite(&character->pos_x,         1, 
sizeof(character->pos_x),         fp);
    fwrite(&character->pos_y,         1, 
sizeof(character->pos_y),         fp);
    fwrite(&character->pos_z,         1, 
sizeof(character->pos_z),         fp);
    fwrite(&character->direction,     1, 
sizeof(character->direction),     fp);

    fclose(fp);

    
// save inventory
    if(character->char_ics)
    {
        
char ics_file[MAX_PATH];
        sprintf(ics_file, "ICS%s", filename);
        character->char_ics->save(ics_file);
    }

    
return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

bool cCharController::load_char(long id, const char* filename)
{
    sCharacter* character = get_char(id);
    
if(character == NULL)
        
return false;

    FILE* fp = fopen(filename, "rb");
    
if(fp == NULL)
        
return false;

    
// read in character data
    fread(&character->char_def,      1, sizeof(character->char_def),      fp);
    fread(&character->health_points, 1, 
sizeof(character->health_points), fp);
    fread(&character->mana_points,   1, 
sizeof(character->mana_points),   fp);
    fread(&character->ailments,      1, 
sizeof(character->ailments),      fp);
    fread(&character->pos_x,         1, 
sizeof(character->pos_x),         fp);
    fread(&character->pos_y,         1, 
sizeof(character->pos_y),         fp);
    fread(&character->pos_z,         1, 
sizeof(character->pos_z),         fp);
    fread(&character->direction,     1, 
sizeof(character->direction),     fp);

    fclose(fp);

    
// load inventory
    if(character->char_ics)
    {
        
char ics_file[MAX_PATH];
        sprintf(ics_file, "ICS%s", filename);
        character->char_ics->load(ics_file);
    }

    
return true;
}

Both of the two preceding functions take the character’s identification number to
save or load, as well as the filename to use.

That about rounds up the functions used to prepare, add, and remove the characters
from the game. Now it’s time to get them all moving around performing their actions.
Previously you saw the functions used to update the individual character types; now
comes the single function you’ll call to update all characters at once:

///////////////////////////////////////////////////////////////////////////////////////////////////

void cCharController::update(long elapsed)
{    
    
if(m_root_char == NULL)
        
return;

    
static long effect_counter = 0;
    effect_counter += elapsed;

    
float x_move, y_move, z_move;
    sCharacter* next_char;

    
// loop through all characters
    for(sCharacter* char_ptr = m_root_char; char_ptr != NULL; char_ptr = next_char)
    {
        next_char = char_ptr->next;     
// remember next character

        // only update if enabled, not asleep or paralyzed.
        if(! char_ptr->update_enable)
            
continue;

        
// update action timer if in use
        if(char_ptr->action_timer != 0)
        {
            char_ptr->action_timer -= elapsed;

            
if(char_ptr->action_timer < 0)
                char_ptr->action_timer = 0;
        }

        
if(char_ptr->msg_timer > 0)
            char_ptr->msg_timer -= elapsed;

        
// reset charge counter if attacking, spell, or item.
        if(char_ptr->action == CHAR_ATTACK || char_ptr->action == CHAR_SPELL || char_ptr->action == CHAR_ITEM)
            char_ptr->charge = 0.0f;

        
// kill character if no health left
        if(char_ptr->health_points <= 0 && char_ptr->action != CHAR_DIE)
            set_char_action(char_ptr, CHAR_DIE, 0);

        
bool to_process = true;     // mark that processing can continue later on
        bool dead_char  = false;    // mark character as still alive

        // do not allow on update if asleep or paralyzed
        if((char_ptr->ailments & AILMENT_SLEEP) || (char_ptr->ailments & AILMENT_PARALYZE))
            to_process = 
false;

        
// process actions
        if(char_ptr->action_timer == 0)
        {
            
switch(char_ptr->action)
            {
            
case CHAR_ATTACK:
                
if(to_process)
                    attack(char_ptr, char_ptr->victim);

                
break;

            
case CHAR_SPELL:
                
if(to_process)
                    m_spell_controller->add(char_ptr);

                
break;

            
case CHAR_ITEM:
                
if(to_process)
                    use_item(char_ptr, char_ptr, char_ptr->item_index, char_ptr->char_item);

                
break;

            
case CHAR_DIE:
                death(char_ptr->attacker, char_ptr);
                dead_char  = 
true;
                to_process = 
false;
                
break;
            }
        }

        
// clear movement
        x_move = y_move = z_move = 0.0f;

        
// only continue if allowed (in case character died)
        if(to_process)
        {
            
// only allow updates if lock/timer not in use
            if(char_ptr->action_timer == 0 && !char_ptr->is_lock)
            {                    
                char_ptr->action = CHAR_IDLE;   
// reset action

                
if(char_ptr->type == CHAR_PC)
                    pc_update(char_ptr, elapsed, &x_move, &y_move, &z_move);
                
else
                    npc_monster_update(char_ptr, elapsed, &x_move, &y_move, &z_move);

                
// check for validity of movement (clear if invalid)
                if(!check_move(char_ptr, &x_move, &y_move, &z_move))
                {
                    x_move = y_move = z_move = 0.0f;
                    char_ptr->action = CHAR_IDLE;
                }
            }

            process_update(char_ptr, x_move, y_move, z_move);

            char_ptr->charge += (elapsed/1000.0f * get_charge_rate(char_ptr));

            
if(char_ptr->charge > 100.0f)
                char_ptr->charge = 100.0f;
        }

        
// process timed ailments (only in live characters)
        if(!dead_char && char_ptr->ailments)
        {
            
// sleeping characters have 4% to wake up
            if((char_ptr->ailments & AILMENT_SLEEP) && rand()%100 < 4)
                char_ptr->ailments &= ~AILMENT_SLEEP;

            
// paralyzed character have 2% chance to recover
            if((char_ptr->ailments & AILMENT_PARALYZE) && rand()%100 < 2)
                char_ptr->ailments &= ~AILMENT_PARALYZE;

            
// poison removes 2 hp every 4 seconds
            if((char_ptr->ailments & AILMENT_POISON) && effect_counter >= 4000)
            {
                char_ptr->health_points -= 2;
                set_char_msg(char_ptr, "Poison -2 HP", 500, D3DCOLOR_RGBA(0, 255, 64, 255));
            }
        }
    }

    
// reset effect counter (after 4 seconds)
    if(effect_counter >= 4000)
        effect_counter = 0;
}

The Update function is called once every frame. Taking a single argument (the time
elapsed since the last update), the Update function calls upon each character’s
respective update function, validates each character’s movements and actions, and
wraps up by processing the actions. Then a call to Render is in order to display all
characters visible within the specified frustum.
///////////////////////////////////////////////////////////////////////////////////////////////////

void cCharController::render(long elapsed, cFrustum* frustum, float z_dist)
{
    m_frustum = frustum;

    
// construct the viewing frustum (if none passed)
    if(m_frustum == NULL)
    {
        cFrustum view_frustum;
        view_frustum.create(z_dist);
        m_frustum = &view_frustum;
    }

    DWORD time = 0;

    
// get time to update animations (30fps) if elapsed value passed == -1
    if(elapsed == -1)
        time = timeGetTime() / 30;

    
// variables for printing messages
    bool got_matrices = false;
    D3DXMATRIX mat_world, mat_view, mat_proj;
    D3DVIEWPORT9 viewport;

    
// loop through each character and draw
    for(sCharacter* char_ptr = m_root_char; char_ptr != NULL; char_ptr = char_ptr->next)
    {
        
// update animation based on elapsed time passed
        if(elapsed != -1)
        {
            char_ptr->last_anim_time += elapsed/30;
            time = char_ptr->last_anim_time;
        }

        
float max_y, radius;
        cObject& 
object = char_ptr->object;
        
object.get_bounds(NULL, NULL, NULL, NULL, &max_y, NULL, &radius);

        
// draw character if in viewing frustum
        if(! m_frustum->is_sphere_in(object.get_x_pos(), object.get_y_pos(), object.get_z_pos(), radius))
            
continue;

        
object.update_anim(time, true);
        
object.render();
        
        
if(char_ptr->char_def.weapon != -1)
            char_ptr->weapon_object.render();

        
// draw message if needed
        if(char_ptr->msg_timer > 0)
        {
            
// get the matrices and viewport if not done already
            if(!got_matrices)
            {
                got_matrices = 
true;

                
// get the world, projection, and view transformations.
                D3DXMatrixIdentity(&mat_world);
                get_display_view_matrix(&mat_view);
                get_display_proj_matrix(&mat_proj);
                get_display_viewport(&viewport);
            }

            D3DXVECTOR3 pos;
            D3DXVECTOR3 src_pos(char_ptr->pos_x, char_ptr->pos_y + (max_y * 0.5f), char_ptr->pos_z);

            
// project a 3D vector from object space into screen space
            D3DXVec3Project(&pos, &src_pos, &viewport, &mat_proj, &mat_view, &mat_world);

            draw_font(m_font, char_ptr->msg, pos.x, pos.y, 0, 0, char_ptr->msg_color, DT_LEFT);
        }
    }
}

With render, you have a few optional arguments. You use the first one to control
the animation timing of the characters. In a task-switchable environment such as
Windows, merely using the time elapsed from the last processed frame is unacceptable;
you must instead specify a fixed amount of time passed and ensure that your
game engine sticks to updates at that rate.

As for the viewing frustum pointer, the application can provide its own pre-created
object, or pass NULL (and an optional Z-distance) to create its own frustum.

 

posted on 2007-12-04 18:55 lovedday 阅读(275) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论