メインコンテンツまでスキップ

チュートリアル:ジオメトリのアニメーション

📚 Source Page

JUCE アプリケーションでシンプルなアニメーションを作成します。AnimatedAppComponentクラスを使用して、静的なジオメトリ形状に命を吹き込みます。

レベル: 初級
プラットフォーム: Windows, macOS, Linux, iOS, Android
クラス: AnimatedAppComponent, Path, Point

はじめに

このチュートリアルのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルを Projucer で開いてください。

この手順でサポートが必要な場合は、チュートリアル:Projucer Part 1: Projucer を始めようを参照してください。

デモプロジェクト

完成すると、デモプロジェクトは複数のPathPointオブジェクトで構成された魚の連続的でスムーズなアニメーションを画面に表示します。

デモプロジェクトウィンドウ
デモプロジェクトウィンドウ
ヒント

ここで紹介するコードは、JUCE サンプルのAnimationAppExampleとほぼ同様です。

AnimatedAppComponent クラス

シンプルなアニメーション JUCE アプリケーションを作成する際に継承すると便利なクラスはAnimatedAppComponentクラスです。AudioAppComponentOpenGLAppComponentクラスがそれぞれオーディオアプリケーションや OpenGL アプリケーションに役立つのと同様に、AnimatedAppComponentはアニメーション作成に有益な関数を提供します:

  • setFramesPerSecond():この関数を使用すると、アプリケーションの起動時に FPS を設定でき、アニメーションを可能な限りスムーズに実行できます。呼び出されると、指定された頻度で再描画プロセスも開始されます。
  • update():この関数は setFramesPerSecond()関数で設定されたレートで呼び出され、ここでアニメーションパラメータのステップバイステップの進行が実行されます。
  • getFrameCounter():定義された FPS レートでアニメーション開始以来の update()関数の総呼び出し回数を返します。これはアニメーションを計算するための周期的な数学関数で役立ちます。
  • getMillisecondsSinceLastUpdate():フレームレートに関係なく正確にタイミングされたアニメーションを作成するために、最後の update()関数呼び出しからの経過時間をミリ秒で返すもう 1 つの便利な関数です。

これらの関数と親Componentクラスの paint()関数を使用して、シンプルなアニメーションの作成を開始できます。

円のアニメーション

MainContentComponentクラスで見られるように、MainContentComponent はAnimatedAppComponentから継承しています。

アニメーションを作成する最初のステップは、アニメーションのフレームレートを設定することです。MainContentComponent コンストラクタで、setFramesPerSecond()関数を呼び出して以下のように[1]で行います:

MainContentComponent()
{
setSize (800, 600);
setFramesPerSecond (60); // [1]
}

ここでは FPS を 60 に設定し、これによりアニメーションが毎秒 60 回更新されるように 60Hz の周波数でタイマーが内部的に呼び出されます。これはほとんどの画面のリフレッシュレートとほぼ同等で、スムーズなアニメーションになります。

シンプルな円を円形の動きでアニメーションさせることから始めましょう。paint()関数で、まずGraphicsクラスの setColour()関数を呼び出して円を描画したい色を設定します[2]。次に、形状が従う円形パスの半径を定義します[3]。以下のように示されます:

void paint (juce::Graphics& g) override
{
//...

g.setColour (getLookAndFeel().findColour (Slider::thumbColourId)); // [2]

int radius = 150; // [3]

juce::Point<float> p (getWidth() / 2.0f + 1.0f * radius,
getHeight() / 2.0f + 1.0f * radius); // [4]

g.fillEllipse (p.x, p.y, 30.0f, 30.0f); // [5]
}

次に、指定された時間フレームでの形状の中心位置を表すPointを作成します[4]。ここでは、円形の動きを作成するために、まず画面の幅と高さを 2 で割って画面の中心を見つけます。次に、両方に半径値を追加して形状の x 座標と y 座標をオフセットします。

最後に、fillEllipse()関数を使用して、前に定義したPoint座標と直径 30 を引数として実際の円を描画します。

今アプリケーションを実行すると、円はどうなると思いますか?そうです、座標系が左上隅から始まるため、円は半径値によって反対方向にのみ押し出され、画面の右下隅に静的に描画されます。以下のように示されます:

静的な円
静的な円

実際の動きを作成するためにPointの宣言を変更しましょう。

void paint (juce::Graphics& g) override
{
// (Our component is opaque, so we must completely fill the background with a solid colour)
g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));

g.setColour (getLookAndFeel().findColour (juce::Slider::thumbColourId));

int radius = 150;

juce::Point<float> p ((float) getWidth() / 2.0f + 1.0f * (float) radius * std::sin ((float) getFrameCounter() * 0.04f),
(float) getHeight() / 2.0f + 1.0f * (float) radius * std::cos ((float) getFrameCounter() * 0.04f));

g.fillEllipse (p.x, p.y, 30.0f, 30.0f);
}

ここでは、getFrameCounter()関数を使用してアニメーション開始からのフレーム数のカウンターを取得し、その値を使用してそれぞれ幅と高さのサイン関数とコサイン関数を使用して-1 .. 1の間の値を計算します。フレームカウンターへの 0.04 のスカラー乗算は、円形の動きを作成するために周期関数が交互になる速度を制御します。

今アプリケーションを実行すると、円形の動きが表示されるはずです。

ヒント

このコードの修正版のソースコードは、デモプロジェクトのAnimationTutorial_02.hファイルにあります。

パスのアニメーション

円の代わりに、次に円形パスに沿って線をアニメーションさせましょう。

前のセクションと同じコードベースを使用して、単一のアニメーションされたPointではなく、Pathが作成される複数のPointオブジェクトを作成します。paint()関数を以下のように変更します:

void paint (juce::Graphics& g) override
{
// (Our component is opaque, so we must completely fill the background with a solid colour)
g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));

g.setColour (getLookAndFeel().findColour (juce::Slider::thumbColourId));

auto numberOfDots = 15; // [1]

juce::Path spinePath; // [2]

for (auto i = 0; i < numberOfDots; ++i) // [3]
{
int radius = 150;

juce::Point<float> p ((float) getWidth() / 2.0f + 1.0f * (float) radius * std::sin ((float) getFrameCounter() * 0.04f + (float) i * 0.12f),
(float) getHeight() / 2.0f + 1.0f * (float) radius * std::cos ((float) getFrameCounter() * 0.04f + (float) i * 0.12f));

if (i == 0)
spinePath.startNewSubPath (p); // if this is the first point, start a new path..
else
spinePath.lineTo (p); // ...otherwise add the next point
}

// draw an outline around the path that we have created
g.strokePath (spinePath, juce::PathStrokeType (4.0f)); // [4]
}
  • [1]:まずパスに沿って作成するドットの数を定義します。
  • [2]:次にドットを結ぶ線を描画するためのPathオブジェクトを作成します。
  • [3]:各ドットに対して、前と同じPointオブジェクトを作成しますが、今回はループ内の各反復でアニメーションをオフセットします。円形の動きで各ドット間に 0.12 のオフセットの追加に注目してください。反復が最初の場合、Pathオブジェクトで startNewSubPath()関数を呼び出して新しいサブパスを作成し、それ以外の場合は現在のドットをPath上の以前に定義されたドットに接続します。
  • [4]:最後に、Pointオブジェクトに沿って作成されたPathの周りにアウトラインを描画します。

アプリケーションを実行すると、円形に描かれた線が表示されます。

円形のパス
円形のパス
注記

演習:アプリケーションの FPS を変更し、アニメーションの滑らかさがどのように変化するか注意してください。映画アニメーションの標準 24FPS レートではどうなりますか?

ヒント

このコードの修正版のソースコードは、デモプロジェクトのAnimationTutorial_03.hファイルにあります。

魚のアニメーション

これまで作成したPathPointオブジェクトから魚をアニメーションさせることで、もう少し興味深いことを試してみましょう。

円形パスに沿って描かれた実際のポイントを表示し、魚の体を作成するために、for ループに円を描画する行を追加しましょう。fillEllipse()関数を使用し、線に沿った各ドットに対して増加する幅と高さを指定します[1]。以下のようにします:

void paint (juce::Graphics& g) override
{
//...
g.setColour (getLookAndFeel().findColour (juce::Slider::thumbColourId));

auto fishLength = 15;

juce::Path spinePath;

for (auto i = 0; i < fishLength; ++i)
{
int radius = 150;

juce::Point<float> p (getWidth() / 2.0f + 1.0f * radius * std::sin (getFrameCounter() * 0.04f + i * 0.12f),
getHeight() / 2.0f + 1.0f * radius * std::cos (getFrameCounter() * 0.04f + i * 0.12f));

// draw the circles along the fish
g.fillEllipse (p.x - i, p.y - i, 2.0f + 2.0f * i, 2.0f + 2.0f * i); // [1]

if (i == 0)
spinePath.startNewSubPath (p); // if this is the first point, start a new path..
else
spinePath.lineTo (p); // ...otherwise add the next point
}

// draw an outline around the path that we have created
g.strokePath (spinePath, juce::PathStrokeType (4.0f));
}

アプリケーションを実行すると、魚のように見え始めるものが表示されますが、まだ魚のような動きはしていません。

魚の体
魚の体

魚の動きを模倣するためにアニメーションを少し変更しましょう。

void paint (juce::Graphics& g) override
{
//...

for (auto i = 0; i < fishLength; ++i)
{
auto radius = 100 + 10 * std::sin (getFrameCounter() * 0.1f + i * 0.5f); // [2]

juce::Point<float> p (getWidth() / 2.0f + 1.0f * radius * std::sin (getFrameCounter() * 0.04f + i * 0.12f),
getHeight() / 2.0f + 1.0f * radius * std::cos (getFrameCounter() * 0.04f + i * 0.12f));

//...
}
//...
}

ここでは、同じ getFrameCounter()関数を使用して円の半径にサイン関数とフレームカウンターを使用した変調を適用し、スカラーと線に沿った各ドットに対してわずかに異なるオフセットを使用します[2]。これにより、アプリケーションを実行すると蛇のような動きが得られるはずです。

動きは説得力がありますが、驚きがなく、初期の円形軌道上でかなり早く繰り返されるため、少し単調に見えます。

void paint (juce::Graphics& g) override
{
//...

for (auto i = 0; i < fishLength; ++i)
{
auto radius = 100 + 10 * std::sin (getFrameCounter() * 0.1f + i * 0.5f);

juce::Point<float> p (getWidth() / 2.0f + 1.5f * radius * std::sin (getFrameCounter() * 0.02f + i * 0.12f),
getHeight() / 2.0f + 1.0f * radius * std::cos (getFrameCounter() * 0.04f + i * 0.12f)); // [3]

//...
}
//...
}

Point作成時にサイン関数とコサイン関数のレートをオフセットし、半径の幅と高さに異なる比率を提供すると[3]、より説得力のある結果が得られます。

アプリケーションを最後にもう一度実行して、動きのランダム性の改善に注目してください。

注記

演習:魚の長さを変更するか、同様の方法を使用して独自のカスタムアニメーション形状を作成してください。

ヒント

このコードの修正版のソースコードは、デモプロジェクトのAnimationTutorial_04.hファイルにあります。

まとめ

このチュートリアルでは、JUCE アプリケーションでジオメトリ形状をアニメーションする方法を学びました。特に以下のことを行いました:

  • AnimatedAppComponentクラスの仕組みを探りました。
  • Graphicsクラスを使用して形状を描画しました。
  • フレームごとに形状をアニメーションさせました。

関連項目