前回,こんな有様↓になった.
//HP回復効果(ファンクタ)
class RecoverHP
{
public: //効果定義用
//回復効果量決定手段.引数は回復行動実行者
using CacAmountFunc = std::function< int( const Character & ) >;
( const CalcAmountFunc &CalcAmount ) : m_CalcAmount(CalcAmount) {}
RecoverHP
public: //処理
std::vector<ProcResult> operator()(
&Actor,
Character std::vector<Character*> Tgts
) const
{
const int dHP = m_CalcAmount( Actor ); //回復量
std::vector<ProcResult> Results;
for( auto Tgt : Tgts ) //対象全員に適用
{
->ChangeHP( dHP );
Tgt.emplace_back( HPChanged{ Tgt, dHP } );
Results}
return Results;
}
private:
m_CalcAmount;
CalcRawAmountFunc };
ところで,キャラクタはそれぞれ異なる「スキル」を持っていて,例えば「自身がポーションを使用する場合,効果が2回発動する(平たく言えば2倍回復できる)」とかいう輩がいることになっている.
上記の実装にはそういうのが入ってない.
一体これはどうやって入れ込めば良いのだろうか?
キャラクタがスキルを「持っている」をそのままコード化(?)するなら,とりあえずは
//スキルの種類
enum class SkillID{ /*略*/ }
//キャラクタ
class Character
{
/*なんやかんや…*/
public:
//所有するスキルの種類を返す
() const;
SkillID Skill};
みたいな感じかな.問題はスキル効果発動の実装方法の側だ.
……とはいえ,今回作る物に関して言えば,
「とにかく様々なタイミングで様々な特別な効果を生じるんですけど?」
「しかも今後もスキル種類を増やしていく予定があるんですけど?(その具体的な内容は未定です!)」
……みたいな手に負えなそうな厄介な話を考えているのではなくて,
「キャラクタが数人いて,それぞれが固有のスキルを持っている」という程度の話なので,ごく少数個の決まったものを実装するだけだ.
であれば……
最も簡素な実装方法とは,
【各スキルの効果が発生する処理の実装箇所に「キャラクタがそのスキルを持っているかをチェックして→所持していたら特別な処理をする」という実装を直接埋め込む】
であろう.
上記の RecoverHP
を例にすれば
public: //処理
std::vector<ProcResult> operator()(
&Actor,
Character std::vector<Character*> Tgts
) const
{
const int dHP = m_CalcAmount( Actor ); //回復量
//★効果発動回数を決める処理を追加
int nTimes = 1;
if(
.Skill()==SkillID::ポーション効果2倍 &&
Actor//←これがわかるようにする必要はある
ポーションを使用した場面である )
{ nTimes = 2; }
std::vector<ProcResult> Results;
for( auto Tgt : Tgts ) //対象全員に適用
{
for( int i=0; i<nTimes; ++i ) //★nTimes回繰り返すように変更
{
->ChangeHP( dHP );
Tgt.emplace_back( HPChanged{ Tgt, dHP } );
Results}
}
return Results;
}
とか何とかやっちゃえばいい.
不格好だが,作る物が完全に確定している(:今後,スキル種類が増えるとか,既存スキルの仕様が変わるとかいうことはない)のだから,これでも十分であろうと思う.
ただ, 何故だかわからないけども何か嫌な気もする .
何か「スキル型」というのを用意して,N種類の「スキルの効果発生タイミングで呼ばれるメソッド」をとりあえず全部持たせる:
//何か「スキル型」というのがあって……
struct ISkill
{
//HP回復効果量を変更する
virtual std::vector<int> ChangeRecovHPAmount(
int RawAmount, //大元の(魔法やアイテムの定義上の)効果量
const Context &ctxt //何か情報.少なくとも「ポーション」によるのか他の何かなのかがわかるような情報がここから得られるような.
) const
{ return { RawAmount }; } //デフォルト実装は「何も変更しない」
//他のいくつかの効果も同様な感じで
//...
};
//こんな関数か何かで具体実装への参照が取れるとか
//(あるいは,SkillIDなんて定数は無しにして, Character::Skill() メソッドが const ISkill & を返してくるとかにしてもいいかも)
const ISkill &DefinitionOf( SkillID );
としたらどうか.
Nがでかい場合だと ISkill
は謎のメソッドが大量に並ぶ地獄になりそうだが,今回はせいぜい数個だから我慢できるレベルではあるまいか.
そしたら件のスキルの具体的な処理内容は
//ポーション効果2回発動スキル
struct PotionDouble : public ISkill
{
public:
virtual std::vector<int> ChangeRecovHPAmount(
int RawAmount,
const Context &ctxt
) const override
{
if( ctxt を確認したらポーション使用時である場合 )
{ return { RawAmount, RawAmount }; } //2回にする
else
{ return { RawAmount }; }
}
//他のメソッドはデフォルト実装のままで良いのでoverrideしない
};
みたいなクラスの内側に入って,HP回復処理のところは
public: //処理
std::vector<ProcResult> operator()(
&Actor,
Character std::vector<Character*> Tgts
) const
{
const int RawAmount = m_CalcAmount( Actor ); //回復量
//★キャラクタの所有するスキル効果を加味した回復量実効値を得る
std::vector<int> ActualAmounts = DefinitionOf( Actor.Skill() )
.ChangeRecovHPAmount( RawAmount, m_Ctxt );
std::vector<ProcResult> Results;
for( auto Tgt : Tgts ) //対象全員に適用
{
for( int dHP : ActualAmounts ) //★ループ化
{
->ChangeHP( dHP );
Tgt.emplace_back( HPChanged{ Tgt, dHP } );
Results}
}
return Results;
}
private:
m_Ctxt; //★追加情報.ctor あたりで与えればよいか Context
みたくなる.若干はマシになるか?
自分が作る物に関してであれば,(1)でも(2)でもまぁいいだろうと思う.
(多種多様なスキルがあるゲームの場合,こういうのはどうやってるんだろう??)
ISkill
とかの地獄化具合を軽減したほうが良いんじゃないか? という気がする.