Arborを使ったスクリプティング
ArborはUnityでのビジュアルプログラミングを提供してくれる非常に便利なツールです。 現在バージョン3が発売されています。 私はバージョン2のArborを持っていたのですが、バージョン3になって機能が増えたらしく、セールが来たタイミングで購入しました。
ArborにはさまざまなBehaiver(挙動)が用意されています。 演算ノードも加わり、用意されているものだけでも十分にゲームを創ることが可能です。 ですが、ユーザのスクリプティングを視野に入れたツールのため、たまにかゆいところに手が届かないときもあります。 例えば、マウスの位置を取得する演算ノードがなかったり、TweenをWorldl座標で行う挙動がなかったりします。 そういうときには、自分で演算ノードや挙動を用意する必要があります。
ビジュアルプログラミングのツールを使ってるのに、スクリプトはあまり書きたくないと思われる方は多いでしょう。 僕もその口です。 ですが、用意されたものだけで理想の動作を目指すよりも、スクリプトを組んだ方が往々にして早いです。 理由は単純で、コピペでスクリプトが組めるからです。
Arborでは、用意されている挙動や演算ノードのスクリプトを読むことができます。 以下のスクリプトはTweenPositionのものです。
using UnityEngine; using UnityEngine.Serialization; using System.Collections; namespace Arbor.StateMachine.StateBehaviours { #if ARBOR_DOC_JA /// <summary> /// 座標を徐々に変化させる。 /// </summary> #else /// <summary> /// Gradually change position. /// </summary> #endif [AddComponentMenu("")] [AddBehaviourMenu("Tween/TweenPosition")] [BuiltInBehaviour] public sealed class TweenPosition : TweenBase, INodeBehaviourSerializationCallbackReceiver { #region Serialize fields #if ARBOR_DOC_JA /// <summary> /// 対象となるTransform。<br/> /// TypeがConstantの時に指定しない場合、ArborFSMがアタッチされているGameObjectのTransformとなる。 /// </summary> #else /// <summary> /// Transform of interest.<br/> /// If Type is Constant and nothing is specified, ArborFSM is the Transform of the attached GameObject. /// </summary> #endif [SerializeField] private FlexibleTransform _Target = new FlexibleTransform(); #if ARBOR_DOC_JA /// <summary> /// 開始した状態からの相対的な変化かどうか。 /// </summary> #else /// <summary> /// Whether the relative change from the start state. /// </summary> #endif [SerializeField] private bool _Relative = false; #if ARBOR_DOC_JA /// <summary> /// 開始地点。 /// </summary> #else /// <summary> /// Starting point. /// </summary> #endif [SerializeField] private FlexibleVector3 _From = new FlexibleVector3(); #if ARBOR_DOC_JA /// <summary> /// 目標地点。 /// </summary> #else /// <summary> /// Target point. /// </summary> #endif [SerializeField] private FlexibleVector3 _To = new FlexibleVector3(); [SerializeField] [HideInInspector] private int _SerializeVersion; #region old [SerializeField, FormerlySerializedAs( "_Target" )] [HideInInspector] private Transform _OldTarget = null; [SerializeField, FormerlySerializedAs( "_From" )] [HideInInspector] private Vector3 _OldFrom = Vector3.zero; [SerializeField, FormerlySerializedAs( "_To" )] [HideInInspector] private Vector3 _OldTo = Vector3.zero; #endregion // old #endregion // Serialize fields Transform _MyTransform; Transform cachedTarget { get { Transform transform = _Target.value; if (transform == null && _Target.type == FlexibleTransform.Type.Constant ) { if( _MyTransform == null ) { _MyTransform = GetComponent<Transform>(); } transform = _MyTransform; } return transform; } } void SerializeVer1() { _Target = (FlexibleTransform)_OldTarget; _From = (FlexibleVector3)_OldFrom; _To = (FlexibleVector3)_OldTo; } void INodeBehaviourSerializationCallbackReceiver.OnBeforeSerialize() { if (_SerializeVersion == 0) { SerializeVer1(); _SerializeVersion = 1; } } void INodeBehaviourSerializationCallbackReceiver.OnAfterDeserialize() { if (_SerializeVersion == 0) { SerializeVer1(); _SerializeVersion = 1; } } Vector3 _StartPosition; protected override void OnTweenBegin() { Transform target = cachedTarget; if (_Relative && target != null) { _StartPosition = target.localPosition; } else { _StartPosition = Vector3.zero; } } protected override void OnTweenUpdate(float factor) { Transform target = cachedTarget; if (target != null) { target.localPosition = _StartPosition + Vector3.Lerp(_From.value, _To.value, factor); } } } }
なっが。
このスクリプトをコピペしてWorld座標でTweenするスクリプトを作ります。
元のスクリプトは長いですが、変更点は3つだけです。
[AddBehaviourMenu("Tween/TweenPosition")]
の中身とクラス名を任意のものに変更し、localPositionと書かれている部分をpositionに書き換えるだけで、目的のものができあがります。
具体的な変更点を以下に挙げます。
[AddBehaviourMenu("Tween/TweenPosition")]
を[AddBehaviourMenu("MyScripts/Tween/TweenWorldPosition")]
に- クラス名
TweenPosition
をTweenWorldPosition
に localPosition
をposition
で置き換え
これらの修正をしたスクリプトが以下のものです。
using UnityEngine; using UnityEngine.Serialization; using System.Collections; namespace Arbor.StateMachine.StateBehaviours { #if ARBOR_DOC_JA /// <summary> /// 座標を徐々に変化させる。 /// </summary> #else /// <summary> /// Gradually change world position. /// </summary> #endif [AddComponentMenu("")] [AddBehaviourMenu("MyScripts/Tween/TweenWorldPosition")] [BuiltInBehaviour] public sealed class TweenWorldPosition : TweenBase, INodeBehaviourSerializationCallbackReceiver { #region Serialize fields #if ARBOR_DOC_JA /// <summary> /// 対象となるTransform。<br/> /// TypeがConstantの時に指定しない場合、ArborFSMがアタッチされているGameObjectのTransformとなる。 /// </summary> #else /// <summary> /// Transform of interest.<br/> /// If Type is Constant and nothing is specified, ArborFSM is the Transform of the attached GameObject. /// </summary> #endif [SerializeField] private FlexibleTransform _Target = new FlexibleTransform(); #if ARBOR_DOC_JA /// <summary> /// 開始した状態からの相対的な変化かどうか。 /// </summary> #else /// <summary> /// Whether the relative change from the start state. /// </summary> #endif [SerializeField] private bool _Relative = false; #if ARBOR_DOC_JA /// <summary> /// 開始地点。 /// </summary> #else /// <summary> /// Starting point. /// </summary> #endif [SerializeField] private FlexibleVector3 _From = new FlexibleVector3(); #if ARBOR_DOC_JA /// <summary> /// 目標地点。 /// </summary> #else /// <summary> /// Target point. /// </summary> #endif [SerializeField] private FlexibleVector3 _To = new FlexibleVector3(); [SerializeField] [HideInInspector] private int _SerializeVersion; #region old [SerializeField, FormerlySerializedAs( "_Target" )] [HideInInspector] private Transform _OldTarget = null; [SerializeField, FormerlySerializedAs( "_From" )] [HideInInspector] private Vector3 _OldFrom = Vector3.zero; [SerializeField, FormerlySerializedAs( "_To" )] [HideInInspector] private Vector3 _OldTo = Vector3.zero; #endregion // old #endregion // Serialize fields Transform _MyTransform; Transform cachedTarget { get { Transform transform = _Target.value; if (transform == null && _Target.type == FlexibleTransform.Type.Constant ) { if( _MyTransform == null ) { _MyTransform = GetComponent<Transform>(); } transform = _MyTransform; } return transform; } } void SerializeVer1() { _Target = (FlexibleTransform)_OldTarget; _From = (FlexibleVector3)_OldFrom; _To = (FlexibleVector3)_OldTo; } void INodeBehaviourSerializationCallbackReceiver.OnBeforeSerialize() { if (_SerializeVersion == 0) { SerializeVer1(); _SerializeVersion = 1; } } void INodeBehaviourSerializationCallbackReceiver.OnAfterDeserialize() { if (_SerializeVersion == 0) { SerializeVer1(); _SerializeVersion = 1; } } Vector3 _StartPosition; protected override void OnTweenBegin() { Transform target = cachedTarget; if (_Relative && target != null) { _StartPosition = target.position; } else { _StartPosition = Vector3.zero; } } protected override void OnTweenUpdate(float factor) { Transform target = cachedTarget; if (target != null) { target.position = _StartPosition + Vector3.Lerp(_From.value, _To.value, factor); } } } }
長すぎて変更点がわからない?それだけ変更点が少ないということです。
一方で、短いスクリプトもあります。 マウスカーソルのWorld座標を取得する演算ノードを自作すると50行も行きません。
using UnityEngine; using System.Collections; namespace Arbor { #if ARBOR_DOC_JA /// <summary> /// マウスの位置を取得する /// </summary> #else /// <summary> /// Get mouse Position /// </summary> #endif [AddComponentMenu("")] [AddCalculatorMenu("MyScripts/Vectior3/MousePosition")] [BuiltInBehaviour] public sealed class MousePosition : Calculator { #region Serialize fields #if ARBOR_DOC_JA /// <summary> /// マウスの位置 /// </summary> #else /// <summary> /// mouse position /// </summary> #endif [SerializeField] private OutputSlotVector3 _Result = new OutputSlotVector3(); #endregion public override void OnCalculate() { Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); _Result.SetValue(mousePosition); } } }
このスクリプトでは、ScreenToWorldPointで得たマウスカーソルのWorld座標を出力変数に設定しています。 スクリプトが短い理由は明白で、行うことが少ないからです。 このスクリプトも、適当な演算ノードのスクリプトをコピペして作っています。 コピペするだけでスクリプトが書けるのでArborは思った以上に敷居が低いです。
所感ですが、値段や日本産という点を考えると、PlaymakerよりもArborを買うほうが良いです。 私は、Playmakerは使いこなせませんが、Arborはある程度使えます。 Unityでビジュアルプログラミングをしたい皆さん、せっかくなのでArborを買いましょう。
わたしArborちょっとつかえる。