まず、バトルを制御するための仕組みから構築します。
この仕組みは、戦闘が状態遷移やシーケンスの制御がややこしいので専用の物を用意しました。
基本的なところは、シナリオの命令システムと似ていますのでシナリオのシステムが理解できるなら問題ないと思います。
システムの名称は、PSCとしました。
PSC(独自に命名)=プログラマブル・シーケンス・コマンド=programmable sequence commandの略です。
※ PSCは一般用語ではありませんので、ご注意下さい。
実は、今更ですがこのシステムでSHOPを組むとSHOPも簡単になったりします。
[battlePSC.h]
バトルPSC処理のヘッダ側です。
このファイルは新規作成です。
●ファイルの頭から構造体の宣言まで
// PSCデータ構造体の仮宣言
typedef struct tag_PSCData PSCData_t;
はtag_PSCDataの仮の構造をPSCData_tと名前で宣言しています。
理由はtag_PSCData構造体内で使うPscFunc_tが更に引数にPSCData_tを入れ子に持つというややこしい構造のせいです。
// PSC関数の型の宣言
typedef int (*PscFunc_t)(PSCData_t *pscData,int frame);
PSC関数の型を宣言しています。パラメータのPSCData_tに注意です。
tag_PSCDataはPSCデータを定義するテーブルを書くのに使う構造体です。
まぁ、シナリオのScnData_tと意味合い的にはよく似ています。
見た通りシナリオよりもパラメータが多いですね。
ScnData_tと違いコマンドではなく関数ポインタをそのまま使うのがPSCの特長です。
#ifndef INCLUDE_BATTLE_PSC_H
#define INCLUDE_BATTLE_PSC_H
//----------------------------------------------------------------------
// 構造体
//----------------------------------------------------------------------
// PSCデータ構造体の仮宣言
typedef struct tag_PSCData PSCData_t;
// PSC関数の型の宣言
typedef int (*PscFunc_t)(PSCData_t *pscData,int frame);
// PSCデータ構造体。
// PSCデータの最後は必ず処理関数をNULLにしてください。それがストッパになります。
struct tag_PSCData {
PscFunc_t pscFunc; //処理関数
char *str; //文字列orラベル
int p1; //パラメータ1
int p2; //パラメータ2
int p3; //パラメータ3
char *strP1; //文字列パラメータ1
};
PSCを制御するための関数群です。
PSC命令の最大の特長はシナリオと違いサブルーチンがある事でしょうか。
//----------------------------------------------------------------------
// 関数の宣言
//----------------------------------------------------------------------
// 初期化・終了関連
extern void btlpsc_InitPSC(PSCData_t *pPSCData,PscFunc_t labelFunc,PscFunc_t jpFuncs[]); // PSCの環境初期化
extern void btlpsc_ResetPSC(); // PSCの状態をリセットする。
// 制御系
extern PSCData_t* btlpsc_GetNowPSC(); // 今のPSCを得る。
extern void btlpsc_NextPSC(); // 次のPSCに進める。
extern int btlpsc_UpdatePSCPointOrFrame(); // PSC命令位置更新かフレームのカウント
extern int btlpsc_GetFramePSC(); // PSC命令毎のフレームカウンタを得る。
extern void btlpsc_JumpPSCLabel(const char*label); // ラベルの位置にPSCジャンプ
extern int btlpsc_CallGetPscPoint(); // CALL用に今のPSC位置を得る。
extern void btlpsc_ReturnPscPoint(int pscPoint); // PSC位置を戻す。
#endif /*INCLUDE_BATTLE_PSC_H*/
バトルPSC処理のプログラム側です。
このファイルは新規作成です。
●先頭部分から内部関数の宣言まで
内部関数は、ラベル処理のための関数です。
//----------------------------------------------------------------------
// バトルの制御のための仕組み。それがPSCです。
// PSC(独自に命名)=プログラマブル・シーケンス・コマンド=programmable sequence command
// PSCは一般用語ではありませんので、ご注意下さい。
//----------------------------------------------------------------------
#include
#include "main.h"
#include "battlePSC.h"
//----------------------------------------------------------------------
// 内部関数
//----------------------------------------------------------------------
// ラベルを検索してJUMPポイントを持ち帰る。失敗すると-1
int btlpsc_SearchLabel(const char*label);
LABEL_TABLE_MAXは定義できる最大のラベル数です。
PSC_DATA_MAXは、PSCコマンドの最大行数。無限ループのガード用なのでPSCの規模に合わせて変える必要があります。
LabelTable_tはラベル情報を貯めるためのテーブルの構造体です。
//----------------------------------------------------------------------
// 定数
//----------------------------------------------------------------------
// 最大値
#define LABEL_TABLE_MAX (100) //ラベルテーブルの最大値
#define PSC_DATA_MAX (1000) //まさか1000行はないだろう。
//----------------------------------------------------------------------
// 構造体
//----------------------------------------------------------------------
// ラベルテーブル
typedef struct {
const char *name; //ラベル名
int PscPoint; //PSCのラベルがある命令のポイント。
} LabelTable_t;
PscControlDataはPSC管理のための構造体で構造体宣言と実体宣言を兼用して行っています。
PSCDataは実行中のPSCテーブルのポインタです。
labelFuncはPSCテーブル中のラベルを示す関数のポインタです。
これはラベル関数が任意に定義できるためで、btlpsc_InitPSCの呼び出し元が伝える必要があります。
PscPointは、今現在のPSC命令位置です。
bNextPointは、次の命令に進むフラグです。
ここら辺はシナリオの制御にも有りましたね。
PscFrameは、PSC命令毎のフレームでPSC命令が切り替わるたびに0になります。
LabelNumsは、PSCテーブルから抽出されたラベルの数です。
LabelTabelは、PSCテーブルから抽出されたラベル情報を蓄えるテーブルです。最大個数はLABEL_TABLE_MAX
//----------------------------------------------------------------------
// 変数
//----------------------------------------------------------------------
// PSCの管理構造体定義 兼 実体データ
static struct {
// PSCデータ
PSCData_t *PSCData; // 実行中のPSCデータのポインタ
PscFunc_t labelFunc; // ラベル関数
// PSC制御
int PscPoint; // 今現在のPSC命令位置
int bNextPoint; // 次の命令に進むフラグ
int PscFrame; // PSC命令毎のフレーム
// ラベルのテーブル
int LabelNums; // ラベル数
LabelTable_t LabelTabel[LABEL_TABLE_MAX]; //ラベルテーブル
} PscControlData = {NULL}; //クリアのための初期値。
この関数は、PSCの環境初期化を行います。
主な仕事はラベル情報のテーブルを作ることとJUMP系の命令でちゃんとラベルが存在するかチェックを行う事です。
最初にPSCデータのポインタとラベル命令の関数ポインタを保存します。
関数ポインタ値でラベル命令を見分けるようにするわけですね。
まず、ラベル情報をテーブルに溜め込みます。
PSC_DATA_MAXは暴走防止のための上限値ガードです
pscFuncがNULLの場合はPSCデータが終わりなのでラベル処理の終わりです。
ラベル関数だった場合は、LabelTabelにラベルの位置とラベル名を格納してLabelNumsにラベルの数をカウントしてます。
この時、LABEL_TABLE_MAXを超えるとエラーです。
ラベルテーブルが出来たら、JUMP系命令のラベルが正しいかチェックを行います。
まず、PSC_DATA_MAXは暴走防止のための上限値ガードです
pscFuncがNULLの場合はPSCデータが終わりなのでラベル処理の終わりってのも同じです。
jpFuncsにJUMP系関数のポインタが入っていますので、命令毎にJUMP系か判断してJUMP系なら同じラベルがラベルテーブルにあるか検索します。
ここの処理はどうみても規模が大きくなると重いのでラベルの検索のハッシュ化やJUMP系のチェックはコールバックにするなどの工夫が必要かもしれません。※ 今回は説明しません。
あと、せっかくラベル検索しているので、この時点でJUMP先をラベルではなく命令ポインタに置き換えた方が高速化出来ます。これも作る皆さんの工夫にお任せします。
//----------------------------------------------------------------------
// PSCの環境初期化
//----------------------------------------------------------------------
void btlpsc_InitPSC(PSCData_t *pPSCData,PscFunc_t labelFunc,PscFunc_t jpFuncs[])
{
// PSCの情報を初期化する。
PscControlData.PSCData = pPSCData;
PscControlData.labelFunc = labelFunc;
// ラベル情報を検索してテーブル化する。
PscControlData.LabelNums = 0; //ラベル数を初期化
for( int p=0 ; p PscControlData.LabelNums );
// ラベルの名前と位置を覚える。
PscControlData.LabelTabel[PscControlData.LabelNums].name = PscControlData.PSCData[p].str;
PscControlData.LabelTabel[PscControlData.LabelNums].PscPoint = p;
// ラベル数をカウント。
PscControlData.LabelNums++;
}
}
// JUMP系の関数のラベルの実在チェック
for( int p=0 ; p<PSC_DATA_MAX ; p++ ) {
// ストッパ?
if( PscControlData.PSCData[p].pscFunc == NULL ) {
// ループを抜ける
break;
}
// JUMP系かチェック
for( int fno=0 ; fno<=100 ; fno++ ) {
// 100回は廻らない。それバグ。
MACRO_ASSERT( fno<100 );
// JUMP系命令終了?
if( jpFuncs[fno]==NULL ) {
// ループを抜ける。
break;
}
// JUMP系命令?
if( PscControlData.PSCData[p].pscFunc == jpFuncs[fno] ) {
// ラベルを検索する。
int pscPoint = btlpsc_SearchLabel(PscControlData.PSCData[p].str);
MACRO_ASSERT(pscPoint!=-1);
}
}
}
}
バトルを開始時に初期化する必要のある変数を初期化しています。
//----------------------------------------------------------------------
// PSCの状態をリセットする。
//----------------------------------------------------------------------
void btlpsc_ResetPSC()
{
// 再生PSCの位置を初期化
PscControlData.PscPoint = 0; // 今現在のPSC位置
PscControlData.bNextPoint=FALSE; // 次の命令に進むフラグ
PscControlData.PscFrame = 0; // PSC命令毎のフレーム
}
今現在(PscPoint)のPSC命令のポインタを持ち帰ります。
//----------------------------------------------------------------------
// 今のPSC得る。
//----------------------------------------------------------------------
PSCData_t* btlpsc_GetNowPSC()
{
// 現在のPSCは無効?
if( PscControlData.PSCData==NULL ) {
// 無効なのでPSCも無効
return NULL;
} else {
// 今のPSCを持ち帰る。
return &PscControlData.PSCData[PscControlData.PscPoint];
}
}
PSC命令を次に進めるフラグを立てます。
//----------------------------------------------------------------------
// 次のPSCに進める。
//----------------------------------------------------------------------
void btlpsc_NextPSC()
{
// 現在のPSCは有効?
if( PscControlData.PSCData!=NULL ) {
// PSC終端以外なら
if( PscControlData.PSCData[PscControlData.PscPoint].pscFunc != NULL ) {
// 次のPSC命令に進むフラグを立てる。
PscControlData.bNextPoint=TRUE;
}
}
}
bNextPointが有効なら、フラグをクリアしてPscPointを+1加算してPscFrameを0に戻します。戻り値はTRUE。
bNextPointが無効なら、PscFrameをカウントアップします。戻り値はFALSE。
//----------------------------------------------------------------------
// PSC命令位置更新かフレームのカウント。どちらで動作したかを戻り値で返す。
//----------------------------------------------------------------------
int btlpsc_UpdatePSCPointOrFrame()
{
// 次のPSC命令に進むフラグ?
if( PscControlData.bNextPoint ) {
// フラグをクリア
PscControlData.bNextPoint=FALSE;// 次の命令に進むフラグ
// 次のPSCに進める。PSC命令毎のフレームは初期化。
PscControlData.PscPoint++; // 今現在のPSC位置
PscControlData.PscFrame = 0; // PSC命令毎のフレーム
// PSC命令位置更新。
return TRUE;
} else {
// PSC命令毎のフレームをカウントする。
PscControlData.PscFrame++;
// フレームのカウント。
return FALSE;
}
}
現在のPSC命令の経過フレーム数を返します。
//----------------------------------------------------------------------
// PSC命令毎のフレームカウンタを得る。
//----------------------------------------------------------------------
int btlpsc_GetFramePSC()
{
return PscControlData.PscFrame;
}
指定されたラベルにPscPointをジャンプします。
btlpsc_SearchLabel()関数でラベルテーブルを検索しています。
//----------------------------------------------------------------------
// ラベルの位置にPSCジャンプ
//----------------------------------------------------------------------
void btlpsc_JumpPSCLabel(const char*label)
{
// ラベルを検索する。
int pscPoint = btlpsc_SearchLabel(label);
MACRO_ASSERT(pscPoint!=-1);
// PSCのポイントを書き換える。
PscControlData.PscPoint = pscPoint;
PscControlData.PscFrame = 0; // PSC命令毎のフレーム
// 次のPSCに進める。
btlpsc_NextPSC();
}
指定されたラベルのPscPointを返します。検索できなかったら-1が戻ります。
//----------------------------------------------------------------------
// ラベルを検索してJUMPポイントを持ち帰る。失敗すると-1
//----------------------------------------------------------------------
int btlpsc_SearchLabel(const char*label)
{
// ラベルを検索する。
for( int i=0 ; i<PscControlData.LabelNums ; i++ ) {
// 同じ名前か?
if( 0==strcmp(PscControlData.LabelTabel[i].name,label) ) {
// PSCのポイントを持ち帰る。
return PscControlData.LabelTabel[i].PscPoint;
}
}
// 見つからなかっ
return -1;
}
今のPSC命令の位置(PscPoint)を返します。
Call命令で使用します。
//----------------------------------------------------------------------
// CALL用に今のPSC位置を得る。
//----------------------------------------------------------------------
int btlpsc_CallGetPscPoint()
{
// PSCのポイントを返す。
return PscControlData.PscPoint;
}
PSC命令の位置(PscPoint)を強制的に書き換えます。
Return命令で使用します。
//----------------------------------------------------------------------
// PSC位置を戻す。
//----------------------------------------------------------------------
void btlpsc_ReturnPscPoint(int pscPoint)
{
// PSCのポイントを書き換える。
PscControlData.PscPoint = pscPoint;
PscControlData.PscFrame = 0; // PSC命令毎のフレーム
}
続いては、実際のバトル制御とPSC命令の実体です。