AI制作の手引き その3「AI記述」 by ADI 2:19 2012/12/14 GetHitVarについて 23:46 2012/10/16 %とRandomに補足追記 00:17 2011/11/10 MoveHit&&NumTarget=0 17:28 2011/10/29 最後の1P2Pの話を修正 ・・・ 07:42 2009/10/30 とりあえず終了。 |
序、その1、その2と長い準備を経て、ようやくAI本体の記述に入ります。 ただし、ここで紹介しているものはあくまで一例です。 応用の方法はいくらでもあります。 ステートコントローラーの内容や、トリガー用の情報などは、 >このサイト<の>このページ(ステートコントローラー一覧(HTML))<と >このページ(トリガー情報の索引)<を参考にしています。 よく見ることになる為、すぐ開けるようにしておきましょう。 >Trigger用の情報 >Varなどの利用 |
まずAI記述は、-1ステートの最後尾から記述することにします。 処理上は-3ステートでも構いませんが。 ;■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ ;■ CNS式・AI記述 by○○○ ■ ;■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ と言ったような目印で始めるといいかもしれません。 目印は分類ごとにつけたりするのもいいでしょう。 自分が一番分かりやすいと思う方法で。 ■動作記述の基本 [State -1, *動作名*] Type = ChangeState ;命令:ステート変更 Value = *ステート番号* 記述の始まり TriggerAll = Alive && RoundState = 2 && Var(*AIフラグ*);AI最低限記述 Alive > K.O.されていないかどうか。これが0の場合、動いてはいけない。 RoundState = 2 > 試合開始から決着までの間。試合中である。 > 0,1は試合開始前、3,4は決着後。 Var(*AIフラグ*) > いわずもがな。 個別に動作の確認を行う場合は、;コメントアウトしますが、 絶対に付け忘れてはいけない記述です。 TriggerAll = *前提条件* ;移動ステートの前提条件 元の記述にあるトリガーグループを付け加えます。 ちなみに屈み・歩き・ジャンプ始動は、 Ctrl && StateType != A が必要です。 地上ガードは Ctrl && InGuardDist && MoveType!=A && StateType!= A 空中ガードは Ctrl && InGuardDIst && MoveType!=A && StateType = A State,120(ガード始動)へ飛ばすならStateTypeはいりません。 Trigger1 = *状況条件* ;Commandの代替条件 基本的なトリガーを後で紹介します。 説明抜き [State -1, *動作名*] Type = ChangeState Value = *ステート番号* TriggerAll = Alive && RoundState = 2 && Var(*AIフラグ*) TriggerAll = *前提条件* Trigger1 = *状況条件* これを設計図に合わせてどんどんと並べていきますが、大事なことを。 ChangeState の命令処理を行った場合、次のステート処理に移ります。 >ChangeStateの行われた記述よりも下は参照されません。 >簡潔に言うと上にある方が優先されます。 その為、ガードなどを上に通常動作は下の方に置くこととなるでしょう。 |
.よく使われるトリガー・条件記述 これらは使いながら覚えていくものなので、念入りに読む必要はありません。 ただ、ざっと読み流しておいてください。 例では既に条件式ですが、これ以外の指定方法があることもあります。 細かいことは、このページ冒頭で紹介している例のページをどうぞ。 分類分けはしていますが、それ以外でも使えます。 ■Triggerなどのおさらい Trigger1 = ***;の条件は「真(0以外)」か「偽(0)」かを判定しています。 数値をそのまま指定した場合は、「0以外(真)」と「0(偽)」で判定します。 ■使える演算子や数値指定 情報や数字とあわせて使います。
■情報 確率 Trigger1 = Random < xxx ; ランダム xxx * 0.1% 1.Randomは、それぞれで0から999までの数字を不確定に返します。 2.毎フレーム異なる数字を返しますので Random < 100 の 1F10% でも、 6F(0.1秒)で46.86% 30F(0.5秒)で95%を超えます。 3.同じ箇所に複数あっても、別々の数値を出します。 二つの技をRandom < 500で並べた場合、上側が50%、下側は25%の計算です。 .MUGENのCPUAIなどの不確定要素とRandomのないAI同士の対戦は何試合しても同じ試合になります。 .その他確率を下げても同じ条件での行動の種類が少ない場合、同じ状態になりやすいです。 (ちなみに、Randomは一定の数値から↑と↓で0.0x%程確率が異なる。 通常使う条件なら大きくても0.5%程度の誤差なので特に気にする必要は無いが。) Trigger1 = Random % x ;Randomで0〜(x-1)までの数値を返します。※数値によってはかたよる。 Trigger1 = Random % 3 = 0 ;0〜2の数値を返しそれが0である条件。約3分の1の確率。 .1000を割り切れない数値の場合、0.数%分くらいは確率が偏ります。 .複数の動作をそれぞれ同じ確率に割り振りたい場合は Randomでなく、Randomを代入したVarで行う方が確実ですが、 多少の違いを許容できるのならば、Random%(割り振る技の数)=0を1つ目に、 そこからRandom%(割り振る技の数-上側にある割り振られた技の数)=0と並べていくと、 例えば割り振る技が5つなら、Random%5=0 (100/5=20%) Random%4=0 ((100-20)/4=20%) Random%3=0 ((100-40)/3=20%) Random%2=0 ((100-60)/2=20%) 1(20%)とほぼ均一にできます。 距離 ※数値に小数があります。 Trigger1 =(P2BodyDist X = [xx,xxx]) ; 相手までの距離 xx〜xxx Trigger1 =(P2Dist Y = [-xxx,-xx]) ; 相手までの高さ -xxx〜-xx(-xxは相手が上) Trigger1 =(P2BodyDist Y = [-xxx,-xx]) ; 同上 Trigger1 = Pos Y < -xxx ; 地面からの高さ -xxx未満(高い方) Trigger1 = Vel X > 0 ; X軸・横の速度(前へ進んでいる) Trigger1 = Vel Y < 0 ; Y軸・縦の速度(およそ-1以下・上昇中である) Trigger1 = BackEdgeBodyDist < xx ; 後ろ画面端までの距離 xx未満。 Trigger1 = FrontEdgeBodyDist > xx ; 前の画面端までの距離 xxより大きい。 ※ 画面端とは「画面内の端」です。ステージの端を参照するトリガーはありません。 Trigger1 =(P2Dist X = [xx,xxx]) ; 座標基準の相手までの距離 xx〜xxx ※ P2BodyDist Xは「衝突判定(Ctrl+Cで足元にでる横棒)」を含めていますが、 ※ P2Dist Xは「座標位置(Ctrl+Cで出る中央のポイント)」基準です。 ※ 基本的にAIでは当たり判定に近いP2BodyDist Xだけで構いません。 基本動作 Trigger1 = Ctrl ; コントロールフラグ( 0,1 ) 基本操作が行えるかどうか。 Trigger1 = InGuardDist ; ガード可能範囲内( 0,1 ) レバーを後ろ傾けるとガードする範囲内。 ※ InGuardDistは、相手側のAttack.Dist,Guard.Distの範囲内という条件です。 Trigger1 = Power >= xxxx ; パワーゲージ量がxxxx以上である。1000で一本。 Trigger1 = StateType = S ; ステートタイプ:立ち Trigger1 = StateType = C ; ステートタイプ:屈み Trigger1 = StateType = A ; ステートタイプ:空中 Trigger1 = StateType = L ; ステートタイプ:倒れ Trigger1 = StateType !=A ; ステートタイプ:空中でない( L,倒れ状態も含む ) キャンセル系 Trigger1 = StateNo = xxxx ; ステート番号がxxxxである。 Trigger1 =(StateNo = [xxx,xxxx]); ステート番号がxxxからxxxxである。 Trigger1 = AnimElem = x,>=0 ; アニメーションが開始からx枚目まで、でない。 Trigger1 = AnimElem = x,<=0 ; アニメーションが開始からx枚目までの時。 ※AnimElem固有の指定方法です。 ※厳密に言うとアニメx枚目の1F目から,何Fか、だそうです。 Trigger1 = AnimElemTime(x) = y ; アニメーションx枚目のyフレーム目。 Trigger1 = AnimElemNo(x) = y ; アニメーションのxフレーム後がy枚目。 Trigger1 = AnimElemNo(0) = x ; アニメーションの現在のフレームがx枚目。 Trigger1 = Time >= xx ; 同ステート内での経過フレーム、Timeがxx以上 ※Time=0は基本的に使えません。例外としてジャンプ抑制などはありますが。 Trigger1 = MoveContact ; 攻撃が当たった。 Trigger1 = MoveHit ; 攻撃を直撃させた。 Trigger1 = MoveGuarded ; 攻撃をガードされた。 Trigger1 = MoveGuarded && P2StateType = S ; ガードされた+相手が立ち状態 Trigger1 = MoveGuarded && P2StateType = C ; ガードされた+相手が屈み状態 Trigger1 = MoveContact >= 10 ; Win用 攻撃が当たってから10フレーム以降。 ※MoveContactなどは、基本的に「攻撃したステート内」でしか反応しません。 ※ただHitdef(攻撃判定命令)のP1StateNoでのステート移行後は1Fだけ反応するもよう? ※Win版ではこれらは1or0でなく、0か当ててからのフレーム数で返します。 ※ReversalDefで相手の攻撃をとった際にも、MoveContactは発生するようです。 Trigger1 = HitCount > 4 ;(基本的に)同ステート内での攻撃の直撃回数。5回以上ヒット ※ステート側のStatedefのオプションにHitCountPersist=1がある場合、 ※ 直前のステートの命中回数も含まれますが、滅多に使われてません。 追撃系 Trigger1 = PrevStateNo = xxxx ; 直前のステート番号がxxxxである。 Trigger1 = P2MoveType = H ; 相手ムーブタイプ:相手、くらい動作中 Trigger1 = P2StateType = L ; 相手ステートタイプ:相手、倒れ中 Trigger1 = P2StateType !=L ; 相手ステートタイプ:相手、倒れ以外。 Trigger1 = P2StateNo = xxx ; 相手ステート番号が、xxxである。 ※ StateType = L には起き上がり動作も含まれています。 反撃系 Trigger1 = P2MoveType = A ; 相手ムーブタイプ:相手、攻撃動作中 Varの条件 Trigger1 = Var(**) > xxx ; 特殊ゲージなどの量を考慮する場合などに。 ライフ量 Trigger1 = Life >= xxxx ; ライフがxxxx以上である。※絶対数値 Trigger1 = P2Life <= xxxx ; 相手ライフがxxxx以下。※絶対数値 ※ Lifeの総量はキャラによって異なる。 Trigger1 = Life*100/Lifemax < xx ; Win用パーセント計算 ※Lifeに*100してからMaxを割る事 チームモード・ラウンド数 Trigger1 = TeamMode = Single ; シングルマッチ・自分1人。 Trigger1 = TeamMode = Simul ; いわゆるタッグ・自分側2人同時。 Trigger1 = TeamMode = Turns ; ターンチーム制・負けるとキャラが交代する方式。 Trigger1 = RoundNo >= x ; ラウンド数 xラウンド以降 ※ RoundNo は2本先取3ラウンド制の場合、引き分け抜きなら3ラウンドまでですが、 ※ ターンチーム制で4対4だと7ラウンドまであることを留意しましょう。 Trigger1 = RoundsExisted >= x ; キャラがいたラウンドの経過数 ※ RoundsExisted は、キャラの初めてのラウンドが0で、残り続けると1、2と増えます。 ※ ターンチーム制の2人目以降などの為にあります。 その他 数値をいじるときなどに使われる、数値処理。 Trigger1 = Floor( xx ); 数値xxの小数点以下の位を切り捨てて処理します。1.4などを1に。 Trigger1 = Ceil( xx ) ; 数値xxの小数点以下の位を切り上げて処理します。1.4などを2に。 Trigger1 = Floor( Pos Y ) = 0 ; 例:Pos Yが0から0.999999,,,,,の間である条件式。 ※ Varなどで距離の数値を扱う際、これらで処理をしないとエラーが流れます。 Trigger1 = Abs( xx ) ; 数値xxを絶対値にする処理をします。-12などを12に。負数を処理する時などに。 Trigger1 = **** = IfElse( xx , yy , zz ) ;xxが真ならyyを、偽ならzzを返す条件処理。 ※ 主にパラメーターの指定などで使われますが、状態で数値を変えたい時に。 ※ ただし、通常の条件も()で囲うと、中の条件式が真なら1を偽なら0を返す為、 ※ IfElse( xx , 5 , 10 )という条件なら、5 +( xx )*5 と一緒の処理になります。 >リダイレクト 上記の例では意図して使いませんでしたが、 別のユニットの情報を引き出せるリダイレクトというものがあります。 記述方法は、引き出したいトリガー情報の手前に ***, と付け加えることで、 ***で指定された対象の持っている、トリガー情報を参照できます。 ただし***で指定している対象がいない場合、 参照したときエラーを表示しますので注意しましょう よく使う EnemyNear EnemyNear,*** EnemyNear(0),*** : 一番近い敵 ※倒れている相手含む※ EnemyNear(1),*** : 2番目に近い敵 ヘルパーは対象外 AI記述でもよく使うことになりますが、このままでは死んでいる相手も参照してしまう為、 タッグを考慮するとEnemyNear(!(EnemyNear,Alive||NumEnemy=1)),とった工夫が必要です。 ただ、それだと記述が長くなってしまうので、それの()内とほぼ同じ処理のVarを用意し、 EnemyNear(Var(**)),と言う様な形にすると記述が短くて済みます。 [State -1, EnemyNear(Var)];AI記述の先頭の方に。 Type = Varset var(*敵確認Var*) = !(EnemyNear,Alive) ; 一番近い敵が生きているなら0を、そうでないなら1を代入 TriggerAll = Var(*AIフラグ*);AI処理条件 Trigger1 = NumEnemy > 1 ; 相手が2人以上の時(3人以上は基本的にありえません。) ;EnemyNear(Var(*敵確認Var*)),*** あまり使わない PlayerID(**) Target(**) Partner Enemy PlayerID(*ID数値*), : 指定IDのユニットへのリダイレクト IDは、ユニットが発生した時点で個別の数値が割り当てられます。 Helperを用いる複雑な情報処理を必要とする場合などにだけ使われます。 Target(*HitID数値*), : 指定HitID番号の攻撃を当てたターゲットへのリダイレクト HitIDとは、HitDefで指定した攻撃のID番号の事。省略時、Target, 攻撃を当てた相手です。 ターゲットを確実に指定したい場合に使われます。 Partner, : パートナー(タッグ時の相方)へのリダイレクト NumPartnerでいることを確認してから使いましょう。※ヘルパーは対象外です。 タッグ時に特殊な動作を行わせたい場合などに使われます。 Enemy, Enemy(0), : 1人目の相手へのリダイレクト。相手がタッグの場合、先に選ばれた方。 Enemy(1), : 2人目の相手へのリダイレクト。後に選ばれた方。※ヘルパーは対象外。 システム的な処理を行う場合に使われます。 ヘルパー系統 Helper(**) Parent Root Helper(*ヘルパーID数値*), : 指定ヘルパーID番号のヘルパーへのリダイレクト。 ヘルパーIDはHelperのステートコントローラーで指定したID番号のこと。 指定できるのは自身の出したヘルパーのみで、相手のヘルパーは不可。 Parent, : ヘルパーから、そのヘルパーを出した親へのリダイレクト。 Root, : ヘルパーから、本体へのリダイレクト。親がいないと参照不可。 ヘルパーを制御する場合にのみ使われます。今回はまず使いません。 数の確認は、ほとんどがNum***で可能です。 NumEnemy NumTarget Numpartner NumHelper(**) PlayerIDは、PlayerIDExist(*ID番号*)で存在の確認が可能できます。 リダイレクトを用いた条件式の例としては、EnemyNear,Timeなどを用いた遅延処理、 Trigger1 = EnemyNear,Vel X > 0 ; 相手が接近してきている。というようなトリガーに、 Trigger1 = Enemy,MoveType!=A && Enemy(!(NumEnemy=1)),MoveType!=A && InGuardDist 相手が攻撃中でない+ガード可能範囲内 と言う様なトリガーなど。 InGuardDistは本体だけの場合、攻撃動作中(MoveType=A)にしかでませんが、 飛び道具などを使っている場合、本体が攻撃中でない+ガード可能範囲内という状態が起こる為、 相手が飛び道具を打ってきた、というような条件式になります。 ちなみに状況は限られてしまいますが、Helperを使えば、 飛び道具のおおよその位置も認識することは可能です。 リダイレクトには様々な使い方があります。 >P2*** と EnemyNear, の違い P2StateType = A と、 EnemyNear,StateType = A の違いについて。 それを説明する前にP2***について説明します。 P2***は「自分の中にある相手の情報」で、性質が異なり、種類も限られています。 P2***の種類は P2BodyDist X P2Dist X P2BodyDist Y P2Dist Y の距離判定の他、 P2Life P2MoveType P2StateType P2StateNo の4種類と P2Name だけです。 参照する相手は「StateNo=5150(死亡ステート)でない、一番近いPlayerユニット」で、 EnemyNaerと最も異なる点は「HelperType=Playerの相手ヘルパーを含む」ことでしょう。 つまり P2StateType = A と、 EnemyNear,StateType = A の違いは、 P2はHelperType=Playerを含み、StateNo=5150(死亡ステート)を含まないこと EnemyNear,はヘルパーを一切含まず、相手の状態も関係ないことです。 ただ、HelperType=Playerを用いるキャラはかなり特殊ですし、 どちらかというと気をつけるべきは、それを使う方ですので、 P2***は、EnemyNear(!(EnemyNear,StateNo!=5150||NumEnemy=1)),と変わりないと考えても、 大丈夫だと思います。 (改造などによって)3人以上を同時に相手することには対応できませんが あと最後に一つ大きな違いとして、P2***はリダイレクト可能というものもあります。 使い道は限られますが。 |
では、トリガーの説明を参照しながら、実際に記述を始めていきます。 状況条件のトリガーは、「自分の反応をさせたい状況は、 どういった情報から参照できるのか」という風に考えるといいかも。 例えば「対空攻撃」は 「相手が自分の上側」 → P2Dist Y < -1 ; 相手が自分よりも上の座標 「自分は地上にいる」 → StateType != A ; 自分が空中でない 「コンボ中ではない」 → P2MoveType != H ; 相手がくらい動作でない と言うような具合で、状況条件を考えます。 が、細かい記述に関しては個々人のセンスです。 「私の使っている方法」も一部紹介しています。 1.システム記述 上の項で紹介している、リダイレクト用のVarserなどの項目。 Varの操作などは、命令を処理しても次のステートへは移行しないので、 ChangeStateを行う動作記述よりも前に記述します。 リダイレクトの例以外では確率を使った技の割り振りを行う場合に、 技割り振り用にRandomを代入したVarを作っておくと確実な振り分けが可能です。 [State adi, Random] Type = Varset var(*技分けVar*) = Random Trigger1 = Var(*AIフラグ*);AI処理条件 ;このフレーム中変化しない為、確実な技振りに使えます。 2.防御関係記述 ■反撃・反応(切り返し動作など) 攻撃的防御。優先順位上、こちらはガードよりも前に記述しましょう。 使う攻撃は素早い攻撃、発生無敵の技など。 [State -1, Counter Attack] Type = ChangeState Value = *ステート番号* TriggerAll = *AI最低限記述* TriggerAll = Ctrl ; 大抵、前提条件にありますが。 TriggerAll = *前提条件* TriggerAll = *状況条件* TriggerAll = Random < *** ; 確率 TriggerAll = Random < 10 || Time > x || EnemyNear(x),Time > x ; 遅延処理 ; 遅延処理の必要性についてはこちらを参照。xは5〜10くらい これに使える条件 Trigger1 = P2MoveType = A ; 相手の攻撃に反応 Trigger2 = StateNo = 140 || PrevStateNo = 140 ; ガード直後 Trigger3 = PrevStateNo = [5000,5099] ; くらい動作直後 Trigger4 = PrevStateNo = [5100,5199] ; 倒れ動作直後 Trigger5 = PrevStateNo = [5200,5299] ; 空中体勢立て直し・他 の後 遅延処理は「かけないと性能によるゴリ押しが可能」な為、加えることが慣例です。 ちなみに、反撃・反応は攻撃や無敵移動ばかりではありません。 バックステップやジャンプといった回避動作もキャラによっては有用です。 私の使っている反撃の処理はVarを使った「反応レベル式」 特定の状況でVaraddを行い、一定量を越した場合に反応をさせるもの。 長いため詳しくは>こちら<をどうぞ。 不可欠でもありません。 ■ガード ガード始動ステートの120番へと飛ばします。 地上ガードや立ち屈み、空中ガードはそこから分岐しています。 [State adi, Guard] Type = ChangeState Value = 120 ; ガード始動ステート TriggerAll = *AI最低限記述* TriggerAll = Ctrl && InGuardDist && MoveType!=A ; ガード前提条件 TriggerAll = Random < 10 || Time > x || EnemyNear(x),Time > x ; 遅延処理 Trigger1 = Random < xxx ; どのくらいの割合にするか。 細密な制御をしたい場合はガード用にVarを使ったりしますが、割愛。 遅延処理やRandomの確率をつけているのは、つけないと硬すぎるため。 ちなみに、ガード前提条件にあるMoveType!=Aは一般的でないです。 ガード条件はCtrl&&InGuardDistだけだと認識されているため、 Ctrl&&MoveType= A自体が珍しいため、MoveTypeは大抵書かれません。 あと、実はこれだけですと欠点があります。詳しくは次ページにて。 ※重要※ Type=AssertSpecial の Flag* = No***Guard は「ガードできない状態」の命令です。 全てのファイルから No***Guard のAssertSpecialを探し、 NoStandGuard NoCrouchGuard NoAirGuard のどれか一つ以上あるステート番号と、 AssertSpecialの条件式を、ガードの条件式に加えましょう。 AssertSpecialのTriggerが、Trigger1=1であった場合は、StateNo!=***を条件に。 何かしら条件がある場合は、StateNo!=***||StateNo=*** && !*条件* というような行を追加します。 このガード記述の条件に、P2MoveType=Aが無いのは、 「P2MoveType!=A && InGuardDist」の場合も反応しなくなる為。 飛び道具はP2MoveType!=Aでも攻撃判定があり、 それが条件にあると飛び道具のガードが甘くなってしまいます。 Varなどを使って制御している場合はややこしいですが、 使っていない場合はこんなもんです。 「前へジャンプしながらガードし続ける」と言った動作が気になる場合は、 Vel X <= 0 などで速度を感知させてやるとか、 ステート側の処理に手を加えたりすることになるかと。 |
3.コンボ関係記述 ■技コンボ ここで言うコンボは大別して2種類あります。 1.技動作をキャンセルして別の技を出せるようになっている キャンセルコンボ 2.技動作終了時などのタイミングを見計らって技を出し繋ぐ 目押しコンボ どちらも「技→技」という動作ですが、大きな違いは「Ctrlの有無」 「他の動作が不可能な状態」か「他の動作が可能な状態」かの違いです。 受け付けフレームが短くとも、技動作を途中中断するものがキャンセルコンボ。 これらの動作確認をしたい場合、AIを起動せず、 確認する技のAI最低限条件を1;や1||でAI用の制限を無視させ、 手操作でそれをつなぐ技を出させるといいでしょう。 >キャンセルコンボ [State adi] Type = ChangeState Value = **** ; つなぐ攻撃 TriggerAll = *AI最低限記述* TriggerAll = MoveContact ; 攻撃を当てた(基本、前提条件にあるもの) TriggerAll = StateNo = xxx ; 当てた攻撃(基本、前提条件にあるもの) TriggerAll = *前提条件* TriggerAll = Random < xxx ; 確率 Trigger1 = P2MoveType = H ; 相手がくらい状態。 Trigger1 = *状況条件* ; 可能な距離など キャンセルコンボはそれ以外にできる動作がない事も多い為、 他の動作がない限り、簡単に安定させることができます。 気をつけるべきは、相手による繋がりやすさの違いくらいでしょうか。 直撃やガードなどの条件は、先ほども紹介していた Trigger1 = MoveHit ; 攻撃を直撃させた。 Trigger1 = MoveGuarded ; 攻撃をガードされた。 Trigger1 = MoveGuarded && P2StateType = S ; ガードされた+相手が立ち状態 Trigger1 = MoveGuarded && P2StateType = C ; ガードされた+相手が屈み状態 Trigger1 = EnemyNear,StateNo = [5000,5099] ; 相手くらい動作(倒れ受身含まず) Trigger1 = EnemyNear,StateNo = [5000,5019] ; 相手地上くらい動作 Trigger1 = EnemyNear,StateNo = [5020,5059] ; 相手空中くらい動作 こういったものを使うといいでしょう。 >11/11/10追記 分からないなら考慮する必要はありませんが、Enemynear,StateNoは結構重要。 というのアーマーやブロッキングも大体MoveHitの判定が立つので、 MoveHitだけだとアーマーやブロッキングに対しても同様の攻撃を返してしまいます。 確実に判別したい場合はNumTargetとTarget,リダイレクトで参照するのが確実です。 アーマーやブロッキングに対してはほぼNumTargetは発生しないため、 MoveHit&&NumTarget=0の時にブロッキングなどに対する攻撃をさせることもできます。 >目押しコンボ・つなぎコンボ [State adi];終了時目押し用 Type = ChangeState Value = **** ; つなぐ攻撃 TriggerAll = *AI最低限記述* TriggerAll = Ctrl ; 基本、前提条件にある) TriggerAll = *前提条件* TriggerAll = Random < xxx ; 確率 Trigger1 = P2MoveType = H ; 相手がくらい状態である。 Trigger1 = PrevStateNo = **** ; 直前のステート Trigger1 = *状況条件* ; 可能な距離など 目押しコンボは2種類にわかれます。 1つ目は 技の動作終了後、可能な限り早く入力する目押し 2つ目は 相手の吹き飛び具合を見てからの目押し 前者も安易にできるものではないものの、後者はより困難なものです。 原因は「 Ctrl 」の存在。それがある為、間隔が広いほど困難になるのです。 >11/02/17追記 理由はその2で説明したCNSで制御していない動作があるため。 >CtrlがあるとMUGEN側のCPU,AIが勝手に動作して条件が合わなくなってしまうのです。 >こうしたのはCPU,AIを起動していない状態では確認できないため注意しましょう。 代表的な方法としてはVarを使う方法があります。 まず「全ての動作の制御をするVar」を決め、 どういった動作を行うべきかという判断をそのVarで行い、 コンボを行う際にはそのコンボ以外の動作を行わない、という制御の方式です。 コンボでのVarSetのタイミングは StateNo=xxx && MoveHit などで行い、 P2MoveType!=H(くらい状態でない)やP2StateType=L(倒れ状態)でリセットするという具合。 Varの制御を確りと理解してからするべきですが。 その他には、正確に制御するAI用ステートを作るのが最も確実でしょう。 ただし、キャラ制作者でない場合、制作者様に確認をしておくこと。 概要を述べると、次をコンボで繋ぐ技の最後の処理で、基本動作ステートへ飛ばず 「Ctrl=0で基本動作と全く同じ動作を行い、特定のタイミングで攻撃ステートへ移す」 というAI専用の擬似基本動作ステートへ飛ばす、というもの。 勿論、繋がらないと判断した場合、基本動作ステートへと戻します。 AI専用ステートを作ることになりますが、あくまでしている動作は「基本動作」に限定しています。 ステートに関してよく知っている方以外にはオススメできませんが…。 >Pcs.txt と方法を掲示してみてはいますが、技や相手によっては入らないことも多々あり、 コンボをしようとしても無駄な場合がありますので、無理にやるものではないかと。 あとPrevStateNoもCtrl=1の場合、「ステート戻り(x→0)→・・・)→・・・)→ →次のフレーム(レバー処理、ステート番号歩きへ(0→20) →キャラ処理(PrevStaetNo=0)」 という事もあるため、Varなどで管理する方がいいかもしれません。 AI制作者としては早期に覚えたい点ですが今は割愛。その4にて説明しています。 ちなみに異なる相手にコンボが入らない主な理由は3つ。 1.コンボ脱出用のシステムがある。 > 〜抜けや、空中受身可能時無敵など。 2.当たり判定の形が異なる。 > 特に身長によって判定の基準座標が異なる。 3.落下速度が異なる。 > 落下中を狙うものは特に入りづらい。 1に関しては単純に参照するトリガーが無い為、割愛。 2は、「EnemyNear(x),Const(Size.***.Pos.Y)」などを使うことでおおよそ感知できます。 空中コンボでは「EnemyNear(x),Const(Size.Mid.Pos.Y)」で 「近い相手(x),相手の設定数値(体の中心のY座標)」を知ることができ、 その座標は設定されていれば、基本的な空中動作の基準位置に近いことが多いです。 それをP2Dist Yに+することで、吹っ飛び動作中であっても当たり判定のありそうな 相手の中心座標へめがけて攻撃をすることができ、コンボを安定させられます。 ( P2Dist Y + EnemyNear(x),Const(Size.Mid.Pos.Y) ) 地上コンボには「EnemyNear(x),Const(Size.Head.Pos.Y)」で 「近い相手(x),相手の設定数値(頭のY座標)」を知ることができ、 その座標は設定されていれば、体のおおよその高さが分かります。 それを使うことで、どの程度の大きさかが分かるため、 小さい相手へ打点の高い技などを使わないように調整する形です。 ただし、相手の設定数値がいいかげんであったり、 くらい動作中などの体勢自体が特殊である場合には使えません。 ■移動→攻撃 特定の動作を行った後、もしくは行っている最中に行う攻撃など。 種類はダッシュ攻撃、飛び込み・めくり、設置→(移動→)攻撃なども含みます。 >ダッシュ攻撃 [State adi] Type = ChangeState Value = **** ; ダッシュ攻撃 TriggerAll = *AI最低限記述* TriggerAll = StateNo = 100 ; ダッシュ中 TriggerAll = *前提条件* TriggerAll = Random < xxx ; 確率 Trigger1 = *その他状況条件* ; 可能な距離など 補足は特に無し。 条件StateType=Cの技は、これに近い処理を行わせることになる。 >飛び込み・めくり [State adi] Type = ChangeState Value = **** ; 空中攻撃 TriggerAll = *AI最低限記述* TriggerAll = StateType = A ; 空中 TriggerAll = P2StateType != A ; 相手空中でない TriggerAll = *前提条件* TriggerAll = Random < xxx ; 確率 TriggerAll = Vel X > 0 && Vel Y > 0 Trigger1 = *その他状況条件* ; 可能な距離など Trigger2 = (P2BodyDist X = [-50,0]);X密着 Trigger2 = (EnemyNear(x),Pos Y = [-10,10]);相手地面近く Trigger2 = (Floor(P2Dist Y+EnemyNear(x),const(size.head.pos.y)) = [-25,25]) Trigger2 = *その他状況条件* ; Trigger2は、めくり用の条件。 (P2Dist Y+EnemyNear(x),const(size.head.pos.y)) とは、 「相手との高さの座標差+近い相手(x)の,頭の高さ」を意味するもので、 条件は P2BodyDist X = [-50,0] と合わせ「頭すれすれ」の位置を指しています 相手の数値設定が甘かったり、キャラの体勢が特殊だとうまく機能しませんが。 こんなややこしい方法を取っているのは、 MUGENでは相手を選べず、相手の身長には差がある為。 ちなみに頭の高さについては、めくり以外にも、 打点の高い攻撃があたる高さなのか、と言った判断にも使えます。 >設置→xx まず飛び道具の設置を行う技のステートを探し、何タイプの飛び道具か確認します。 Type = Projectile がある場合は、Projタイプの飛び道具と分かります。 それが無く Type = Helper がある場合は、Helperタイプの飛び道具でしょう。 1.Projの飛び道具は、NumProjで総数を、NumProjID(*ProjID*)で個別の数を参照できます。 ですが、個数以外の事を知りたい場合はHelperなどを射出して知る必要があります。 2.Helperの飛び道具は、NumHeloer(*HelperID*)で数の確認を、 Helper(**),RootDist Xなどで、ヘルパー側からの座標を確認できます。 ただし複数の場合、制御系統に手を加えないと、どれか一つしか参照できません。 と、かなり融通が効きにくい為、柔軟な使用方法は困難です。 その為、設置技後や数を条件にして、特定の動作を行う、というのが無難でしょう。 記述に関しては、目押しコンボなどから流用できますので割愛。 有用性に関しては自分で使ってみて調べるといいかも。 |
AIにおいて、 100%当たる条件はありませんし、 100%外れる条件もありません。 いくら速度計算やステート監視をしようが、 当たらないときは当たりませんし、 外そうとして当たるときもありえます。 限定しすぎた動作は弱点にもなりますし、 不安定な動作は武器にもなりえます。 |
4.動作関係記述 AIの核とも言うべき部分です。 言うべき部分ですが、特に↑の言葉以外言うことはありません。 [State adi] Type = ChangeState Value = **** TriggerAll = *AI最低限記述* TriggerAll = Ctrl ; 基本、前提条件にある TriggerAll = *前提条件* TriggerAll = Random < xxx ; 確率 Trigger1 = P2MoveType != H ; 相手がくらい動作中でない Trigger1 = *状況条件* ; それぞれの動作に合わせて記述を並べていくだけ。 特に確率は動作の信頼度に合わせて調整しましょう。 基本動作に限りませんが、 動作の確率が極端に高いと、ガードできるタイミングが減り、 結果ガードがとても緩い状態になってしまうこともあります。 行動の多さが強さではありません。 ■起き攻め ( P2MoveType=H ) Trigger1 = P2StateType = L ; 相手倒れ状態 を使いますが、倒れバウンド→倒れ待機→倒れから起き上がり までが倒れ状態なので、 いつ攻撃を行うか、といった条件が欲しい場合は別の記述が必要です。 Trigger1 = P2StateNo = [5100,5109] ;バウンド〜 Trigger1 = P2StateNo = [5110,5119] ;〜倒れ待機〜 Trigger1 = P2StateNo = [5120,5149] ;〜起き上がり キャラによっては、極めて起き上がりの早いものもありますが、 それを参照するトリガーはありませんので、難しい所です。 StateNo = 5120 && AnimTime * -1 < xx ;というような方法で 起き上がりの直後を狙うことも可能ですが、通常起き上がり直後3Fは無敵で、 直後12Fまで投げ無敵が付いているため、気をつけましょう。 ■攻撃動作 Trigger1 = P2MoveType != H ; 相手くらい中でない というものを基本に入れておくと、 PrevStateNoを使ったコンボ動作の中断を幾分か避けられます。 反面、その動作で拾いなおしたりすることもしなくなりますが。 もっと確実なのは、動作を制御するVarを用いることです。 かなり今更な話ですが、相手が動くことを考慮した場合、 Trigger1 = P2Dist Y = [-yyy,-yy] ; どの程度の高さまで届くか。 Trigger1 = P2BodyDist X = [xx,xxx] ; どの程度の距離まで届くか。 などで指定する攻撃範囲は実際の範囲よりも狭めにとっておくか、 発生フレームが遅めならばやや広めに指定するといいかもしれません。 そのあたりは個々人の裁量ですが、少なくとも範囲丁度は控えましょう。 理由は相手の当たり判定がそれよりも狭い可能性がある上、 試合中相手が全くの静止状態になることはガード中以外滅多にないため。 相手の移動を考慮させたい場合は( P2BodyDist X-(EnemyNear(x),Vel X*発生F) )で 発生フレーム分(※要調整)の速度補整をかけられます。 ただ発生フレームが遅い場合は相手が止まってしまうこともありますが。 それ以外には、PrevStateNo!=*発動技ステート*というような記述を加えることで、 無闇な連打を控えさせるといった配慮や、 「PrevStateNo = *キャンセルコンボ始動攻撃* && Ctrl」で コンボ始動が当たらなかったことを察知したりなどしておくといいかも。 特にコンボ始動が当たらなかった場合は、 「相手が無敵状態で当たらなかった」という可能性もあり、 一旦バックステップなどを退避させる、というような動作をさせたり、 私はしています。 ■移動動作 攻撃動作の上に書いたり、一緒に書いたりする場合もあります。 >歩き動作 [State adi] Type = ChangeState Value = 20 TriggerAll = *AI最低限記述* TriggerAll = Ctrl ; 基本、前提条件にある TriggerAll = StateType = S ;立ち状態 Trigger1 = Random < xxx ; 確率 ※AssertSpecialのNoWalkのあるステート番号でないことも条件に。 >地上ジャンプ始動 [State adi] Type = ChangeState Value = 40 TriggerAll = *AI最低限記述* TriggerAll = Ctrl ; 基本、前提条件にある TriggerAll = StateType = S ;立ち状態 Trigger1 = Random < xxx ; 確率 空中ジャンプはCnsなどを書換え限度数をVarで管理する必要があります。 その方法の一つとしては、>こちら<をどうぞ ダッシュに関しては技と同様ですが、前後ダッシュを繰り返したりするのは、 PrevStateNoで制限しておく方が見栄えはいいかもしれません。 見栄えの問題ですが。 5.挑発 別に不可欠なものではありませんが。 |
.確認 動作の確認は、記述の途中途中で行っておきましょう。 記述ミスが大量にある、といった状態は面倒です。 してしまいやすいミスの例としては、 × Trogger*** → ○ Trigger*** :Tr o ggerでは反応しません × =[-10,-80] → ○ =[-80,-10] :[低い方,高い方]でないと反応しません × =[x,xx] && → ○ =[x,xx])&& :=[,]を使った直後に&&を置くとエラーを返します。 また不具合を起こしやすいものとして P2BodyDist X < 30 (30よりも近い)という条件は、 「相手が後ろにいても(-9999でも)反応します」ので、=[-xx,30]という指定の方が安全。 例えば密着時限定の投げ技は[-40,5]と言った指定の方が有効的。 全ての動作を一通り記述し終わりましたら、実践を行い調整します。 様々なタイプのキャラと戦わせ、悪い場所を調整します。 近接型・突進 迎撃 投げ技 当身投げ 設置型 中距離型 遠距離射撃型などなど 無敵回避を多用する ブロッキングを使う アーマー系などなども 高性能なキャラであれば、性能のゴリ押しでも勝てたりしますが、 並みのキャラの場合、勝つためにはAIの優秀さが必要不可欠です。 基本的な記述は、ここまでで完成です。 その4へ続く。 |
>Const(Size.***.Pos.*)についての話 例では相手の頭や体の中心の高さを感知して使っていますが、 AIにおいて使うのはやや特殊なようです。 しかし、この数値自体はBindToTargetなどを行う際に使われるそうで、 言ってしまえば必須スプライトと同様の設定必須数値です。 大体の位置とはかかれていますが、重要な項目ですので、 明らかに設定がおかしい場合はバグ報告してあげましょう。 まあAIにおいて使うのはあくまで特殊な例ですが、 AIにとっては数少ない相手の大きさや位置を知る手段ですので、 可能な限り設定しておいてもらいたいものです。 ちなみに基本的なキャラのしゃがみ時の頭の高さは、 これで設定されている体の中心の高さでもあることが多いです。 もちろん、特殊なキャラを想定している位置ではありませんが。 あと、ほとんどのキャラのくらい判定はこれらの高さよりも高いので。 これらの高さで判定しておけば、当たらないことはかなり避けられるはず。 >StateTypeとCtrlの話。 StateTypeはCtrl動作に関わっているものです。 例えば、Statetype=S&&Ctrlに、レバー上ならばジャンプステートに飛ぶなど、まとめると StateType = S && Ctrl →左右,歩き(20) 下側,屈み(10) 上側,ジャンプ(40) StateType = C && Ctrl →下側,ステート維持 下側以外,立ち上がり(12) StateType = A && Ctrl →上側,空中ジャンプ(45) StateType = L && Ctrl →無し(※通常でも動けない) InGuardDist →後ろ側,ガード(120) ( StateNo = 20 からはレバー左右以外でState,0に ) というステート変更の処理が、フレームの最初に行われています。 ただしステートタイプの変更などの処理は、自身のフレーム中に行われます。 例えば、AIにおいてStateType=Cの条件を満たして動作させたい場合、 まず、State,10へと飛ばしてStateTypeをCにし、 次のフレームでStateType=Cの攻撃を行わせることが可能です。 その場合のStateNo=10の条件を入れると、反応しにくくなります。 フレームの最初でステート番号が変更され、Stateが12になっている為。 StateType=CからStateType=Sの場合は、State,12へと飛ばし〜という形になる。 ただし、地上攻撃の条件がStateType!=A&&CtrlとCommandである場合、 これらの心配をする必要は無い。 |
>ラウンドステートの話 まずRoundStateについて分解すると、 RoundState = 0 ; 黒画面からのフェードイン中 RoundState = 1 ; イントロ〜ラウンドコール RoundState = 2 ;■開始合図〜試合中 RoundState = 3 ; 終了合図 RoundState = 4 ; アウトロ〜フェードアウト RoundState = 2を、AIの最低条件にしているのは、 コマンドは基本的に2でしか受け付けないからです。 もちろん、全く受け付けないわけではありませんが。 しかしRoundState=2は、MUGEN側で、 「 RoundState = 2 に入った0.数秒(初期アドオン30F)後、 キャラクターのステート番号を0に、Ctrlを1にする。 」 という処理が行われております。 その為ほとんどのキャラクターは、イントロ後StateNo=0&&!Ctrlの状態で待機し、 その処理でコントロールが戻されたとき試合を始める、と言う形をとっています。 厳密には(RoundState=2)=(行動可能)ではなく、 MUGENの処理でコントロールを戻された時から試合が始まり、 それよりも前に動くことはフライングである、とも言えるのです。 特殊な制御を行う場合、気をつけておかなければ平気でフライングします。 キャラクターによっては仕様上そうなってしまっていたり、 AI制作者によっては気にしていない人もいますが、 0.数秒でも初手を入れるに十分な時間ですので注意しましょう。 またK.O.されたら、したら試合終了ではありません。 Simul(タッグ)の場合、!Alive && RoundState = 2は基本的に起きるものです。 !Aliveの状態では!Ctrlのはずですので、まず動けもしないはずですが、 Ctrlなどの条件式も忘れている場合、「ゾンビ化」してしまいます。 その為、AliveはAIの最低条件に入れているわけです。 >HitDefAttrの話 私はほぼ使ってません。 HitDefAttrとは攻撃の種類を判別するトリガーで、AIにおいては EnemyNear(x),HitDefAttr = SCA, **, ** という感じに指定します。 一見相手の攻撃を判別する便利なトリガーですが、欠点もあります。 このHitDefAttrというのは、 Type = HitDefで指定されたAttrの設定値を求めるのですが、 Type = HitDefの指定のタイミングが大抵、判定の発生と同時なのです。 一応最初から指定することもできるのですが、 慣例上判定が存在する時のみ指定されている事が多いです。 1P側では避けられず、2P側では避けられるといった差が出るのです。 なので本文での説明もしていません。 ※修正追記※処理の順番について ユニットの処理は基本的にIDの若い順(1P→2P→3P→)ですが、 Movetype=A(攻撃動作中)のキャラは通常の順番より前に処理が行われます。 ( 実際はユニット番号の若い順。詳しくは>こちら<の途中参照 ) なので正確な順番は (始) 基本操作などの管理処理(多分ここ) →(続) (続) MoveType=Aの1P → MoveType=Aの2P →(MoveType=Aの3P)→(続) (続)(MoveType=Aの4P)→ 以降IDの若い順にMovetype=Aのユニット→(続) (続) MoveType!=Aの1P → MoveType!=Aの2P → Movetype!=Aの3P →(続) (続)(MoveType!=Aの4P)→ 以降IDの若い順にMovetype!=Aのユニット,→(続) (続) 攻撃などの判定処理など(多分ここ) →(次のフレームへ) という感じ、だと思います。 なのでMoveType=Aの相手が判定と同時にHitDefAttrを出した場合なら、 自身がMoveType=Aでなければ確実HitDefAttrを同じフレームで認識できます。 ( 同じMovetype=Aの場合は2P側のみですが、 Movetype=Aで自由に動けるキャラは稀です。 ) と言っても猶予フレームは1Fであるため、 対処させるためには超反応のような処理をさせなければなりませんが、 HitDefを最初から出している技に対しても反応してしまう可能性があるので注意。 使うなら(HitDefAttr = xx,xx)&&(Time > xx)といった条件にしましょう。 >GetHitVar()の話 GetHitVarは「くらい状態の情報」です。 リダイレクトを利用すれば、相手のくらい状態の情報を確認できます。 ■基本 Trigger1 = EnemyNear,GetHitVar(HitShakeTime) ;PauseTimeの硬直時間 Trigger1 = EnemyNear,GetHitVar(HitTime) ;ヒット硬直時間 Trigger1 = EnemyNear,GetHitVar(HitShakeTime)+EnemyNear,GetHitVar(HitTime);合計 ※自分側も硬直中から動こうとすると自分の硬直も計算する必要があります。注意。 ※ガード中はHitTimeよりも速くCtrlTimeで動けるようになるので要注意。 ■ガード系 Trigger1 = EnemyNear,GetHitVar(SlifeTime)-EnemyNear,Time ;ガードスライド時間 Trigger1 = EnemyNear,GetHitVar(CtrlTime)-EnemyNear,Time ;ガード硬直時間 ※-Timeなので注意。Timeに*EnemyNear,HitShakeOverをつけたほうがいいかも。 ※HitShakeOver;PauseTimeが終了したかどうか ※必ず、EnemyNear,StateNo=[150,159]などと併用すること。 硬直時間を計算すれば硬直に対する追撃の汎用記述を作れます。 例:空中攻撃→着地追撃の条件。ガード硬直に対する追撃条件など。 ■特殊・使う場合注意の必要な記述 Trigger1 = EnemyNear,GetHitVar(Fall.recover) = 0 ;浮いた相手が確実に倒れるか ※HitDefなどで設定されたFall.Recoverが0の場合のみ0を返すため、 「長いFall.RecoverTimeにより受身ができず確実に倒れる」場合は感知できない。 ※ある程度感知するにはEnemyNear,HitShakeOver=1などから時間をVerで計測し、 EnemyNear,GetHitVar(fall.recovertime)-Var(x)などで計算する必要がある。 Trigger1 = EnemyNear,GetHitVar(HitCount) ;※相手のヒット回数 ※画面表示のヒット数ではなく、当人が受けたヒット回数 ※ただしコンボの後、ガードさせ〜硬直中にガードを崩すと、 HitCountがリセットされず、直前のコンボ分の回数が残るため要注意。 |