思考日記:アイテムや魔法を「使う」はどう実装するのか問題(2)

もくじへ戻る


スキルの効果を入れるには?

前回,こんな有様↓になった.

//HP回復効果(ファンクタ)
class RecoverHP
{
public:  //効果定義用
  //回復効果量決定手段.引数は回復行動実行者
  using CacAmountFunc = std::function< int( const Character & ) >;

  RecoverHP( const CalcAmountFunc &CalcAmount ) : m_CalcAmount(CalcAmount) {}

public:  //処理
  std::vector<ProcResult> operator()(
    Character &Actor,
    std::vector<Character*> Tgts
  ) const
  {
    const int dHP = m_CalcAmount( Actor );  //回復量

    std::vector<ProcResult> Results;
    for( auto Tgt : Tgts )  //対象全員に適用
    {
      Tgt->ChangeHP( dHP );
      Results.emplace_back( HPChanged{ Tgt, dHP } );
    }
    return Results;
  }

private:
  CalcRawAmountFunc m_CalcAmount;
};

ところで,キャラクタはそれぞれ異なる「スキル」を持っていて,例えば「自身がポーションを使用する場合,効果が2回発動する(平たく言えば2倍回復できる)」とかいう輩がいることになっている.
上記の実装にはそういうのが入ってない.
一体これはどうやって入れ込めば良いのだろうか?

スキルの実装方法

キャラクタがスキルを「持っている」をそのままコード化(?)するなら,とりあえずは

//スキルの種類
enum class SkillID{ /*略*/ }

//キャラクタ
class Character
{
  /*なんやかんや…*/

public:
  //所有するスキルの種類を返す
  SkillID Skill() const;
};

みたいな感じかな.問題はスキル効果発動の実装方法の側だ.

……とはいえ,今回作る物に関して言えば,

「とにかく様々なタイミングで様々な特別な効果を生じるんですけど?」
「しかも今後もスキル種類を増やしていく予定があるんですけど?(その具体的な内容は未定です!)」

……みたいな手に負えなそうな厄介な話を考えているのではなくて,
「キャラクタが数人いて,それぞれが固有のスキルを持っている」という程度の話なので,ごく少数個の決まったものを実装するだけだ.
であれば……

(1)ダイレクトに実装

最も簡素な実装方法とは,
【各スキルの効果が発生する処理の実装箇所に「キャラクタがそのスキルを持っているかをチェックして→所持していたら特別な処理をする」という実装を直接埋め込む】
であろう.

上記の RecoverHP を例にすれば

public:  //処理
  std::vector<ProcResult> operator()(
    Character &Actor,
    std::vector<Character*> Tgts
  ) const
  {
    const int dHP = m_CalcAmount( Actor );  //回復量
    
    //★効果発動回数を決める処理を追加
    int nTimes = 1;
    if(
       Actor.Skill()==SkillID::ポーション効果2倍  &&
       ポーションを使用した場面である  //←これがわかるようにする必要はある
    )
    {  nTimes = 2;  }

    std::vector<ProcResult> Results;
    for( auto Tgt : Tgts )  //対象全員に適用
    {
      for( int i=0; i<nTimes; ++i ) //★nTimes回繰り返すように変更
      {
        Tgt->ChangeHP( dHP );
        Results.emplace_back( HPChanged{ Tgt, dHP } );
      }
    }
    return Results;
  }

とか何とかやっちゃえばいい.
不格好だが,作る物が完全に確定している(:今後,スキル種類が増えるとか,既存スキルの仕様が変わるとかいうことはない)のだから,これでも十分であろうと思う.

ただ, 何故だかわからないけども何か嫌な気もする

(2)「スキル型」みたいな

何か「スキル型」というのを用意して,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()(
    Character &Actor,
    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 )  //★ループ化
      {
        Tgt->ChangeHP( dHP );
        Results.emplace_back( HPChanged{ Tgt, dHP } );
      }
    }
    return Results;
  }
  
private:
  Context m_Ctxt;  //★追加情報.ctor あたりで与えればよいか

みたくなる.若干はマシになるか?


考えまとめ

自分が作る物に関してであれば,(1)でも(2)でもまぁいいだろうと思う.
(多種多様なスキルがあるゲームの場合,こういうのはどうやってるんだろう??)