三途の川おさかなのブログ

だらだらしている

目的地に距離の割合で近づく

Unity で目的地に距離の割合で近づく処理の可変フレームレート対応ができた。

  1. 可変フレームレート対応してないもの
    https://lh3.googleusercontent.com/pw/ACtC-3dD-98IHyG-0T5pQkAbWqluDQPOQeZbn_aXtlojgDG8urUrMsCLbF-Idzl5HTjI20kDD3Bo50x9_SywrPL1to92NoCT5KxCdotthi7VQ2zU-VzozurRf3OwPEnckFEjz4ZujQ3AzqD9Cqy32mhFdFLEmg=w300-h200-no

  2. 可変フレームレート対応したもの
    https://lh3.googleusercontent.com/pw/ACtC-3c_kQxfJBUuJ1ZCHFSEu1EP9Pe3Y34movZJg0ZX15Fhr7K5-22MoBbfAEz1meZ9jBzrOjnMzgWnMp8AYYUiUpjDIg9l1qJJ1QVS5AP1uNxlAg73Gp-qrbHj1CoMV8xT0Z0PgoW0-F1kUAOMlj04njPBWg=w300-h200-no

これはマウスポインターの位置を球が追従するプログラム。
「1.」は
自身の位置 += 目的地へのベクトル * 割合
を毎フレーム実行している。割合には 0.1 を入れている。毎フレーム、目的地への距離の10%ずつ近づくことになる。遠ければ速く、近ければ遅く近づく。気持ちの良い動き。画像は左からFPS60、30、10 で動かしたときの比較になっている。FPSが下がるほど、動きが遅くなっている。

「2.」は「1.」を可変フレームレート対応したもの。式は
自身の位置 += 目的地へのベクトル * (1.0 - ((1.0 - 割合) ^ (Time.deltaTime * 60)))
FPSに関係なく 1/60 秒ごとに目的地への距離の10%ずつ近づく意味になる。
「1.」の処理に合わせるために1/60秒ごとの割合で計算しているが、その用途じゃなければ、秒ごとの割合にした方が良い。次の式になる。
自身の位置 += 目的地へのベクトル * (1.0 - (1.0 - 割合) ^ Time.deltaTime))
割合が 0.1 なら 1秒 ごとに 10% ずつ近づく。

ところでこの「距離の割合」。変数名としてどう表現すべきなのか悩む。変数名でわからせようとするとなると「秒速目的地への距離の割合」で、 speedTargetDistanceRatioPerSec 的な感じだろうか?
素直に speed にしてコメントを振るか。speed だと秒速かフレーム速のイメージが強いんだよな。。。

以下は球の動きに使った実際のコード。

using UnityEngine;

public class Chaser : MonoBehaviour {
    // 追跡対象オフセット.
    public Vector3 chaseTargetOffset = new Vector3(0f, 1f, 0f);
    // 1フレームごとの距離を詰める割合.
    public float distanceRatio = 0.1f;
    public int calcType = 1;

    void Update() {
        Vector3 targetPos; // マウスとXY平面の交差点を目的地にする.
        {
            var pointerScreenPos = Input.mousePosition;
            var pointerWldPos = Camera.main.ScreenToWorldPoint(pointerScreenPos);
            var plane = new Plane(Vector3.back, Vector3.zero);
            var pointerRay = Camera.main.ScreenPointToRay(pointerScreenPos);
            plane.Raycast( pointerRay, out var distance );
            targetPos = pointerRay.GetPoint( distance );
        }

        // posA を posB に毎フレーム ratio の割合で近づける.
        var posA = transform.position;
        var posB = targetPos + chaseTargetOffset;
        var toB = posB - posA; // Bへ向かうベクトル.

        switch (calcType) {
            case 1:
            // フレームレートの変動により時間内の移動量に差が出てしまう.
            // 60FPS なら 1 / 60 秒ごとに 0.1f 近づく.
            // 30FPS なら 1 / 30 秒ごとに 0.1f 近づく.
            posA += toB * distanceRatio;
            break;
            case 2:
            // case 1 のフレームレート依存を解消.
            // 1 / 60 秒ごとに 0.1f 近づくことになる.
            // 「* 60f」は case 1 と単位を合わせるためにつけてるもの.
            // 取り除いて、秒ごとの割合にした方が使い勝手が良い.
            posA += toB * (1.0f - Mathf.Pow(1.0f - distanceRatio, Time.deltaTime * 60f));
            break;
        }

        transform.position = posA;
    }
}