Behavior Designerで超簡単なAIを作る回 (Day2)

# オリジナルのTaskを作成する
自作の**Action**と、**ConDitional**を作成してTaskとして追加し、オリジナルのAIを組んでみます。
公式のサンプルを元に視界内にTargetが入る(Conditional)と、Targetに向かって進む(Action)Aiを作成。

## 自作Action
まず下準備として以下のnamespaceを追加します

```csharp

using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
```

そして、Actionクラスを継承する形で自作のActionを作成します。

```csharp
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class WithinSight : Conditional
{

}
```

Conditionalクラスを確認してみるとTaskクラスを継承しています。
中身を見るとわかるのですが、かなりUnityのスクリプトライフサイクルに似た形で実装されています。

![TaskFlowChart.png](https://qiita-image-store.s3.amazonaws.com/0/82250/04490384-f4dc-094d-b278-ce78831747b5.png)

基本的にはOnAwake,OnStart,OnUpdate,OnEndといって仮想メソッドをoverrideして自前のTaskを作ることになります。

### スクリプト解説

公式のサンプルを参考にし、最終的に以下のような実装になりました。

```csharp:WithinSight.cs
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class WithinSight : Conditional
{
    [SerializeField]
    private float _fieldOfViewAngle;
    [SerializeField]
    private string _targetTag;
    [SerializeField]
    private SharedGameObject _targetGameObject;

    private Transform[] possibleTargets;

    public override void OnAwake()
    {
        var targets = GameObject.FindGameObjectsWithTag(_targetTag);
        possibleTargets = new Transform[targets.Length];
        for(int i = 0; i < targets.Length; i++)
        {
            possibleTargets[i] = targets[i].transform;
        }
    }

    public override TaskStatus OnUpdate()
    {
        for(int i = 0; i < possibleTargets.Length; i++)
        {
            if(CheckWithinSight(possibleTargets[i], _fieldOfViewAngle))
            {
                _targetGameObject.Value = possibleTargets[i].gameObject;
                return TaskStatus.Success;
            }
        }
        return TaskStatus.Failure;
    }

    public bool CheckWithinSight(Transform targetTransform, float fieldOfViewAngle)
    {
        Vector3 direction = targetTransform.position - transform.position;
        return Vector3.Angle(direction, transform.forward) < fieldOfViewAngle;
    }
}
```

`SharedGameObject`変数は、Behaviour Designer独自の型で、publicな変数のような形でどのTaskからでも参照できるようにする際に使用します。
例えば、今回のように`ShadredGameObject`型を使うことによって、一度TargetとなるGameObjectをEditor上でInspectorに設定すればその変数を使いまわす形で各Taskのプロパティに設定することができます。

![SnapCrab_NoName_2017-8-13_3-9-32_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/82250/3de08046-9932-7709-efae-8d78abaf4e78.png)
Name:Target,Type:GameObjectの**Global Variable**を作成

![SnapCrab_NoName_2017-8-13_3-10-22_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/82250/c446ad47-e454-4c26-3cd4-d46e55515545.png)
先ほどのGlobal Variableな変数を**SharedGameObject**に設定

次に、`OnUpdate`をoverrideしています。
OnUpdateはTaskStatusを返すメソッドで、Inactive,Failure,Success,Runningを返すことができます。
今回は視野角内にTargetがいた時に**Succenss**を返し、いなかった時は**Failure**を返しています。
こうすることでSequenceを使用した際に、Successの時のみ次のTaskを実行させることができます。

## 自作Conditional

次に、Conditionalを作っていきます。
Actionと同様にnamespaceを追加し、今度はConditionalクラスを継承します。

```csharp
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class MoveTowards : Action
{

}
```

Actionクラスと同じようにTaskクラスを継承しているので基本的なスクリプトライフサイクルは一緒です。

### スクリプト解説

Actionタスクは以下のような実装になりました。

```csharp
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class MoveTowards : Action
{
    [SerializeField]
    private float _speed = 0;
    [SerializeField]
    private SharedGameObject _targetGameObject;

    public override TaskStatus OnUpdate()
    {
        if(Vector3.SqrMagnitude(transform.position - _targetGameObject.Value.transform.position) < 0.1f)
        {
            return TaskStatus.Success;
        }
        transform.position = Vector3.MoveTowards(transform.position, _targetGameObject.Value.transform.position, _speed * Time.deltaTime);
        transform.LookAt(_targetGameObject.Value.transform);
        return TaskStatus.Running;
    }
}
```

先ほどとは違い、`OnUpdate`では自分自身と、Targetとの距離の2乗が0.1より小さくなった時に**Success**を返すことでそのSequenceを正常終了します。
また、0.1より大きいときはまだ追跡中と見なしてRunningを返します。
そうすることでTargetに接触するまで追いかけ続けます。

`MoveTowards.cs`内でも`SharedGameObject`を使用していますが、これを使うことで意図せずに`WithinSight.cs`と異なったGameObjectをTargetにすることを防ぐことができます。

# 動作確認

Treeは以下のようになりました。

![SnapCrab_NoName_2017-8-13_3-33-18_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/82250/74353046-215e-ca25-e50d-3dd886cb709e.png)

実際に動かすとこんな感じ

![SimpleNavMesh3.gif](https://qiita-image-store.s3.amazonaws.com/0/82250/a1d9b6ec-ba52-de57-1ed3-30013200f46e.gif)

`WithinSight.cs`内の`OnUpdate`で**Failure**を返している箇所を**Running**に変更することで、視野角内にTargetが入った瞬間に追跡するといった挙動に変更することができます。

![SimpleNavMesh4.gif](https://qiita-image-store.s3.amazonaws.com/0/82250/10099cc8-e82a-f722-815a-9c96fffee5db.gif)



# まとめ
このようにBehavior Designerの*Action*クラス,*Conditional*クラスを継承することで簡単に自作のTaskを追加することができました。