皆さん、動きに遊びが欲しくありませんか?
私は最近、カードゲームを作っており、UI制御を頑張っているのですが、なんかカードの動きが物足りなくないか?なんか面白い動きできないか?と思ったわけです。調べてみるとDOTweenというものがあって、そこにはなんと35種類ものうごきがあるわけじゃありませんか!ということでここから、線形補間と2次関数のイージングを取り上げてみようと思います。
概要
①イージングっどんなことしてるの?
②線形補間を実装しよう!
➂二次関数イージングを実装しよう!
・t^2(だんだん早く)のイージング
・(t-1)^2+1(だんだん遅く)のイージング
・2つのグラフを使って遅く→早く→遅く(QuadInOut)を実装してみよう
④最後に
そもそもイージングってどんなことをしているの?
イージングを実装する前にまず考えることです。まずイージングをするにあたって必要な情報は、始まりの位置、終了位置、現在どれだけ時間たったかを表す割合です。基本的に、イージングというのは全体の距離を1.0(=全体的な割合) として扱い、そこに 割合tをもとに計算した、イージング関数の戻り値を掛け合わせて位置を決めています。言葉だけだとわかりずらいので、下記の基本的なイージングであるLerp補間をもとに説明しようと思います。
線形補間(Lerp)を実装しよう!
Lerpは基本的な線形補間で、startからgoalまで等速で動いていきます。グラフはこれです
y=tの式
このグラフの見方は、横軸を時間の割合、縦軸を現在の位置として考えます。
実際に実装した関数はこれ。
float Easing::Lerp(const float start, const float end, const float t)
{
// 線形補間
if (t >= 1.0f)
{
return end;
}
//距離を求める
float dis = end - start;
//全体的な距離に割合を掛けた値が距離になる
float ret = start + t * dis;
return ret;
}
これで線形補間完成です。このように、時間の割合tに割合を掛けることで、補間することができます。え?こんなのじゃ全然遊びにならないって?まぁそうです。なので、次は少しだけ緩急のあるようなイージングを実装したいと思います。ここまでは序章です。
二次関数イージング(Quad)を実装しよう!
ということでここでは、速さに緩急をつけたいと思います。Lerpでは割合をかけるだけでしたが、ここでは、きちんと計算結果を距離にかけます。
一言に二次関数イージングといっても、二次関数のグラフには2種類あって、下に凸のグラフ、上に凸のグラフがあり、それぞれで動きが少し違います。
y=t^2のグラフ
y=-(t-1)^2+1のグラフ

比べてみると、y=t^2のグラフは、最初緩やかな速度から、だんだん早くなっていって、ぴたっと止まるように動きそうですね。
逆にy=-(t-1)^2+1のグラフは、最初が早く、だんだん速度が0に近づいて行っているのが分かります。なんとなく最後はきれいに止まってくれそうで、色々なUIの動きに使えそうですね。
ということでやってみましょう。
まずy=t^2のイージングから
float Easing::EaseQuadIn(const float start, const float end, const float t)
{
if (t >= 1.0f)return end;
//距離を計算
float dis = end - start;
//二次関数を計算(t^2)
float quadIn = powf(t, 2.0f);
//実際に現在位置を計算
float ret = start + dis * quadIn;
return ret;
}
波形を見ると、確かにだんだん早くなっていってぴたっと止まることが確認できると思います。
続いてy=-(t-1)^2+1のイージング
float Easing::EaseQuadOut(const float start, const float end, const float t)
{
if (t >= 1.0f)return end;
//距離を計算
float dis = end - start;
//2次関数を計算-(t-1)^2+1
float quad = -(powf((t - 1), 2.0f)) + 1.0f;
//距離を計算
float ret = start + dis * quad;
return ret;
}
確かにだんだん遅くなってますね。いろんなゲームでUIが右から左にだんだんゆっくり動いて行ってきれいに止まるUIとかもできそうです。
これらの二次関数グラフを応用して別のイージングを実装してみよう!
これまで一つのグラフで動きを作ってきましたが、実は2つの二次関数グラフを使うことでまた一味違うイージングを作ることができるんです!これから作るのは、**QuadInOut(遅く→早く→遅く)**という動きを作ってみようと思います。
その前に…
グラフの計算をするたびに、計算式を書くのは少しめんどくさいし、あとで見たときにわかりずらくなるのではないか?と思ったので、中学で学んだ知識を思い出してみました。二次関数グラフでは、頂点と凸というものがありましたね。頂点はグラフの一番とがっているところ、凸は上と下、どっちにとがっているかです。

例えばさっき作った下凸のy=t^2の頂点は
(x,y)={0,0}
さっき頂点の例で示したy=-(t-1)^2+1の頂点は
(x,y)={1,1}
です。実際に凸と頂点の情報を持った式を見てみましょう。
y=a(x-b)^2+c
a:絶対値が大きいほどグラフが狭まる。+値だと下に凸、-値だと上に凸。
b:頂点のx座標
c:頂点のy座標

このような法則性が分かったので以下のような構造体を作ってみました。
//関数
struct MATH_FUNC
{
//解の公式 a*(x-b)^2+c
float accel = 1.0f; //開き具合(公式のa)
Vector2F graph_vertex = { 0.0f,0.0f }; //軸の頂点(b,c)
//二次関数の計算
float QuadFunc(const float t)
{
float pow = powf(t - graph_vertex.x, 2.0f);
return (accel * pow) + graph_vertex.y;
}
};
これでかなりカスタム性が上がったと思います!
では2つのグラフを合わせて、QuadInOut(遅く→早く→遅く)を実装してみよう!
ということで、これまで通り、グラフを見てみようと思います。

これを線で1本に繋げてみると…

赤グラフ:凸:下凸で、aの絶対値は2
軸の座標:{0,0}
よって式は y=2(x-0)^2+0
黒グラフ:凸:上凸でaの絶対値は2
軸の座標」{1,1}
よって式は y=-2(x-1)^2+1
このように、ゆっくりスタートしてゆっくりゴールするようなグラフになっているのが分かります。
これを紐解くと、
始めは赤グラフの式で、tが0.5以上になった時は黒グラフの式になることが分かると思います。
これらを踏まえて実装した関数はこちら
float Easing::EaseQuadInOut(const float start, const float end, const float t)
{
if (t >= 1.0f)return end;
//距離を計算
float dis = end - start;
//二次関数の構造体を定義
MATH_FUNC quad;
if (t <= 0.5f)
{
quad.graph_vertex = { 0.0f,0.0f };
quad.accel = 2.0f;
}
else
{
quad.graph_vertex = { 1.0f,1.0f };
quad.accel = -2.0f;
}
//構造体の関数を使って計算
float ret = start + dis * quad.QuadFunc(t);
return ret;
}
このようにaの値(accel)と軸の座標(graph_vertex)を定義するだけで出来てしまいます!
実行すると…

このように、遅く→早く→遅く が実装できました!
QuadOutInについて
実は、QuadInOutがあるならQuadOutInもあるので、興味があれば実装してみてください。早く→遅く→早く というような動きになります。グラフと式を置いておきますね。

黒グラフ:凸:上凸でaの絶対値は2
軸の座標:(x,y)={0.5,0.5}
赤グラフ:凸:下凸でaの絶対値は2
軸の座標:(x,y)={0.5,0.5}
最後に
イージングは、基本的にtの割合をイージング関数内で計算した結果を距離にかければよい、というようなことさえ理解できると、ばねの動きや、バウンスの動きを実装できるので、イージング関数を調べてやってみてください。正直、ばねのイージング式は、難しくて理解はできていないのですが、イージングの式さえあれば実装できると思います。ここまでご覧くださり、ありがとうございました。
ここでやったQuad系に加えて、バウンス(ボールが地面で跳ねるような動き)とばねを追加してみました。






