State Machine Behaviour のやり方～Unity日記#2

#初めに

皆さんこんばんは！今回はState Machine Behaviourですー！

#
皆さんゲームを作るにあたって必要不可欠なAIってどうやって組んでいますか～？

・大抵はSwichやifよるFSM(Finite State Machine)で状態遷移などがメジャーですよね!(^^)!　
　(ifはあんまりないですが.....そういうやり方として！)

　*FSMは有限ステートマシーンと言います。とある状態を複数持ち何かしらのトリガー（きっかけ）によって別の状態に遷移する事をいいます！！
　
・FSMのメリット
　・コードの実装が容易！
　・メンテナンスがしやすい　（ステートごとで管理を行っているので、バグ発生した場所が簡単見つけることが出来ます！）
　　
・FSMのデメリット
　・状態が増えればメンテナンスがしにくい
　・不具合が修正しにくい

　　メリットがデメリットになる場合があります。特にSate依存の処理の時などが起こりやすいです。

　・因みにゲームを作っている時など現在の状態が終了した時,特定のアニメーションの時間に対して何かしたい時ってありますよね！！
　　FSMでも出来ない事は無いのですが実装は少々めんどくさいのです。
　　何故なら今どのアニメーションが再生されているか？という情報をスクリプトから取得する必要があるためです。
　　ですがデメリットとこれを簡単に実装する方法がSMBです。

#State Machine Behaviour

・SMB(State Machine Behaviour)といいAnimatorの状態遷移に合わせてCallbackしてくれるものです。
　
・SMBはAnimatorを依存とするので状態が増えてもメンテナンスがしやすいですし特定のタイミングで処理を実行する事が出来ます！！


ざっくりと説明しましたが実際にやってみましょうー！！


１．AnimatorContllorを選択しStateをクリックしAdd　Behaviorをクリックしてください！
２．押したらnew Scriptと出るのでクリックし名前を決めてください。


![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1038675/7094cd62-cb89-5661-dfff-c4fc4a27b2a0.png)
３．Asseteｓフォルダーに作ったScrpitが作成されているのでクリック
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1038675/80d88497-412c-e36a-8d5c-2ad207376543.png)

4.クリックしたらこんな感じのコードが書かれているかと思います！！皆さんはコメント含め全部英語表記ですが解説のため日本語を入れています

```
    // Stateに入った時に一度だけ実効されます。Start関数みたいなものです。
    //override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    
    //}

    //Update関数です。
    //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    
    //}

    // stateが終了、ほかのstateに移動するとき一回だけ呼ばれます。
    //override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    
    //}

    // RootMotionに関する処理を書く関数です
    //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that processes and affects root motion
    //}

    // IKに関する処理を書く関数です
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that sets up animation IK (inverse kinematics)
    //}
```
5.Add　Behaviorをクリックし先ほど作ったScriptをクリックしてアタッチしてください！D&Dでも大丈夫です。これで準備完了です。

##やり方


・準備が完了したらBaseになるコードを組んでいきましょう！

```
//Base
public class Enemy_Behaviour : MonoBehaviour
{　
    [SerializeField]
    float SerchArea;　　　//捜索距離
    void SerchPlayer()
    {　　
        //プレイヤーの情報を取得
        var Player = playerManager.GetPlayer();

        //敵とプレイヤーの位置を取得
        Vector3 vec= Player.transform.position - transform.position;

        //一定距離以内でプレイヤーを探す
        if (vec.sqrMagnitude < SerchArea * SerchArea)
        {
            Debug.Log("Player:発見!!");
        }
        else
        {
            Debug.Log("Player:居ない!!");
        }
    }
}
 ```
　・普通にC#Scriptを作りプレイヤーを捜索するコードを書いてみました。あとはさっき作ったSMBのスクリプトで呼んであげましょう！！
　・BaseになるスクリプトをPrefubにアタッチするのを忘れないで下さいね！！

```
public class SMB_Idle : StateMachineBehaviour
{
    //Enemy_Behaviourクラスの実体
    private Enemy_Behaviour behavior;

    public sealed override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {　
　　　　//Enemy_Behaviourを取得
        behavior = animator.GetComponent<Enemy_Behaviour>();
    }

    public sealed override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {  
      //捜索関数を呼び出す
      behavior.SerchPlayer();
    }
　　　
    public sealed override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {  
　　　　//IdleStateが抜けた時に処理を書きたい場合ここに書いてください
    }
}
```

・こんな感じで呼び出せます。これでIdle時だけプレイヤーを捜索するみたいな機能が実装できました！
#注意
　因みにですがSMBスクリプトを張り付けている＆その状態に入った時のStateに対してしか有効なので状態がAttackに遷移すると何も起きません。
　ですのでまた新しくAttack用のSMBスクリプトを作り、アタッチしなければなりませんので注意してください。下みたいな感じで！！

・状態がAttackの時Attack＿SMBスクリプトが有効、Idle＿SMBは実行されません。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1038675/9a84b752-3a21-23c6-d193-e819d22f40e8.png)
・状態がIdleの時Idle＿SMBスクリプトが有効、Attack＿SMBは実行されません。
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1038675/c16ccb1b-ed5b-854e-3b4b-f420e976c3ce.png)
以上です。お疲れ様でした！
#さいごに！

・State依存になる場合はSMBを使った方が楽というお話ですた！！
　因みにSwichが悪とか使わない方がいいという記事ではありませんで宜しくお願い致します！！

特定のアニメーションで特定のタイミングで処理したい場合の処理を書いておきます！！（書くの忘れてました）

```
MonoBehaviourを継承してる場合
 Animator anim;

void Start()
{
  anim=GetCompoment<Animator>();
}
void Update()
{
 //現在再生されているアニメーションがタグ名かつ特定のタイミングが来たら。長さの最大値は1.0fです（1.0の時の長さが最後の時です。半分が0.5ｆです）
 if(anim.GetCurrentAnimatorStateInfo(0).IsTag("ＴＡＧ名")&&
　　anim.GetCurrentAnimatorStateInfo(0).normalizedTime >= 長さ（タイミング）)
 {
    
 }


 //現在再生されているアニメーションが名前かつ特定のタイミングが来たら。長さの最大値は1.0fです（1.0の時の長さが最後の時です。半分が0.5ｆです）
 if(anim.GetCurrentAnimatorStateInfo(0).IsName("名前")&&
　　anim.GetCurrentAnimatorStateInfo(0).normalizedTime >= 長さ（タイミング）)
 {
    
 }
}

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
　StateMachineBehaviourの場合

  public sealed override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
  { 
    現在再生されている時間が特定のタイミングが来たら。
    if(stateInfo.normalizedTime>=長さ）
    {
     
    }
  } 
　　　
```