[Unity初心者Tips]どれが良いかわかる！ものを動かす方法はこうして決める

Unityでサンプル通りにやってみてから、「さて、自分でいっちょ作ってみるか！」と思い立つも、壁になるのが物体を動かす方法です。どう「動かしたいのか」で、実装する方法は変わります。以下を参考に、何をしたら良いか、考えてみてください。

- 実験！
- [方法:one::瞬間移動で物理を無視するTranslate](https://qiita.com/JunShimura/items/ab243cbd29e63e4f27c5#%E6%96%B9%E6%B3%951%E7%9E%AC%E9%96%93%E7%A7%BB%E5%8B%95%E3%81%A7%E7%89%A9%E7%90%86%E3%82%92%E7%84%A1%E8%A6%96%E3%81%99%E3%82%8Btranslate)
- [方法:two::isKinematicとMovePositionでゴリ押し](https://qiita.com/JunShimura/items/ab243cbd29e63e4f27c5#%E6%96%B9%E6%B3%952iskinematic%E3%81%A8moveposition%E3%81%A7%E3%82%B4%E3%83%AA%E6%8A%BC%E3%81%97)
- [方法:three::ぶつかってぶつかられて動くAddForce](https://qiita.com/JunShimura/items/ab243cbd29e63e4f27c5#%E6%96%B9%E6%B3%953%E3%81%B6%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%A6%E3%81%B6%E3%81%A4%E3%81%8B%E3%82%89%E3%82%8C%E3%81%A6%E5%8B%95%E3%81%8Faddforce)

# 方法:one:：瞬間移動で物理を無視するTranslate
これが一番、単純な方法ですが、場所が瞬間で移動するワープです、途中の動きはありません。

## 実装方法
以下はどれも同じになります。

### ```transform.position```に代入する
例）現在位置から(1,2,3)の位置へ場所を移す

```c#:SubstituteTransformPosition
transform.position=transform.position +new Vector3(1,2,3);
```
[スクリプトリファレンス:Transform.position](https://docs.unity3d.com/ja/current/ScriptReference/Transform-position.html)

### `transform.translate`を用いる 
上記と全く同じ意味を持ちます。
例）現在位置から(1,2,3)の位置へ場所を移す

```c#:Transform.Translate
transform.Translate(1,2,3);
```

[スクリプトリファレンス:Transform.Translate](https://docs.unity3d.com/ja/current/ScriptReference/Transform.Translate.html)

### Animationで動かす
標準の```Animator```と```Animation```を使ったものは、こちらとほぼ同様になります。

## ```position```への代入を使ってみたサンプル
これを使うと、一瞬でその位置に移動します。そのため、物体の当たり判定などを考慮すると、適切ではない状態になりやすいです。
例として、これを使って往復運動するものを用意しました。

```c#:Turn.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Turn : MonoBehaviour
{

    [Header("かかる時間"), Range(0.0625f, 30)]
    public float duration = 1.0f;
    [Header("移動する距離"), Range(0, 30)]
    [SerializeField] private float _distance;

    public float distance
    {
        set
        {
            _distance = value;
            targetPosition = GetTargetPosition(value);
        }
        get
        {
            return _distance;
        }

    }
 
    float pastTime = 0;
    Vector3 homePosition;
    Vector3 targetPosition;

    private Vector3 GetTargetPosition(float distance)
    {
        return transform.localPosition + transform.forward * distance;
    }
    // Use this for initialization
    private void Awake()
    {
        homePosition = transform.localPosition;
        targetPosition = GetTargetPosition(distance);
    }

    // Update is called once per frame
    void Update()
    {
        pastTime += Time.deltaTime;
        Vector3 newPosition = Vector3.Lerp(homePosition, targetPosition, Mathf.PingPong(pastTime, duration) / duration);
        transform.localPosition = newPosition;
    }

}
```
このScriptは、変数```duration```に格納された時間（秒数）で、```distance```に格納された距離を移動し、往復を繰り返します。
利用しているClassなどはそれぞれ、リファレンスで参照してください。
これを用いたSceneのサンプルがこちらになります。
これを移動して見える大きなcubeにアタッチしています。さらに、周囲のおおきなCubeと小さなCubeは共通で以下がアタッチされています。

-  ```BoxCollider```
-  ```Rigidbody```:```Collision Detection=Continuous Dynamic```にしてできるだけ当たり判定をする設定


[![Image from Gyazo](https://i.gyazo.com/fa5a917099485bf2a58b9200dc8bcff5.gif)](https://gyazo.com/fa5a917099485bf2a58b9200dc8bcff5)

見ると判る通り、ポリゴンが喰いこんで物理的にあり得ない状況が生まれています。Colliderが瞬間で移動するため、当たった瞬間が検知されず、内包を許しています。こうした状況を生むため、Colliderを持った物体には注意が必要です。

## 使い途は、Colliderが関係ないリスポーン処理やGUIに

考えられる使い途は、Colliderを持たないもの、持っていても```Layer Collision Matrix```で無効になっていたり、周囲に何もないのが明らかな空間へ移動する場合に適しています。例えば、以下のようなとき。

- Canvas等で表示するだけのGUIで、```Collider```を持たない```Text```などを空間に表示する。
- 壁も通り抜けちゃうお化けの```GameObject```
- リスポーンなどで一旦、画面から消えてリスポーン地点に戻す（周囲に何もない）
- 再利用するためにカメラから一旦、見えないところに移動して待機しているNPC（処理が重い時によく使うテクニックです）

など、色々あります。基本的に物理を無視するので、```Rigidbody```が必要なものに使う場合は、```Collider```が干渉しないか調べて問題ないなら使う、ということになります。逆に、リスポーンでリスポーン地点に戻るときにこれじゃない方法を用いると、途中の障害物にぶつかって戻れない、なんていうことになりますからね！

# 方法:two::isKinematicとMovePositionでゴリ押し

上記の方法と同じで指定位置へ確実に移動しますが、物理挙動を周囲に与えることができます。

## 実装方法

以下をそれぞれ、対象の物体にアタッチします。

### ```Rigidbody```をアタッチし、```isKinematic```にする
Componentをアッタッチし、Inspectorで```isKinematic```を☑します。
[![Image from Gyazo](https://i.gyazo.com/6c50cf38fb6a252a8f80b2e69e63d8c9.png)](https://gyazo.com/6c50cf38fb6a252a8f80b2e69e63d8c9)

こうすることで、移動先を指定し、さらに物理挙動も与えられます。
[リファレンス：Rigidbody](https://docs.unity3d.com/ja/current/Manual/class-Rigidbody.html)

### Rigidbody.MovePositionを使うScriptコンポーネント
移動したい先の座標を指定します。この座標は絶対座標です。
例：相対位置(1,2,3)の場所に強制移動

```c#
GetComponent<Rigidbody>().MovePosition(transform.position+new Vecttor3(1,2,3));
```

詳しくは以下を見てください。
[スクリプトリファレンス：Rigidbody.MovePosition](https://docs.unity3d.com/jp/current/ScriptReference/Rigidbody.MovePosition.html)

## ```MovePosition```を使ってみたサンプル
確実に移動し、その物理挙動を周囲に影響を与えるサンプルとして、以下を作りました。
前記と同様の処理を、MovePositionで実装しています。

```c#:TurnKinematic.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

[RequireComponent(typeof(Rigidbody))]

public class TurnKinematic : MonoBehaviour
{

    [Header("かかる時間"), Range(0.0625f, 30)]
    public float duration = 1.0f;
    [Header("移動する距離"), Range(0, 30)]
    [SerializeField] private float _distance;

    public float distance
    {
        set
        {
            _distance = value;
            targetPosition = GetTargetPosition(value);
        }
        get
        {
            return _distance;
        }

    }
    private Vector3 GetTargetPosition(float distance)
    {
        if (targetPosition == Vector3.zero)
        {
            return transform.position + transform.forward * distance;
        }
        else
        {
            return (transform.position + targetPosition).normalized * distance;

        }
    }


    float pastTime = 0;
    Vector3 homePosition;
    private Vector3 targetPosition = Vector3.zero;
    Rigidbody rigidBody;

    // Use this for initialization
    private void Awake()
    {
        homePosition = transform.position;
        targetPosition = GetTargetPosition(_distance);
        rigidBody = GetComponent<Rigidbody>();
        if (rigidBody == null)
        {
            Debug.LogError("Unable to GetComponent rigidbody at Awake");
        }
        else if (!rigidBody.isKinematic && this.enabled)
        {
            Debug.LogWarning("Rigidbody is not Kinematic");
        }

    }

    private void FixedUpdate()
    {
        pastTime += Time.fixedDeltaTime;
        rigidBody.MovePosition(
            Vector3.Lerp(homePosition, targetPosition, Mathf.PingPong(pastTime, duration) / duration));
    }
}

```

周囲の物体は上記と同じになっています。
[![Image from Gyazo](https://i.gyazo.com/0041bee665b09155b3e5d12a734e1fdc.gif)](https://gyazo.com/0041bee665b09155b3e5d12a734e1fdc)

このように、ポリゴンが食い込むこともなく、きちんと指定通りの場所に移動します。速度を上げても適切に動作します。
[![Image from Gyazo](https://i.gyazo.com/84e20668a9cface534d8aa739fea175d.gif)](https://gyazo.com/84e20668a9cface534d8aa739fea175d)

## ```MovePosition```の使い途は抗うことができない物体に
周りにある```Rigidbody```を持つものに影響を与えますが、自身は何にぶつかっても押し通すことができますから、使い方には注意が要ります。

利用する対象には以下のようなものが考えられます。

- ダンジョンのエレベーターやエスカレーター、自動ドア
- 宇宙空間に浮かんでいて、何かにぶつかられてもびくともしない巨大な星や宇宙船
- 地震で動く地面
- 乗客で速度が落ちたりしない、回り続ける観覧車

## ⚠️```MovePosition```は強力なので注意も必要！

動く範囲に同じようなMovePositionで動くものが在って衝突すると重なり合います、適切でないと厄介なときもあるでしょう。
[![Image from Gyazo](https://i.gyazo.com/0849bd05e0de08430d0a860be9e09732.gif)](https://gyazo.com/0849bd05e0de08430d0a860be9e09732)

また、```isKinematic```の☑を外した場合、一応、動きはしますが、衝突した物体の影響を受けます。
[![Image from Gyazo](https://i.gyazo.com/acf260464545b5c7e8c2c83613c84283.gif)](https://gyazo.com/acf260464545b5c7e8c2c83613c84283)

これだと、後述するAddForceによる動きに近くなりますが、自身が受けた状態は相手に従い、ぶつかった相手側は食い込むことを許すのでそのあとの挙動がColliderが重なり合い、強く反発するところが違います。こうした特殊な状態を作り出したいゲームの仕様はあまり、無いように思われますし、こういうのがたまたまゲームに残っていて使い道があると、グリッチ扱いですね。

# 方法:three::ぶつかってぶつかられて動くAddForce

力学の計算をして相互作用したい場合に用います。

## 実装方法

以下のコンポーネントをそれぞれアタッチします。

### ```RigidBody```をアタッチし、```isKinematic```☐にする
Componentをアッタッチし、Inspectorで```isKinematic```をの☑は外し☐にします。
[![Image from Gyazo](https://i.gyazo.com/530b33c443ea5c10db8f95625443fdb0.png)](https://gyazo.com/530b33c443ea5c10db8f95625443fdb0)

こうすることで、物理挙動を受け付けさせます。☑isKinematicの場合は、AddForceを受け付けません。

[リファレンス：Rigidbody](https://docs.unity3d.com/ja/current/Manual/class-Rigidbody.html)

### Rigidbody.AddForceを使うScriptコンポーネント
移動したい先の座標を指定します。この座標は絶対座標です。
例：相対位置(1,2,3)のベクトルの方向に力を加える

```c#
GetComponent<Rigidbody>().AddForce(new Vector3(1,2,3),ForceMode.VelocityChange);
```

この場合では質量を無視するので、速度を代入する記述でも、同じ効果が得られます。

```c#
GetComponent<Rigidbody>().velocity+=new Vector3(1,2,3);
```


[リファレンス:Rigidbody.AddForce](https://docs.unity3d.com/ja/current/ScriptReference/Rigidbody.AddForce.html)

ここで指定するForceModeで色々、挙動が変わります。試してみると面白いでしょう。

[リファレンス:ForceMode](https://docs.unity3d.com/ja/current/ScriptReference/ForceMode.html)

## ```AddForce```を使った例

これまでの例と同じ```Scene```で、動かすScriptを以下のものと差し替えました。

```c#:TurnPhysical.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

[RequireComponent(typeof(Rigidbody))]

public class TurnPhysical : MonoBehaviour
{

    [Header("かかる時間"), Range(0.0625f, 30)]
    public float duration = 1.0f;
    [Header("移動する距離"), Range(0, 30)]
    [SerializeField] private float _distance;

    public float distance
    {
        set
        {
            _distance = value;
            targetPosition = GetTargetPosition(value);
        }
        get
        {
            return _distance;
        }

    }
    private Vector3 GetTargetPosition(float distance)
    {
        if (targetPosition == Vector3.zero)
        {
            return transform.position + transform.forward * distance;
        }
        else
        {
            return (transform.position + targetPosition).normalized * distance;

        }
    }

    float pastTime = 0;
    private Vector3 homePosition;
    private Vector3 targetPosition = Vector3.zero;
    private Vector3 nextPosition;
    Rigidbody rigidBody;

    // Use this for initialization
    private void Awake()
    {
        homePosition = transform.position;
        targetPosition = GetTargetPosition(_distance);
        rigidBody = GetComponent<Rigidbody>();
        if (rigidBody == null)
        {
            Debug.LogError("Unable to GetComponent rigidbody at Awake");
        }
        else if (rigidBody.isKinematic && this.enabled)
        {
            Debug.LogWarning("Rigidbody is Kinematic");
        }

    }
  
    private void FixedUpdate()
    {
        pastTime += Time.fixedDeltaTime;
        nextPosition = Vector3.Lerp(homePosition, targetPosition, Mathf.PingPong(pastTime, duration) / duration);
        rigidBody.AddForce(nextPosition - transform.position - rigidBody.velocity, ForceMode.VelocityChange);
    }
}

```
これを、以下の設定で実行してみます。
[![Image from Gyazo](https://i.gyazo.com/82f139cf9ee8fd6d0a633ca67430b7fe.png)](https://gyazo.com/82f139cf9ee8fd6d0a633ca67430b7fe)

実行した結果がこちらです。
[![Image from Gyazo](https://i.gyazo.com/a656092b08c02a9d2f92ab42bc758dcb.gif)](https://gyazo.com/a656092b08c02a9d2f92ab42bc758dcb)

このように、自ら動きつつ、周囲の物体に接触すると影響を受けているのが分かります。

## ```AddForce```は動きまわる物体に

isKinematicが☐の状態の```Rigidbody```を持つ者同士は、相互に作用・反作用します。空間で絶対的に固定しなくないものに用います。

- 飛び回るボール:baseball:
- 銃弾や矢:bow_and_arrow:
- ぶつかりあうキャラクター:runner_tone2:

#まとめ：動かしたいもので３パターン

以上をまとめると以下になります。

|実装方法|動き|Colliderの食い込む可能性|用途|
|:--|:--|:--:|:--|
|TranslateやPosition代入|瞬間移動する|あり|リスポーンの戻りやUIアニメーション|
|isKinematic☑でMovePosition|自分の動きはごり押し、他は影響を受ける|isKinematic☑同士|エレベーターや動く壁|
|isKinematic☐で```AddForce```|周囲と影響しあう|無し(```CollisionDetection```による)|動くボール|

どう動かしたいかを考えて、選びましょう。また、それぞれが独立で利用するものではなく、組み合わせもあります。例えば３Dゲームのキャラクターなら、

- 通常の動きは```AddForce```でWASDの操作で動かす
- パワーアップアイテムでダッシュするときは```MovePosition```でごり押し
- 敵にやられてリスポーンポイントまで戻るときは```Translate```

といった具合です。

# 補足
尚、上記は直線運動でしたが、これが回転でも同じ３種類になります、参考にしてみてください。

|直線|回転|
|:--|:--|
|:one:```Translate```や```Position```代入|```Rotate```や```Rotation```代入|
|:two:```isKinematic```☑でMovePosition|```isKinematic```☑で```MoveRotation```|
|:three:```isKinematic```☐で```AddForce```|```isKinematic```☐で```AddTorque```|

ここで紹介した動画になっているプロジェクトデータは以下から入手可能です。
[https://github.com/JunShimura/Roll-a-Ball-Custom-1stDev-2018](https://github.com/JunShimura/Roll-a-Ball-Custom-1stDev-2018)

それではよいUnityLifeを！