Siv3D で TaskSystem を使ったエフェクト制作

  • 3
    Like
  • 0
    Comment

Siv3D Advent Calendar 11 日目の記事です。


Siv3D August 2016から、タスクシステムを含むフレームワークが同梱されています。
今回は、そのタスクシステムを利用して簡単なエフェクトを作ります。

※2017 年 2 月内リリース予定の「Siv3D February 2017」の同梱タスクシステムで制作しています。

タスクシステムとは?

3.gif
弾幕シューティング大量のオブジェクトを扱う際によく用いられる制作方法で、昔から使われています。

大量のオブジェクトを扱う制作は、様々な処理が絡み合い、ソースコードが乱雑になっている事があります。
タスクシステムは「登録」「配列管理」「関数呼び出し」「消去」「当たり判定」などを自動化できる為、短い時間で簡潔に書く事ができます。

6.gif
簡潔に書けるタスクシステム簡潔に書ける Siv3D合わさるとどうなるか試してみたいと思います。

更に細かな説明はこちらにあります。
「C++オリジナルタスクシステムの紹介」
http://qiita.com/Rinifisu/items/b4395ba2cff7da0363d8

最低限必要な処理

以下のソースコードでタスクシステムを利用できます。
内部で様々な自動処理を行う関数が揃っています。

Main.cpp
#include <Siv3D.hpp>
#include <ThirdParty/rnfs.h>

void Main()
{
    while (System::Update())
    {
        Task::All::Update();
        TaskCall::All::Update();
    }

    Task::All::Clear();
}

点を一定時間表示

タスクシステムを扱えるクラスを1つ作ります。
今回は Foam(泡)エフェクトを作りたいので、クラス名にします。

Main.cpp
#include <Siv3D.hpp>
#include <ThirdParty/rnfs.h>

class Foam : public Task //継承
{
private:
    Vec2        m_pos;
    TaskCall    m_update;

public:
    Foam(const Vec2 & pos)
        : Task(TaskDestroyMode::Time, 1.0) //1秒後に自動消滅
        , m_pos(pos), m_update(this, &Foam::Update) //Update を毎フレーム自動呼び出し
    { }

    void Update()
    {
        Circle(m_pos, 5.0).draw();
    }
};

void Main()
{
    while (System::Update())
    {
        Create<Foam>(Mouse::Pos());

        Task::All::Update();
        TaskCall::All::Update();
    }

    Task::All::Clear();
}

1.gif

通常であれば、1秒後に消去する為に「タイマーを用意」「配列に登録し、時間経過したものを消す」処理が必要です。
タスクシステムは自動化している為、最低限の情報だけで済みます。

TaskCallは、毎フレーム自動呼び出しをしたい関数を都合の良いタイミングで設定・変更できます。

移動と縮小

上記の処理に「適当な方向に移動」「縮小して消滅」を加えてみます。

Main.cpp
#include <Siv3D.hpp>
#include <ThirdParty/rnfs.h>

class Foam : public Task
{
private:
    Vec2        m_pos;
    Vec2        m_moveVec; //移動法線
    TaskCall    m_update;

public:
    Foam(const Vec2 & pos)
        : Task(TaskDestroyMode::Time, 1.0)
        , m_pos(pos), m_moveVec(RandomVec2())
        , m_update(this, &Foam::Update)
    { }

    void Update()
    {
        m_pos += m_moveVec; //座標移動
        Circle(m_pos, remainingTime() * 5.0).draw(Palette::Aqua); //残り時間 × 5
    }
};

void Main()
{
    while (System::Update())
    {
        Create<Foam>(Mouse::Pos());

        Task::All::Update();
        TaskCall::All::Update();
    }

    Task::All::Clear();
}

3.gif

この 2 ステップで既にエフェクトとして使えるものが出来上がりました。
Siv3D のお陰もあり、更に短いコード量で済みました。

円の描画部分でテクニックを加えました。
前回、1 秒後に消滅する処理を加えましたが、その消滅までの残り時間が取得できます。
それを描画する円の半径として利用することで、時間経過で縮小する処理が出来上がりました。

複製して準備する

Foam クラスをコピーして、新たに Shower(シャワー) クラスを作成します。
一部の処理を少し変えて実行します。

Main.cpp
#include <Siv3D.hpp>
#include <ThirdParty/rnfs.h>

class Foam : public Task
{
//省略
};

class Shower : public Task
{
private:
    Vec2        m_pos;
    TaskCall    m_update;

public:
    Shower()
        : Task(TaskDestroyMode::Time, 5.0) //消滅時間を5秒に変更
        , m_pos(Mouse::Pos())
        , m_update(this, &Shower::Update)
    { }

    void Update()
    {
        Circle(m_pos, remainingTime() * 5.0).draw(Palette::Aqua);
    }
};

void Main()
{
    while (System::Update())
    {
        //左クリックで生成
        if (Input::MouseL.clicked) Create<Shower>();

        Task::All::Update();
        TaskCall::All::Update();
    }

    Task::All::Clear();
}

2.gif

左クリックShower1つ生成するようにし、5秒で消滅するようにしました。
現時点では Shower というクラス名の意味を成していません。

完成

1行を置き換えるだけでShowerが出来上がります
他にも少し手を加えます。

Main.cpp
#include <Siv3D.hpp>
#include <ThirdParty/rnfs.h>

class Foam : public Task
{
private:
    Vec2        m_pos;
    Vec2        m_moveVec;
    TaskCall    m_update;

public:
    Foam(const Vec2 & pos)
        : Task(TaskDestroyMode::Time, 2.0)
        , m_pos(pos), m_moveVec(RandomVec2())
        , m_update(this, &Foam::Update)
    { }

    void Update()
    {
        m_pos += m_moveVec;
        Circle(m_pos, remainingTime() * 5.0).drawFrame(1, 1, Palette::Aqua); //枠だけ描画にする
    }
};

class Shower : public Task
{
private:
    Vec2        m_pos;
    TaskCall    m_update;

public:
    Shower()
        : Task(TaskDestroyMode::Time, 5.0)
        , m_pos(Mouse::Pos())
        , m_update(this, &Shower::Update)
    { }

    void Update()
    {
        Create<Foam>(m_pos); //描画処理を Form を毎フレーム生成する処理に置き換え
    }
};

void Main()
{
    while (System::Update())
    {
        if (Input::MouseL.clicked) Create<Shower>();

        Task::All::Update();
        TaskCall::All::Update();
    }

    Task::All::Clear();
}

1.gif

Shower の更新処理で座標を引き継ぎFoam を生成する処理を作りました。
エフェクト完成です。
Foam(泡)というより、縮んでいるBalloon(風船)ですね。

通常だとクラス間の生成準備と手間が必要ですが、タスクシステムなら自動化しています。
#include して Create で簡単生成できます。

簡潔に書けるタスクシステム簡潔に書ける Siv3Dが合わさると、「短時間」「簡潔なコード」「綺麗なエフェクト」ができる事がわかりました。

タスクシステムを学ぶ

他にも「タスク管理変数」「シーン管理」「当たり判定」「どこでもタスク呼び出し」などの機能も用意してありますが、この記事での紹介は割愛します。
専用の Qiita 記事を用意していますので、こちらで覚えましょう!

「Siv3D でタスクシステムを使った制作を行う」
http://qiita.com/Rinifisu/items/945b1a6972760a55c2fe

「C++オリジナルタスクシステムの紹介」
http://qiita.com/Rinifisu/items/b4395ba2cff7da0363d8

「TaskSystem Project - GitHub」
https://github.com/Rinifisu/TaskSystem

9.gif


明日の Siv3D Advent Calendar の記事は @jin_siro さんです。
よろしくお願いします。