【Unity】PingPongでオブジェクトを往復移動させる

次のように行ったり来たりするギミックを作りたいんだけど、簡単に動かす方法はないの~?

PingPongメソッドを使えば簡単にできるわ!

Unityには、値を往復させるMathf.PingPong()メソッドが用意されています。これを用いると、動画のようにオブジェクトを動かす処理を簡潔なコードで書けます。

// 往復した値を時間から計算
var y = Mathf.PingPong(Time.time, 2);

// y座標を往復させて上下運動させる
transform.localPosition = new Vector3(0, y, 0);

往復処理は剰余演算を駆使して実装できますが、単純な往復だけならPingPongメソッドを使うと楽です。

本記事ではMathf.PingPong()を使ってオブジェクトを往復させる方法を解説します。応用として、指定した振幅や周期で往復させる方法も紹介します。

この作品はユニティちゃんライセンス条項の元に提供されています

動作環境
  • Unity2021.1.17f1

Mathf.PingPong()メソッド

0と指定された値の間を往復する値を返すメソッドです。

メソッドの形式は次の通りです。

public static float PingPong(float t, float length);

第1引数tには取得位置第2引数lengthには往復範囲の上限値を指定します。

戻り値は、0とlengthの間を往復した値です。

横軸をt、縦軸を戻り値としてグラフにすると、以下のようになります。

lengthに比例して往復時間も長くなります。往復するときの速さはlengthによらず一定になるとも言えます。

オブジェクトを上下に往復運動させるサンプル

オブジェクトをy軸で往復させるサンプルです。

PingPongVertical.cs
using UnityEngine;

public class PingPongVertical : MonoBehaviour
{
    // 往復する長さ
    [SerializeField] private float _length = 1;

    private void Update()
    {
        // 往復した値を時間から計算
        var value = Mathf.PingPong(Time.time, _length);

        // y座標を往復させて上下運動させる
        transform.localPosition = new Vector3(0, value, 0);
    }
}

実行結果

上記スクリプトを往復運動させたいオブジェクトにアタッチし、インスペクタからLengthの値を調整します。実行すると、次のようにオブジェクトが上下(y軸方向)に動きます。

指定した振幅と周期で往復させる方法

Mathf.PingPong()メソッドは、第2引数lengthによって往復間隔を変えられることがわかりました。しかし、このままでは往復するときの振幅と周期を個別に指定できません。

振幅と周期を独立して指定できるようにするためには、引数や戻り値に一工夫加える必要があります。

次のグラフのような値を得ることを目標に解説していきます。

振幅を指定できるようにする

振幅は第2引数lengthの値で調整できます。指定した振幅で往復させるコードは次のようになります。

var amplitude = 2; // 振幅
var t = Time.time; // 現在時間

// 指定された振幅のPingPong
var value = Mathf.PingPong(t + amplitude, 2 * amplitude) - amplitude;

これで得られる計算結果は次のグラフのようになります。

周期を指定できるようにする

先の処理に対して、任意の周期で振動できるようにしていきます。

周期は時間の進行速度を変えることで調整できます。進行が速くなるほど周期は反比例して小さくなります。

指定した周期と振幅で往復させるコードは次のようになります。

var amplitude = 2; // 振幅
var period = 2; // 一往復する周期(秒)
var t = 4 * amplitude * Time.time / period; // 時間の進行速度を調整

// 指定された振幅と周期のPingPong
var value = Mathf.PingPong(t, 2 * amplitude) - amplitude;

これで、以下のように指定した周期の値が得られるようになります。

位相を指定する

ここまでの計算では、最小値から始まり増加→減少→増加→・・・を繰り返す往復になります。

しかしながら、開始のタイミングをずらしたい場合もあり得ます。この場合、次のように時間に補正値を加えることで位相をずらすことが可能です。

var amplitude = 2; // 振幅
var period = 2; // 一往復する周期(秒)
var phase = 0.25f; // 位相(1で1周分)

// 位相を考慮した時間補正
var t = 4 * amplitude * (Time.time / period + phase + 0.25f);

// 指定された振幅、周期、位相のPingPong
var value = Mathf.PingPong(t, 2 * amplitude) - amplitude;

位相は1で1往復分としました。また、位相0でPingPongの値が0になるように括弧内に0.25(=1/4)を加えて調整しています。

これで期待した結果が得られるようになりました。

改良版サンプル

指定した振幅、周期、位相でオブジェクトを往復運動させるサンプルスクリプトです。
往復するときの軸をXYZで指定できるようにしました。

PingPongMover.cs
using UnityEngine;

public class PingPongMover : MonoBehaviour
{
    // 振幅
    [SerializeField] private float _amplitude = 1;

    // 周期
    [SerializeField] private float _period = 1;

    // 位相
    [SerializeField, Range(0, 1)] private float _phase = 0;

    // 動かす軸
    private enum Axis
    {
        X,
        Y,
        Z
    }

    [SerializeField] private Axis _axis;

    private Transform _transform;
    private Vector3 _initPosition;

    // 初期化
    private void Awake()
    {
        _transform = transform;
        _initPosition = _transform.localPosition;
    }

    private void Update()
    {
        // 周期と位相を考慮した現在時間計算
        var t = 4 * _amplitude * (Time.time / _period + _phase + 0.25f);

        // 往復した値を計算
        var value = Mathf.PingPong(t, 2 * _amplitude) - _amplitude;

        // 変位計算
        var changePos = Vector3.zero;

        switch (_axis)
        {
            case Axis.X:
                changePos.x = value;
                break;
            case Axis.Y:
                changePos.y = value;
                break;
            case Axis.Z:
                changePos.z = value;
                break;
        }

        // 位置を反映
        _transform.localPosition = _initPosition + changePos;
    }
}

ゲームオブジェクトに上記スクリプトをアタッチし、Amplitude、Period、Phaseにそれぞれ振幅、周期、位相を指定します。Axisに往復させる軸を指定します。

実行結果

振幅、周期、位相を少しずつ変えたパターンのデモです。

期待通りの動きになっていることが確認できました。

さいごに

Mathf.PingPong()メソッドを用いることで、簡単に往復処理を実装することが可能です。三角関数(Mathf.Sin()、Mathf.Cos()など)を使って往復させることも可能ですが、PingPongは機械的な動きをするのが特徴です。

方法の一つとして参考にしてください。

参考サイト