イージングを使おう

イージングの模式図

お久しぶりの方はお久しぶり、はじめましての方ははじめまして。肩書は考え中、ニシャスでございます。
ゲーム作ってますか? 私は作ろうと思いながら多忙で2019年を越してしまい、10月頃に労組から開放されたものの、この2、3ヶ月は大して作らずに遊んでしまっていたことを白状します。

この記事はSiv3D Advent Calender2020の21日目の記事です。

12/21は冬至なんですよね。かぼちゃ食べるのを忘れないように。間違えても冬至のかぼちゃを忘れてハロウィンを祝っちゃあいけない。
まあアップしたの12/22になっちゃいましたけど

さて、ゲームを作るとき、例えばタイトルやスコアなんかを登場させるときなどにアニメーションを使うことありますよね!
一番簡単なのは、フレームごとに

        if(pos.x < 400)
        {
          pos.x += 4;
        }
    

とかやる方法です。こうすると止まってほしい位置までは同じ速度で動き続けて所望の位置でピタッと止まる動作になるわけなのですが、
この方法だとどうしてもなんかこう、チープな感じになってしまいます。

もちろんチープな感じを出すのもゲームの味の一つだと思います(これは本気で思っています)が、少しかっこよく見せようと思っているあなたには、「イージング」をお勧めします。

イージング

実世界においては、「フレームごとに3ピクセル進む」ような単純な等速直線運動はあんまりないらしくて、何かしら緩急がついているのが「自然」みたいです。

ただ個人的な感覚としては「自然か不自然か」というよりは「かっこいいかダサいか」の2通りの方が表現としてはより近いです。

こういう「心地よく感じる条件」って突き詰めようとすると楽しいですよね。

イージングではありませんが、自然界にあるものを数学的な法則にあてはめたものの一例に「フィボナッチ数列」というのがあるのをご存知でしょうか。

「1,1」から始まって、2,3,5,8……と、直前2項の和が連なっている数列、というやつなのですが、自然界によく見られるのだそうです。デザインを美しくするための比率などと言われう「黄金比」もこの数列と関係があるそうです。

若干話がそれましたが緩急のある動きというのも似たようなものなのではないかと思います。小気味よいとか美しいとか、そんな印象を持つようにできているんじゃないでしょうかね。人間って生き物は。

Siv3Dでイージングを使う

さて、動画編集ソフトやWebページのアニメーションでも使われるイージング、Siv3Dで使ってみましょうじゃありませんか。

Siv3Dではイージング用の関数が用意されています。

簡単に書いてしまうと

Easehogehoge(t)という形で、時間t(0.0~1.0)における値(始点0.0、終点1.0)を返してくれます。 実際にEasehogehogeという関数があるわけではなくて、hogehogeの部分が七変化します。イージングにもかなり色々種類がありますので詳しくはeasings.netでも見ていただければと思います。

実際には"EaseInOutCubic"みたいな名前で打ち込むことになります。

ちなみにSiv3Dのイージング関数は引数tを0.0~1.0の間で使うのですが、1.0を超えた数を与えても1.0として解釈されるようなので多少乱暴に扱えるのも嬉しいです。

実例

とはいっても、実際イージングでどんな事ができるのかわからないと使いたい気持ちにもならないと思いますので、実例をご紹介。

1. 矢印のエンドレスアニメーション

エレベーターの液晶で見たやつ。2個の矢印をEaseInOutCubicで動かしています。

止まっているように見えるのはEaseInOutCubicの最初と最後の部分は変化量が非常に少ないからです。

Shape2D::Arrow(Vec2(400, 300), Vec2(400, 100), 90, Vec2(120, 90)).asPolygon().moveBy(Vec2(0, EaseInOutCubic((double)(Scene::FrameCount() % 120) / 120.0) * -600 + 82)).draw(Palette::Black);
Shape2D::Arrow(Vec2(400, 300), Vec2(400, 100), 90, Vec2(120, 90)).asPolygon().moveBy(Vec2(0, EaseInOutCubic((double)(Scene::FrameCount() % 120) / 120.0) * -600 + 600 + 82)).draw(Palette::Black);
    

2.なんとなくかっこいいゲームのメニュー表示

メニューがすすすすっと現れるやつです。

前述したとおりイージング関数のtは1より大きくても1として扱ってくれるので、Stopwatchの経過秒数をそのまま食わせることができます。

Stopwatch sw[5];
for (int i = 0; i < 5; i++)
{
  if(t == 0)sw[i].reset();
  else if(t > 0.03 * i && !sw[i].isStarted())sw[i].start();
}

font(U"EASY MODE").drawAt(EaseOutExpo(sw[0].sF()) * 800 - 400, 100, Palette::Black);
font(U"HARD MODE").drawAt(EaseOutExpo(sw[1].sF()) * -800 + 1200, 180, Palette::Black);
font(U"BAKE A BREAD").drawAt(EaseOutExpo(sw[2].sF()) * 800 - 400, 260, Palette::Black);
font(U"OPTIONS").drawAt(EaseOutExpo(sw[3].sF()) * -800 + 1200, 340, Palette::Black);
font(U"EXIT").drawAt(EaseOutExpo(sw[4].sF()) * 800 - 400, 420, Palette::Black);
    

ゲームオーバー

イージング関数にバウンドがあるんですね。EaseInBounce、EaseOutBounce、EaseInOutBounceがありますがEaseOutBounce以外はまず出番ないと思います。

tは0.0~2.0で変化するようにさせて、下記コードで「前半でバウンド、後半で静止」を実現しています。

tex_gameover.drawAt(Vec2(400, EaseOutBounce(t) * 400 - 100));

ハイスコア更新

EaseOutBackを使うと「勢い余ってちょっと行き過ぎちゃったけど戻るぜ!」ってエフェクトも簡単に実装できます。もうちょっとうまい表現がありそうな気もしてます。

tは0.0~4.0で動かしてます。

newrecord.drawAt(Vec2(EaseOutBack(t)*800-400, 300), ColorF(1.0, (t>=1.0) ? 1.0 : t));

結論

みんなもイージングで気持ちのいいアニメーションを作ろう!

あと日頃からなにか作っていたほうが書く側としては気持ちよく書けただろうなあと思いました反省