回答編集履歴

2 追記

haru666

haru666 score 1521

2017/01/26 13:03  投稿

デザインパターン云々…でいうと…
自分に思いつくのは今回の要件ではStrategyパターンぐらいなので、応用例を書いておきます。
※私はゲームプログラミングしたことがないので命名はかなり適当です。参考にしないでね
```C#
   // 条件を判定する要素の規定インターフェース
   public interface IConditionPredicate
   {
       string ConditionId { get; }
       // paramの型をどうするかは一考の余地がありますが
       bool Check(EN_AI enemy, string[] parameters);
   }
   // 判定条件一つ一つをクラス化して…
   public class ValueConditionPredicate : IConditionPredicate
   {
       public string ConditionId { get; } = "Val";
       public bool Check(EN_AI enemy, string[] parameters)
       {
           int value;
           if (parameters.Length != 1 || !int.TryParse(parameters[0], out value)) return false;
           return enemy.ConditionValue == value;
       }
   }
   public class RandomConditionPredicate : IConditionPredicate
   {
       private readonly Random randomGenerator = new Random(Guid.NewGuid().GetHashCode());
       public string ConditionId { get; } = "Rnd";
       public bool Check(EN_AI enemy, string[] parameters)
       {
           int value;
           if (parameters.Length != 1 || !int.TryParse(parameters[0], out value)) return false;
           return randomGenerator.Next() % 100 < value;
       }
   }
   public class HpConditionPredicate : IConditionPredicate
   {
       public string ConditionId { get; } = "HP";
       public bool Check(EN_AI enemy, string[] parameters)
       {
           if (parameters.Length != 2) return false;
           string function = parameters[0];
           int value;
           if (!int.TryParse(parameters[1], out value)) return false;
           int percent = enemy.Hp.Percent;
           switch (function)
           {
               case "G":
                   return value >= percent;
               case "L":
                   return value <= percent;
               case "E":
                   return value == percent;
           }
           return false;
       }
   }
   // それを元に判定するクラスを作ります。
   public class ConditionChecker
   {
       private IDictionary<string, KeyValuePair<string, string[]>> Replacements;
       private IDictionary<string, IConditionPredicate> predicates;
       // 条件を追加
       public void AddConditionPredicate(IConditionPredicate predicate)
       {
           predicates.Add(predicate.ConditionId, predicate);
       }
       // とあるIDを別のIDに変換するためのマッピングを登録
       public void AddReplacement(string name, string conditionId, string[] parameters)
       {
           Replacements.Add(name, new KeyValuePair<string, string[]>(conditionId, parameters));
       }
       // マッピングされた置換を適用する関数
       private void Adjust(ref string name, ref string[] parameters)
       {
           KeyValuePair<string, string[]> replace;
           if (!Replacements.TryGetValue(name, out replace)) return;
           name = replace.Key;
           parameters = replace.Value;
       }
       // 初期化サンプル
       // こういうのをある程度自動化する方法もあります。
       // IConditionPredicateを実装したクラスをリフレクションでインスタンス化します
       // 例:IConditionPredicateを実装したクラスをリフレクションでインスタンス化します
       public void InitializingSample()
       {
           AddConditionPredicate(new ValueConditionPredicate());
           AddConditionPredicate(new RandomConditionPredicate());
           AddConditionPredicate(new HpConditionPredicate());
           // ConditionIDがHP_MaxだったらHP100%と一致、という風な書き換え条件を登録
           AddReplacement("HP_Max", "HP", new string[] { "E", "100" });
       }
       // 本題の判定関数
       public bool Check(EN_AI enemy, string conditionId, string[] parameters)
       {
           Adjust(ref conditionId, ref parameters);
           IConditionPredicate predicate;
           if (!predicates.TryGetValue(conditionId, out predicate))
           {
               throw new Exception();
           }
           return predicate.Check(enemy, parameters);
       }
   }
```
こんな感じでちょっとずつばらしていくと、判定毎に複雑なロジックを実装できます。
上記サンプルはExcelに複数行で条件を登録することを想定しています。
例えばHP80%以上の場合、HP | G | 80 とかそんな風に登録します。
複雑な条件を別の書き方ができるように、HP_Max ってIDをHP | E | 100 に変換したりもしています。
初期化部分を工夫していけば、今後の条件追加やロジック追加が簡単になります。
また、IConditionPredicateそのものを拡張してEN_AIっていうモンスター単体のオブジェクトから、BattleContextのような戦闘全体を把握できるステートも渡せるように改良すれば、例えば「3ターン目に実行する」とか、「味方が死んでいたら」とかそんな条件文も作ることができるようになります。
何にせよ、完璧なカスタマイズ性よりは、まず動くものを作るのが一番大事ですから、こういうのに挑戦するのはまだ先で良いと思います。
※オマケの部分
```C#
   public interface ICharaStatus_FVRPG
   {
       int MaxHp { get; set; }
       int Hp { get; set; }
   }
   public class EN_AI
   {
       public int ConditionValue { get; set; }
       public ICharaStatus_FVRPG MyStatus { private get; set; }
       public HpAccessor Hp { get { return new HpAccessor(MyStatus); } }
   }
   public struct HpAccessor
   {
       private readonly ICharaStatus_FVRPG status;
       public HpAccessor(ICharaStatus_FVRPG status)
       {
           this.status = status;
       }
       public int Percent
       {
           get
           {
               return Calc_HpPercent(status);
           }
       }
       private static int Calc_HpPercent(ICharaStatus_FVRPG status)
       {
           return status.Hp * 100 / status.MaxHp;
       }
   }
```
1 誤字

haru666

haru666 score 1521

2017/01/26 12:02  投稿

デザインパターン云々…でいうと…
自分に思いつくのは今回の要件ではStrategyパターンぐらいなので、応用例を書いておきます。
※私はゲームプログラミングしたことがないので命名はかなり適当です。参考にしないでね
```C#
   // 条件を判定する要素の規定インターフェース
   public interface IConditionPredicate
   {
       string ConditionId { get; }
       // paramの型をどうするかは一考の余地がありますが
       bool Check(EN_AI enemy, string[] parameters);
   }
   // 判定条件一つ一つをクラス化して…
   public class ValueConditionPredicate : IConditionPredicate
   {
       public string ConditionId { get; } = "Val";
       public bool Check(EN_AI enemy, string[] parameters)
       {
           int value;
           if (parameters.Length != 1 || !int.TryParse(parameters[0], out value)) return false;
           return enemy.ConditionValue == value;
       }
   }
   public class RandomConditionPredicate : IConditionPredicate
   {
       private readonly Random randomGenerator = new Random(Guid.NewGuid().GetHashCode());
       public string ConditionId { get; } = "Rnd";
       public bool Check(EN_AI enemy, string[] parameters)
       {
           int value;
           if (parameters.Length != 1 || !int.TryParse(parameters[0], out value)) return false;
           return randomGenerator.Next() % 100 < value;
       }
   }
   public class HpConditionPredicate : IConditionPredicate
   {
       public string ConditionId { get; } = "HP";
       public bool Check(EN_AI enemy, string[] parameters)
       {
           if (parameters.Length != 2) return false;
           string function = parameters[0];
           int value;
           if (!int.TryParse(parameters[1], out value)) return false;
           int percent = enemy.Hp.Percent;
           switch (function)
           {
               case "G":
                   return value >= percent;
               case "L":
                   return value <= percent;
               case "E":
                   return value == percent;
           }
           return false;
       }
   }
   // それを元に判定するクラスを作ります。
   public class ConditionChecker
   {
       private IDictionary<string, KeyValuePair<string, string[]>> Replacements;
       private IDictionary<string, IConditionPredicate> predicates;
       // 条件を追加
       public void AddConditionPredicate(IConditionPredicate predicate)
       {
           predicates.Add(predicate.ConditionId, predicate);
       }
       // とあるIDを別のIDに変換するためのマッピングを登録
       public void AddReplacement(string name, string conditionId, string[] parameters)
       {
           Replacements.Add(name, new KeyValuePair<string, string[]>(conditionId, parameters));
       }
       // マッピングされた置換を適用する関数
       private void Adjust(ref string name, ref string[] parameters)
       {
           KeyValuePair<string, string[]> replace;
           if (!Replacements.TryGetValue(name, out replace)) return;
           name = replace.Key;
           parameters = replace.Value;
       }
       // 初期化サンプル
       // こういうのをある程度自動化する方法もあります。
       // IConditionPredicateを実装したクラスをリフレクションでインスタンス化します
       public void InitializingSample()
       {
           AddConditionPredicate(new ValueConditionPredicate());
           AddConditionPredicate(new RandomConditionPredicate());
           AddConditionPredicate(new HpConditionPredicate());
           // ConditionIDがHP_MaxだったらHP100%と一致、という風な書き換え条件を登録
           AddReplacement("HP_Max", "HP", new string[] { "E", "100" });
       }
       // 本題の判定関数
       public bool Check(EN_AI enemy, string conditionId, string[] parameters)
       {
           Adjust(ref conditionId, ref parameters);
           IConditionPredicate predicate;
           if (!predicates.TryGetValue(conditionId, out predicate))
           {
               throw new Exception();
           }
           return predicate.Check(enemy, parameters);
       }
   }
```
こんな感じでちょっとずつばらしていくと、判定毎に複雑なロジックを実装できます。
上記サンプルはExcelに複数行で条件を登録することを想定しています。
例えばHP80%以上の場合、HP | G | 80 とかそんな風に登録します。
複雑な条件を別の書き方ができるように、HP_Max ってIDをHP | E | 100 に変換したりもしています。
初期化部分を工夫していけば、今後の条件追加やロジック追加が簡単になります。
また、IConditionPredicateそのものを拡張してEN_AIっていうモンスター単体のオブジェクトから、例えばBattleContextのような戦闘全体を把握できるステートも渡せるように改良すれば、例えば「3ターン目に実行する」とか、「味方が死んでいたら」とかそんな条件文も作ることができるようになります。
また、IConditionPredicateそのものを拡張してEN_AIっていうモンスター単体のオブジェクトから、BattleContextのような戦闘全体を把握できるステートも渡せるように改良すれば、例えば「3ターン目に実行する」とか、「味方が死んでいたら」とかそんな条件文も作ることができるようになります。
何にせよ、完璧なカスタマイズ性よりは、まず動くものを作るのが一番大事ですから、こういうのに挑戦するのはまだ先で良いと思います。
※オマケの部分
```C#
   public interface ICharaStatus_FVRPG
   {
       int MaxHp { get; set; }
       int Hp { get; set; }
   }
   public class EN_AI
   {
       public int ConditionValue { get; set; }
       public ICharaStatus_FVRPG MyStatus { private get; set; }
       public HpAccessor Hp { get { return new HpAccessor(MyStatus); } }
   }
   public struct HpAccessor
   {
       private readonly ICharaStatus_FVRPG status;
       public HpAccessor(ICharaStatus_FVRPG status)
       {
           this.status = status;
       }
       public int Percent
       {
           get
           {
               return Calc_HpPercent(status);
           }
       }
       private static int Calc_HpPercent(ICharaStatus_FVRPG status)
       {
           return status.Hp * 100 / status.MaxHp;
       }
   }
```

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る