ゲームでは数学的な計算をする場面が多いです。
特に、時間の関数となることが多いでしょう。
- 物体を一定速度で動かす(時間に対して位置が変化)
- スプライトの透明度を変化させてフェードアウト(時間に対して透明度が変化)
DOTween というライブラリが有名ですが、
ただ純粋な線形変換(直線的な補完)をしたいだけならオーバーキルかもしれません。
ラムダ式で GC.Alloc とか発生しますしね。(関連する話)
面倒臭いコード
DOVirtual.Float(
from : 1.0f, // Tween開始時の値
to : 5.0f, // 終了時の値
duration : 1.0f, // Tween時間
// 値が変化するたびに実行される
onVirtualUpdate: value => {
// "value" を使った処理
}
).SetEase(Ease.Linear); // 線形イースにする
線形変換とか言いましたが、やっていることは本質的に直線の方程式です。
意外と簡単な気がしませんか?
f(x) = A(x - B) + C
この直線の方程式を汎用的なメソッドにしてみました。
作成したプログラム
using System.Runtime.CompilerServices;
using UnityEngine;
public static class RemapUtils
{
/// <summary>
/// 区間を線形変換します。<br/>
/// value の定義域は [<see cref="fromMin"/>, <see cref="fromMax"/>] に留まりません。<br/>
/// ------------------------------<br/>
/// 言い換えると、直線の方程式です。<br/>
/// 2点 (<see cref="fromMin"/>, <see cref="toMin"/>), (<see cref="fromMax"/>, <see cref="toMax"/>) を通る直線について、<br/>
/// x = <see cref="value"/> の時の y の値を返します。<br/>
/// ------------------------------<br/>
/// ※ 0除算はチェックしません。<br/>
/// ※ 本質的に直線の方程式なので、実は引数の値の大小関係は問いません。<br/>
/// <example>
/// <code>
/// 2f.Remap(1f, 3f, 4f, 8f) // 6f
/// 2f.Remap(1f, 1f, 4f, 8f) // 0除算!
/// 2f.Remap(3f, 1f, 8f, 4f) // 6f
/// </code>
/// </example>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Remap(this float value, float fromMin, float fromMax, float toMin, float toMax)
=> (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 Remap(this float value, float fromMin, float fromMax, Vector2 toMin, Vector2 toMax)
=> (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 Remap(this float value, float fromMin, float fromMax, Vector3 toMin, Vector3 toMax)
=> (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color Remap(this float value, float fromMin, float fromMax, Color toMin, Color toMax)
=> (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin;
}
大事な所を抜き出してみます。
float Remap(float value, float fromMin, float fromMax, float toMin, float toMax)
=> (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin;
value が fromMin から fromMax まで変化する時、
戻り値は toMin から toMax の範囲に線形変換されます。
以下のように傾きが計算され、上記の式に従って計算を行います。
float A = (toMax - toMin) / (fromMax - fromMin);
return (value - fromMin) * A + toMin;
拡張メソッドにしてあるので、このように使うことができます。ちょっと便利。
_ = 2f.Remap(1f, 3f, 4f, 8f); // 6f