Controlling Players and Characters(24)

Controlling Players and Characters(24)

 

The cSpellController Class

Because the spell controller is needed only for tracking the spell meshes and
animation, the class definition is relatively small:

typedef  class  cSpellController
{
private :
    cCharController*    m_char_controller;

    cFrustum*           m_frustum;

    sSpell              m_spells[NUM_SPELL_DEF];
    sSpellTracker*      m_spell_tracker;

    
long                 m_num_mesh_anim;    
    sMeshAnim*          m_mesh_anim;

    
char                 m_texture_path[MAX_PATH];

Now check the private data of the spell controller before examining the public
functions. The spell controller uses a frustum object. The frustum
object (m_frustum) can be supplied from outside code or calculated from within
the spell-rendering function.

Next comes the MSL, which is contained in the array m_spells. Notice that the
macro NUM_SPELL_DEF defines the size of the MSL array, which means that you
can easily adjust the size for later enhancements.

Following the MSL is the linked list pointer m_spell_tracker, which tracks the spells
that have been cast and are being displayed. Next comes m_num_mesh_anim (which stores
the number of meshes used) and m_mesh_anim (a list of meshes).

Because this example uses 3-D meshes to represent the spells, you need to load textures,
and in order for the spell controller to find those textures, you must store a
directory path that indicates the location of the bitmaps to be used as textures.

Something you haven’t seen up to now is the m_char_controller pointer, which points to the
character controller class object in use. This class pointer triggers the spell effects.

cSpellController contains two private functions: SetAnimData and SpellSound. The
set_animData function sets up the mesh to use as well as the movement of the mesh.
play_spell_sound is called whenever a spell mesh is used; it’s your job to override this function
to play the appropriate sound as specified in the function’s argument list.

With private data and functions covered, you can move on to the class’s public functions
(the Constructor, Destructor, init, shutdown, free, get_spell, add, update, and render):

public :
    cSpellController()
    {
        m_char_controller   = NULL;

        m_frustum           = NULL;
        m_spell_tracker     = NULL;

        m_mesh_anim         = NULL;
        m_num_mesh_anim     = 0;

        m_texture_path[0]   = '\0';

        ZeroMemory(m_spells, 
sizeof (m_spells));
    }

    ~cSpellController()
    {
        shutdown();
    }

    
void  free()
    {
        delete m_spell_tracker;
        m_spell_tracker = NULL;
    }

    sSpell* get_spell(
long  spell_index)
    {
        
return  &m_spells[spell_index];
    }

    sSpell* get_spell_list()
    {
        
return  m_spells;
    }

    
void  attach(cCharController* char_controller)
    {
        m_char_controller = char_controller;
    }

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

    
bool  init(PCSTR msl_file,  long  num_mesh_anim, PCSTR* mesh_anim_name, PCSTR texture_path);
    
void  shutdown();
    
bool  add(sCharacter* caster);
    
void  update( long  elapsed);
    
void  render(cFrustum* frustum,  float  z_dist);

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

private :
    
void  set_anim_data(sSpellTracker* tracker,  long  cur_anim);
    
virtual   void  play_spell_sound( long  index) { }

}

 

cSpellController::cSpellController and cSpellController::~sSpellController

Typical in C++ classes, the constructor and destructor clear the class data and free
all used resources, respectively. The destructor relies on a separate function (the
Shutdown function) to clear the data.


cSpellController::init and cSpellController::shutdown

Before using the spell controller class, you must initialize it. When you finish with
the class, you call Shutdown to free up the resources.

bool  cSpellController::init(PCSTR msl_file,  long  num_mesh_anim, PCSTR* mesh_anim_name, PCSTR texture_path)
{
    free();

    
if (msl_file == NULL || mesh_anim_name == NULL)
        
return   false ;

    
// load the spells

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

    fread(m_spells, 1, 
sizeof (m_spells), fp);
    fclose(fp);

    
if (texture_path)
        strcpy(m_texture_path, texture_path);

    
// get mesh names
     if ((m_num_mesh_anim = num_mesh_anim) != 0)
    {
        m_mesh_anim = 
new  sMeshAnim[num_mesh_anim];

        
for ( long  i = 0; i < m_num_mesh_anim; i++)
            strcpy(m_mesh_anim[i].filename, mesh_anim_name[i]);
    }

    
return   true ;
}

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

void  cSpellController::shutdown()
{
    free();

    
// release spell meshes
     for ( long  i = 0; i < m_num_mesh_anim; i++)
    {
        m_mesh_anim[i].mesh.free();
        m_mesh_anim[i].anim.free();
        m_mesh_anim[i].count = 0;
    }

    delete[] m_mesh_anim;
    m_mesh_anim = NULL;

    m_num_mesh_anim = 0;
}

cSpellController::free

When you’re done with a spell controller class instance but want to reuse it without
having to shut it down, a call to cSpellController::Free is in order. Free releases the
spell tracking list as well as the mesh list.

cSpellController::get_spell

Outside code might need access to the MSL, and GetSpell fills that need. Providing
the MSL reference number returns a pointer into the array of the loaded MSL.

cSpellController::add

Now the real fun begins! Add is the function you use the most because it initiates a spell.

bool  cSpellController::add(sCharacter* caster)
{
    
// add a spell to spell tracker list

    
long  spell_index = caster->spell_index;

    
// make sure character is allowed to cast spell
     if (!(caster->char_def.magic_spell[spell_index/32] & (1 << (spell_index & 31))))
        
return   false ;

    
// make sure caster has enough mana to cast
     if (caster->mana_points < m_spells[spell_index].cost)
        
return   false ;
  
    
// allocate a new spell stracker and link into head of list

    sSpellTracker* spell_tracker = 
new  sSpellTracker;

    
if (m_spell_tracker)
        m_spell_tracker->prev = spell_tracker;

    spell_tracker->next = m_spell_tracker;

    m_spell_tracker = spell_tracker;

    
// set structure data
    spell_tracker->spell_index = spell_index;
    spell_tracker->caster      = caster;
    spell_tracker->affect_type = caster->target_type;
    spell_tracker->source_x    = caster->pos_x;
    spell_tracker->source_y    = caster->pos_y;
    spell_tracker->source_z    = caster->pos_z;
    spell_tracker->target_x    = caster->target_x;
    spell_tracker->target_y    = caster->target_y;
    spell_tracker->target_z    = caster->target_z;

    
// setup the mesh/animation movement data
    set_anim_data(spell_tracker, 0);

    
return   true ;
}

cSpellController::set_anim_data

The private function set_anim_data initializes the three meshes in use by a spell. If one
of the meshes is not used (as specified by the POSITION_NONE value in mesh_pos), the
next mesh in the three is used. After all three meshes are used up, the spell’s
effects are triggered.

oid cSpellController::set_anim_data(sSpellTracker* tracker,  long  cur_anim)
{
    sSpell& spell = m_spells[tracker->spell_index];

    
long  mesh_index;
    tracker->cur_anim = cur_anim;

    
// process spell effect if no more animations left while storing the current animation number.
     if (tracker->cur_anim >= 3)
    {
        
if (m_char_controller)
            m_char_controller->spell(tracker->caster, tracker, m_spells);

        
// remove any mesh and animation if any, or just decrease reference count.
         for ( int  i = 0; i < 3; i++)
        {
            
if (spell.mesh_pos[i] != POSITION_NONE)
            {
                mesh_index = spell.mesh_index[i];

                
if (--m_mesh_anim[mesh_index].count == 0)
                {
                    m_mesh_anim[mesh_index].mesh.free();
                    m_mesh_anim[mesh_index].anim.free();
                }
            }
        }

        
// remove spell tracker from list

        
if (tracker->prev)
            tracker->prev->next = tracker->next;
        
else
            m_spell_tracker = tracker->next;

        
if (tracker->next)
            tracker->next->prev = tracker->prev;

        tracker->prev = tracker->next = NULL;
        delete tracker;

        
return ;
    }

    
// setup local data
     float  source_x = tracker->source_x;
    
float  source_y = tracker->source_y;
    
float  source_z = tracker->source_z;
    
float  target_x = tracker->target_x;
    
float  target_y = tracker->target_y;
    
float  target_z = tracker->target_z;

    
// goto next animation if no mesh to use
     if (spell.mesh_pos[cur_anim] == POSITION_NONE)
        
return  set_anim_data(tracker, cur_anim+1);

    mesh_index = spell.mesh_index[cur_anim];
    sMeshAnim& mesh_anim = m_mesh_anim[mesh_index];
    
    
// load mesh and animation if needed
     if (mesh_anim.count == 0)
    {
        mesh_anim.mesh.load(mesh_anim.filename, m_texture_path);
        mesh_anim.anim.load(mesh_anim.filename, &mesh_anim.mesh);
        mesh_anim.anim.set_loop(spell.mesh_loop[cur_anim], "Anim");
    }

    cObject& spell_obj = tracker->
object ;
    spell_obj.create(&mesh_anim.mesh);
    mesh_anim.count++;

    
float  x_angle = 0.0f, y_angle = 0.0f;
    
float  x_diff, y_diff, z_diff, dist;
    
float  scale = 1.0f;

    
// setup mesh movements
     switch (spell.mesh_pos[cur_anim])
    {
    
case  POSITION_CASTER:
        tracker->x_pos = source_x;
        tracker->y_pos = source_y;
        tracker->z_pos = source_z;
        tracker->holding_time = spell.mesh_speed[cur_anim];

        
if (tracker->caster)
            y_angle = tracker->caster->direction;

        
break ;

    
case  POSITION_TOTARGET:
        
// store position and speed information
        tracker->x_pos = source_x;
        tracker->y_pos = source_y;
        tracker->z_pos = source_z;
        tracker->speed = spell.mesh_speed[cur_anim];

        
// calculate mevement
        x_diff = fabs(target_x - source_x);
        y_diff = fabs(target_y - source_y);
        z_diff = fabs(target_z - source_z);

        dist = sqrt(x_diff * x_diff + y_diff * y_diff + z_diff * z_diff);

        tracker->dist_to_target = dist;

        
if (! float_equal(dist, 0.0f))
        {
            tracker->x_add = (target_x - source_x) / dist;
            tracker->y_add = (target_y - source_y) / dist;
            tracker->z_add = (target_z - source_z) / dist;

            
// calculate angles
            x_angle = -atan(tracker->y_add);
            y_angle = atan2(tracker->x_add, tracker->z_add);
        }

        
break ;

    
case  POSITION_TARGET:
        tracker->x_pos = target_x;
        tracker->y_pos = target_y;
        tracker->z_pos = target_z;
        tracker->holding_time = spell.mesh_speed[cur_anim];

        
// calculate distance from source to target
        x_diff = fabs(target_x - source_x);
        z_diff = fabs(target_z - source_z);
        dist   = sqrt(x_diff * x_diff + z_diff * z_diff);

        tracker->x_add = (target_x - source_x) / dist;
        tracker->z_add = (target_z - source_z) / dist;

        y_angle = atan2(tracker->x_add, tracker->z_add);
        
break ;

    
case  POSITION_TOCASTER:
        
// store position and speed information
        tracker->x_pos = target_x;
        tracker->y_pos = target_y;
        tracker->z_pos = target_z;
        tracker->speed = spell.mesh_speed[cur_anim];

        
// calculate movement
        x_diff = fabs(source_x - target_x);
        y_diff = fabs(source_y - target_y);
        z_diff = fabs(source_z - target_z);

        dist = sqrt(x_diff * x_diff + y_diff * y_diff + z_diff * z_diff);

        tracker->dist_to_target = dist;

        
if (! float_equal(dist, 0.0f))
        {
            tracker->x_add = (source_x - target_x) / dist;
            tracker->y_add = (source_y - target_y) / dist;
            tracker->z_add = (source_z - target_z) / dist;

            
// calculate angles
            x_angle = -atan(tracker->y_add);
            y_angle = atan2(tracker->x_add, tracker->z_add);
        }

        
break ;

    
case  POSITION_SCALE:
        
// store position and speed information
        tracker->x_pos = source_x;
        tracker->y_pos = source_y;
        tracker->z_pos = source_z;
        tracker->holding_time = spell.mesh_speed[cur_anim];

        
// get distance from source to target and size of mesh

        x_diff = fabs(target_x - source_x);
        y_diff = fabs(target_y - source_y);
        z_diff = fabs(target_z - source_z);

        dist = sqrt(x_diff * x_diff + y_diff * y_diff + z_diff * z_diff);

        
float  length;
        spell_obj.get_bounds(NULL, NULL, NULL, NULL, NULL, &length, NULL);

        scale = dist / length;

        tracker->x_add = (target_x - source_x) / dist;
        tracker->y_add = (target_y - source_y) / dist;
        tracker->z_add = (target_z - source_z) / dist;

        x_angle = -atan(tracker->y_add);
        y_angle = atan2(tracker->x_add, tracker->z_add);

        
break ;
    }

    spell_obj.rotate(x_angle, y_angle, 0.0f);
    spell_obj.scale(1.0f, 1.0f, scale);
    spell_obj.set_anim_set(&mesh_anim.anim, "Anim", timeGetTime()/30);

    
if (spell.mesh_sound[cur_anim] != -1)
        play_spell_sound(spell.mesh_sound[cur_anim]);
}

cSpellController::update

Spells need to move, have their timing updated, and have their mesh initiated at
the various steps. Update is responsible for all those functions. To use Update, just pass
the amount of time (in milliseconds) that has elapsed from the last call to Update
(or the amount of time you want the controller to update the spells).

void  cSpellController::update( long  elapsed)
{
    
// update all spells based on elapsed time

    sSpellTracker* next_tracker;

    
// scan through all spells in use
     for (sSpellTracker* tracker = m_spell_tracker; tracker != NULL; tracker = next_tracker)
    {
        
// remember next spell in list
        next_tracker = tracker->next;

        
long  spell_index = tracker->spell_index;
        
long  cur_anim    = tracker->cur_anim;

        
bool  goto_next_anim =  false ;

        
// update/move/countdown spell object
         switch (m_spells[spell_index].mesh_pos[cur_anim])
        {
        
case  POSITION_NONE:
            goto_next_anim = 
true ;
            
break ;

        
case  POSITION_CASTER:
        
case  POSITION_TARGET:
        
case  POSITION_SCALE:
            tracker->holding_time -= elapsed;

            
if (tracker->holding_time <= 0)
                goto_next_anim = 
true ;

            
break ;

        
case  POSITION_TOTARGET:
        
case  POSITION_TOCASTER:
            
float  speed = elapsed/1000.0f * tracker->speed;
            tracker->dist_to_target -= speed;

            
if (tracker->dist_to_target > 0.0f)
            {
                tracker->x_pos += (tracker->x_add * speed);
                tracker->y_pos += (tracker->y_add * speed);
                tracker->z_pos += (tracker->z_add * speed);
            }
            
else
                goto_next_anim = 
true ;

            
break ;
        }

        
// update next animation if any
         if (goto_next_anim)
            set_anim_data(tracker, cur_anim+1);
    }
}
 

cSpellController::render

The last of the functions, Render, is used to render all spell meshes that are in effect.
Providing the Render function with an optional frustum and viewing distance helps
alter the way the meshes are rendered.

void  cSpellController::render(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;
    }

    
// get time to update animations (30 fps)
    DWORD time = timeGetTime() / 30;

    
// loop through each spell and draw
     for (sSpellTracker* tracker = m_spell_tracker; tracker != NULL; tracker = tracker->next)
    {
        
float  radius;
        tracker->
object .get_bounds(NULL, NULL, NULL, NULL, NULL, NULL, &radius);

        
// draw spell if in viewing frustum
         if (m_frustum->is_sphere_in(tracker->x_pos, tracker->y_pos, tracker->z_pos, radius))
        {
            tracker->
object .move(tracker->x_pos, tracker->y_pos, tracker->z_pos);
            tracker->
object .update_anim(time, TRUE);
            tracker->
object .render();
        }
    }
}

你可能感兴趣的:(Controlling Players and Characters(24))