のののーと

何か作った時のメモ書き

ファジー理論で思考ルーチン

週末開発その3で相手の思考ルーチンにファジー理論を用いてみたのでそのメモ。 自分は手前のキャラで相手が奥のキャラ。

ゲームはこちら

BasicCardBattle | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

f:id:nonoui:20180129184436p:plain

※unity version 2017.2.1

ファジー理論とは

めちゃめちゃ参考になった記事。記事を読んで今回のコードを書いてみた。

qiita.com

caitsithware.com

qiita.com

自分の言葉で表現するならば、物事を0と1の二択で決めるのではなく、0.3や0.6といった場合も考慮して決定する考え方といった感じでしょうか。

今回のゲームに当てはめると

今回はカードが5枚で固定なので現在の状況でどのカードを選択するのが一番有効なのかをファジー理論を用いて考える。

  • アタック
  • ガード
  • カウンター
  • バフ
  • リカバリ

判断材料の選定

現在の状況を数値で判断するために、 下記の項目を判断材料として用意する。自分と相手の分で用意する。

  • ライフ
  • 各カードのコスト
  • 各カードのレベル
  • バフによる攻撃倍率

メンバーシップ関数を用意しよう

判断材料の数値を0〜1で正規化するためにメンバーシップ関数なるものを用意する。AnimationCurveで代用。 ついで論理演算メソッドも用意する。はじめに紹介した記事がとても参考になった。

AnimationCurveはこんな感じ。quadraticUpは序盤が急な曲線でquadraticDownはその反対。 基本は右肩上がりに統一して、その反対の値が欲しい場合は論理否定で対応する。

f:id:nonoui:20180129164430p:plain

いざ、決断へ

先程のソースコードを使って各カードの優位性を評価して、もっとも数値が高いカードが選ぶ。 例としてキャラの性格が「柔軟性」の場合の数値の算出方法は以下の通りである。 「~」は論理否定(反転)、「and」は論理積、「or」は論理和を意味する。 例えば、アタック値なら相手のライフが少ない、または自分の攻撃倍率が高い場合は、値が大きくなる。 ガード値は自分のガードコストが相手のアタックコストより小さい(つまり先手を取れる)、または相手の攻撃倍率が高い場合は値が大きくなる、といった具合である。

  • アタック値 = ~相手のライフ or 自分の攻撃倍率
  • ガード値 = ~自分のガードコストと相手のアタックコストの割合 or 相手の攻撃倍率
  • カウンター値 = 自分のカウンターコストと相手のアタックコストの割合 or 相手の攻撃倍率
  • バフ値 = 自分のライフ
  • リカバリー値 = ~自分のライフ and ~自分のリカバリーレベル

結果

1ターン目の様子がこちら。画像下部のログが各カード値の算出結果。「ATK=攻撃カード」「GUD=防御カード」「CUT=反撃カード」「BUF=攻撃倍率上昇カード」「RCV=回復カード」 のこと。最も高い値は1.0のBUFであるため、最初から強化していくスタイルのようだ。 f:id:nonoui:20180129171338p:plain

2ターン目の様子がこちら。1ターン目と算出値が変わっているがまだ余裕と判断したのか、更にバフをかけるUnity界のあいどる。 f:id:nonoui:20180129171350p:plain

4ターン目の様子がこちら。画像右中央にステータスが表示されているが、ここに書かれている攻撃倍率はほぼ最大に近い。よって攻撃一択と判断し、ここぞと言わんばかりに攻撃を仕掛けてくるあいどる。 f:id:nonoui:20180129171357p:plain

こんな感じでcomがカードを選択する。 自分の体力が減っていれば下記のようにカウンターを選ぶこともあるだろう(前のターンで防御を選んだためGUDの値が低くなっている)。 f:id:nonoui:20180129171405p:plain

感想

comが理由を持って状況を判断してくれるため、ゲームしている感がいいね。 今回は、数値を決める時にランダムな要素を入れてないため、同じ条件下なら同じ行動を起こすだろう。例えば、性格が「柔軟性」の場合、1ターン目は必ず強化してくる。 こういうcomもいた方が攻略しがいがあるかもしれない。相手は○○するから自分はこうするか、みたいな感じで。どうなんだろうか。

あと、最後のカードを決定する際の処理について最大値が複数あった場合を考慮していない。下記のコードはその部分。 現状では先にif文の条件に当てはまったものを優先している。例えば、攻撃(ATK)と反撃(CUT)の値が同じだった場合は攻撃の方がif文が先なので優先、みたいな。 折角ファジー理論でif if ifの連続を回避していたのに...もっとスマートな方法はありそう。 最大値が複数ある場合はどれでもいいってことだから、やっぱりランダム...ですかねぇ...()

float maxValue = Mathf.Max(attackValue, guardValue, counterValue, buffValue, recoveryValue);
if (maxValue == attackValue) return myCardList.FindIndex(n => n.cardType == CardType.ATTACK);
if (maxValue == guardValue) return myCardList.FindIndex(n => n.cardType == CardType.GUARD);
if (maxValue == counterValue) return myCardList.FindIndex(n => n.cardType == CardType.COUNTER);
if (maxValue == buffValue) return myCardList.FindIndex(n => n.cardType == CardType.BUFF);

return myCardList.FindIndex(n => n.cardType == CardType.RECOVERY);

ライセンス表記

ゲームで3Dモデルをつかったので。 © Unity Technologies Japan/UCL