徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【Unity】PingPong()を使って往復する値の処理を作りたい

Unity にはたくさんライブラリ関数があるから助かるなぁ。
そんな中で、 Mathf.PingPong() は使ったことがない関数。
これを使って往復移動する処理を使ってみたお話。


この記事にはUnity2020.3.19f1を使用しています。
.Netのバージョン設定には.Net4.x を使用しています。

まずは地道に往復処理を作ろう

さて、ここにキャラクターが2人います。
f:id:urahimono:20210925234149j:plain

この左のキャラクターが右のキャラクターに移動して、
元の場所に戻る処理が作りますよー。
f:id:urahimono:20210925234201j:plain

// Unit.cs
using UnityEngine;
using System.Collections;
public class Unit : MonoBehaviour
{
    /// <summary>
    /// 攻撃時間
    /// </summary>
    [SerializeField] float   m_attackTime       = 0.3f;
    /// <summary>
    /// 攻撃移動先
    /// </summary>
    [SerializeField] Vector3 m_targetPosition = new Vector3(1.0f, 0.0f, 0.0f);

    private void Update()
    {
        // Todo:
        // ボタンを連打されるとコルーチンが動作中に別のコルーチンが動作してしまうので
        // あとで連打対応を入れようね♡
        if(Input.GetMouseButtonDown(0))
        {
            StartCoroutine(Attack(m_attackTime, m_targetPosition));
        }
    }

    /// <summary>
    /// 攻撃用アクション
    /// </summary>
    /// <param name="aTime">アクション時間</param>
    /// <param name="aToPosition">移動先</param>
    /// <returns></returns>
    private IEnumerator Attack(float aTime, Vector3 aToPosition)
    {
        float   startTime     = Time.time;
        Vector3 startPosition = transform.position;

        while (Time.time < startTime + aTime)
        {
            float t = (Time.time - startTime) / aTime;
                  t = Mathf.Clamp01(t);
            Vector3 updatedPosition = Vector3.Lerp(startPosition, aToPosition, t);
            transform.position      = updatedPosition;
            yield return null;
        }

        transform.position = aToPosition;
    }
} // class Unit

f:id:urahimono:20210925234219g:plain

とりあえず片道移動はできましたよ。
では戻る処理も追記していきましょう。

// Unit.cs
private IEnumerator Attack(float aTime, Vector3 aToPosition)
{
    float   halfTime       = aTime / 2.0f;
    Vector3 startPosition  = transform.position;
    yield return Move(halfTime, aToPosition);
    yield return Move(halfTime, startPosition);
}

/// <summary>
/// 移動処理
/// </summary>
/// <param name="aTime">移動時間</param>
/// <param name="aToPosition">移動先座標</param>
/// <returns></returns>
private IEnumerator Move(float aTime, Vector3 aToPosition)
{
    float   startTime      = Time.time;
    Vector3 startPosition  = transform.position;

    while (Time.time < startTime + aTime)
    {
        float t = (Time.time - startTime) / aTime;
              t = Mathf.Clamp01(t);
        Vector3 updatedPosition = Vector3.Lerp(startPosition, aToPosition, t);
        transform.position      = updatedPosition;
        yield return null;
    }
    transform.position = aToPosition;
}

f:id:urahimono:20210925234232g:plain

移動用の処理を関数を分けて、往路と復路で呼び出せばOKと。
これでも問題ないんだけど、もっとスマートにできんものかしらね?

さて、ここで今回のお題でもある Mathf.PingPong() を使ってスマートな処理に変えてみましょうか。

PingPong と書いてピンポンと読む

値が往復する値といえば、sin値とcos値を思い浮かぶ人が多いと思いますが、
今回は意地でもMathf.PingPong()を使います。

docs.unity3d.com

Mathf.PingPong()
第1引数に渡した数値が第2引数の数値を超えた場合は、
0と第2引数の間を往復する値が返ってくるのだ!

……うーむ、日本語で説明するとイマイチわかんないなぁ。
Mathf.PingPong()に渡す引数と結果を一覧するとこんな感じ。

  • Mathf.PingPong(1, 5) => 1
  • Mathf.PingPong(5, 5) => 5
  • Mathf.PingPong(7, 5) => 3
  • Mathf.PingPong(9, 5) => 1
  • Mathf.PingPong(13, 5) => 3

数値が往復してますね。
sin値とcos値は360度周期で使うと -1 ~ 1 の値になりますが、
Mathf.PingPong() は 0 ~ 指定値です。

これは使えそうだ!
Vector3.Lerp()t として渡していた値を Mathf.PingPong() を使って往復するようにしてみましょう。

// Unit.cs
private IEnumerator Attack(float aTime, Vector3 aToPosition)
{
    float   startTime      = Time.time;
    Vector3 startPosition  = transform.position;

    while (Time.time < startTime + aTime)
    {
        float t = (Time.time - startTime) / aTime;
              t = Mathf.Clamp01(t);
              t = Mathf.PingPong(t * 2, 1.0f);
        Vector3 updatedPosition = Vector3.Lerp(startPosition, aToPosition, t);
        transform.position      = updatedPosition;
        yield return null;
    }
    transform.position = startPosition;
}

別関数に分けるに必要がなくなりました。
先ほどは往路と復路で時間を半分にして処理していましたが、
今回は t の値の変化値を2倍にして、 Mathf.PingPong() で往復させるようにしまいた。

ああー……、
思ったほど処理がスマートになっておらん気がする……。
Mathf.PingPong()を使って素敵な処理を作ろう選手権があったら、
「もう少し頑張りま賞」止まりだったなぁ。

とりあえずMathf.PingPong()という関数がなかなかに使えそうな関数だとわかったので良しとしましょう。
Mathf.PingPong()を使った素敵な処理を思いついた方はぜひご一報を。