オリジナルのTaskを作成する
自作のActionと、ConDitionalを作成してTaskとして追加し、オリジナルのAIを組んでみます。
公式のサンプルを元に視界内にTargetが入る(Conditional)と、Targetに向かって進む(Action)Aiを作成。
自作Action
まず下準備として以下のnamespaceを追加します
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
そして、Actionクラスを継承する形で自作のActionを作成します。
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
public class WithinSight : Conditional
{
}
Conditionalクラスを確認してみるとTaskクラスを継承しています。
中身を見るとわかるのですが、かなりUnityのスクリプトライフサイクルに似た形で実装されています。
基本的にはOnAwake,OnStart,OnUpdate,OnEndといって仮想メソッドをoverrideして自前のTaskを作ることになります。
スクリプト解説
公式のサンプルを参考にし、最終的に以下のような実装になりました。
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のプロパティに設定することができます。
Name:Target,Type:GameObjectのGlobal Variableを作成
先ほどのGlobal Variableな変数をSharedGameObjectに設定
次に、OnUpdate
をoverrideしています。
OnUpdateはTaskStatusを返すメソッドで、Inactive,Failure,Success,Runningを返すことができます。
今回は視野角内にTargetがいた時にSuccenssを返し、いなかった時はFailureを返しています。
こうすることでSequenceを使用した際に、Successの時のみ次のTaskを実行させることができます。
自作Conditional
次に、Conditionalを作っていきます。
Actionと同様にnamespaceを追加し、今度はConditionalクラスを継承します。
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
public class MoveTowards : Action
{
}
Actionクラスと同じようにTaskクラスを継承しているので基本的なスクリプトライフサイクルは一緒です。
スクリプト解説
Actionタスクは以下のような実装になりました。
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は以下のようになりました。
実際に動かすとこんな感じ
WithinSight.cs
内のOnUpdate
でFailureを返している箇所をRunningに変更することで、視野角内にTargetが入った瞬間に追跡するといった挙動に変更することができます。
まとめ
このようにBehavior DesignerのActionクラス,Conditionalクラスを継承することで簡単に自作のTaskを追加することができました。