5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Siv3DAdvent Calendar 2020

Day 22

なめらかな連続するTweenアニメーションを簡単に作るクラスをつくった

Last updated at Posted at 2020-12-21

YoutubeAPIを叩く記事にしようと思っていたのですが、間に合わなかったので代替です。

動機

こんなものを見つけました。
いろいろ読んでいくと面白そうな機能がありました。そして、

これを参考にして、Siv3D版につくったら結構便利なのでは...?

と思ったので作ってみました。

プロC++erではないので拙いコードですがご容赦ください

できたもの

tween-demo.gif
水色の線は直線(Linear)で、オレンジの曲線はイージングでなめらかに遷移します。

ソースコード

リファレンスに合わせて波括弧はオールマンスタイルを使っています

とりあえずMain.cppはこんな感じで書けます。

Main.cpp
# include <Siv3D.hpp> // OpenSiv3D v0.4.3
# include "Tween.hpp"

void Main()
{
    Tween::Timeline timeline(0); // 初期値は0

    timeline
        .then(200, 5.0s)     // 200まで5秒で動く
        .then(400, 5.0s)     // そこから400まで5秒で動く
        .wait(1.0s)          // 1秒待ち
        .then(600, 5.0s);    // そこから600まで5秒で動く
 
    timeline.start();
	
    while (System::Update())
    {
        // 更新する
        timeline.update();

        ClearPrint();

        Print << timeline.elapsed();
        // .getValueか()演算子で値を取得
        Print << timeline();
    }
}

イージングを使ってなめらかに動かすには.thenの引数にSiv3Dのイージングの関数を書いてやります。
具体的には

timeline
    .then(200, 5.0s, EaseInOutSine)
    .then(400, 5.0s, EaseInOutElastic)
    .wait(1.0s)
    .then(600, 5.0s, EaseOutBounce);

こんな感じです。

ヘッダー部分

Tween.hpp
# pragma once
# include <Siv3D.hpp>

namespace Tween
{
    struct trans_t
    {
        double to;
        Duration begin, end;
        std::function<double(double)> easingFunction = EaseInLinear;
    };

    // template <typename T>
    class Timeline
    {
    public:

        explicit Timeline(const double initialValue = 0.0, bool startImmediately = false)
            :m_initialValue(initialValue), m_value(initialValue), m_isRunning(false)
        {
            if (startImmediately)
            {
                m_isRunning = true;
                m_stopwatch.start();
            }
        }

        ~Timeline()
        {
        }

        Timeline& then(const double to, const Duration dur = 0.0s, double easingFunction(double) = EaseInLinear)
        {
            addTransition(to, dur, easingFunction);
            return *this;
        }

        Timeline& wait(const Duration dur)
        {
            const double prevTarget = (m_transitions.isEmpty() ? 0 : m_transitions.back().to);
            addTransition(prevTarget, dur, EaseInLinear);
            return *this;
        }

        bool update()
        {
            if (m_transitions.isEmpty()) return false;

            if (m_transitions.size() <= m_currentIndex)
            {
                m_value = m_transitions.back().to;
                m_isRunning = false;
                return false;
            }

            if (m_transitions[m_currentIndex].end < m_stopwatch.elapsed()) {
                m_currentIndex++;
                if (m_transitions.size() <= m_currentIndex)
                {
                    m_value = m_transitions.back().to;
                    m_isRunning = false;
                    return false;
                }
            }
            
            const double t = (m_transitions[m_currentIndex].end - m_transitions[m_currentIndex].begin).count();
            const double c = m_transitions[m_currentIndex].to - (m_currentIndex > 0 ? m_transitions[m_currentIndex - 1].to : m_initialValue);
            double e = 1;
            if (t > 0) e = m_transitions[m_currentIndex].easingFunction(Min((m_stopwatch.sF() - m_transitions[m_currentIndex].begin.count()), t) / t);
            
            m_value = c*e + (m_currentIndex > 0 ? m_transitions[m_currentIndex - 1].to : m_initialValue);

            return true;
        }

        void start() { m_isRunning = true; m_stopwatch.start(); }
        void clear()
        {
            m_currentIndex = 0;
            m_initialValue = 0.0;
            m_transitions.clear();
            m_isRunning = false;
            m_value = 0.0;
            m_stopwatch.reset();
        }
        void restart()
        {
            m_currentIndex = 0;
            m_value = m_initialValue;
            m_stopwatch.restart();
        }
        bool isEmpty() { return m_transitions.isEmpty(); }
        bool isRunning() { return m_isRunning; }
        size_t size() { return m_transitions.size(); }
        double getValue() { return m_value; }
        double operator()() { return getValue(); }
        Duration elapsed() { return m_stopwatch.elapsed(); }

    private:
        void addTransition(const double to, const Duration dur, double easingFunction(double))
        {
            const Duration prevDur = (m_transitions.isEmpty() ? 0.0s : m_transitions.back().end);
            m_transitions.emplace_back(trans_t{ to, prevDur, prevDur + dur, easingFunction });
        }

        Stopwatch m_stopwatch;
        Array<trans_t> m_transitions;
        int32 m_currentIndex = 0;
        double m_initialValue = 0.0;
        double m_value = 0.0;
        bool m_isRunning;
    };
}

殴り書きなのでちょっとコードが汚いかもです...

冒頭のやつのコード

開くとあります。ちょっと長いです
Demo.cpp

# include <Siv3D.hpp> // OpenSiv3D v0.4.3
# include "Tween.hpp"

void Main()
{

    Scene::SetBackground(Palette::White);

    Tween::Timeline timelineEase(600), timelineLinear(600);

    timelineEase
        .then(200, 5.0s, EaseInOutSine)
        .then(400, 5.0s, EaseInOutElastic)
        .wait(1.0s)
        .then(600, 5.0s, EaseOutBounce);
    timelineLinear
        .then(200, 5.0s)
        .then(400, 5.0s)
        .wait(1.0s)
        .then(600, 5.0s);

    constexpr Size canvasSize(800, 600);
    constexpr int32 thickness = 4;
    Image image(canvasSize, Palette::White);
    DynamicTexture texture(image);

    timelineEase.start();
    timelineLinear.start();

    Point posEase((int32)(timelineEase.elapsed().count() * 50), (int32)timelineEase());
    Point posLinear = posEase;
    
    while (System::Update())
    {
        ClearPrint();

        timelineEase.update();
        timelineLinear.update();

        const Point fromL = posLinear;
        posLinear = { (int32)(timelineLinear.elapsed().count() * 50), (int32)timelineLinear() };
        const Point toL = posLinear;

        Line(fromL, toL).overwrite(image, thickness, Palette::Cyan);

        const Point fromE = posEase;
        posEase = { (int32)(timelineEase.elapsed().count() * 50), (int32)timelineEase() };
        const Point toE = posEase;

        Line(fromE, toE).overwrite(image, thickness, Palette::Orange);

        texture.fill(image);

        if (KeyC.down())
        {
            timelineEase.restart();
            timelineLinear.restart();

            posLinear = { (int32)(timelineLinear.elapsed().count() * 50), (int32)timelineLinear() };

            posEase = { (int32)(timelineEase.elapsed().count() * 50), (int32)timelineEase() };

            image.fill(Palette::White);

            texture.fill(image);
        }

        texture.draw();

        Print << timelineEase.elapsed();
    }
}

参考にしたもの

Tween library for Arduino (hideakitai)

あとがき

初投稿なのであまり文章が良くないかもしれませんがいかがでしたでしょうか。
Siv3Dは最近になって初めて触れたのでこれから活用していこうと思います!

来年はもっといい記事・内容を書けるようにしたいところ。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?