Unity 6の2Dで2つの地点を往復する移動を作る(MoveTowardsと条件分岐)

広告

この記事は「Unity 6 2D移動の総合ガイド」の一部です。前の記事では、指定した1つの地点へ移動しました。今回は、2つの地点を行ったり来たりする移動を作ります。横スクロールゲームの敵や、動く足場によく使われる動きです。

この記事で分かること

  • 目的地を切り替えて、往復する仕組み
  • 到着したかどうかを、Vector3.Distance()で判定する方法
  • 条件演算子(? :)の使い方

1. 完成する動き

オブジェクトが、PointAとPointBの間を、自動で行ったり来たりします。

四角いオブジェクトが右端まで進むと向きを変え、左端まで往復し続ける動き

2. 準備

共通の準備は総合ガイドの「共通の準備」をご覧ください。前の記事のTargetMoveが付いている場合は、取り外してください。

① 2つの地点を作る

Hierarchyで右クリックしてCreate Emptyを選び、空のGameObjectを2つ作ります。

名前Positionの例
PointA(-4, 0, 0)
PointB(4, 0, 0)

Z座標はどちらも0にしてください。前の記事の「注意点:Z座標をそろえる」と同じ理由です。

② 移動するオブジェクトを用意する

Squareを使い、PositionをPointAと同じ位置(-4, 0, 0)にしておきます。この記事もTransformを直接動かす方法なので、Rigidbody 2Dは不要です。

3. スクリプトを作る

PingPongMoveという名前でスクリプトを作り、移動するオブジェクトに取り付けます。

PingPongMove.cs

using UnityEngine;

public class PingPongMove : MonoBehaviour
{
    [SerializeField] Transform pointA;
    [SerializeField] Transform pointB;

    [SerializeField] float speed = 3f;

    Transform target;

    void Start()
    {
        target = pointB;
    }

    void Update()
    {
        if (pointA == null || pointB == null)
            return;

        transform.position = Vector3.MoveTowards(
            transform.position,
            target.position,
            speed * Time.deltaTime
        );

        if (Vector3.Distance(
            transform.position,
            target.position) < 0.01f)
        {
            target = target == pointA
                ? pointB
                : pointA;
        }
    }
}

InspectorのPoint AとPoint Bに、HierarchyのPointAとPointBをそれぞれドラッグして登録します。ゲームを再生すると、往復を始めます。

4. コードの解説

① 目的地を変数で持つ

Transform target;

現在の目的地を表す変数です。Startで最初の目的地をPointBに設定しています。前の記事では目的地が1つでしたが、今回はこの変数の中身を入れ替えることで、目的地を変えます。

② 到着したかどうかを判定する

if (Vector3.Distance(
    transform.position,
    target.position) < 0.01f)

Vector3.Distance()は、2点間の距離を計算します。距離が0.01未満なら、「到着した」とみなします。座標が完全に一致するかを調べるのではなく、「ほぼ同じ位置」を基準にするのが安全です。

③ 目的地を切り替える(条件演算子)

target = target == pointA ? pointB : pointA;

これは条件演算子で、次のif文と同じ意味です。

if (target == pointA)
{
    target = pointB;
}
else
{
    target = pointA;
}

「目的地がPointAならPointBに、そうでなければPointAに切り替える」と読みます。最初は読みやすいif文でも構いません。慣れると、条件演算子は短く書けて便利です。

④ 動作の流れ

  1. 最初は、PointBに向かう
  2. PointBに到着すると、目的地をPointAに切り替える
  3. PointAに到着すると、目的地をPointBに切り替える
  4. これを繰り返す

目的地を切り替えるだけで、同じコードを使って往復できる点がポイントです。

5. よくあるつまずき

症状確認すること
何も動かないPoint AとPoint Bの両方を登録しているか
片道だけで止まる到着判定の距離(0.01)を極端に小さくしていないか
画面外へ出てしまうPointA・PointB・移動するオブジェクトのZ座標がそろっているか
落ちてしまうRigidbody 2Dが残っていないか

6. 練習問題

  1. 端に到着したら、1秒停止してから折り返すように変更してみましょう
  2. 移動する向きにあわせて、スプライトの左右を反転させてみましょう

ヒント(1つ目)

  • 「待ち時間」を表す変数を用意して、毎フレームTime.deltaTimeずつ減らします。
  • 0より大きい間は、移動せずに処理を終えます。

ヒント(2つ目)

  • スプライトの左右反転は、SpriteRendererのflipXでできます。
  • 目的地のX座標と、現在のX座標を比べると、向きが分かります。

解答例:1秒停止する(まず自分で試してから見ましょう)

[SerializeField] float waitTime = 1f;

float waitTimer;

void Update()
{
    if (pointA == null || pointB == null)
        return;

    // 待ち時間が残っている間は、動かない
    if (waitTimer > 0f)
    {
        waitTimer -= Time.deltaTime;
        return;
    }

    transform.position = Vector3.MoveTowards(
        transform.position,
        target.position,
        speed * Time.deltaTime
    );

    if (Vector3.Distance(
        transform.position,
        target.position) < 0.01f)
    {
        target = target == pointA
            ? pointB
            : pointA;

        waitTimer = waitTime;
    }
}

到着したときにwaitTimerに1秒をセットし、次のフレームからは、時間が経つまで動かないようにしています。このように、「状態」を表す変数を使う考え方は、最後の総合演習(敵AI)でも役立ちます。

7. 発展:動く足場に使うときの注意

この移動を足場に使うと、上に乗ったプレイヤーが足場に連れて行かれないことがあります。この問題への対応は、「動く足場への追従」という発展テーマとして、あとで学びましょう。

8. まとめ

  • 目的地を変数にして、入れ替えることで往復できる
  • Vector3.Distance()で、到着したかどうかを判定する
  • 条件演算子は、簡単なif文を短く書ける

次の記事では、2地点ではなく、複数の地点を順番に巡回します。

訪問数 1 回, 今日の訪問数 1回

広告