Unity ベルトコンベアをつくる

#作るもの

* 物体が上に載ると設定した方向に物体を移動させるベルトコンベア

![v1.gif](https://qiita-image-store.s3.amazonaws.com/0/47146/15931d84-388b-21b7-edbe-703b3c25f0c7.gif)


#作り方

## 1. ベルトコンベアのスクリプトを作成する

```csharp:Conveyor.cs
using System.Collections.Generic;
using UnityEngine;

namespace ConveyorSamples
{
    public class Conveyor : MonoBehaviour
    {
        /// <summary>
        /// ベルトコンベアの稼働状況
        /// </summary>
        public bool IsOn = false;

        /// <summary>
        /// ベルトコンベアの設定速度
        /// </summary>
        public float TargetDriveSpeed = 3.0f;

        /// <summary>
        /// 現在のベルトコンベアの速度
        /// </summary>
        public float CurrentSpeed { get { return _currentSpeed; } }

        /// <summary>
        /// ベルトコンベアが物体を動かす方向
        /// </summary>
        public Vector3 DriveDirection = Vector3.forward;

        /// <summary>
        /// コンベアが物体を押す力（加速力）
        /// </summary>
        [SerializeField] private float _forcePower = 50f;

        private float _currentSpeed = 0;
        private List<Rigidbody> _rigidbodies = new List<Rigidbody>();

        void Start()
        {
            //方向は正規化しておく
            DriveDirection = DriveDirection.normalized;
        }

        void FixedUpdate()
        {
            _currentSpeed = IsOn ? TargetDriveSpeed : 0;

            //消滅したオブジェクトは除去する
            _rigidbodies.RemoveAll(r => r == null);

            foreach (var r in _rigidbodies)
            {
                //物体の移動速度のベルトコンベア方向の成分だけを取り出す
                var objectSpeed = Vector3.Dot(r.velocity, DriveDirection);

                //目標値以下なら加速する
                if (objectSpeed < Mathf.Abs(TargetDriveSpeed))
                {
                    r.AddForce(DriveDirection * _forcePower, ForceMode.Acceleration);
                }
            }
        }

        void OnCollisionEnter(Collision collision)
        {
            var rigidBody = collision.gameObject.GetComponent<Rigidbody>();
            _rigidbodies.Add(rigidBody);
        }

        void OnCollisionExit(Collision collision)
        {
            var rigidBody = collision.gameObject.GetComponent<Rigidbody>();
            _rigidbodies.Remove(rigidBody);
        }
    }
}

```

##2. ベルトコンベアになるオブジェクトを作る

適当にCubeやPlaneで良いのでColliderが付いたオブジェクトを作って配置する

![1.png](https://qiita-image-store.s3.amazonaws.com/0/47146/d80b15d7-8f76-97b5-1697-546bcd447617.png)

##3. コンベア用のスクリプトを貼り付けてパラメータを設定する

![2.png](https://qiita-image-store.s3.amazonaws.com/0/47146/c542527a-ae1e-e8aa-dcf4-f6d0d42e9de0.png)

`IsOn`:ベルトコンベアの稼動状態
`TargetDriveSpeed`:ベルトコンベアの速度（ベルトコンベア上の物体が到達する最高速度）
`ForcePower`: ベルトコンベアが物体を押す力
`DriveDirection`: ベルトコンベアの進行方向

今回は画面右（x+方向）に移動させたいのでDriveDirectionに(1,0,0)を指定

##4.コンベアの摩擦係数を調整する

`PhysicsMaterial`を作成してベルトコンベアに設定する
できるだけ摩擦を小さめにしておくと良い。
![3.png](https://qiita-image-store.s3.amazonaws.com/0/47146/3c3ee406-5400-f9e0-c9fe-846f8400adab.png)
(今回は静止摩擦係数を0.25、動摩擦係数を0.2に設定)

##5.動作確認

あとはベルトコンベアの上にRigidBodyをつけたオブジェクトを配置して動作確認をする。
挙動が気に食わない場合は、摩擦の大きさとConveyorスクリプトの`ForcePower`を調整すること。

#解説

単純にコンベアに触れているRigidBodyをリストに保存しておいて、ベルトコンベアの速度に達するまで力を加え続けるだけの実装です。
簡単な実装の割に、ちゃんと物理演算に即した動きになってくれます。

 
![v2.gif](https://qiita-image-store.s3.amazonaws.com/0/47146/889e074e-ed2c-388a-75f5-ea54b3c06d0d.gif)
(コンベアの途中に壁があると進行方向が変わる & ベルトコンベアが詰まって流れなくなる)

![v3.gif](https://qiita-image-store.s3.amazonaws.com/0/47146/4655e220-4146-0401-1522-2c4501a130c5.gif)
(斜め置きしたり、ベルトコンベアを連鎖させたりもできる)

#拡張

エディタ拡張で次のようなものを用意しておくと、ベルトコンベアの進行方向をGameObjectのForward方向に一致させることができるようになるので設定が楽になる。

```csharp:ConbeyorEditor.cs
using UnityEditor;
using UnityEngine;

namespace ConveyorSamples
{
    [CustomEditor(typeof(Conveyor))]
    public class ConbeyorEditor : UnityEditor.Editor
    {

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            if (GUILayout.Button("Follow Direction To Transfrom"))
            {
                var t = target as Conveyor;
                t.DriveDirection = t.transform.forward.normalized;
            }
            serializedObject.ApplyModifiedProperties();

        }
    }
}
```

![5.png](https://qiita-image-store.s3.amazonaws.com/0/47146/01826fce-95a1-a0e7-7ce4-de8534849213.png)


#この方法の問題点

この方法は単純にベルトコンベア上のオブジェクトの「重心」に対して力をかけてしまっている。
そのため、大きな物体を置いた時にオブジェクトの動きに違和感がでてしまうことがある。

![v4.gif](https://qiita-image-store.s3.amazonaws.com/0/47146/5c3a0a6c-fbd0-ec4b-4d8a-339f748a4d6a.gif)
(本来この棒はベルトコンベアから受けたトルクによって左側へ回転運動する（倒れる）の自然である)

解決策としては`AddForce`ではなく`AddForceAtPosition`を使い、実際にオブジェクトとベルトコンベアが触れている接点に対して力を加えてしまえばよい。ただ接点の相対位置を求めるのが面倒くさいので今回はパスした。なので小さいオブジェクトしか動かさないのであればこの手法で良いと思う。

#その他

RedererのTexutreのOffsetをスクリプトから書き換えるようにすればコンベアのアニメーションも作れるはず