【Unity基礎】positionを使用してオブジェクトを動かす方法

Unityでオブジェクトを動かす方法はいろいろありますが、最も基本となる「Position」を使用して移動させる方法について解説したいと思います。

Positionを使った移動には次の3種類あります。

  • transform.position
  • Rigidbody.position
  • Rigidbody.MovePosition

transform.positionを使用してオブジェクトを動かす方法

transform.positionを使用してオブジェクトを移動させる方法は、オブジェクトの座標を直接書き換えるような方法です。

つまりInspectorのTransform, Positionの値を変更するのと同じこと。
スクリプト的には、次の例のようにVector3で表されるpositionを、transform.positionに代入するだけです。

transform.position = new Vector3(0, 0, 1);

それだけなんですが、書き方によっていろいろ違った動かし方ができます。

transform.positionを使用して特定の位置(座標)に瞬間移動する方法

transform.positionを使用して指定した位置(座標)にオブジェクトを移動させる方法です。

void Update () {
    // Upキーで座標0, 0, 1に瞬間移動する
    if (Input.GetKey("up")) {
    transform.position = new Vector3(0, 0, 1);
}

例えばHierarchyビューにCubeオブジェクトを、Projectビューにスクリプトを作成し、Cubeにスクリプトをアタッチします。
スクリプトのUpdate内に上のコードを書いてゲームを再生し、カーソルキー上を押すと座標(0, 0, 1)に瞬間移動します。

この方法はオブジェクトがどの位置に存在するかにかかわらず、指定した絶対的な座標に移動させることができます。

transform.positionを使用してオブジェクトを移動させる方法(オブジェクトの向きに影響されない)

さっきの例は指定した位置に瞬間移動しましたが、時間の経過とともに移動する例です。
キー操作によってオブジェクトを前進させたり、後退させたりといった具合に。

スクリプトのUpdate内に次のコードを書き、オブジェクトにアタッチすると、カーソルキーの操作によりオブジェクトを動かすことが出来ます。

void Update() {
    Vector3 pos = transform.position;

    // Upキーで前に進む
    if (Input.GetKey("up")) {
        pos.z += 0.1f;    // z座標へ0.1加算
    }
    // Downキーで後ろに進む
    if (Input.GetKey("down")) {
        pos.z -= 0.1f;    // z座標へ0.1減算
    }
    //right キーで右に進む
    if (Input.GetKey("right")) {
        pos.x += 0.1f;    // x座標へ0.1加算
    }
    //left キーで左に進む
    if (Input.GetKey("left")) {
        pos.x -= 0.1f;    // x座標へ0.1減算
    }
    //Aキーで上に進む
    if (Input.GetKey("a")) {
        pos.y += 0.1f;    // y座標へ0.1加算
    }
    //Zキーで下に進む
    if (Input.GetKey("z")) {
        pos.y -= 0.1f;    // y座標へ0.1減算
    }
    
    transform.position = pos;  // 座標を設定
}

肝となるのは、“pos”というVector3を作成しておいて、transform.positionにposを代入する点。
この方法では、オブジェクトの向きに関係なくupキーを押すとZ軸方向に進みます。
上キーを押すと北に進む、昔のドラクエみたいな移動方法です。

上の例では

pos.z += 0.01f;

のようにx, y, zの値を加減していますが次のように書くことも出来ます。

pos = pos + new Vector3(0.1f, 0, 0.1f);

この場合、x方向とz方向に0.1進むので、オブジェクトは斜め前に進むことになります。

transform.position.zには直接代入出来ない

“pos”というVector3型の変数を作成して値を変更し、それをtransform.positionに代入する、なんてまどろっこしいことをせずにtransform.position.z(またはx, y)に直接値を代入したらいいのに、って思いますよね。
しかし、先程も書いたようにこれは出来ません。

動かない例を次に示します。

void Update() {
    // transform.position.zに直接代入することは出来ない。
    if (Input.GetKey("up")) {
        transform.position.z += 0.1f;
    }
}

ただし、「transform.positionを使用して特定の位置(座標)に瞬間移動する方法」で書いたようにtransform.positionにVector3を入れることはできるので、次のように書くことはできます。

void Update() {
    //  Upキーで前に進む
    if (Input.GetKey("up")) {
    transform.position += new Vector3(0, 0, 0.1f);
}

transform.positionを使用してオブジェクトを移動させる方法(オブジェクトの向きに影響される)

次の例は、Upキーでオブジェクトの向いている方向に対して前後左右に進む方法です。
さっきの方法は、ワールド座標を指定して動かしていたのでオブジェクトがどちらに向いていても関係ありませんでした。
しかし、この方法ではオブジェクトのローカル座標に影響されます。つまり斜めを向いていたら、その方向に進ませることができます。

オブジェクトを回転出来る(向きを変える)ようにしたら、昔のバイオハザードみたいな移動法になりますね。

void Update () {
    // Upキーで前に進む
    if (Input.GetKey("up")) {
        transform.position += transform.forward * 1.0f * Time.deltaTime;
    }
    // Downキーで前に進む
    if (Input.GetKey("down")) {
        transform.position -= transform.forward * 1.0f * Time.deltaTime;
    }
    if (Input.GetKey("right")) {
        transform.position += transform.right * 1.0f * Time.deltaTime;
    }
    if (Input.GetKey("left")) {
        transform.position -= transform.right * 1.0f * Time.deltaTime;
    }
}

Transform.forwardを使っていますが、これはオブジェクトの前方(z軸)を表す変数です。
つまり、ワールドのz軸とオブジェクトのz軸が別の方を向いていても、きちんとオブジェクトの前方へ進んでくれます。

関連リンク
https://docs.unity3d.com/jp/current/ScriptReference/Transform-forward.html

他にも“Transform.forward”とか“Transform.Up”などの変数が使えます。
詳しくはこちらのスクリプトリファレンスを参照ください。
https://docs.unity3d.com/jp/current/ScriptReference/Transform.html

Rigidbody.positionを使用してRigidbodyオブジェクトを移動させる方法

オブジェクトにRigidbodyコンポーネントがついている場合、Transform.positionではなくRigidbody.positionの使用が推奨されています。
Transform.positionではなくRigidbody.positionのほうが処理が軽いみたいです。

関連リンク
https://docs.unity3d.com/ja/current/ScriptReference/Rigidbody-position.html

Rigidbody.positionの使い方は、Rigidbodyコンポーネントを入れる変数を作成し、Rigidbodyコンポーネントを取得して入れます。
(変数名).position = new Vector3(1.5f, 3.0f, -1.1f);
と言うように、Rigidbody.positionにVector3の座標を入れてあげるとその位置にオブジェクトが移動します。

Rigidbody.positionを使用してオブジェクトを動かす場合のスクリプト例を次に示します。

// Rigidbodyコンポーネントを入れる変数"rb"を宣言する。
public Rigidbody rb;

void Start () {
    // Rigidbodyコンポーネントを取得する
    rb = GetComponent<Rigidbody>();
}

void FixedUpdate () {
    // Upキーで前に進む
    if (Input.GetKey("up")) {
        rb.position = transform.position + transform.forward * 1.0f * Time.deltaTime;
    }
    // Downキーで後ろに進む
    if (Input.GetKey("down")) {
        rb.position = transform.position - transform.forward * 1.0f * Time.deltaTime;
    }
    //right キーで右に進む
    if (Input.GetKey("right")) {
        rb.position = transform.position + transform.right * 1.0f * Time.deltaTime;
    }
    //left キーで左に進む
    if (Input.GetKey("left")) {
        rb.position = transform.position - transform.right * 1.0f * Time.deltaTime;
    }
    //Aキーで上に進む
    if (Input.GetKey("a")) {
        rb.position = transform.position + transform.up * 1.0f * Time.deltaTime;
    }
    //Zキーで下に進む
    if (Input.GetKey("z")) {
        rb.position = transform.position - transform.up * 1.0f * Time.deltaTime;
    }
}

MovePositionを使用してRigidbodyを移動させる方法

Rigidbodyクラスのメソッド「MovePosition」を使って移動する方法です。
UnityリファレンスのMovePositionのページ によると次のように説明されています。

Rigidbody オブジェクトを指定する位置へ移動します

Rigidbody の interpolation (補間)設定にしたがって、Rigidbody を動かすために Rigidbody.MovePosition を使用します。

Rigidbody で Rigidbody interpolation(補間)を有効にした場合、Rigidbody.MovePosition を呼び出すとレンダリングされる中間フレームで2つの位置の間のスムーズな遷移となります。これは、各 FixedUpdate で継続的に rigidbody を動かしたい場合、使用される必要があります。

指定の位置に瞬間移動させる場合はRigidbody.positionを使用し、継続的に動かす場合はMovePosition、と使い分ければ良いと思います。

// Rigidbodyコンポーネントを入れる変数"rb"を宣言する。
public Rigidbody rb;

void Start() {
    // Rigidbodyコンポーネントを取得する
    rb = GetComponent<Rigidbody>();
}

void FixedUpdate() {
    // Upキーで前に進む
    if (Input.GetKey("up")) {
        rb.MovePosition(transform.position + transform.forward * 1.0f * Time.deltaTime);
    }
    // Downキーで後ろに進む
    if (Input.GetKey("down")) {
        rb.MovePosition(transform.position - transform.forward * 1.0f * Time.deltaTime);
    }
    //right キーで右に進む
    if (Input.GetKey("right")) {
        rb.MovePosition(transform.position + transform.right * 1.0f * Time.deltaTime);
    }
    //left キーで左に進む
    if (Input.GetKey("left")) {
        rb.MovePosition(transform.position - transform.right * 1.0f * Time.deltaTime);
    }
    //Aキーで上に進む
    if (Input.GetKey("a")) {
        rb.MovePosition(transform.position + transform.up * 1.0f * Time.deltaTime);
    }
    //Zキーで下に進む
    if (Input.GetKey("z")) {
        rb.MovePosition(transform.position - transform.up * 1.0f * Time.deltaTime);
    }
}

Rigidbody.positionとMovePositionの挙動の違い

Rigidbodyオブジェクトを継続的に動かすときはMovePositionを使うとのことですが、実際に試してみるとtransform.positionやRigidbody.positionとほとんど違いがありません。
リファレンスには、kinematicがONじゃないとtransform.positionと同じになる、と書かれています。

If the rigidbody has isKinematic set false then it works differently. It works like transform.position=newPosition and teleports the object (rather than a smooth transition).

出典: https://docs.unity3d.com/2018.3/Documentation/ScriptReference/Rigidbody.MovePosition.html

MovePositionは、「2つの位置の間のスムーズな遷移となります。」とリファレンスに書かれていますが、それを実現するには次のようにします。
ただし、「Is Kinematic」をONにしておく必要があります。

ちなみに「Is Kinematic」をONにすると物理演算の影響を受けなくなり、重力による落下もしなくなります。

他のオブジェクトに物理的な影響を与えたり、中間のフレームを補完する

上のGif動画の右がRigidbody.position、左がMovePositionを使って動かしていて、どちらも「Is Kinematic」がON、「Interpolate」がInterpolateに設定しています。

上にキューブが乗っている場合、MovePositionの方はキューブを載せたまま移動していて、より自然な感じになります。

ちなみに、Rigidbody関連の処理はスクリプトのUpdate関数内ではなく、FixedUpdate関数内に書いたほうがいいみたいです。
Updateが呼ばれるタイミングは1フレームごとなのに対し、FixedUpdateが呼ばれるタイミングは1秒間に何回と決まっていて、タイミングが一致しません。
下記のGif動画は、先ほどと同じ「Is Kinematic」がON、「Interpolate」がInterpolateの設定で、キューブが左右の2つの位置をMovePositionにより移動させています。

2つの位置の瞬間的な移動であり、中間の位置にキューブが存在するタイミングはないはずですが、時々中間の地点にキューブが描画されているのが分かるでしょうか?
Interpolateの設定をしていることにより、中間の位置が補完されて描画されるため、このようなことが起こります。
FixedUpdateによる移動と描画のタイミングが違うため、フレームによっては2つの位置の間が描画されるというわけです。

でも、間の壁はすり抜けちゃってますね。
Collision Detectionの設定をすればこの壁をぶっ飛ばすことができます。

速く移動した時に中間の物体に影響を与える方法

上の画像の例では、MovePositionを使って壁の向こう側の地点に瞬時にキューブを移動させていますが、壁をぶっ飛ばすことが出来ています。

このように瞬間的に移動しつつも間の物体に影響を与えるには「Is Kinematic」をON、「Collision Detection」をContinuous Dynamicに設定します。
この設定では、ゲーム内ではキューブがテレポートしているのではなく、超高速で移動していることになると言うわけです。
もちろんぶっ飛ばされる壁にもRigidbodyコンポーネントをアタッチして、物理演算の影響下に置いておく必要があります。

positionを使った移動では、動きが早いとすり抜ける

  • transform.position
  • Rigidbody.position
  • Rigidbody.MovePosition

positionを使った3つの移動方法を紹介しましたが、これらはオブジェクトがなめらかに移動しているのではなく瞬間移動(テレポート)していると考えられます。

なので、動きが速いと上の画像のように移動経路の間にある物体をすり抜ける減少が起こります。
動きがゆっくりなら壁にぶつかって止めることが出来ますが、動きが速いとすり抜けてしまうということです。

Rigidbody.MovePositionのみ、上で書いたように「Collision Deetection」の設定をすることで間の壁をふっとばすことは出来ますが、「Is Kinematic」がONなので、壁にぶつかって止まることはありません。

positionを使って動かすときは、この点には注意しておいたほうがいいでしょう。

コメント