Controlling Players and Characters(15)
To see more details about game script,see 游戏脚本的实现。
Scripting and Characters
Scripting keeps popping its head up throughout these last few chapters, and true
to form, scripting plays a major role when dealing with characters. Scripts work
with conversations, spells, character movement, and much more.
What you need at this point is a clean-cut method of processing the game scripts.
The best way to do this is to create a class to entwine into your character and other
application processing.
The Script Class
it becomes even easier to execute scripts when you put the whole script processing in a class.
typedef struct sEntry
{
long type; // type of blank entry (ENTRY_TEXT, ENTRY_BOOL, )
union
{
long io_value; // used for saving/loading
long length; // length of text (0 terminator)
long selection; // selection in choice
BOOL bool_value;
long long_value;
float float_value;
};
char * text; // entry text buffer
sEntry()
{
memset( this , 0, sizeof (* this ));
}
~sEntry()
{
delete[] text;
}
} *sEntryPtr;
////////////////////////////////////////////////////////////////////////////////////
typedef struct sScriptInfo
{
long action_index; // [0, number of actions - 1]
long num_entries; // number of entries in this action
sEntry* entries; // array of entries
sScriptInfo* prev; // previous in linked list
sScriptInfo* next; // next in linked list
sScriptInfo()
{
memset( this , 0, sizeof (* this ));
}
~sScriptInfo()
{
delete[] entries;
delete next;
}
} *sScriptInfoPtr;
////////////////////////////////////////////////////////////////////////////////////
typedef class cScript
{
private :
long m_num_actions;
sScriptInfo* m_root_script_info;
private :
virtual bool prepare() { return true ; }
virtual bool release() { return true ; }
virtual sScriptInfo* process(sScriptInfo* script_info)
{
return script_info->next;
}
public :
cScript()
{
m_num_actions = 0;
m_root_script_info = NULL;
}
~cScript()
{
free();
}
void free()
{
delete m_root_script_info;
m_root_script_info = NULL;
m_num_actions = 0;
}
sScriptInfo* get_root_script_info()
{
return m_root_script_info;
}
bool load( const char * filename);
void execute( const char * filename);
} *cScriptPtr;
Although deceptively small, the cScript class packs a punch. Loading a script is
accomplished via the Load function. Once it’s loaded, you can process the script
with a call to Execute. If you don’t want to hassle with loading a script
before processing, a call to the Execute function takes a script file
to load and execute in the same function call (plus it frees the script when execution is complete).
{
free();
FILE* fp;
if ((fp = fopen(filename, "rb")) == NULL)
return false ;
fread(&m_num_actions, 1, sizeof (m_num_actions), fp);
sScriptInfo* head = NULL;
sScriptInfo* ptr = NULL;
// loop through each script action
for ( long i = 0; i < m_num_actions; i++)
{
// allocate a script structure and link in
sScriptInfo* info = new sScriptInfo;
info->next = NULL;
if (ptr == NULL)
head = info;
else
ptr->next = info;
ptr = info;
fread(&info->action_index, 1, sizeof (info->action_index), fp);
fread(&info->num_entries, 1, sizeof (info->num_entries), fp);
// get entry data (if any)
if (info->num_entries)
{
info->entries = new sEntry[info->num_entries];
// load in each entry
for ( long j = 0; j < info->num_entries; j++)
{
fread(&info->entries[j].type, 1, sizeof (info->entries[j].type), fp);
fread(&info->entries[j].io_value, 1, sizeof (info->entries[j].io_value), fp);
// get text (if any)
if (info->entries[j].type == ENTRY_TEXT && info->entries[j].length)
{
info->entries[j].text = new char [info->entries[j].length];
fread(info->entries[j].text, 1, info->entries[j].length, fp);
}
}
}
}
fclose(fp);
m_root_script_info = head;
return true ;
}
void cScript::execute( const char * filename)
{
// load script if none already
if (filename)
load(filename);
// prepare script data for execution
if (! prepare())
return ;
// start at beginning of script
sScriptInfo* ptr = m_root_script_info;
if (ptr == NULL)
return ;
// loop until no more script actions
while (ptr)
{
// Call script function and break on NULL return value.
// Any other return type is the pointer to the next function, which is typically ptr->next.
ptr = process(ptr);
}
release();
// release script if execute loaded it
if (filename)
free();
}
TIP
Using the Load function to load a script is useful if the script is processed many times
because you don’t have to free it between uses. Loading a script within the Execute
function forces the script to be loaded and freed every time, wasting precious time.
The way the cScript class processes scripts is ingenious. You actually have to derive the cScript class to
parse each script action as it is processed. That’s the purpose of the Process function.
Once a script is loaded, the Process function is called for every script action to process.
Each script pointer is queried for the script action number, and you must decide
what to do with the action. Then you need to update the script pointers by returning
the pointer to the next script action in the linked list.